Compare commits
260 Commits
0.0.5
...
ussuri-eol
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ded5189ba | ||
|
|
1419988ace | ||
|
|
21addfcca0 | ||
|
|
56681c318e | ||
|
|
5bd1b2feeb | ||
|
|
05139e97e8 | ||
|
|
c4e27f2e6d | ||
|
|
1a98ae3101 | ||
|
|
4571dcb492 | ||
|
|
9cd38596cd | ||
|
|
17f75a9c00 | ||
|
|
6a5f46615c | ||
|
|
036ec8746a | ||
|
|
56474b54df | ||
|
|
998e72a03a | ||
|
|
7d93f625d9 | ||
|
|
3216f64d14 | ||
|
|
e5440f809d | ||
|
|
d45538cf41 | ||
|
|
ce7b872c26 | ||
|
|
a0a7b4a24b | ||
|
|
c2e7441444 | ||
|
|
5cf1f0d246 | ||
|
|
c992876f96 | ||
|
|
bc9ac570bf | ||
|
|
048b3a0bd9 | ||
|
|
58234ab51c | ||
|
|
1eb26df991 | ||
|
|
47d15a74a3 | ||
|
|
597e452dbc | ||
|
|
262799e3c0 | ||
|
|
f3c117e17c | ||
|
|
e3ed8939b8 | ||
|
|
38b2b847c8 | ||
|
|
2fe9422e04 | ||
|
|
b0011487e2 | ||
|
|
db689c650f | ||
|
|
1c2079ef71 | ||
|
|
038e2f6984 | ||
|
|
334ef1ec33 | ||
|
|
4455105df5 | ||
|
|
7aca76b32b | ||
|
|
a614c845b2 | ||
|
|
8929cad166 | ||
|
|
103d4dac05 | ||
|
|
0fe7b7c087 | ||
|
|
01d1aa0da9 | ||
|
|
71034945a3 | ||
|
|
1658f71943 | ||
|
|
e7e720ca1d | ||
|
|
c3b5d573ad | ||
|
|
2dd285549d | ||
|
|
c14a1db371 | ||
|
|
26d570239d | ||
|
|
63c9890f21 | ||
|
|
86fd679be3 | ||
|
|
604007cbd3 | ||
|
|
84d19ef13c | ||
|
|
4c47bd020f | ||
|
|
4a5fe7dbc9 | ||
|
|
2d1ac6e744 | ||
|
|
f93ac0b598 | ||
|
|
194195a97a | ||
|
|
9cf5a90b5c | ||
|
|
a4cec6cb18 | ||
|
|
b8726f4bf5 | ||
|
|
7e084ac5c4 | ||
|
|
8cf80d3041 | ||
|
|
0b41772728 | ||
|
|
a09807472d | ||
|
|
f84d1c9b20 | ||
|
|
9d4a1612c5 | ||
|
|
3a22b3fcdd | ||
|
|
6c11094dc6 | ||
|
|
478ebe71ed | ||
|
|
a4a5088ff0 | ||
|
|
87058d3e24 | ||
|
|
aba2875905 | ||
|
|
50927b5c38 | ||
|
|
f13c3faef4 | ||
|
|
e11f2e8006 | ||
|
|
dd2e3ff3a3 | ||
|
|
346c241876 | ||
|
|
1bfd6d0ee4 | ||
|
|
7a7f708282 | ||
|
|
ea42b2ab3e | ||
|
|
996cc1625f | ||
|
|
a7de5b981e | ||
|
|
982a8c361d | ||
|
|
855cbe5cbb | ||
|
|
41d93cdca0 | ||
|
|
888008676c | ||
|
|
c14aea8079 | ||
|
|
e1f7540bac | ||
|
|
6a53851650 | ||
|
|
84e4ff3733 | ||
|
|
e774b2178c | ||
|
|
e3d987a2ce | ||
|
|
9b77c76ed0 | ||
|
|
aa5d56495f | ||
|
|
d5842f6196 | ||
|
|
423e4d5b48 | ||
|
|
30ee5f4829 | ||
|
|
4839860b73 | ||
|
|
28ab39e6fb | ||
|
|
a907bc2a34 | ||
|
|
cb0b8b0515 | ||
|
|
05c98f7a20 | ||
|
|
2d6a97e84a | ||
|
|
7bb1cc2c7a | ||
|
|
b56f70e7fd | ||
|
|
cd7d456491 | ||
|
|
eb70a9d460 | ||
|
|
5eab398150 | ||
|
|
1a7243354e | ||
|
|
5ded9c1f19 | ||
|
|
2fc5904648 | ||
|
|
71e029c629 | ||
|
|
289370ab38 | ||
|
|
795fa730a8 | ||
|
|
3f91adc2d8 | ||
|
|
1b3b860565 | ||
|
|
d358465dcf | ||
|
|
b11e847b52 | ||
|
|
a82f43ae23 | ||
|
|
9936200bf8 | ||
|
|
75a7e6276f | ||
|
|
7b2a2e8c92 | ||
|
|
22d5a7ec38 | ||
|
|
6f2feab60d | ||
|
|
3233c39c52 | ||
|
|
7eb818c0ee | ||
|
|
b658068fa6 | ||
|
|
378a7a38f9 | ||
|
|
1f907c95b8 | ||
|
|
1bf9f74604 | ||
|
|
f898a169cb | ||
|
|
2800c5f1c2 | ||
|
|
2d1d7624e8 | ||
|
|
995fe11dbe | ||
|
|
956f0207e7 | ||
|
|
61a46a6752 | ||
|
|
0d9611ba9b | ||
|
|
e3f50485fd | ||
|
|
cd75dda7bb | ||
|
|
1924aba63a | ||
|
|
e55840dd82 | ||
|
|
0b630c1de8 | ||
|
|
3edac93182 | ||
|
|
74f828c876 | ||
|
|
641dacabc3 | ||
|
|
776ab9fabe | ||
|
|
df7c98a7ae | ||
|
|
e4980fbb1e | ||
|
|
6821a3352c | ||
|
|
b16aa98e8a | ||
|
|
1110520952 | ||
|
|
b5e952d573 | ||
|
|
86b15d38b8 | ||
|
|
68d0a7ece4 | ||
|
|
3c92869735 | ||
|
|
26f966f91f | ||
|
|
5b122a89bc | ||
|
|
8a4995a4fc | ||
|
|
68c676c46f | ||
|
|
16fee61954 | ||
|
|
c67b1b490f | ||
|
|
ee725acb94 | ||
|
|
1a64452662 | ||
|
|
6b6332bd99 | ||
|
|
c68869270e | ||
|
|
0eb328ed07 | ||
|
|
e2e1e67db2 | ||
|
|
a625f7a702 | ||
|
|
71b9c54d0a | ||
|
|
72a42ed2e7 | ||
|
|
5217c987e3 | ||
|
|
345c0cbc36 | ||
|
|
d4e21b53c0 | ||
|
|
b1fe13ddd6 | ||
|
|
f21f04e937 | ||
|
|
4881aa259a | ||
|
|
733472aa51 | ||
|
|
15763e9343 | ||
|
|
dc459ba408 | ||
|
|
629cc47e88 | ||
|
|
5b8522b1f5 | ||
|
|
22665522da | ||
|
|
3a77770597 | ||
|
|
c94edfa3f2 | ||
|
|
01eab7d347 | ||
|
|
d1577ed830 | ||
|
|
3d7c7cd0b1 | ||
|
|
078f344adb | ||
|
|
dd463cce70 | ||
|
|
eeede3cc68 | ||
|
|
462284d817 | ||
|
|
6ec3904818 | ||
|
|
214ad23fbe | ||
|
|
fefecbcfe7 | ||
|
|
ecffc10b16 | ||
|
|
041cb460f9 | ||
|
|
6e40fea91e | ||
|
|
afa95cb8eb | ||
|
|
1db46a2283 | ||
|
|
c243de9232 | ||
|
|
89f1e4eac7 | ||
|
|
ced9795f05 | ||
|
|
e834eb86bf | ||
|
|
7340b04b80 | ||
|
|
339086ce64 | ||
|
|
3a469df201 | ||
|
|
3710127a5f | ||
|
|
faf5c656af | ||
|
|
fc425e741d | ||
|
|
74a382ecb6 | ||
|
|
59ef5ebda0 | ||
|
|
e273aa5b55 | ||
|
|
fae4ef7a03 | ||
|
|
f3ca2e6944 | ||
|
|
ac61ea3499 | ||
|
|
e689b3233e | ||
|
|
1f422f4c5c | ||
|
|
fda6cdfb7b | ||
|
|
448bb4468f | ||
|
|
ce263ecc9e | ||
|
|
d2d3a475e2 | ||
|
|
e0a2b0edcd | ||
|
|
49aae96f22 | ||
|
|
6113f97d2f | ||
|
|
b7ceed787e | ||
|
|
f18cf64f9d | ||
|
|
c0bddb93d5 | ||
|
|
25beeb0819 | ||
|
|
4c6a252b76 | ||
|
|
7c3ab24bf1 | ||
|
|
b304d5978d | ||
|
|
bfc9c63952 | ||
|
|
0fc2893234 | ||
|
|
51e14a5231 | ||
|
|
7e81c138ec | ||
|
|
d8da515092 | ||
|
|
271998a5e8 | ||
|
|
ef4f89da06 | ||
|
|
6b3ba9f4af | ||
|
|
44f20e3019 | ||
|
|
321b6f640a | ||
|
|
86b59bd334 | ||
|
|
c25f584fa0 | ||
|
|
2879e0df18 | ||
|
|
811a4f0ad0 | ||
|
|
9e8380d8d5 | ||
|
|
6d10a66474 | ||
|
|
6f30c77f41 | ||
|
|
368eaf9aed | ||
|
|
ea3e6e775a | ||
|
|
4599b87f1c | ||
|
|
a0a38d5e68 | ||
|
|
7b16de4908 | ||
|
|
9292e6fb2e |
@@ -1,7 +1,7 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = smaugclient
|
||||
omit = smaugclient/tests/*,smaug/openstack/*
|
||||
source = karborclient
|
||||
omit = karborclient/tests/*,karborclient/openstack/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,6 +24,7 @@ pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
cover
|
||||
.tox
|
||||
nosetests.xml
|
||||
.testrepository
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
host=review.opendev.org
|
||||
port=29418
|
||||
project=openstack/python-smaugclient.git
|
||||
project=openstack/python-karborclient.git
|
||||
|
||||
8
.zuul.yaml
Normal file
8
.zuul.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
- project:
|
||||
templates:
|
||||
- check-requirements
|
||||
- openstack-cover-jobs
|
||||
- openstack-lower-constraints-jobs
|
||||
- openstack-python3-ussuri-jobs
|
||||
- openstackclient-plugin-jobs
|
||||
- publish-openstack-docs-pti
|
||||
@@ -1,17 +1,17 @@
|
||||
If you would like to contribute to the development of OpenStack, you must
|
||||
follow the steps in this page:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html
|
||||
https://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
If you already have a good understanding of how the system works and your
|
||||
OpenStack accounts are set up, you can skip to the development workflow
|
||||
section of this documentation to learn how changes to OpenStack should be
|
||||
submitted for review via the Gerrit tool:
|
||||
|
||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
https://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
Bugs should be filed on Launchpad, not GitHub:
|
||||
|
||||
https://launchpad.net/python-smaugclient
|
||||
https://launchpad.net/python-karborclient
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
||||
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
||||
57
README.rst
57
README.rst
@@ -1,18 +1,24 @@
|
||||
Smaug
|
||||
========================
|
||||
Team and repository tags
|
||||
========================
|
||||
|
||||
.. image:: https://governance.openstack.org/tc/badges/python-karborclient.svg
|
||||
:target: https://governance.openstack.org/tc/reference/tags/index.html
|
||||
|
||||
.. Change things from this point on
|
||||
|
||||
======
|
||||
Karbor
|
||||
======
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/python-smaugclient.svg
|
||||
:target: https://pypi.python.org/pypi/python-smaugclient/
|
||||
.. image:: https://img.shields.io/pypi/v/python-karborclient.svg
|
||||
:target: https://pypi.org/project/python-karborclient/
|
||||
:alt: Latest Version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/python-smaugclient.svg
|
||||
:target: https://pypi.python.org/pypi/python-smaugclient/
|
||||
:alt: Downloads
|
||||
|
||||
Karbor Mission Statement
|
||||
|
||||
Smaug Mission Statement
|
||||
|
||||
* Formalize Application Data Protection in OpenStack (APIs, Services, Plugins, …)
|
||||
* Formalize Application Data Protection in OpenStack (APIs, Services, Plugins, ...)
|
||||
* Be able to protect Any Resource in OpenStack(as well as their dependencies)
|
||||
* Allow Diversity of vendor solutions, capabilities and implementations
|
||||
without compromising usability
|
||||
@@ -25,37 +31,40 @@ Smaug Mission Statement
|
||||
* `Specs`_
|
||||
* `How to Contribute`_
|
||||
|
||||
.. _PyPi: https://pypi.python.org/pypi/python-smaugclient
|
||||
.. _Launchpad project: https://launchpad.net/python-smaugclient
|
||||
.. _Blueprints: https://blueprints.launchpad.net/python-smaugclient
|
||||
.. _Bugs: https://bugs.launchpad.net/python-smaugclient
|
||||
.. _Source: https://git.openstack.org/cgit/openstack/python-smaugclient
|
||||
.. _How to Contribute: http://docs.openstack.org/infra/manual/developers.html
|
||||
.. _PyPi: https://pypi.org/project/python-karborclient
|
||||
|
||||
.. _Launchpad project: https://launchpad.net/python-karborclient
|
||||
.. _Blueprints: https://blueprints.launchpad.net/python-karborclient
|
||||
.. _Bugs: https://bugs.launchpad.net/python-karborclient
|
||||
.. _Source: https://opendev.org/openstack/python-karborclient
|
||||
.. _Specs: https://docs.openstack.org/karbor/latest/specs/index.html
|
||||
.. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html
|
||||
|
||||
|
||||
Python Smaugclient
|
||||
Python Karborclient
|
||||
-------------------
|
||||
python-smaugclient is a client library for Smaug built on the Smaug API.
|
||||
It provides a Python API (the ``smaugclient`` module) and a command-line tool
|
||||
(``smaug``).
|
||||
python-karborclient is a client library for karbor built on the karbor API.
|
||||
It provides a Python API (the ``karborclient`` module) and a command-line tool
|
||||
(``karbor``).
|
||||
|
||||
Project Resources
|
||||
-----------------
|
||||
|
||||
Project status, bugs, and blueprints are tracked on Launchpad:
|
||||
|
||||
* Client bug tracker
|
||||
* https://launchpad.net/python-smaugclient
|
||||
* https://launchpad.net/python-karborclient
|
||||
|
||||
* Smaug bug tracker
|
||||
* https://launchpad.net/smaug
|
||||
* Karbor bug tracker
|
||||
* https://launchpad.net/karbor
|
||||
|
||||
Developer documentation can be found here:
|
||||
|
||||
http://docs.openstack.org/developer/smaug
|
||||
https://docs.openstack.org/karbor/latest/
|
||||
|
||||
Additional resources are linked from the project wiki page:
|
||||
|
||||
https://wiki.openstack.org/wiki/Smaug
|
||||
https://wiki.openstack.org/wiki/karbor
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
5
doc/requirements.txt
Normal file
5
doc/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
# 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.
|
||||
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
|
||||
openstackdocstheme>=1.18.1 # Apache-2.0
|
||||
@@ -23,7 +23,7 @@ sys.path.insert(0, os.path.abspath('../..'))
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
#'sphinx.ext.intersphinx',
|
||||
'oslosphinx'
|
||||
'openstackdocstheme'
|
||||
]
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
@@ -37,7 +37,7 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'python-smaugclient'
|
||||
project = u'python-karborclient'
|
||||
copyright = u'2013, OpenStack Foundation'
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
@@ -55,9 +55,15 @@ pygments_style = 'sphinx'
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
# html_theme_path = ["."]
|
||||
# html_theme = '_theme'
|
||||
html_theme = 'openstackdocs'
|
||||
# html_static_path = ['static']
|
||||
|
||||
# openstackdocstheme options
|
||||
repository_name = 'openstack/python-karborclient'
|
||||
bug_project = 'python-karborclient'
|
||||
bug_tag = ''
|
||||
html_last_updated_fmt = '%Y-%m-%d %H:%M'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
============
|
||||
Contributing
|
||||
============
|
||||
.. include:: ../../CONTRIBUTING.rst
|
||||
16
doc/source/contributor/index.rst
Normal file
16
doc/source/contributor/index.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
============
|
||||
Contributing
|
||||
============
|
||||
|
||||
General Info
|
||||
------------
|
||||
|
||||
.. include:: ../../../CONTRIBUTING.rst
|
||||
|
||||
Approved Specs
|
||||
--------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
../specs/index
|
||||
@@ -1,20 +1,20 @@
|
||||
Welcome to smaugclient's documentation!
|
||||
========================================================
|
||||
Welcome to karborclient's documentation!
|
||||
========================================
|
||||
|
||||
Contents:
|
||||
Contents
|
||||
--------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 1
|
||||
|
||||
readme
|
||||
installation
|
||||
usage
|
||||
contributing
|
||||
install/index
|
||||
user/index
|
||||
contributor/index
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
------------------
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
||||
12
doc/source/install/index.rst
Normal file
12
doc/source/install/index.rst
Normal file
@@ -0,0 +1,12 @@
|
||||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
At the command line::
|
||||
|
||||
$ pip install python-karborclient
|
||||
|
||||
Or, if you have virtualenvwrapper installed::
|
||||
|
||||
$ mkvirtualenv python-karborclient
|
||||
$ pip install python-karborclient
|
||||
@@ -1,12 +0,0 @@
|
||||
============
|
||||
Installation
|
||||
============
|
||||
|
||||
At the command line::
|
||||
|
||||
$ pip install python-smaugclient
|
||||
|
||||
Or, if you have virtualenvwrapper installed::
|
||||
|
||||
$ mkvirtualenv python-smaugclient
|
||||
$ pip install python-smaugclient
|
||||
@@ -1 +1,5 @@
|
||||
============
|
||||
Introduction
|
||||
============
|
||||
|
||||
.. include:: ../../README.rst
|
||||
|
||||
13
doc/source/specs/index.rst
Normal file
13
doc/source/specs/index.rst
Normal file
@@ -0,0 +1,13 @@
|
||||
Specs
|
||||
=====
|
||||
|
||||
This section contains detailed specification documents for
|
||||
different features inside Karbor Client.
|
||||
|
||||
Approved Specs
|
||||
--------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
karbor-support-in-python-openstackclient
|
||||
166
doc/source/specs/karbor-support-in-python-openstackclient.rst
Normal file
166
doc/source/specs/karbor-support-in-python-openstackclient.rst
Normal file
@@ -0,0 +1,166 @@
|
||||
..
|
||||
This work is licensed under a Creative Commons Attribution 3.0 Unported
|
||||
License.
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/legalcode
|
||||
|
||||
========================================
|
||||
Karbor support in python-openstackclient
|
||||
========================================
|
||||
|
||||
Implement a new set of karbor commands as python-openstackclient plugins.
|
||||
|
||||
Launchpad Blueprint:
|
||||
https://blueprints.launchpad.net/python-karborclient/+spec/karbor-support-python-openstackclient
|
||||
|
||||
|
||||
Problem Description
|
||||
===================
|
||||
|
||||
python-openstackclient is becoming the default command line client for many
|
||||
OpenStack projects. Karbor would benefit from implementing all of its client
|
||||
commands as a single python-openstackclient plugin implemented in the
|
||||
python-karborclient repository.
|
||||
|
||||
Proposed Change
|
||||
===============
|
||||
|
||||
The intent of this spec is to identify the commands to be implemented and
|
||||
establish conventions for command and argument names. This spec is not
|
||||
intended to be a full and correct specification of command and argument names.
|
||||
The details can be left to the code reviews for the commands themselves.
|
||||
|
||||
The following conventions will be adopted for command names:
|
||||
|
||||
* As the ``OpenStackClient`` convention, the command name shall always take
|
||||
the following form:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
openstack [<global-options>] <object-1> <action> [<object-2>] \
|
||||
[command-arguments]
|
||||
|
||||
|
||||
As a example:
|
||||
The following ``karbor`` commands about plan will be implemented for ``openstack``
|
||||
initially suggesting these command names:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
karbor plan-create <name> <provider_id> <resources>
|
||||
openstack data protection plan create <name> <provider_id> <resources>
|
||||
|
||||
karbor plan-delete <plan>
|
||||
openstack data protection plan delete <plan>
|
||||
|
||||
karbor plan-list
|
||||
openstack data protection plan list
|
||||
|
||||
karbor plan-show <plan>
|
||||
openstack data protection plan show <plan>
|
||||
|
||||
karbor plan-update <name> <resources> <status>
|
||||
openstack data protection plan update <name> <resources> <status>
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
None
|
||||
|
||||
Database
|
||||
--------
|
||||
|
||||
None
|
||||
|
||||
Public API
|
||||
----------
|
||||
|
||||
None
|
||||
|
||||
Public API Security
|
||||
-------------------
|
||||
|
||||
None
|
||||
|
||||
Python API
|
||||
----------
|
||||
|
||||
None
|
||||
|
||||
CLI (python-karborclient)
|
||||
-------------------------
|
||||
|
||||
A new directory named osc will be created under /karborclient/osc
|
||||
for the ``OpenStackClient`` plugin and the commands mentioned above.
|
||||
|
||||
Internal API
|
||||
------------
|
||||
|
||||
None
|
||||
|
||||
Guest Agent
|
||||
-----------
|
||||
|
||||
None
|
||||
|
||||
Alternatives
|
||||
------------
|
||||
|
||||
None
|
||||
|
||||
Dashboard Impact (UX)
|
||||
=====================
|
||||
|
||||
None
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
Assignee(s)
|
||||
-----------
|
||||
|
||||
Primary assignee:
|
||||
chenying
|
||||
|
||||
|
||||
Milestones
|
||||
----------
|
||||
|
||||
|
||||
Work Items
|
||||
----------
|
||||
|
||||
CLI commands as stated above.
|
||||
Unit tests
|
||||
|
||||
Upgrade Implications
|
||||
====================
|
||||
|
||||
None
|
||||
|
||||
Dependencies
|
||||
============
|
||||
|
||||
python-openstackclient
|
||||
osc-lib
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
Unit tests will be located in: /karborclient/tests/unit/osc/
|
||||
|
||||
Documentation Impact
|
||||
====================
|
||||
|
||||
OpenStack Client adoption list will be updated to include python-karborclient.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
https://docs.openstack.org/python-openstackclient/latest/
|
||||
|
||||
Appendix
|
||||
========
|
||||
|
||||
None
|
||||
@@ -1,7 +0,0 @@
|
||||
========
|
||||
Usage
|
||||
========
|
||||
|
||||
To use smaugclient in a project::
|
||||
|
||||
import smaugclient
|
||||
7
doc/source/user/index.rst
Normal file
7
doc/source/user/index.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
=====
|
||||
Usage
|
||||
=====
|
||||
|
||||
To use karborclient in a project::
|
||||
|
||||
import karborclient
|
||||
@@ -16,4 +16,4 @@ import pbr.version
|
||||
|
||||
|
||||
__version__ = pbr.version.VersionInfo(
|
||||
'python-smaugclient').version_string()
|
||||
'python-karborclient').version_string()
|
||||
@@ -1,18 +1,20 @@
|
||||
# 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.
|
||||
from smaugclient.common import utils
|
||||
|
||||
|
||||
def Client(version, *args, **kwargs):
|
||||
module = utils.import_versioned_module(version, 'client')
|
||||
client_class = getattr(module, 'Client')
|
||||
return client_class(*args, **kwargs)
|
||||
# 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.
|
||||
from oslo_utils import importutils
|
||||
|
||||
|
||||
def Client(version, *args, **kwargs):
|
||||
module = importutils.import_versioned_module(
|
||||
'karborclient', version, 'client'
|
||||
)
|
||||
client_class = getattr(module, 'Client')
|
||||
return client_class(*args, **kwargs)
|
||||
@@ -1,221 +1,218 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright 2013 Spanish National Research Council.
|
||||
# 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.
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
import abc
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import six
|
||||
from stevedore import extension
|
||||
|
||||
from smaugclient.openstack.common.apiclient import exceptions
|
||||
|
||||
|
||||
_discovered_plugins = {}
|
||||
|
||||
|
||||
def discover_auth_systems():
|
||||
"""Discover the available auth-systems.
|
||||
|
||||
This won't take into account the old style auth-systems.
|
||||
"""
|
||||
global _discovered_plugins
|
||||
_discovered_plugins = {}
|
||||
|
||||
def add_plugin(ext):
|
||||
_discovered_plugins[ext.name] = ext.plugin
|
||||
|
||||
ep_namespace = "smaugclient.openstack.common.apiclient.auth"
|
||||
mgr = extension.ExtensionManager(ep_namespace)
|
||||
mgr.map(add_plugin)
|
||||
|
||||
|
||||
def load_auth_system_opts(parser):
|
||||
"""Load options needed by the available auth-systems into a parser.
|
||||
|
||||
This function will try to populate the parser with options from the
|
||||
available plugins.
|
||||
"""
|
||||
group = parser.add_argument_group("Common auth options")
|
||||
BaseAuthPlugin.add_common_opts(group)
|
||||
for name, auth_plugin in six.iteritems(_discovered_plugins):
|
||||
group = parser.add_argument_group(
|
||||
"Auth-system '%s' options" % name,
|
||||
conflict_handler="resolve")
|
||||
auth_plugin.add_opts(group)
|
||||
|
||||
|
||||
def load_plugin(auth_system):
|
||||
try:
|
||||
plugin_class = _discovered_plugins[auth_system]
|
||||
except KeyError:
|
||||
raise exceptions.AuthSystemNotFound(auth_system)
|
||||
return plugin_class(auth_system=auth_system)
|
||||
|
||||
|
||||
def load_plugin_from_args(args):
|
||||
"""Load required plugin and populate it with options.
|
||||
|
||||
Try to guess auth system if it is not specified. Systems are tried in
|
||||
alphabetical order.
|
||||
|
||||
:type args: argparse.Namespace
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
auth_system = args.os_auth_system
|
||||
if auth_system:
|
||||
plugin = load_plugin(auth_system)
|
||||
plugin.parse_opts(args)
|
||||
plugin.sufficient_options()
|
||||
return plugin
|
||||
|
||||
for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)):
|
||||
plugin_class = _discovered_plugins[plugin_auth_system]
|
||||
plugin = plugin_class()
|
||||
plugin.parse_opts(args)
|
||||
try:
|
||||
plugin.sufficient_options()
|
||||
except exceptions.AuthPluginOptionsMissing:
|
||||
continue
|
||||
return plugin
|
||||
raise exceptions.AuthPluginOptionsMissing(["auth_system"])
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseAuthPlugin(object):
|
||||
"""Base class for authentication plugins.
|
||||
|
||||
An authentication plugin needs to override at least the authenticate
|
||||
method to be a valid plugin.
|
||||
"""
|
||||
|
||||
auth_system = None
|
||||
opt_names = []
|
||||
common_opt_names = [
|
||||
"auth_system",
|
||||
"username",
|
||||
"password",
|
||||
"tenant_name",
|
||||
"token",
|
||||
"auth_url",
|
||||
]
|
||||
|
||||
def __init__(self, auth_system=None, **kwargs):
|
||||
self.auth_system = auth_system or self.auth_system
|
||||
self.opts = dict((name, kwargs.get(name))
|
||||
for name in self.opt_names)
|
||||
|
||||
@staticmethod
|
||||
def _parser_add_opt(parser, opt):
|
||||
"""Add an option to parser in two variants.
|
||||
|
||||
:param opt: option name (with underscores)
|
||||
"""
|
||||
dashed_opt = opt.replace("_", "-")
|
||||
env_var = "OS_%s" % opt.upper()
|
||||
arg_default = os.environ.get(env_var, "")
|
||||
arg_help = "Defaults to env[%s]." % env_var
|
||||
parser.add_argument(
|
||||
"--os-%s" % dashed_opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
default=arg_default,
|
||||
help=arg_help)
|
||||
parser.add_argument(
|
||||
"--os_%s" % opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
@classmethod
|
||||
def add_opts(cls, parser):
|
||||
"""Populate the parser with the options for this plugin.
|
||||
"""
|
||||
for opt in cls.opt_names:
|
||||
# use `BaseAuthPlugin.common_opt_names` since it is never
|
||||
# changed in child classes
|
||||
if opt not in BaseAuthPlugin.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@classmethod
|
||||
def add_common_opts(cls, parser):
|
||||
"""Add options that are common for several plugins.
|
||||
"""
|
||||
for opt in cls.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@staticmethod
|
||||
def get_opt(opt_name, args):
|
||||
"""Return option name and value.
|
||||
|
||||
:param opt_name: name of the option, e.g., "username"
|
||||
:param args: parsed arguments
|
||||
"""
|
||||
return (opt_name, getattr(args, "os_%s" % opt_name, None))
|
||||
|
||||
def parse_opts(self, args):
|
||||
"""Parse the actual auth-system options if any.
|
||||
|
||||
This method is expected to populate the attribute `self.opts` with a
|
||||
dict containing the options and values needed to make authentication.
|
||||
"""
|
||||
self.opts.update(dict(self.get_opt(opt_name, args)
|
||||
for opt_name in self.opt_names))
|
||||
|
||||
def authenticate(self, http_client):
|
||||
"""Authenticate using plugin defined method.
|
||||
|
||||
The method usually analyses `self.opts` and performs
|
||||
a request to authentication server.
|
||||
|
||||
:param http_client: client object that needs authentication
|
||||
:type http_client: HTTPClient
|
||||
:raises: AuthorizationFailure
|
||||
"""
|
||||
self.sufficient_options()
|
||||
self._do_authenticate(http_client)
|
||||
|
||||
@abc.abstractmethod
|
||||
def _do_authenticate(self, http_client):
|
||||
"""Protected method for authentication.
|
||||
"""
|
||||
|
||||
def sufficient_options(self):
|
||||
"""Check if all required options are present.
|
||||
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
missing = [opt
|
||||
for opt in self.opt_names
|
||||
if not self.opts.get(opt)]
|
||||
if missing:
|
||||
raise exceptions.AuthPluginOptionsMissing(missing)
|
||||
|
||||
@abc.abstractmethod
|
||||
def token_and_endpoint(self, endpoint_type, service_type):
|
||||
"""Return token and endpoint.
|
||||
|
||||
:param service_type: Service type of the endpoint
|
||||
:type service_type: string
|
||||
:param endpoint_type: Type of endpoint.
|
||||
Possible values: public or publicURL,
|
||||
internal or internalURL,
|
||||
admin or adminURL
|
||||
:type endpoint_type: string
|
||||
:returns: tuple of token and endpoint strings
|
||||
:raises: EndpointException
|
||||
"""
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright 2013 Spanish National Research Council.
|
||||
# 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.
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
import abc
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import six
|
||||
from stevedore import extension
|
||||
|
||||
from karborclient.common.apiclient import exceptions
|
||||
|
||||
|
||||
_discovered_plugins = {}
|
||||
|
||||
|
||||
def discover_auth_systems():
|
||||
"""Discover the available auth-systems.
|
||||
|
||||
This won't take into account the old style auth-systems.
|
||||
"""
|
||||
global _discovered_plugins
|
||||
_discovered_plugins = {}
|
||||
|
||||
def add_plugin(ext):
|
||||
_discovered_plugins[ext.name] = ext.plugin
|
||||
|
||||
ep_namespace = "karborclient.common.apiclient.auth"
|
||||
mgr = extension.ExtensionManager(ep_namespace)
|
||||
mgr.map(add_plugin)
|
||||
|
||||
|
||||
def load_auth_system_opts(parser):
|
||||
"""Load options needed by the available auth-systems into a parser.
|
||||
|
||||
This function will try to populate the parser with options from the
|
||||
available plugins.
|
||||
"""
|
||||
group = parser.add_argument_group("Common auth options")
|
||||
BaseAuthPlugin.add_common_opts(group)
|
||||
for name, auth_plugin in _discovered_plugins.items():
|
||||
group = parser.add_argument_group(
|
||||
"Auth-system '%s' options" % name,
|
||||
conflict_handler="resolve")
|
||||
auth_plugin.add_opts(group)
|
||||
|
||||
|
||||
def load_plugin(auth_system):
|
||||
try:
|
||||
plugin_class = _discovered_plugins[auth_system]
|
||||
except KeyError:
|
||||
raise exceptions.AuthSystemNotFound(auth_system)
|
||||
return plugin_class(auth_system=auth_system)
|
||||
|
||||
|
||||
def load_plugin_from_args(args):
|
||||
"""Load required plugin and populate it with options.
|
||||
|
||||
Try to guess auth system if it is not specified. Systems are tried in
|
||||
alphabetical order.
|
||||
|
||||
:type args: argparse.Namespace
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
auth_system = args.os_auth_system
|
||||
if auth_system:
|
||||
plugin = load_plugin(auth_system)
|
||||
plugin.parse_opts(args)
|
||||
plugin.sufficient_options()
|
||||
return plugin
|
||||
|
||||
for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)):
|
||||
plugin_class = _discovered_plugins[plugin_auth_system]
|
||||
plugin = plugin_class()
|
||||
plugin.parse_opts(args)
|
||||
try:
|
||||
plugin.sufficient_options()
|
||||
except exceptions.AuthPluginOptionsMissing:
|
||||
continue
|
||||
return plugin
|
||||
raise exceptions.AuthPluginOptionsMissing(["auth_system"])
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseAuthPlugin(object):
|
||||
"""Base class for authentication plugins.
|
||||
|
||||
An authentication plugin needs to override at least the authenticate
|
||||
method to be a valid plugin.
|
||||
"""
|
||||
|
||||
auth_system = None
|
||||
opt_names = []
|
||||
common_opt_names = [
|
||||
"auth_system",
|
||||
"username",
|
||||
"password",
|
||||
"tenant_name",
|
||||
"token",
|
||||
"auth_url",
|
||||
]
|
||||
|
||||
def __init__(self, auth_system=None, **kwargs):
|
||||
self.auth_system = auth_system or self.auth_system
|
||||
self.opts = dict((name, kwargs.get(name))
|
||||
for name in self.opt_names)
|
||||
|
||||
@staticmethod
|
||||
def _parser_add_opt(parser, opt):
|
||||
"""Add an option to parser in two variants.
|
||||
|
||||
:param opt: option name (with underscores)
|
||||
"""
|
||||
dashed_opt = opt.replace("_", "-")
|
||||
env_var = "OS_%s" % opt.upper()
|
||||
arg_default = os.environ.get(env_var, "")
|
||||
arg_help = "Defaults to env[%s]." % env_var
|
||||
parser.add_argument(
|
||||
"--os-%s" % dashed_opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
default=arg_default,
|
||||
help=arg_help)
|
||||
parser.add_argument(
|
||||
"--os_%s" % opt,
|
||||
metavar="<%s>" % dashed_opt,
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
@classmethod
|
||||
def add_opts(cls, parser):
|
||||
"""Populate the parser with the options for this plugin."""
|
||||
for opt in cls.opt_names:
|
||||
# use `BaseAuthPlugin.common_opt_names` since it is never
|
||||
# changed in child classes
|
||||
if opt not in BaseAuthPlugin.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@classmethod
|
||||
def add_common_opts(cls, parser):
|
||||
"""Add options that are common for several plugins."""
|
||||
for opt in cls.common_opt_names:
|
||||
cls._parser_add_opt(parser, opt)
|
||||
|
||||
@staticmethod
|
||||
def get_opt(opt_name, args):
|
||||
"""Return option name and value.
|
||||
|
||||
:param opt_name: name of the option, e.g., "username"
|
||||
:param args: parsed arguments
|
||||
"""
|
||||
return (opt_name, getattr(args, "os_%s" % opt_name, None))
|
||||
|
||||
def parse_opts(self, args):
|
||||
"""Parse the actual auth-system options if any.
|
||||
|
||||
This method is expected to populate the attribute `self.opts` with a
|
||||
dict containing the options and values needed to make authentication.
|
||||
"""
|
||||
self.opts.update(dict(self.get_opt(opt_name, args)
|
||||
for opt_name in self.opt_names))
|
||||
|
||||
def authenticate(self, http_client):
|
||||
"""Authenticate using plugin defined method.
|
||||
|
||||
The method usually analyses `self.opts` and performs
|
||||
a request to authentication server.
|
||||
|
||||
:param http_client: client object that needs authentication
|
||||
:type http_client: HTTPClient
|
||||
:raises: AuthorizationFailure
|
||||
"""
|
||||
self.sufficient_options()
|
||||
self._do_authenticate(http_client)
|
||||
|
||||
@abc.abstractmethod
|
||||
def _do_authenticate(self, http_client):
|
||||
"""Protected method for authentication."""
|
||||
|
||||
def sufficient_options(self):
|
||||
"""Check if all required options are present.
|
||||
|
||||
:raises: AuthPluginOptionsMissing
|
||||
"""
|
||||
missing = [opt
|
||||
for opt in self.opt_names
|
||||
if not self.opts.get(opt)]
|
||||
if missing:
|
||||
raise exceptions.AuthPluginOptionsMissing(missing)
|
||||
|
||||
@abc.abstractmethod
|
||||
def token_and_endpoint(self, endpoint_type, service_type):
|
||||
"""Return token and endpoint.
|
||||
|
||||
:param service_type: Service type of the endpoint
|
||||
:type service_type: string
|
||||
:param endpoint_type: Type of endpoint.
|
||||
Possible values: public or publicURL,
|
||||
internal or internalURL,
|
||||
admin or adminURL
|
||||
:type endpoint_type: string
|
||||
:returns: tuple of token and endpoint strings
|
||||
:raises: EndpointException
|
||||
"""
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,365 +1,359 @@
|
||||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2011 Piston Cloud Computing, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 Grid Dynamics
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""
|
||||
OpenStack Client interface. Handles the REST calls and responses.
|
||||
"""
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
import requests
|
||||
|
||||
from smaugclient.i18n import _
|
||||
from smaugclient.openstack.common.apiclient import exceptions
|
||||
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
"""This client handles sending HTTP requests to OpenStack servers.
|
||||
|
||||
Features:
|
||||
|
||||
- share authentication information between several clients to different
|
||||
services (e.g., for compute and image clients);
|
||||
- reissue authentication request for expired tokens;
|
||||
- encode/decode JSON bodies;
|
||||
- raise exceptions on HTTP errors;
|
||||
- pluggable authentication;
|
||||
- store authentication information in a keyring;
|
||||
- store time spent for requests;
|
||||
- register clients for particular services, so one can use
|
||||
`http_client.identity` or `http_client.compute`;
|
||||
- log requests and responses in a format that is easy to copy-and-paste
|
||||
into terminal and send the same request with curl.
|
||||
"""
|
||||
|
||||
user_agent = "smaugclient.openstack.common.apiclient"
|
||||
|
||||
def __init__(self,
|
||||
auth_plugin,
|
||||
region_name=None,
|
||||
endpoint_type="publicURL",
|
||||
original_ip=None,
|
||||
verify=True,
|
||||
cert=None,
|
||||
timeout=None,
|
||||
timings=False,
|
||||
keyring_saver=None,
|
||||
debug=False,
|
||||
user_agent=None,
|
||||
http=None):
|
||||
self.auth_plugin = auth_plugin
|
||||
|
||||
self.endpoint_type = endpoint_type
|
||||
self.region_name = region_name
|
||||
|
||||
self.original_ip = original_ip
|
||||
self.timeout = timeout
|
||||
self.verify = verify
|
||||
self.cert = cert
|
||||
|
||||
self.keyring_saver = keyring_saver
|
||||
self.debug = debug
|
||||
self.user_agent = user_agent or self.user_agent
|
||||
|
||||
self.times = [] # [("item", starttime, endtime), ...]
|
||||
self.timings = timings
|
||||
|
||||
# requests within the same session can reuse TCP connections from pool
|
||||
self.http = http or requests.Session()
|
||||
|
||||
self.cached_token = None
|
||||
|
||||
def _http_log_req(self, method, url, kwargs):
|
||||
if not self.debug:
|
||||
return
|
||||
|
||||
string_parts = [
|
||||
"curl -i",
|
||||
"-X '%s'" % method,
|
||||
"'%s'" % url,
|
||||
]
|
||||
|
||||
for element in kwargs['headers']:
|
||||
header = "-H '%s: %s'" % (element, kwargs['headers'][element])
|
||||
string_parts.append(header)
|
||||
|
||||
_logger.debug("REQ: %s" % " ".join(string_parts))
|
||||
if 'data' in kwargs:
|
||||
_logger.debug("REQ BODY: %s\n" % (kwargs['data']))
|
||||
|
||||
def _http_log_resp(self, resp):
|
||||
if not self.debug:
|
||||
return
|
||||
_logger.debug(
|
||||
"RESP: [%s] %s\n",
|
||||
resp.status_code,
|
||||
resp.headers)
|
||||
if resp._content_consumed:
|
||||
_logger.debug(
|
||||
"RESP BODY: %s\n",
|
||||
resp.text)
|
||||
|
||||
def serialize(self, kwargs):
|
||||
if kwargs.get('json') is not None:
|
||||
kwargs['headers']['Content-Type'] = 'application/json'
|
||||
kwargs['data'] = json.dumps(kwargs['json'])
|
||||
try:
|
||||
del kwargs['json']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def get_timings(self):
|
||||
return self.times
|
||||
|
||||
def reset_timings(self):
|
||||
self.times = []
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around `requests.Session.request` to handle tasks such as
|
||||
setting headers, JSON encoding/decoding, and error handling.
|
||||
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
requests.Session.request (such as `headers`) or `json`
|
||||
that will be encoded as JSON and used as `data` argument
|
||||
"""
|
||||
kwargs.setdefault("headers", kwargs.get("headers", {}))
|
||||
kwargs["headers"]["User-Agent"] = self.user_agent
|
||||
if self.original_ip:
|
||||
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
|
||||
self.original_ip, self.user_agent)
|
||||
if self.timeout is not None:
|
||||
kwargs.setdefault("timeout", self.timeout)
|
||||
kwargs.setdefault("verify", self.verify)
|
||||
if self.cert is not None:
|
||||
kwargs.setdefault("cert", self.cert)
|
||||
self.serialize(kwargs)
|
||||
|
||||
self._http_log_req(method, url, kwargs)
|
||||
if self.timings:
|
||||
start_time = time.time()
|
||||
resp = self.http.request(method, url, **kwargs)
|
||||
if self.timings:
|
||||
self.times.append(("%s %s" % (method, url),
|
||||
start_time, time.time()))
|
||||
self._http_log_resp(resp)
|
||||
|
||||
if resp.status_code >= 400:
|
||||
_logger.debug(
|
||||
"Request returned failure status: %s",
|
||||
resp.status_code)
|
||||
raise exceptions.from_response(resp, method, url)
|
||||
|
||||
return resp
|
||||
|
||||
@staticmethod
|
||||
def concat_url(endpoint, url):
|
||||
"""Concatenate endpoint and final URL.
|
||||
|
||||
E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
|
||||
"http://keystone/v2.0/tokens".
|
||||
|
||||
:param endpoint: the base URL
|
||||
:param url: the final URL
|
||||
"""
|
||||
return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
"""Send an http request using `client`'s endpoint and specified `url`.
|
||||
|
||||
If request was rejected as unauthorized (possibly because the token is
|
||||
expired), issue one authorization attempt and send the request once
|
||||
again.
|
||||
|
||||
:param client: instance of BaseClient descendant
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
`HTTPClient.request`
|
||||
"""
|
||||
|
||||
filter_args = {
|
||||
"endpoint_type": client.endpoint_type or self.endpoint_type,
|
||||
"service_type": client.service_type,
|
||||
}
|
||||
token, endpoint = (self.cached_token, client.cached_endpoint)
|
||||
just_authenticated = False
|
||||
if not (token and endpoint):
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
pass
|
||||
if not (token and endpoint):
|
||||
self.authenticate()
|
||||
just_authenticated = True
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
if not (token and endpoint):
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_("Cannot find endpoint or token for request"))
|
||||
|
||||
old_token_endpoint = (token, endpoint)
|
||||
kwargs.setdefault("headers", {})["X-Auth-Token"] = token
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
# Perform the request once. If we get Unauthorized, then it
|
||||
# might be because the auth token expired, so try to
|
||||
# re-authenticate and try again. If it still fails, bail.
|
||||
try:
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
except exceptions.Unauthorized as unauth_ex:
|
||||
if just_authenticated:
|
||||
raise
|
||||
self.cached_token = None
|
||||
client.cached_endpoint = None
|
||||
self.authenticate()
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
raise unauth_ex
|
||||
if (not (token and endpoint) or
|
||||
old_token_endpoint == (token, endpoint)):
|
||||
raise unauth_ex
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
kwargs["headers"]["X-Auth-Token"] = token
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
|
||||
def add_client(self, base_client_instance):
|
||||
"""Add a new instance of :class:`BaseClient` descendant.
|
||||
|
||||
`self` will store a reference to `base_client_instance`.
|
||||
|
||||
Example:
|
||||
|
||||
>>> def test_clients():
|
||||
... from keystoneclient.auth import keystone
|
||||
... from openstack.common.apiclient import client
|
||||
... auth = keystone.KeystoneAuthPlugin(
|
||||
... username="user", password="pass", tenant_name="tenant",
|
||||
... auth_url="http://auth:5000/v2.0")
|
||||
... openstack_client = client.HTTPClient(auth)
|
||||
... # create nova client
|
||||
... from novaclient.v1_1 import client
|
||||
... client.Client(openstack_client)
|
||||
... # create keystone client
|
||||
... from keystoneclient.v2_0 import client
|
||||
... client.Client(openstack_client)
|
||||
... # use them
|
||||
... openstack_client.identity.tenants.list()
|
||||
... openstack_client.compute.servers.list()
|
||||
"""
|
||||
service_type = base_client_instance.service_type
|
||||
if service_type and not hasattr(self, service_type):
|
||||
setattr(self, service_type, base_client_instance)
|
||||
|
||||
def authenticate(self):
|
||||
self.auth_plugin.authenticate(self)
|
||||
# Store the authentication results in the keyring for later requests
|
||||
if self.keyring_saver:
|
||||
self.keyring_saver.save(self)
|
||||
|
||||
|
||||
class BaseClient(object):
|
||||
"""Top-level object to access the OpenStack API.
|
||||
|
||||
This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient`
|
||||
will handle a bunch of issues such as authentication.
|
||||
"""
|
||||
|
||||
service_type = None
|
||||
endpoint_type = None # "publicURL" will be used
|
||||
cached_endpoint = None
|
||||
|
||||
def __init__(self, http_client, extensions=None):
|
||||
self.http_client = http_client
|
||||
http_client.add_client(self)
|
||||
|
||||
# Add in any extensions...
|
||||
if extensions:
|
||||
for extension in extensions:
|
||||
if extension.manager_class:
|
||||
setattr(self, extension.name,
|
||||
extension.manager_class(self))
|
||||
|
||||
def client_request(self, method, url, **kwargs):
|
||||
return self.http_client.client_request(
|
||||
self, method, url, **kwargs)
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
return self.client_request("HEAD", url, **kwargs)
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self.client_request("GET", url, **kwargs)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
return self.client_request("POST", url, **kwargs)
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
return self.client_request("PUT", url, **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
return self.client_request("DELETE", url, **kwargs)
|
||||
|
||||
def patch(self, url, **kwargs):
|
||||
return self.client_request("PATCH", url, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_class(api_name, version, version_map):
|
||||
"""Returns the client class for the requested API version
|
||||
|
||||
:param api_name: the name of the API, e.g. 'compute', 'image', etc
|
||||
:param version: the requested API version
|
||||
:param version_map: a dict of client classes keyed by version
|
||||
:rtype: a client class for the requested API version
|
||||
"""
|
||||
try:
|
||||
client_path = version_map[str(version)]
|
||||
except (KeyError, ValueError):
|
||||
msg = _("Invalid %(api_name)s client version '%(version)s'. "
|
||||
"Must be one of: %(version_map)s") % {
|
||||
'api_name': api_name,
|
||||
'version': version,
|
||||
'version_map': ', '.join(version_map.keys())
|
||||
}
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
return importutils.import_class(client_path)
|
||||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2011 Piston Cloud Computing, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 Grid Dynamics
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""
|
||||
OpenStack Client interface. Handles the REST calls and responses.
|
||||
"""
|
||||
|
||||
# E0202: An attribute inherited from %s hide this method
|
||||
# pylint: disable=E0202
|
||||
|
||||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import importutils
|
||||
import requests
|
||||
|
||||
from karborclient.i18n import _
|
||||
from karborclient.openstack.common.apiclient import exceptions
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HTTPClient(object):
|
||||
"""This client handles sending HTTP requests to OpenStack servers.
|
||||
|
||||
Features:
|
||||
|
||||
- share authentication information between several clients to different
|
||||
services (e.g., for compute and image clients);
|
||||
- reissue authentication request for expired tokens;
|
||||
- encode/decode JSON bodies;
|
||||
- raise exceptions on HTTP errors;
|
||||
- pluggable authentication;
|
||||
- store authentication information in a keyring;
|
||||
- store time spent for requests;
|
||||
- register clients for particular services, so one can use
|
||||
`http_client.identity` or `http_client.compute`;
|
||||
- log requests and responses in a format that is easy to copy-and-paste
|
||||
into terminal and send the same request with curl.
|
||||
"""
|
||||
|
||||
user_agent = "karborclient.common.apiclient"
|
||||
|
||||
def __init__(self,
|
||||
auth_plugin,
|
||||
region_name=None,
|
||||
endpoint_type="publicURL",
|
||||
original_ip=None,
|
||||
verify=True,
|
||||
cert=None,
|
||||
timeout=None,
|
||||
timings=False,
|
||||
keyring_saver=None,
|
||||
debug=False,
|
||||
user_agent=None,
|
||||
http=None):
|
||||
self.auth_plugin = auth_plugin
|
||||
|
||||
self.endpoint_type = endpoint_type
|
||||
self.region_name = region_name
|
||||
|
||||
self.original_ip = original_ip
|
||||
self.timeout = timeout
|
||||
self.verify = verify
|
||||
self.cert = cert
|
||||
|
||||
self.keyring_saver = keyring_saver
|
||||
self.debug = debug
|
||||
self.user_agent = user_agent or self.user_agent
|
||||
|
||||
self.times = [] # [("item", starttime, endtime), ...]
|
||||
self.timings = timings
|
||||
|
||||
# requests within the same session can reuse TCP connections from pool
|
||||
self.http = http or requests.Session()
|
||||
|
||||
self.cached_token = None
|
||||
|
||||
def _http_log_req(self, method, url, kwargs):
|
||||
if not self.debug:
|
||||
return
|
||||
|
||||
string_parts = [
|
||||
"curl -i",
|
||||
"-X '%s'" % method,
|
||||
"'%s'" % url,
|
||||
]
|
||||
|
||||
for element in kwargs['headers']:
|
||||
header = "-H '%s: %s'" % (element, kwargs['headers'][element])
|
||||
string_parts.append(header)
|
||||
|
||||
_logger.debug("REQ: %s" % " ".join(string_parts))
|
||||
if 'data' in kwargs:
|
||||
_logger.debug("REQ BODY: %s\n" % (kwargs['data']))
|
||||
|
||||
def _http_log_resp(self, resp):
|
||||
if not self.debug:
|
||||
return
|
||||
_logger.debug(
|
||||
"RESP: [%s] %s\n",
|
||||
resp.status_code,
|
||||
resp.headers)
|
||||
if resp._content_consumed:
|
||||
_logger.debug(
|
||||
"RESP BODY: %s\n",
|
||||
resp.text)
|
||||
|
||||
def serialize(self, kwargs):
|
||||
if kwargs.get('json') is not None:
|
||||
kwargs['headers']['Content-Type'] = 'application/json'
|
||||
kwargs['data'] = jsonutils.dumps(kwargs['json'])
|
||||
try:
|
||||
del kwargs['json']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def get_timings(self):
|
||||
return self.times
|
||||
|
||||
def reset_timings(self):
|
||||
self.times = []
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
"""Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around `requests.Session.request` to handle tasks such as
|
||||
setting headers, JSON encoding/decoding, and error handling.
|
||||
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
requests.Session.request (such as `headers`) or `json`
|
||||
that will be encoded as JSON and used as `data` argument
|
||||
"""
|
||||
kwargs.setdefault("headers", kwargs.get("headers", {}))
|
||||
kwargs["headers"]["User-Agent"] = self.user_agent
|
||||
if self.original_ip:
|
||||
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
|
||||
self.original_ip, self.user_agent)
|
||||
if self.timeout is not None:
|
||||
kwargs.setdefault("timeout", self.timeout)
|
||||
kwargs.setdefault("verify", self.verify)
|
||||
if self.cert is not None:
|
||||
kwargs.setdefault("cert", self.cert)
|
||||
self.serialize(kwargs)
|
||||
|
||||
self._http_log_req(method, url, kwargs)
|
||||
if self.timings:
|
||||
start_time = time.time()
|
||||
resp = self.http.request(method, url, **kwargs)
|
||||
if self.timings:
|
||||
self.times.append(("%s %s" % (method, url),
|
||||
start_time, time.time()))
|
||||
self._http_log_resp(resp)
|
||||
|
||||
if resp.status_code >= 400:
|
||||
_logger.debug(
|
||||
"Request returned failure status: %s",
|
||||
resp.status_code)
|
||||
raise exceptions.from_response(resp, method, url)
|
||||
|
||||
return resp
|
||||
|
||||
@staticmethod
|
||||
def concat_url(endpoint, url):
|
||||
"""Concatenate endpoint and final URL.
|
||||
|
||||
E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
|
||||
"http://keystone/v2.0/tokens".
|
||||
|
||||
:param endpoint: the base URL
|
||||
:param url: the final URL
|
||||
"""
|
||||
return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
"""Send an http request using `client`'s endpoint and specified `url`.
|
||||
|
||||
If request was rejected as unauthorized (possibly because the token is
|
||||
expired), issue one authorization attempt and send the request once
|
||||
again.
|
||||
|
||||
:param client: instance of BaseClient descendant
|
||||
:param method: method of HTTP request
|
||||
:param url: URL of HTTP request
|
||||
:param kwargs: any other parameter that can be passed to
|
||||
`HTTPClient.request`
|
||||
"""
|
||||
|
||||
filter_args = {
|
||||
"endpoint_type": client.endpoint_type or self.endpoint_type,
|
||||
"service_type": client.service_type,
|
||||
}
|
||||
token, endpoint = (self.cached_token, client.cached_endpoint)
|
||||
just_authenticated = False
|
||||
if not (token and endpoint):
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
pass
|
||||
if not (token and endpoint):
|
||||
self.authenticate()
|
||||
just_authenticated = True
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
if not (token and endpoint):
|
||||
raise exceptions.AuthorizationFailure(
|
||||
_("Cannot find endpoint or token for request"))
|
||||
|
||||
old_token_endpoint = (token, endpoint)
|
||||
kwargs.setdefault("headers", {})["X-Auth-Token"] = token
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
# Perform the request once. If we get Unauthorized, then it
|
||||
# might be because the auth token expired, so try to
|
||||
# re-authenticate and try again. If it still fails, bail.
|
||||
try:
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
except exceptions.Unauthorized as unauth_ex:
|
||||
if just_authenticated:
|
||||
raise
|
||||
self.cached_token = None
|
||||
client.cached_endpoint = None
|
||||
self.authenticate()
|
||||
try:
|
||||
token, endpoint = self.auth_plugin.token_and_endpoint(
|
||||
**filter_args)
|
||||
except exceptions.EndpointException:
|
||||
raise unauth_ex
|
||||
if (not (token and endpoint) or
|
||||
old_token_endpoint == (token, endpoint)):
|
||||
raise unauth_ex
|
||||
self.cached_token = token
|
||||
client.cached_endpoint = endpoint
|
||||
kwargs["headers"]["X-Auth-Token"] = token
|
||||
return self.request(
|
||||
method, self.concat_url(endpoint, url), **kwargs)
|
||||
|
||||
def add_client(self, base_client_instance):
|
||||
"""Add a new instance of :class:`BaseClient` descendant.
|
||||
|
||||
`self` will store a reference to `base_client_instance`.
|
||||
|
||||
Example:
|
||||
|
||||
>>> def test_clients():
|
||||
... from keystoneclient.auth import keystone
|
||||
... from karborclient.common.apiclient import client
|
||||
... auth = keystone.KeystoneAuthPlugin(
|
||||
... username="user", password="pass", tenant_name="tenant",
|
||||
... auth_url="http://auth:5000/v2.0")
|
||||
... openstack_client = client.HTTPClient(auth)
|
||||
... # create nova client
|
||||
... from novaclient.v1_1 import client
|
||||
... client.Client(openstack_client)
|
||||
... # create keystone client
|
||||
... from keystoneclient.v2_0 import client
|
||||
... client.Client(openstack_client)
|
||||
... # use them
|
||||
... openstack_client.identity.tenants.list()
|
||||
... openstack_client.compute.servers.list()
|
||||
"""
|
||||
service_type = base_client_instance.service_type
|
||||
if service_type and not hasattr(self, service_type):
|
||||
setattr(self, service_type, base_client_instance)
|
||||
|
||||
def authenticate(self):
|
||||
self.auth_plugin.authenticate(self)
|
||||
# Store the authentication results in the keyring for later requests
|
||||
if self.keyring_saver:
|
||||
self.keyring_saver.save(self)
|
||||
|
||||
|
||||
class BaseClient(object):
|
||||
"""Top-level object to access the OpenStack API.
|
||||
|
||||
This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient`
|
||||
will handle a bunch of issues such as authentication.
|
||||
"""
|
||||
|
||||
service_type = None
|
||||
endpoint_type = None # "publicURL" will be used
|
||||
cached_endpoint = None
|
||||
|
||||
def __init__(self, http_client, extensions=None):
|
||||
self.http_client = http_client
|
||||
http_client.add_client(self)
|
||||
|
||||
# Add in any extensions...
|
||||
if extensions:
|
||||
for extension in extensions:
|
||||
if extension.manager_class:
|
||||
setattr(self, extension.name,
|
||||
extension.manager_class(self))
|
||||
|
||||
def client_request(self, method, url, **kwargs):
|
||||
return self.http_client.client_request(
|
||||
self, method, url, **kwargs)
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
return self.client_request("HEAD", url, **kwargs)
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self.client_request("GET", url, **kwargs)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
return self.client_request("POST", url, **kwargs)
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
return self.client_request("PUT", url, **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
return self.client_request("DELETE", url, **kwargs)
|
||||
|
||||
def patch(self, url, **kwargs):
|
||||
return self.client_request("PATCH", url, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_class(api_name, version, version_map):
|
||||
"""Returns the client class for the requested API version
|
||||
|
||||
:param api_name: the name of the API, e.g. 'compute', 'image', etc
|
||||
:param version: the requested API version
|
||||
:param version_map: a dict of client classes keyed by version
|
||||
:rtype: a client class for the requested API version
|
||||
"""
|
||||
try:
|
||||
client_path = version_map[str(version)]
|
||||
except (KeyError, ValueError):
|
||||
msg = _("Invalid %(api_name)s client version '%(version)s'. "
|
||||
"Must be one of: %(version_map)s") % {
|
||||
'api_name': api_name,
|
||||
'version': version,
|
||||
'version_map': ', '.join(version_map.keys())}
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
return importutils.import_class(client_path)
|
||||
@@ -1,465 +1,462 @@
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Exception definitions.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from smaugclient.i18n import _
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""The base exception class for all exceptions this library raises.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MissingArgs(ClientException):
|
||||
"""Supplied arguments are not sufficient for calling a function."""
|
||||
def __init__(self, missing):
|
||||
self.missing = missing
|
||||
msg = _("Missing arguments: %s") % ", ".join(missing)
|
||||
super(MissingArgs, self).__init__(msg)
|
||||
|
||||
|
||||
class ValidationError(ClientException):
|
||||
"""Error in validation on API client side."""
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedVersion(ClientException):
|
||||
"""User is trying to use an unsupported version of the API."""
|
||||
pass
|
||||
|
||||
|
||||
class CommandError(ClientException):
|
||||
"""Error in CLI tool."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthorizationFailure(ClientException):
|
||||
"""Cannot authorize API client."""
|
||||
pass
|
||||
|
||||
|
||||
class ConnectionRefused(ClientException):
|
||||
"""Cannot connect to API service."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthPluginOptionsMissing(AuthorizationFailure):
|
||||
"""Auth plugin misses some options."""
|
||||
def __init__(self, opt_names):
|
||||
super(AuthPluginOptionsMissing, self).__init__(
|
||||
_("Authentication failed. Missing options: %s") %
|
||||
", ".join(opt_names))
|
||||
self.opt_names = opt_names
|
||||
|
||||
|
||||
class AuthSystemNotFound(AuthorizationFailure):
|
||||
"""User has specified an AuthSystem that is not installed."""
|
||||
def __init__(self, auth_system):
|
||||
super(AuthSystemNotFound, self).__init__(
|
||||
_("AuthSystemNotFound: %s") % repr(auth_system))
|
||||
self.auth_system = auth_system
|
||||
|
||||
|
||||
class NoUniqueMatch(ClientException):
|
||||
"""Multiple entities found instead of one."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointException(ClientException):
|
||||
"""Something is rotten in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointNotFound(EndpointException):
|
||||
"""Could not find requested endpoint in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class AmbiguousEndpoints(EndpointException):
|
||||
"""Found more than one matching endpoint in Service Catalog."""
|
||||
def __init__(self, endpoints=None):
|
||||
super(AmbiguousEndpoints, self).__init__(
|
||||
_("AmbiguousEndpoints: %s") % repr(endpoints))
|
||||
self.endpoints = endpoints
|
||||
|
||||
|
||||
class HttpError(ClientException):
|
||||
"""The base exception class for all HTTP exceptions.
|
||||
"""
|
||||
http_status = 0
|
||||
message = _("HTTP Error")
|
||||
|
||||
def __init__(self, message=None, details=None,
|
||||
response=None, request_id=None,
|
||||
url=None, method=None, http_status=None):
|
||||
self.http_status = http_status or self.http_status
|
||||
self.message = message or self.message
|
||||
self.details = details
|
||||
self.request_id = request_id
|
||||
self.response = response
|
||||
self.url = url
|
||||
self.method = method
|
||||
formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
|
||||
if request_id:
|
||||
formatted_string += " (Request-ID: %s)" % request_id
|
||||
super(HttpError, self).__init__(formatted_string)
|
||||
|
||||
|
||||
class HTTPRedirection(HttpError):
|
||||
"""HTTP Redirection."""
|
||||
message = _("HTTP Redirection")
|
||||
|
||||
|
||||
class HTTPClientError(HttpError):
|
||||
"""Client-side HTTP error.
|
||||
|
||||
Exception for cases in which the client seems to have erred.
|
||||
"""
|
||||
message = _("HTTP Client Error")
|
||||
|
||||
|
||||
class HttpServerError(HttpError):
|
||||
"""Server-side HTTP error.
|
||||
|
||||
Exception for cases in which the server is aware that it has
|
||||
erred or is incapable of performing the request.
|
||||
"""
|
||||
message = _("HTTP Server Error")
|
||||
|
||||
|
||||
class MultipleChoices(HTTPRedirection):
|
||||
"""HTTP 300 - Multiple Choices.
|
||||
|
||||
Indicates multiple options for the resource that the client may follow.
|
||||
"""
|
||||
|
||||
http_status = 300
|
||||
message = _("Multiple Choices")
|
||||
|
||||
|
||||
class BadRequest(HTTPClientError):
|
||||
"""HTTP 400 - Bad Request.
|
||||
|
||||
The request cannot be fulfilled due to bad syntax.
|
||||
"""
|
||||
http_status = 400
|
||||
message = _("Bad Request")
|
||||
|
||||
|
||||
class Unauthorized(HTTPClientError):
|
||||
"""HTTP 401 - Unauthorized.
|
||||
|
||||
Similar to 403 Forbidden, but specifically for use when authentication
|
||||
is required and has failed or has not yet been provided.
|
||||
"""
|
||||
http_status = 401
|
||||
message = _("Unauthorized")
|
||||
|
||||
|
||||
class PaymentRequired(HTTPClientError):
|
||||
"""HTTP 402 - Payment Required.
|
||||
|
||||
Reserved for future use.
|
||||
"""
|
||||
http_status = 402
|
||||
message = _("Payment Required")
|
||||
|
||||
|
||||
class Forbidden(HTTPClientError):
|
||||
"""HTTP 403 - Forbidden.
|
||||
|
||||
The request was a valid request, but the server is refusing to respond
|
||||
to it.
|
||||
"""
|
||||
http_status = 403
|
||||
message = _("Forbidden")
|
||||
|
||||
|
||||
class NotFound(HTTPClientError):
|
||||
"""HTTP 404 - Not Found.
|
||||
|
||||
The requested resource could not be found but may be available again
|
||||
in the future.
|
||||
"""
|
||||
http_status = 404
|
||||
message = _("Not Found")
|
||||
|
||||
|
||||
class MethodNotAllowed(HTTPClientError):
|
||||
"""HTTP 405 - Method Not Allowed.
|
||||
|
||||
A request was made of a resource using a request method not supported
|
||||
by that resource.
|
||||
"""
|
||||
http_status = 405
|
||||
message = _("Method Not Allowed")
|
||||
|
||||
|
||||
class NotAcceptable(HTTPClientError):
|
||||
"""HTTP 406 - Not Acceptable.
|
||||
|
||||
The requested resource is only capable of generating content not
|
||||
acceptable according to the Accept headers sent in the request.
|
||||
"""
|
||||
http_status = 406
|
||||
message = _("Not Acceptable")
|
||||
|
||||
|
||||
class ProxyAuthenticationRequired(HTTPClientError):
|
||||
"""HTTP 407 - Proxy Authentication Required.
|
||||
|
||||
The client must first authenticate itself with the proxy.
|
||||
"""
|
||||
http_status = 407
|
||||
message = _("Proxy Authentication Required")
|
||||
|
||||
|
||||
class RequestTimeout(HTTPClientError):
|
||||
"""HTTP 408 - Request Timeout.
|
||||
|
||||
The server timed out waiting for the request.
|
||||
"""
|
||||
http_status = 408
|
||||
message = _("Request Timeout")
|
||||
|
||||
|
||||
class Conflict(HTTPClientError):
|
||||
"""HTTP 409 - Conflict.
|
||||
|
||||
Indicates that the request could not be processed because of conflict
|
||||
in the request, such as an edit conflict.
|
||||
"""
|
||||
http_status = 409
|
||||
message = _("Conflict")
|
||||
|
||||
|
||||
class Gone(HTTPClientError):
|
||||
"""HTTP 410 - Gone.
|
||||
|
||||
Indicates that the resource requested is no longer available and will
|
||||
not be available again.
|
||||
"""
|
||||
http_status = 410
|
||||
message = _("Gone")
|
||||
|
||||
|
||||
class LengthRequired(HTTPClientError):
|
||||
"""HTTP 411 - Length Required.
|
||||
|
||||
The request did not specify the length of its content, which is
|
||||
required by the requested resource.
|
||||
"""
|
||||
http_status = 411
|
||||
message = _("Length Required")
|
||||
|
||||
|
||||
class PreconditionFailed(HTTPClientError):
|
||||
"""HTTP 412 - Precondition Failed.
|
||||
|
||||
The server does not meet one of the preconditions that the requester
|
||||
put on the request.
|
||||
"""
|
||||
http_status = 412
|
||||
message = _("Precondition Failed")
|
||||
|
||||
|
||||
class RequestEntityTooLarge(HTTPClientError):
|
||||
"""HTTP 413 - Request Entity Too Large.
|
||||
|
||||
The request is larger than the server is willing or able to process.
|
||||
"""
|
||||
http_status = 413
|
||||
message = _("Request Entity Too Large")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
self.retry_after = int(kwargs.pop('retry_after'))
|
||||
except (KeyError, ValueError):
|
||||
self.retry_after = 0
|
||||
|
||||
super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RequestUriTooLong(HTTPClientError):
|
||||
"""HTTP 414 - Request-URI Too Long.
|
||||
|
||||
The URI provided was too long for the server to process.
|
||||
"""
|
||||
http_status = 414
|
||||
message = _("Request-URI Too Long")
|
||||
|
||||
|
||||
class UnsupportedMediaType(HTTPClientError):
|
||||
"""HTTP 415 - Unsupported Media Type.
|
||||
|
||||
The request entity has a media type which the server or resource does
|
||||
not support.
|
||||
"""
|
||||
http_status = 415
|
||||
message = _("Unsupported Media Type")
|
||||
|
||||
|
||||
class RequestedRangeNotSatisfiable(HTTPClientError):
|
||||
"""HTTP 416 - Requested Range Not Satisfiable.
|
||||
|
||||
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 ExpectationFailed(HTTPClientError):
|
||||
"""HTTP 417 - Expectation Failed.
|
||||
|
||||
The server cannot meet the requirements of the Expect request-header field.
|
||||
"""
|
||||
http_status = 417
|
||||
message = _("Expectation Failed")
|
||||
|
||||
|
||||
class UnprocessableEntity(HTTPClientError):
|
||||
"""HTTP 422 - Unprocessable Entity.
|
||||
|
||||
The request was well-formed but was unable to be followed due to semantic
|
||||
errors.
|
||||
"""
|
||||
http_status = 422
|
||||
message = _("Unprocessable Entity")
|
||||
|
||||
|
||||
class InternalServerError(HttpServerError):
|
||||
"""HTTP 500 - Internal Server Error.
|
||||
|
||||
A generic error message, given when no more specific message is suitable.
|
||||
"""
|
||||
http_status = 500
|
||||
message = _("Internal Server Error")
|
||||
|
||||
|
||||
# NotImplemented is a python keyword.
|
||||
class HttpNotImplemented(HttpServerError):
|
||||
"""HTTP 501 - Not Implemented.
|
||||
|
||||
The server either does not recognize the request method, or it lacks
|
||||
the ability to fulfill the request.
|
||||
"""
|
||||
http_status = 501
|
||||
message = _("Not Implemented")
|
||||
|
||||
|
||||
class BadGateway(HttpServerError):
|
||||
"""HTTP 502 - Bad Gateway.
|
||||
|
||||
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 ServiceUnavailable(HttpServerError):
|
||||
"""HTTP 503 - Service Unavailable.
|
||||
|
||||
The server is currently unavailable.
|
||||
"""
|
||||
http_status = 503
|
||||
message = _("Service Unavailable")
|
||||
|
||||
|
||||
class GatewayTimeout(HttpServerError):
|
||||
"""HTTP 504 - Gateway Timeout.
|
||||
|
||||
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(HttpServerError):
|
||||
"""HTTP 505 - HttpVersion Not Supported.
|
||||
|
||||
The server does not support the HTTP protocol version used in the request.
|
||||
"""
|
||||
http_status = 505
|
||||
message = _("HTTP Version Not Supported")
|
||||
|
||||
|
||||
# _code_map contains all the classes that have http_status attribute.
|
||||
_code_map = dict(
|
||||
(getattr(obj, 'http_status', None), obj)
|
||||
for name, obj in six.iteritems(vars(sys.modules[__name__]))
|
||||
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
|
||||
)
|
||||
|
||||
|
||||
def from_response(response, method, url):
|
||||
"""Returns an instance of :class:`HttpError` or subclass based on response.
|
||||
|
||||
:param response: instance of `requests.Response` class
|
||||
:param method: HTTP method used for request
|
||||
:param url: URL used for request
|
||||
"""
|
||||
|
||||
req_id = response.headers.get("x-openstack-request-id")
|
||||
#NOTE(hdd) true for older versions of nova and cinder
|
||||
if not req_id:
|
||||
req_id = response.headers.get("x-compute-request-id")
|
||||
kwargs = {
|
||||
"http_status": response.status_code,
|
||||
"response": response,
|
||||
"method": method,
|
||||
"url": url,
|
||||
"request_id": req_id,
|
||||
}
|
||||
if "retry-after" in response.headers:
|
||||
kwargs["retry_after"] = response.headers["retry-after"]
|
||||
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
if content_type.startswith("application/json"):
|
||||
try:
|
||||
body = response.json()
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(body, dict):
|
||||
error = list(body.values())[0]
|
||||
kwargs["message"] = error.get("message")
|
||||
kwargs["details"] = error.get("details")
|
||||
elif content_type.startswith("text/"):
|
||||
kwargs["details"] = response.text
|
||||
|
||||
try:
|
||||
cls = _code_map[response.status_code]
|
||||
except KeyError:
|
||||
if 500 <= response.status_code < 600:
|
||||
cls = HttpServerError
|
||||
elif 400 <= response.status_code < 500:
|
||||
cls = HTTPClientError
|
||||
else:
|
||||
cls = HttpError
|
||||
return cls(**kwargs)
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
# Copyright 2013 Alessio Ababilov
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Exception definitions.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
|
||||
from karborclient.i18n import _
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""The base exception class for all exceptions this library raises."""
|
||||
pass
|
||||
|
||||
|
||||
class MissingArgs(ClientException):
|
||||
"""Supplied arguments are not sufficient for calling a function."""
|
||||
def __init__(self, missing):
|
||||
self.missing = missing
|
||||
msg = _("Missing arguments: %s") % ", ".join(missing)
|
||||
super(MissingArgs, self).__init__(msg)
|
||||
|
||||
|
||||
class ValidationError(ClientException):
|
||||
"""Error in validation on API client side."""
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedVersion(ClientException):
|
||||
"""User is trying to use an unsupported version of the API."""
|
||||
pass
|
||||
|
||||
|
||||
class CommandError(ClientException):
|
||||
"""Error in CLI tool."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthorizationFailure(ClientException):
|
||||
"""Cannot authorize API client."""
|
||||
pass
|
||||
|
||||
|
||||
class ConnectionRefused(ClientException):
|
||||
"""Cannot connect to API service."""
|
||||
pass
|
||||
|
||||
|
||||
class AuthPluginOptionsMissing(AuthorizationFailure):
|
||||
"""Auth plugin misses some options."""
|
||||
def __init__(self, opt_names):
|
||||
super(AuthPluginOptionsMissing, self).__init__(
|
||||
_("Authentication failed. Missing options: %s") %
|
||||
", ".join(opt_names))
|
||||
self.opt_names = opt_names
|
||||
|
||||
|
||||
class AuthSystemNotFound(AuthorizationFailure):
|
||||
"""User has specified an AuthSystem that is not installed."""
|
||||
def __init__(self, auth_system):
|
||||
super(AuthSystemNotFound, self).__init__(
|
||||
_("AuthSystemNotFound: %s") % repr(auth_system))
|
||||
self.auth_system = auth_system
|
||||
|
||||
|
||||
class NoUniqueMatch(ClientException):
|
||||
"""Multiple entities found instead of one."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointException(ClientException):
|
||||
"""Something is rotten in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class EndpointNotFound(EndpointException):
|
||||
"""Could not find requested endpoint in Service Catalog."""
|
||||
pass
|
||||
|
||||
|
||||
class AmbiguousEndpoints(EndpointException):
|
||||
"""Found more than one matching endpoint in Service Catalog."""
|
||||
def __init__(self, endpoints=None):
|
||||
super(AmbiguousEndpoints, self).__init__(
|
||||
_("AmbiguousEndpoints: %s") % repr(endpoints))
|
||||
self.endpoints = endpoints
|
||||
|
||||
|
||||
class HttpError(ClientException):
|
||||
"""The base exception class for all HTTP exceptions."""
|
||||
http_status = 0
|
||||
message = _("HTTP Error")
|
||||
|
||||
def __init__(self, message=None, details=None,
|
||||
response=None, request_id=None,
|
||||
url=None, method=None, http_status=None):
|
||||
self.http_status = http_status or self.http_status
|
||||
self.message = message or self.message
|
||||
self.details = details
|
||||
self.request_id = request_id
|
||||
self.response = response
|
||||
self.url = url
|
||||
self.method = method
|
||||
formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
|
||||
if request_id:
|
||||
formatted_string += " (Request-ID: %s)" % request_id
|
||||
super(HttpError, self).__init__(formatted_string)
|
||||
|
||||
|
||||
class HTTPRedirection(HttpError):
|
||||
"""HTTP Redirection."""
|
||||
message = _("HTTP Redirection")
|
||||
|
||||
|
||||
class HTTPClientError(HttpError):
|
||||
"""Client-side HTTP error.
|
||||
|
||||
Exception for cases in which the client seems to have erred.
|
||||
"""
|
||||
message = _("HTTP Client Error")
|
||||
|
||||
|
||||
class HttpServerError(HttpError):
|
||||
"""Server-side HTTP error.
|
||||
|
||||
Exception for cases in which the server is aware that it has
|
||||
erred or is incapable of performing the request.
|
||||
"""
|
||||
message = _("HTTP Server Error")
|
||||
|
||||
|
||||
class MultipleChoices(HTTPRedirection):
|
||||
"""HTTP 300 - Multiple Choices.
|
||||
|
||||
Indicates multiple options for the resource that the client may follow.
|
||||
"""
|
||||
|
||||
http_status = 300
|
||||
message = _("Multiple Choices")
|
||||
|
||||
|
||||
class BadRequest(HTTPClientError):
|
||||
"""HTTP 400 - Bad Request.
|
||||
|
||||
The request cannot be fulfilled due to bad syntax.
|
||||
"""
|
||||
http_status = 400
|
||||
message = _("Bad Request")
|
||||
|
||||
|
||||
class Unauthorized(HTTPClientError):
|
||||
"""HTTP 401 - Unauthorized.
|
||||
|
||||
Similar to 403 Forbidden, but specifically for use when authentication
|
||||
is required and has failed or has not yet been provided.
|
||||
"""
|
||||
http_status = 401
|
||||
message = _("Unauthorized")
|
||||
|
||||
|
||||
class PaymentRequired(HTTPClientError):
|
||||
"""HTTP 402 - Payment Required.
|
||||
|
||||
Reserved for future use.
|
||||
"""
|
||||
http_status = 402
|
||||
message = _("Payment Required")
|
||||
|
||||
|
||||
class Forbidden(HTTPClientError):
|
||||
"""HTTP 403 - Forbidden.
|
||||
|
||||
The request was a valid request, but the server is refusing to respond
|
||||
to it.
|
||||
"""
|
||||
http_status = 403
|
||||
message = _("Forbidden")
|
||||
|
||||
|
||||
class NotFound(HTTPClientError):
|
||||
"""HTTP 404 - Not Found.
|
||||
|
||||
The requested resource could not be found but may be available again
|
||||
in the future.
|
||||
"""
|
||||
http_status = 404
|
||||
message = _("Not Found")
|
||||
|
||||
|
||||
class MethodNotAllowed(HTTPClientError):
|
||||
"""HTTP 405 - Method Not Allowed.
|
||||
|
||||
A request was made of a resource using a request method not supported
|
||||
by that resource.
|
||||
"""
|
||||
http_status = 405
|
||||
message = _("Method Not Allowed")
|
||||
|
||||
|
||||
class NotAcceptable(HTTPClientError):
|
||||
"""HTTP 406 - Not Acceptable.
|
||||
|
||||
The requested resource is only capable of generating content not
|
||||
acceptable according to the Accept headers sent in the request.
|
||||
"""
|
||||
http_status = 406
|
||||
message = _("Not Acceptable")
|
||||
|
||||
|
||||
class ProxyAuthenticationRequired(HTTPClientError):
|
||||
"""HTTP 407 - Proxy Authentication Required.
|
||||
|
||||
The client must first authenticate itself with the proxy.
|
||||
"""
|
||||
http_status = 407
|
||||
message = _("Proxy Authentication Required")
|
||||
|
||||
|
||||
class RequestTimeout(HTTPClientError):
|
||||
"""HTTP 408 - Request Timeout.
|
||||
|
||||
The server timed out waiting for the request.
|
||||
"""
|
||||
http_status = 408
|
||||
message = _("Request Timeout")
|
||||
|
||||
|
||||
class Conflict(HTTPClientError):
|
||||
"""HTTP 409 - Conflict.
|
||||
|
||||
Indicates that the request could not be processed because of conflict
|
||||
in the request, such as an edit conflict.
|
||||
"""
|
||||
http_status = 409
|
||||
message = _("Conflict")
|
||||
|
||||
|
||||
class Gone(HTTPClientError):
|
||||
"""HTTP 410 - Gone.
|
||||
|
||||
Indicates that the resource requested is no longer available and will
|
||||
not be available again.
|
||||
"""
|
||||
http_status = 410
|
||||
message = _("Gone")
|
||||
|
||||
|
||||
class LengthRequired(HTTPClientError):
|
||||
"""HTTP 411 - Length Required.
|
||||
|
||||
The request did not specify the length of its content, which is
|
||||
required by the requested resource.
|
||||
"""
|
||||
http_status = 411
|
||||
message = _("Length Required")
|
||||
|
||||
|
||||
class PreconditionFailed(HTTPClientError):
|
||||
"""HTTP 412 - Precondition Failed.
|
||||
|
||||
The server does not meet one of the preconditions that the requester
|
||||
put on the request.
|
||||
"""
|
||||
http_status = 412
|
||||
message = _("Precondition Failed")
|
||||
|
||||
|
||||
class RequestEntityTooLarge(HTTPClientError):
|
||||
"""HTTP 413 - Request Entity Too Large.
|
||||
|
||||
The request is larger than the server is willing or able to process.
|
||||
"""
|
||||
http_status = 413
|
||||
message = _("Request Entity Too Large")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
self.retry_after = int(kwargs.pop('retry_after'))
|
||||
except (KeyError, ValueError):
|
||||
self.retry_after = 0
|
||||
|
||||
super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RequestUriTooLong(HTTPClientError):
|
||||
"""HTTP 414 - Request-URI Too Long.
|
||||
|
||||
The URI provided was too long for the server to process.
|
||||
"""
|
||||
http_status = 414
|
||||
message = _("Request-URI Too Long")
|
||||
|
||||
|
||||
class UnsupportedMediaType(HTTPClientError):
|
||||
"""HTTP 415 - Unsupported Media Type.
|
||||
|
||||
The request entity has a media type which the server or resource does
|
||||
not support.
|
||||
"""
|
||||
http_status = 415
|
||||
message = _("Unsupported Media Type")
|
||||
|
||||
|
||||
class RequestedRangeNotSatisfiable(HTTPClientError):
|
||||
"""HTTP 416 - Requested Range Not Satisfiable.
|
||||
|
||||
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 ExpectationFailed(HTTPClientError):
|
||||
"""HTTP 417 - Expectation Failed.
|
||||
|
||||
The server cannot meet the requirements of the Expect request-header field.
|
||||
"""
|
||||
http_status = 417
|
||||
message = _("Expectation Failed")
|
||||
|
||||
|
||||
class UnprocessableEntity(HTTPClientError):
|
||||
"""HTTP 422 - Unprocessable Entity.
|
||||
|
||||
The request was well-formed but was unable to be followed due to semantic
|
||||
errors.
|
||||
"""
|
||||
http_status = 422
|
||||
message = _("Unprocessable Entity")
|
||||
|
||||
|
||||
class InternalServerError(HttpServerError):
|
||||
"""HTTP 500 - Internal Server Error.
|
||||
|
||||
A generic error message, given when no more specific message is suitable.
|
||||
"""
|
||||
http_status = 500
|
||||
message = _("Internal Server Error")
|
||||
|
||||
|
||||
# NotImplemented is a python keyword.
|
||||
class HttpNotImplemented(HttpServerError):
|
||||
"""HTTP 501 - Not Implemented.
|
||||
|
||||
The server either does not recognize the request method, or it lacks
|
||||
the ability to fulfill the request.
|
||||
"""
|
||||
http_status = 501
|
||||
message = _("Not Implemented")
|
||||
|
||||
|
||||
class BadGateway(HttpServerError):
|
||||
"""HTTP 502 - Bad Gateway.
|
||||
|
||||
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 ServiceUnavailable(HttpServerError):
|
||||
"""HTTP 503 - Service Unavailable.
|
||||
|
||||
The server is currently unavailable.
|
||||
"""
|
||||
http_status = 503
|
||||
message = _("Service Unavailable")
|
||||
|
||||
|
||||
class GatewayTimeout(HttpServerError):
|
||||
"""HTTP 504 - Gateway Timeout.
|
||||
|
||||
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(HttpServerError):
|
||||
"""HTTP 505 - HttpVersion Not Supported.
|
||||
|
||||
The server does not support the HTTP protocol version used in the request.
|
||||
"""
|
||||
http_status = 505
|
||||
message = _("HTTP Version Not Supported")
|
||||
|
||||
|
||||
# _code_map contains all the classes that have http_status attribute.
|
||||
_code_map = dict(
|
||||
(getattr(obj, 'http_status', None), obj)
|
||||
for name, obj in vars(sys.modules[__name__]).items()
|
||||
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
|
||||
)
|
||||
|
||||
|
||||
def from_response(response, method, url):
|
||||
"""Returns an instance of :class:`HttpError` or subclass based on response.
|
||||
|
||||
:param response: instance of `requests.Response` class
|
||||
:param method: HTTP method used for request
|
||||
:param url: URL used for request
|
||||
"""
|
||||
|
||||
req_id = response.headers.get("x-openstack-request-id")
|
||||
# NOTE(hdd) true for older versions of nova and cinder
|
||||
if not req_id:
|
||||
req_id = response.headers.get("x-compute-request-id")
|
||||
kwargs = {
|
||||
"http_status": response.status_code,
|
||||
"response": response,
|
||||
"method": method,
|
||||
"url": url,
|
||||
"request_id": req_id,
|
||||
}
|
||||
if "retry-after" in response.headers:
|
||||
kwargs["retry_after"] = response.headers["retry-after"]
|
||||
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
if content_type.startswith("application/json"):
|
||||
try:
|
||||
body = response.json()
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(body, dict):
|
||||
error = list(body.values())[0]
|
||||
kwargs["message"] = error.get("message")
|
||||
kwargs["details"] = error.get("details")
|
||||
elif content_type.startswith("text/"):
|
||||
kwargs["details"] = response.text
|
||||
|
||||
try:
|
||||
cls = _code_map[response.status_code]
|
||||
except KeyError:
|
||||
if 500 <= response.status_code < 600:
|
||||
cls = HttpServerError
|
||||
elif 400 <= response.status_code < 500:
|
||||
cls = HTTPClientError
|
||||
else:
|
||||
cls = HttpError
|
||||
return cls(**kwargs)
|
||||
@@ -1,177 +1,177 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""
|
||||
A fake server that "responds" to API methods with pre-canned responses.
|
||||
|
||||
All of these responses come from the spec, so if for some reason the spec's
|
||||
wrong the tests might raise AssertionError. I've indicated in comments the
|
||||
places where actual behavior differs from the spec.
|
||||
"""
|
||||
|
||||
# W0102: Dangerous default value %s as argument
|
||||
# pylint: disable=W0102
|
||||
|
||||
import json
|
||||
|
||||
import requests
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from smaugclient.openstack.common.apiclient import client
|
||||
|
||||
|
||||
def assert_has_keys(dct, required=None, optional=None):
|
||||
if required is None:
|
||||
required = []
|
||||
if optional is None:
|
||||
optional = []
|
||||
for k in required:
|
||||
try:
|
||||
assert k in dct
|
||||
except AssertionError:
|
||||
extra_keys = set(dct.keys()).difference(set(required + optional))
|
||||
raise AssertionError("found unexpected keys: %s" %
|
||||
list(extra_keys))
|
||||
|
||||
|
||||
class TestResponse(requests.Response):
|
||||
"""Wrap requests.Response and provide a convenient initialization.
|
||||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
super(TestResponse, self).__init__()
|
||||
self._content_consumed = True
|
||||
if isinstance(data, dict):
|
||||
self.status_code = data.get('status_code', 200)
|
||||
# Fake the text attribute to streamline Response creation
|
||||
text = data.get('text', "")
|
||||
if isinstance(text, (dict, list)):
|
||||
self._content = json.dumps(text)
|
||||
default_headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
else:
|
||||
self._content = text
|
||||
default_headers = {}
|
||||
if six.PY3 and isinstance(self._content, six.string_types):
|
||||
self._content = self._content.encode('utf-8', 'strict')
|
||||
self.headers = data.get('headers') or default_headers
|
||||
else:
|
||||
self.status_code = data
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.status_code == other.status_code and
|
||||
self.headers == other.headers and
|
||||
self._content == other._content)
|
||||
|
||||
|
||||
class FakeHTTPClient(client.HTTPClient):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.callstack = []
|
||||
self.fixtures = kwargs.pop("fixtures", None) or {}
|
||||
if not args and not "auth_plugin" in kwargs:
|
||||
args = (None, )
|
||||
super(FakeHTTPClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def assert_called(self, method, url, body=None, pos=-1):
|
||||
"""Assert than an API method was just called.
|
||||
"""
|
||||
expected = (method, url)
|
||||
called = self.callstack[pos][0:2]
|
||||
assert self.callstack, \
|
||||
"Expected %s %s but no calls were made." % expected
|
||||
|
||||
assert expected == called, 'Expected %s %s; got %s %s' % \
|
||||
(expected + called)
|
||||
|
||||
if body is not None:
|
||||
if self.callstack[pos][3] != body:
|
||||
raise AssertionError('%r != %r' %
|
||||
(self.callstack[pos][3], body))
|
||||
|
||||
def assert_called_anytime(self, method, url, body=None):
|
||||
"""Assert than an API method was called anytime in the test.
|
||||
"""
|
||||
expected = (method, url)
|
||||
|
||||
assert self.callstack, \
|
||||
"Expected %s %s but no calls were made." % expected
|
||||
|
||||
found = False
|
||||
entry = None
|
||||
for entry in self.callstack:
|
||||
if expected == entry[0:2]:
|
||||
found = True
|
||||
break
|
||||
|
||||
assert found, 'Expected %s %s; got %s' % \
|
||||
(method, url, self.callstack)
|
||||
if body is not None:
|
||||
assert entry[3] == body, "%s != %s" % (entry[3], body)
|
||||
|
||||
self.callstack = []
|
||||
|
||||
def clear_callstack(self):
|
||||
self.callstack = []
|
||||
|
||||
def authenticate(self):
|
||||
pass
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
# Check that certain things are called correctly
|
||||
if method in ["GET", "DELETE"]:
|
||||
assert "json" not in kwargs
|
||||
|
||||
# Note the call
|
||||
self.callstack.append(
|
||||
(method,
|
||||
url,
|
||||
kwargs.get("headers") or {},
|
||||
kwargs.get("json") or kwargs.get("data")))
|
||||
try:
|
||||
fixture = self.fixtures[url][method]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return TestResponse({"headers": fixture[0],
|
||||
"text": fixture[1]})
|
||||
|
||||
# Call the method
|
||||
args = parse.parse_qsl(parse.urlparse(url)[4])
|
||||
kwargs.update(args)
|
||||
munged_url = url.rsplit('?', 1)[0]
|
||||
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
|
||||
munged_url = munged_url.replace('-', '_')
|
||||
|
||||
callback = "%s_%s" % (method.lower(), munged_url)
|
||||
|
||||
if not hasattr(self, callback):
|
||||
raise AssertionError('Called unknown API method: %s %s, '
|
||||
'expected fakes method name: %s' %
|
||||
(method, url, callback))
|
||||
|
||||
resp = getattr(self, callback)(**kwargs)
|
||||
if len(resp) == 3:
|
||||
status, headers, body = resp
|
||||
else:
|
||||
status, body = resp
|
||||
headers = {}
|
||||
return TestResponse({
|
||||
"status_code": status,
|
||||
"text": body,
|
||||
"headers": headers,
|
||||
})
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
"""
|
||||
A fake server that "responds" to API methods with pre-canned responses.
|
||||
|
||||
All of these responses come from the spec, so if for some reason the spec's
|
||||
wrong the tests might raise AssertionError. I've indicated in comments the
|
||||
places where actual behavior differs from the spec.
|
||||
"""
|
||||
|
||||
# W0102: Dangerous default value %s as argument
|
||||
# pylint: disable=W0102
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
import requests
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from karborclient.common.apiclient import client
|
||||
|
||||
|
||||
def assert_has_keys(dct, required=None, optional=None):
|
||||
if required is None:
|
||||
required = []
|
||||
if optional is None:
|
||||
optional = []
|
||||
for k in required:
|
||||
try:
|
||||
assert k in dct
|
||||
except AssertionError:
|
||||
extra_keys = set(dct.keys()).difference(set(required + optional))
|
||||
raise AssertionError("found unexpected keys: %s" %
|
||||
list(extra_keys))
|
||||
|
||||
|
||||
class TestResponse(requests.Response):
|
||||
"""Wrap requests.Response and provide a convenient initialization."""
|
||||
|
||||
def __init__(self, data):
|
||||
super(TestResponse, self).__init__()
|
||||
self._content_consumed = True
|
||||
if isinstance(data, dict):
|
||||
self.status_code = data.get('status_code', 200)
|
||||
# Fake the text attribute to streamline Response creation
|
||||
text = data.get('text', "")
|
||||
if isinstance(text, (dict, list)):
|
||||
self._content = jsonutils.dumps(text)
|
||||
default_headers = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
else:
|
||||
self._content = text
|
||||
default_headers = {}
|
||||
if six.PY3 and isinstance(self._content, six.string_types):
|
||||
self._content = self._content.encode('utf-8', 'strict')
|
||||
self.headers = data.get('headers') or default_headers
|
||||
else:
|
||||
self.status_code = data
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.status_code == other.status_code and
|
||||
self.headers == other.headers and
|
||||
self._content == other._content)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
class FakeHTTPClient(client.HTTPClient):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.callstack = []
|
||||
self.fixtures = kwargs.pop("fixtures", None) or {}
|
||||
if not args and "auth_plugin" not in kwargs:
|
||||
args = (None, )
|
||||
super(FakeHTTPClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def assert_called(self, method, url, body=None, pos=-1):
|
||||
"""Assert than an API method was just called."""
|
||||
expected = (method, url)
|
||||
called = self.callstack[pos][0:2]
|
||||
assert self.callstack, \
|
||||
"Expected %s %s but no calls were made." % expected
|
||||
|
||||
assert expected == called, 'Expected %s %s; got %s %s' % \
|
||||
(expected + called)
|
||||
|
||||
if body is not None:
|
||||
if self.callstack[pos][3] != body:
|
||||
raise AssertionError('%r != %r' %
|
||||
(self.callstack[pos][3], body))
|
||||
|
||||
def assert_called_anytime(self, method, url, body=None):
|
||||
"""Assert than an API method was called anytime in the test."""
|
||||
expected = (method, url)
|
||||
|
||||
assert self.callstack, \
|
||||
"Expected %s %s but no calls were made." % expected
|
||||
|
||||
found = False
|
||||
entry = None
|
||||
for entry in self.callstack:
|
||||
if expected == entry[0:2]:
|
||||
found = True
|
||||
break
|
||||
|
||||
assert found, 'Expected %s %s; got %s' % \
|
||||
(method, url, self.callstack)
|
||||
if body is not None:
|
||||
assert entry[3] == body, "%s != %s" % (entry[3], body)
|
||||
|
||||
self.callstack = []
|
||||
|
||||
def clear_callstack(self):
|
||||
self.callstack = []
|
||||
|
||||
def authenticate(self):
|
||||
pass
|
||||
|
||||
def client_request(self, client, method, url, **kwargs):
|
||||
# Check that certain things are called correctly
|
||||
if method in ["GET", "DELETE"]:
|
||||
assert "json" not in kwargs
|
||||
|
||||
# Note the call
|
||||
self.callstack.append(
|
||||
(method,
|
||||
url,
|
||||
kwargs.get("headers") or {},
|
||||
kwargs.get("json") or kwargs.get("data")))
|
||||
try:
|
||||
fixture = self.fixtures[url][method]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return TestResponse({"headers": fixture[0],
|
||||
"text": fixture[1]})
|
||||
|
||||
# Call the method
|
||||
args = parse.parse_qsl(parse.urlparse(url)[4])
|
||||
kwargs.update(args)
|
||||
munged_url = url.rsplit('?', 1)[0]
|
||||
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
|
||||
munged_url = munged_url.replace('-', '_')
|
||||
|
||||
callback = "%s_%s" % (method.lower(), munged_url)
|
||||
|
||||
if not hasattr(self, callback):
|
||||
raise AssertionError('Called unknown API method: %s %s, '
|
||||
'expected fakes method name: %s' %
|
||||
(method, url, callback))
|
||||
|
||||
resp = getattr(self, callback)(**kwargs)
|
||||
if len(resp) == 3:
|
||||
status, headers, body = resp
|
||||
else:
|
||||
status, body = resp
|
||||
headers = {}
|
||||
return TestResponse({
|
||||
"status_code": status,
|
||||
"text": body,
|
||||
"headers": headers,
|
||||
})
|
||||
@@ -20,8 +20,9 @@ import copy
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from smaugclient.common import http
|
||||
from smaugclient.openstack.common.apiclient import exceptions
|
||||
from karborclient.common.apiclient import exceptions
|
||||
from karborclient.common import http
|
||||
|
||||
|
||||
SORT_DIR_VALUES = ('asc', 'desc')
|
||||
SORT_KEY_VALUES = ('id', 'status', 'name', 'created_at')
|
||||
@@ -330,6 +331,9 @@ class Resource(object):
|
||||
return False
|
||||
return self._info == other._info
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def is_loaded(self):
|
||||
return self._loaded
|
||||
|
||||
@@ -18,7 +18,7 @@ import hashlib
|
||||
import os
|
||||
import socket
|
||||
|
||||
import keystoneclient.adapter as keystone_adapter
|
||||
import keystoneauth1.adapter as keystone_adapter
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
@@ -26,10 +26,10 @@ import requests
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
from smaugclient.openstack.common.apiclient import exceptions as exc
|
||||
from karborclient.common.apiclient import exceptions as exc
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
USER_AGENT = 'python-smaugclient'
|
||||
USER_AGENT = 'python-karborclient'
|
||||
CHUNKSIZE = 1024 * 64 # 64kB
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ class HTTPClient(object):
|
||||
|
||||
|
||||
class SessionClient(keystone_adapter.Adapter):
|
||||
"""Smaug specific keystoneclient Adapter.
|
||||
"""karbor specific keystoneauth Adapter.
|
||||
|
||||
"""
|
||||
|
||||
@@ -386,7 +386,7 @@ def _construct_http_client(*args, **kwargs):
|
||||
'service_type': service_type,
|
||||
'region_name': region_name,
|
||||
'service_name': service_name,
|
||||
'user_agent': 'python-smaugclient',
|
||||
'user_agent': 'python-karborclient',
|
||||
}
|
||||
parameters.update(kwargs)
|
||||
return SessionClient(**parameters)
|
||||
@@ -18,15 +18,12 @@ import sys
|
||||
import six
|
||||
import uuid
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import importutils
|
||||
|
||||
import prettytable
|
||||
|
||||
from smaugclient.openstack.common.apiclient import exceptions
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
from karborclient.common.apiclient import exceptions
|
||||
|
||||
|
||||
# Decorator for cli-args
|
||||
@@ -46,19 +43,12 @@ def env(*vars, **kwargs):
|
||||
returns the default defined in kwargs.
|
||||
"""
|
||||
for v in vars:
|
||||
value = os.environ.get(v, None)
|
||||
value = os.environ.get(v)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
|
||||
def import_versioned_module(version, submodule=None):
|
||||
module = 'smaugclient.v%s' % version
|
||||
if submodule:
|
||||
module = '.'.join((module, submodule))
|
||||
return importutils.import_module(module)
|
||||
|
||||
|
||||
def _print(pt, order):
|
||||
if sys.version_info >= (3, 0):
|
||||
print(pt.get_string(sortby=order))
|
||||
@@ -115,7 +105,7 @@ def print_list(objs, fields, exclude_unavailable=False, formatters=None,
|
||||
fields.remove(f)
|
||||
|
||||
pt = prettytable.PrettyTable((f for f in fields), caching=False)
|
||||
pt.aligns = ['l' for f in fields]
|
||||
pt.align = 'l'
|
||||
for row in rows:
|
||||
pt.add_row(row)
|
||||
|
||||
@@ -126,17 +116,41 @@ def print_list(objs, fields, exclude_unavailable=False, formatters=None,
|
||||
_print(pt, order_by)
|
||||
|
||||
|
||||
def print_dict(d, property="Property"):
|
||||
def print_dict(d, property="Property", dict_format_list=None,
|
||||
json_format_list=None):
|
||||
pt = prettytable.PrettyTable([property, 'Value'], caching=False)
|
||||
pt.aligns = ['l', 'l']
|
||||
for r in six.iteritems(d):
|
||||
pt.align = 'l'
|
||||
for r in d.items():
|
||||
r = list(r)
|
||||
if isinstance(r[1], six.string_types) and "\r" in r[1]:
|
||||
r[1] = r[1].replace("\r", " ")
|
||||
if dict_format_list is not None and r[0] in dict_format_list:
|
||||
r[1] = dict_prettyprint(r[1])
|
||||
if json_format_list is not None and r[0] in json_format_list:
|
||||
r[1] = json_prettyprint(r[1])
|
||||
pt.add_row(r)
|
||||
_print(pt, property)
|
||||
|
||||
|
||||
def dict_prettyprint(val):
|
||||
"""dict pretty print formatter.
|
||||
|
||||
:param val: dict.
|
||||
:return: formatted json string.
|
||||
"""
|
||||
return jsonutils.dumps(val, indent=2, sort_keys=True)
|
||||
|
||||
|
||||
def json_prettyprint(val):
|
||||
"""json pretty print formatter.
|
||||
|
||||
:param val: json string.
|
||||
:return: formatted json string.
|
||||
"""
|
||||
return val and jsonutils.dumps(jsonutils.loads(val),
|
||||
indent=2, sort_keys=True)
|
||||
|
||||
|
||||
def find_resource(manager, name_or_id, *args, **kwargs):
|
||||
"""Helper for the _find_* methods."""
|
||||
# first try to get entity as integer id
|
||||
@@ -1,34 +1,28 @@
|
||||
# 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 http://docs.openstack.org/developer/oslo.i18n/usage.html
|
||||
|
||||
"""
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain='smaugclient')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
# Translators for log levels.
|
||||
#
|
||||
# The abbreviated names are meant to reflect the usual use of a short
|
||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||
# the level.
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
||||
# 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/user/usage.html
|
||||
|
||||
"""
|
||||
|
||||
import oslo_i18n
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain='karborclient')
|
||||
|
||||
# The primary translation function using the well-known name "_"
|
||||
_ = _translators.primary
|
||||
|
||||
|
||||
def get_available_languages():
|
||||
return oslo_i18n.get_available_languages('karborclient')
|
||||
56
karborclient/osc/plugin.py
Normal file
56
karborclient/osc/plugin.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
#
|
||||
|
||||
import logging
|
||||
|
||||
from osc_lib import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_DATA_PROTECTION_API_VERSION = '1'
|
||||
API_VERSION_OPTION = 'os_data_protection_api_version'
|
||||
API_NAME = 'data_protection'
|
||||
API_VERSIONS = {
|
||||
'1': 'karborclient.v1.client.Client',
|
||||
}
|
||||
|
||||
|
||||
def make_client(instance):
|
||||
"""Returns a data protection service client"""
|
||||
data_protection_client = utils.get_client_class(
|
||||
API_NAME,
|
||||
instance._api_version[API_NAME],
|
||||
API_VERSIONS)
|
||||
LOG.debug('Instantiating data protection client: %s',
|
||||
data_protection_client)
|
||||
client = data_protection_client(
|
||||
auth=instance.auth,
|
||||
session=instance.session,
|
||||
service_type="data-protect"
|
||||
)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def build_option_parser(parser):
|
||||
"""Hook to add global options"""
|
||||
parser.add_argument(
|
||||
'--os-data-protection-api-version',
|
||||
metavar='<data-protection-api-version>',
|
||||
default=utils.env(
|
||||
'OS_DATA_PROTECTION_API_VERSION',
|
||||
default=DEFAULT_DATA_PROTECTION_API_VERSION),
|
||||
help='Data protection API version, default=' +
|
||||
DEFAULT_DATA_PROTECTION_API_VERSION +
|
||||
' (Env: OS_DATA_PROTECTION_API_VERSION)')
|
||||
return parser
|
||||
283
karborclient/osc/v1/checkpoints.py
Normal file
283
karborclient/osc/v1/checkpoints.py
Normal file
@@ -0,0 +1,283 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 checkpoint action implementations"""
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils as osc_utils
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from karborclient.common.apiclient import exceptions
|
||||
from karborclient.i18n import _
|
||||
from karborclient import utils
|
||||
|
||||
|
||||
def format_checkpoint(checkpoint_info):
|
||||
if 'protection_plan' in checkpoint_info:
|
||||
plan = checkpoint_info['protection_plan']
|
||||
checkpoint_info['protection_plan'] = "Name: %s\nId: %s" % (
|
||||
plan['name'], plan['id'])
|
||||
if 'resource_graph' in checkpoint_info:
|
||||
checkpoint_info['resource_graph'] = jsonutils.dumps(jsonutils.loads(
|
||||
checkpoint_info['resource_graph']), indent=2, sort_keys=True)
|
||||
checkpoint_info.pop("links", None)
|
||||
|
||||
|
||||
class ListCheckpoints(command.Lister):
|
||||
_description = _("List checkpoints.")
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListCheckpoints")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListCheckpoints, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'provider_id',
|
||||
metavar='<provider_id>',
|
||||
help=_('ID of provider.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--all-projects',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Include all projects (admin only)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--plan_id',
|
||||
metavar='<plan_id>',
|
||||
default=None,
|
||||
help=_('Filters results by a plan ID. Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--start_date',
|
||||
type=str,
|
||||
metavar='<start_date>',
|
||||
default=None,
|
||||
help=_('Filters results by a start date("Y-m-d"). Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--end_date',
|
||||
type=str,
|
||||
metavar='<end_date>',
|
||||
default=None,
|
||||
help=_('Filters results by a end date("Y-m-d"). Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--project_id',
|
||||
metavar='<project_id>',
|
||||
default=None,
|
||||
help=_('Filters results by a project ID. Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--marker',
|
||||
metavar='<checkpoint>',
|
||||
help=_('The last checkpoint ID of the previous page.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
type=int,
|
||||
metavar='<num-checkpoints>',
|
||||
help=_('Maximum number of checkpoints to display.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sort',
|
||||
metavar="<key>[:<direction>]",
|
||||
default=None,
|
||||
help=_("Sort output by selected keys and directions(asc or desc), "
|
||||
"multiple keys and directions can be "
|
||||
"specified separated by comma"),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
data_protection_client = self.app.client_manager.data_protection
|
||||
all_projects = bool(parsed_args.project_id) or parsed_args.all_projects
|
||||
search_opts = {
|
||||
'plan_id': parsed_args.plan_id,
|
||||
'start_date': parsed_args.start_date,
|
||||
'end_date': parsed_args.end_date,
|
||||
'project_id': parsed_args.project_id,
|
||||
'all_tenants': all_projects
|
||||
}
|
||||
|
||||
data = data_protection_client.checkpoints.list(
|
||||
provider_id=parsed_args.provider_id, search_opts=search_opts,
|
||||
marker=parsed_args.marker, limit=parsed_args.limit,
|
||||
sort=parsed_args.sort)
|
||||
|
||||
column_headers = ['Id', 'Project id', 'Status', 'Protection plan',
|
||||
'Metadata', 'Created at']
|
||||
|
||||
def plan_formatter(plan):
|
||||
return "Name: %s\nId: %s" % (plan['name'],
|
||||
plan['id'])
|
||||
formatters = {"Protection plan": plan_formatter}
|
||||
return (column_headers,
|
||||
(osc_utils.get_item_properties(
|
||||
s, column_headers, formatters=formatters
|
||||
) for s in data))
|
||||
|
||||
|
||||
class ShowCheckpoint(command.ShowOne):
|
||||
_description = "Shows checkpoint details"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowCheckpoint, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'provider_id',
|
||||
metavar="<provider_id>",
|
||||
help=_('Id of provider.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'checkpoint_id',
|
||||
metavar="<checkpoint_id>",
|
||||
help=_('Id of checkpoint.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
checkpoint = client.checkpoints.get(parsed_args.provider_id,
|
||||
parsed_args.checkpoint_id)
|
||||
format_checkpoint(checkpoint._info)
|
||||
return zip(*sorted(checkpoint._info.items()))
|
||||
|
||||
|
||||
class CreateCheckpoint(command.ShowOne):
|
||||
_description = "Creates a checkpoint"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreateCheckpoint, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'provider_id',
|
||||
metavar='<provider_id>',
|
||||
help=_('ID of provider.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'plan_id',
|
||||
metavar='<plan_id>',
|
||||
help=_('ID of plan.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--extra_info',
|
||||
type=str,
|
||||
nargs='*',
|
||||
metavar='<key=value>',
|
||||
default=None,
|
||||
help=_('The extra info of a checkpoint.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
checkpoint_extra_info = None
|
||||
if parsed_args.extra_info is not None:
|
||||
checkpoint_extra_info = utils.extract_extra_info(parsed_args)
|
||||
checkpoint = client.checkpoints.create(parsed_args.provider_id,
|
||||
parsed_args.plan_id,
|
||||
checkpoint_extra_info)
|
||||
format_checkpoint(checkpoint._info)
|
||||
return zip(*sorted(checkpoint._info.items()))
|
||||
|
||||
|
||||
class DeleteCheckpoint(command.Command):
|
||||
_description = "Delete checkpoint"
|
||||
|
||||
log = logging.getLogger(__name__ + ".DeleteCheckpoint")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(DeleteCheckpoint, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'provider_id',
|
||||
metavar='<provider_id>',
|
||||
help=_('Id of provider.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'checkpoint',
|
||||
metavar='<checkpoint>',
|
||||
nargs="+",
|
||||
help=_('Id of checkpoint.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
failure_count = 0
|
||||
for checkpoint_id in parsed_args.checkpoint:
|
||||
try:
|
||||
client.checkpoints.delete(parsed_args.provider_id,
|
||||
checkpoint_id)
|
||||
except exceptions.NotFound:
|
||||
failure_count += 1
|
||||
self.log.error(
|
||||
"Failed to delete '{0}'; checkpoint not found".
|
||||
format(checkpoint_id))
|
||||
if failure_count == len(parsed_args.checkpoint):
|
||||
raise exceptions.CommandError(
|
||||
"Unable to find and delete any of the "
|
||||
"specified checkpoint.")
|
||||
|
||||
|
||||
class ResetCheckpointState(command.Command):
|
||||
_description = "Reset checkpoint state"
|
||||
|
||||
log = logging.getLogger(__name__ + ".ResetCheckpointState")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ResetCheckpointState, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'provider_id',
|
||||
metavar='<provider_id>',
|
||||
help=_('Id of provider.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'checkpoint',
|
||||
metavar='<checkpoint>',
|
||||
nargs="+",
|
||||
help=_('Id of checkpoint.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--available',
|
||||
action='store_const', dest='state',
|
||||
default='error', const='available',
|
||||
help=_('Request the checkpoint be reset to "available" state '
|
||||
'instead of "error" state(the default).'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
failure_count = 0
|
||||
for checkpoint_id in parsed_args.checkpoint:
|
||||
try:
|
||||
client.checkpoints.reset_state(
|
||||
parsed_args.provider_id, checkpoint_id, parsed_args.state)
|
||||
except exceptions.NotFound:
|
||||
failure_count += 1
|
||||
self.log.error(
|
||||
"Failed to reset state of '{0}'; checkpoint "
|
||||
"not found".format(checkpoint_id))
|
||||
except exceptions.Forbidden:
|
||||
failure_count += 1
|
||||
self.log.error(
|
||||
"Failed to reset state of '{0}'; not "
|
||||
"allowed".format(checkpoint_id))
|
||||
except exceptions.BadRequest:
|
||||
failure_count += 1
|
||||
self.log.error(
|
||||
"Failed to reset state of '{0}'; invalid input or "
|
||||
"current checkpoint state".format(checkpoint_id))
|
||||
if failure_count == len(parsed_args.checkpoint):
|
||||
raise exceptions.CommandError(
|
||||
"Unable to find or reset any of the specified "
|
||||
"checkpoint's state.")
|
||||
111
karborclient/osc/v1/operation_logs.py
Normal file
111
karborclient/osc/v1/operation_logs.py
Normal file
@@ -0,0 +1,111 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 operation_log action implementations"""
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils as osc_utils
|
||||
from oslo_log import log as logging
|
||||
|
||||
from karborclient.i18n import _
|
||||
|
||||
|
||||
class ListOperationLogs(command.Lister):
|
||||
_description = _("List operation_logs.")
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListOperationLogs")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListOperationLogs, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--all-projects',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Include all projects (admin only)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--status',
|
||||
metavar='<status>',
|
||||
help=_('Filter results by status'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--marker',
|
||||
metavar='<operation_log>',
|
||||
help=_('The last operation_log ID of the previous page'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
type=int,
|
||||
metavar='<num-operation_logs>',
|
||||
help=_('Maximum number of operation_logs to display'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sort',
|
||||
metavar="<key>[:<direction>]",
|
||||
default=None,
|
||||
help=_("Sort output by selected keys and directions(asc or desc), "
|
||||
"multiple keys and directions can be "
|
||||
"specified separated by comma"),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--project',
|
||||
metavar='<project>',
|
||||
help=_('Filter results by a project(admin only)')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
data_protection_client = self.app.client_manager.data_protection
|
||||
all_projects = bool(parsed_args.project) or parsed_args.all_projects
|
||||
|
||||
search_opts = {
|
||||
'all_tenants': all_projects,
|
||||
'project_id': parsed_args.project,
|
||||
'status': parsed_args.status,
|
||||
}
|
||||
|
||||
data = data_protection_client.operation_logs.list(
|
||||
search_opts=search_opts, marker=parsed_args.marker,
|
||||
limit=parsed_args.limit, sort=parsed_args.sort)
|
||||
|
||||
column_headers = ['Id', 'Operation Type', 'Checkpoint id',
|
||||
'Plan Id', 'Provider id', 'Restore Id',
|
||||
'Scheduled Operation Id', 'Status',
|
||||
'Started At', 'Ended At', 'Error Info',
|
||||
'Extra Info']
|
||||
|
||||
return (column_headers,
|
||||
(osc_utils.get_item_properties(
|
||||
s, column_headers
|
||||
) for s in data))
|
||||
|
||||
|
||||
class ShowOperationLog(command.ShowOne):
|
||||
_description = "Shows operation_log details"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowOperationLog, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'operation_log',
|
||||
metavar="<operation_log>",
|
||||
help=_('The UUID of the operation_log.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
operation_log = osc_utils.find_resource(client.operation_logs,
|
||||
parsed_args.operation_log)
|
||||
|
||||
operation_log._info.pop("links", None)
|
||||
return zip(*sorted(operation_log._info.items()))
|
||||
282
karborclient/osc/v1/plans.py
Normal file
282
karborclient/osc/v1/plans.py
Normal file
@@ -0,0 +1,282 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 plan action implementations"""
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils as osc_utils
|
||||
from oslo_log import log as logging
|
||||
|
||||
from karborclient.common.apiclient import exceptions
|
||||
from karborclient.i18n import _
|
||||
from karborclient import utils
|
||||
|
||||
|
||||
def format_plan(plan_info):
|
||||
for key in ('resources', 'parameters'):
|
||||
if key not in plan_info:
|
||||
continue
|
||||
plan_info[key] = jsonutils.dumps(plan_info[key],
|
||||
indent=2, sort_keys=True)
|
||||
plan_info.pop("links", None)
|
||||
|
||||
|
||||
class ListPlans(command.Lister):
|
||||
_description = _("List plans.")
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListPlans")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListPlans, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--all-projects',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Include all projects (admin only)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
metavar='<name>',
|
||||
help=_('Filter results by plan name'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--description',
|
||||
metavar='<description>',
|
||||
help=_('Filter results by plan description'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--status',
|
||||
metavar='<status>',
|
||||
help=_('Filter results by status'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--marker',
|
||||
metavar='<plan>',
|
||||
help=_('The last plan ID of the previous page'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
type=int,
|
||||
metavar='<num-plans>',
|
||||
help=_('Maximum number of plans to display'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sort',
|
||||
metavar="<key>[:<direction>]",
|
||||
default=None,
|
||||
help=_("Sort output by selected keys and directions(asc or desc) "
|
||||
"(default: name:asc), multiple keys and directions can be "
|
||||
"specified separated by comma"),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--project',
|
||||
metavar='<project>',
|
||||
help=_('Filter results by a project(admin only)')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
data_protection_client = self.app.client_manager.data_protection
|
||||
all_projects = bool(parsed_args.project) or parsed_args.all_projects
|
||||
|
||||
search_opts = {
|
||||
'all_tenants': all_projects,
|
||||
'project_id': parsed_args.project,
|
||||
'name': parsed_args.name,
|
||||
'description': parsed_args.description,
|
||||
'status': parsed_args.status,
|
||||
}
|
||||
|
||||
data = data_protection_client.plans.list(
|
||||
search_opts=search_opts, marker=parsed_args.marker,
|
||||
limit=parsed_args.limit, sort=parsed_args.sort)
|
||||
|
||||
column_headers = ['Id', 'Name', 'Description', 'Provider id', 'Status']
|
||||
|
||||
return (column_headers,
|
||||
(osc_utils.get_item_properties(
|
||||
s, column_headers
|
||||
) for s in data))
|
||||
|
||||
|
||||
class ShowPlan(command.ShowOne):
|
||||
_description = "Shows plan details"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowPlan, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'plan',
|
||||
metavar="<plan>",
|
||||
help=_('The UUID of the plan.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
plan = osc_utils.find_resource(client.plans, parsed_args.plan)
|
||||
|
||||
format_plan(plan._info)
|
||||
return zip(*sorted(plan._info.items()))
|
||||
|
||||
|
||||
class CreatePlan(command.ShowOne):
|
||||
_description = "Creates a plan"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreatePlan, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'name',
|
||||
metavar='<name>',
|
||||
help=_('The name of the plan.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'provider_id',
|
||||
metavar='<provider_id>',
|
||||
help=_('The UUID of the provider.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'resources',
|
||||
metavar='<id=type=name=extra_info,id=type=name=extra_info>',
|
||||
help=_('Resource in list must be a dict when creating'
|
||||
' a plan. The keys of resource are id ,type, name and '
|
||||
'extra_info. The extra_info field is optional.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--parameters-json',
|
||||
type=str,
|
||||
dest='parameters_json',
|
||||
metavar='<parameters>',
|
||||
default=None,
|
||||
help=_('Plan parameters in json format.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--parameters',
|
||||
action='append',
|
||||
metavar='resource_type=<type>[,resource_id=<id>,key=val,...]',
|
||||
default=[],
|
||||
help=_('Plan parameters, may be specified multiple times. '
|
||||
'resource_type: type of resource to apply parameters. '
|
||||
'resource_id: limit the parameters to a specific resource. '
|
||||
'Other keys and values: according to provider\'s protect '
|
||||
'schema.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--description',
|
||||
metavar='<description>',
|
||||
help=_('The description of the plan.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
if not uuidutils.is_uuid_like(parsed_args.provider_id):
|
||||
raise exceptions.CommandError(
|
||||
"Invalid provider id provided.")
|
||||
plan_resources = utils.extract_resources(parsed_args)
|
||||
utils.check_resources(client, plan_resources)
|
||||
plan_parameters = utils.extract_parameters(parsed_args)
|
||||
plan = client.plans.create(parsed_args.name, parsed_args.provider_id,
|
||||
plan_resources, plan_parameters,
|
||||
description=parsed_args.description)
|
||||
|
||||
format_plan(plan._info)
|
||||
return zip(*sorted(plan._info.items()))
|
||||
|
||||
|
||||
class UpdatePlan(command.ShowOne):
|
||||
_description = "Update a plan"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(UpdatePlan, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
"plan_id",
|
||||
metavar="<PLAN ID>",
|
||||
help=_("Id of plan to update.")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--name",
|
||||
metavar="<name>",
|
||||
help=_("A name to which the plan will be renamed.")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--description",
|
||||
metavar="<description>",
|
||||
help=_("Description to which the plan will be updated.")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--resources",
|
||||
metavar="<id=type=name,id=type=name>",
|
||||
help=_("Resources to which the plan will be updated.")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--status",
|
||||
metavar="<suspended|started>",
|
||||
help=_("status to which the plan will be updated.")
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
data = {}
|
||||
if parsed_args.name is not None:
|
||||
data['name'] = parsed_args.name
|
||||
if parsed_args.description is not None:
|
||||
data['description'] = parsed_args.description
|
||||
if parsed_args.resources is not None:
|
||||
plan_resources = utils.extract_resources(parsed_args)
|
||||
data['resources'] = plan_resources
|
||||
if parsed_args.status is not None:
|
||||
data['status'] = parsed_args.status
|
||||
try:
|
||||
plan = osc_utils.find_resource(client.plans,
|
||||
parsed_args.plan_id)
|
||||
plan = client.plans.update(plan.id, data)
|
||||
except exceptions.NotFound:
|
||||
raise exceptions.CommandError(
|
||||
"Plan %s not found" % parsed_args.plan_id)
|
||||
else:
|
||||
format_plan(plan._info)
|
||||
return zip(*sorted(plan._info.items()))
|
||||
|
||||
|
||||
class DeletePlan(command.Command):
|
||||
_description = "Delete plan"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(DeletePlan, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'plan',
|
||||
metavar='<plan>',
|
||||
nargs="+",
|
||||
help=_('ID of plan.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
failure_count = 0
|
||||
for plan_id in parsed_args.plan:
|
||||
try:
|
||||
plan = osc_utils.find_resource(client.plans, plan_id)
|
||||
client.plans.delete(plan.id)
|
||||
except exceptions.NotFound:
|
||||
failure_count += 1
|
||||
print("Failed to delete '{0}'; plan not "
|
||||
"found".format(plan_id))
|
||||
if failure_count == len(parsed_args.plan):
|
||||
raise exceptions.CommandError(
|
||||
"Unable to find and delete any of the "
|
||||
"specified plan.")
|
||||
198
karborclient/osc/v1/protectables.py
Normal file
198
karborclient/osc/v1/protectables.py
Normal file
@@ -0,0 +1,198 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 protectables action implementations"""
|
||||
|
||||
import functools
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils as osc_utils
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from karborclient.i18n import _
|
||||
from karborclient import utils
|
||||
|
||||
|
||||
class ListProtectables(command.Lister):
|
||||
_description = _("List protectable types.")
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListProtectables")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListProtectables, self).get_parser(prog_name)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
data_protection_client = self.app.client_manager.data_protection
|
||||
|
||||
data = data_protection_client.protectables.list()
|
||||
|
||||
column_headers = ['Protectable type']
|
||||
|
||||
return (column_headers,
|
||||
(osc_utils.get_item_properties(
|
||||
s, column_headers
|
||||
) for s in data))
|
||||
|
||||
|
||||
class ShowProtectable(command.ShowOne):
|
||||
_description = "Shows protectable type details"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowProtectable, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'protectable_type',
|
||||
metavar="<protectable_type>",
|
||||
help=_('Protectable type.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
protectable = osc_utils.find_resource(client.protectables,
|
||||
parsed_args.protectable_type)
|
||||
|
||||
protectable._info.pop("links", None)
|
||||
if 'dependent_types' in protectable._info:
|
||||
protectable._info['dependent_types'] = "\n".join(
|
||||
protectable._info['dependent_types'])
|
||||
return zip(*sorted(protectable._info.items()))
|
||||
|
||||
|
||||
class ListProtectableInstances(command.Lister):
|
||||
_description = _("List protectable instances.")
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListProtectableInstances")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListProtectableInstances, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'protectable_type',
|
||||
metavar="<protectable_type>",
|
||||
help=_('Type of protectable.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--type',
|
||||
metavar="<type>",
|
||||
default=None,
|
||||
help=_('Filters results by protectable type. Default=None.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--marker',
|
||||
metavar="<protectable_instance>",
|
||||
default=None,
|
||||
help=_('The last protectable instance ID of the previous page.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
metavar="<num-protectable_instances>",
|
||||
default=None,
|
||||
help=_('Maximum number of protectable instances to display.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sort',
|
||||
metavar="<key>[:<direction>]",
|
||||
default=None,
|
||||
help=_("Sort output by selected keys and directions(asc or desc), "
|
||||
"multiple keys and directions can be "
|
||||
"specified separated by comma"),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--parameters',
|
||||
type=str,
|
||||
nargs='*',
|
||||
metavar="<key=value>",
|
||||
default=None,
|
||||
help=_('List instances by parameters key and value pair. '
|
||||
'Default=None.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
data_protection_client = self.app.client_manager.data_protection
|
||||
|
||||
search_opts = {
|
||||
'type': parsed_args.type,
|
||||
'parameters': (utils.extract_instances_parameters(parsed_args)
|
||||
if parsed_args.parameters else None),
|
||||
}
|
||||
|
||||
data = data_protection_client.protectables.list_instances(
|
||||
parsed_args.protectable_type, search_opts=search_opts,
|
||||
marker=parsed_args.marker, limit=parsed_args.limit,
|
||||
sort=parsed_args.sort)
|
||||
|
||||
column_headers = ['Id', 'Type', 'Name', 'Dependent resources',
|
||||
'Extra info']
|
||||
|
||||
json_dumps = functools.partial(jsonutils.dumps,
|
||||
indent=2, sort_keys=True)
|
||||
formatters = {
|
||||
"Extra info": json_dumps,
|
||||
"Dependent resources": json_dumps,
|
||||
}
|
||||
return (column_headers,
|
||||
(osc_utils.get_item_properties(
|
||||
s, column_headers, formatters=formatters,
|
||||
) for s in data))
|
||||
|
||||
|
||||
class ShowProtectableInstance(command.ShowOne):
|
||||
_description = "Shows protectable instance details"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowProtectableInstance, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'protectable_type',
|
||||
metavar="<protectable_type>",
|
||||
help=_('Protectable type.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'protectable_id',
|
||||
metavar="<protectable_id>",
|
||||
help=_('Protectable instance id.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--parameters',
|
||||
type=str,
|
||||
nargs='*',
|
||||
metavar="<key=value>",
|
||||
default=None,
|
||||
help=_('Show a instance by parameters key and value pair. '
|
||||
'Default=None.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
|
||||
search_opts = {
|
||||
'parameters': (utils.extract_instances_parameters(parsed_args)
|
||||
if parsed_args.parameters else None),
|
||||
}
|
||||
|
||||
instance = client.protectables.get_instance(
|
||||
parsed_args.protectable_type,
|
||||
parsed_args.protectable_id,
|
||||
search_opts=search_opts)
|
||||
|
||||
json_dumps = functools.partial(jsonutils.dumps,
|
||||
indent=2, sort_keys=True)
|
||||
instance._info.pop("links", None)
|
||||
for key in ('extra_info', 'dependent_resources'):
|
||||
if key not in instance._info:
|
||||
continue
|
||||
instance._info[key] = json_dumps(instance._info[key])
|
||||
|
||||
return zip(*sorted(instance._info.items()))
|
||||
105
karborclient/osc/v1/providers.py
Normal file
105
karborclient/osc/v1/providers.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 provider action implementations"""
|
||||
|
||||
import functools
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils as osc_utils
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from karborclient.i18n import _
|
||||
|
||||
|
||||
class ListProviders(command.Lister):
|
||||
_description = _("List providers.")
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListProviders")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListProviders, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
metavar='<name>',
|
||||
help=_('Filters results by a name. Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--description',
|
||||
metavar='<description>',
|
||||
help=_('Filters results by a description. Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--marker',
|
||||
metavar='<provider>',
|
||||
help=_('The last provider ID of the previous page'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
type=int,
|
||||
metavar='<num-providers>',
|
||||
help=_('Maximum number of providers to display'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sort',
|
||||
metavar="<key>[:<direction>]",
|
||||
default=None,
|
||||
help=_("Sort output by selected keys and directions(asc or desc) "
|
||||
"(default: name:asc), multiple keys and directions can be "
|
||||
"specified separated by comma"),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
data_protection_client = self.app.client_manager.data_protection
|
||||
|
||||
search_opts = {
|
||||
'name': parsed_args.name,
|
||||
'description': parsed_args.description,
|
||||
}
|
||||
|
||||
data = data_protection_client.providers.list(
|
||||
search_opts=search_opts, marker=parsed_args.marker,
|
||||
limit=parsed_args.limit, sort=parsed_args.sort)
|
||||
|
||||
column_headers = ['Id', 'Name', 'Description']
|
||||
|
||||
return (column_headers,
|
||||
(osc_utils.get_item_properties(
|
||||
s, column_headers
|
||||
) for s in data))
|
||||
|
||||
|
||||
class ShowProvider(command.ShowOne):
|
||||
_description = "Shows provider details"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowProvider, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'provider',
|
||||
metavar="<provider>",
|
||||
help=_('The UUID of the provider.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
provider = osc_utils.find_resource(client.providers,
|
||||
parsed_args.provider)
|
||||
json_dumps = functools.partial(jsonutils.dumps,
|
||||
indent=2, sort_keys=True)
|
||||
provider._info.pop("links", None)
|
||||
if 'extended_info_schema' in provider._info:
|
||||
provider._info['extended_info_schema'] = json_dumps(
|
||||
provider._info['extended_info_schema'])
|
||||
return zip(*sorted(provider._info.items()))
|
||||
75
karborclient/osc/v1/quota_classes.py
Normal file
75
karborclient/osc/v1/quota_classes.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 quota classes action implementations"""
|
||||
|
||||
from osc_lib.command import command
|
||||
|
||||
|
||||
def quota_class_set_pretty_show(quota_classes):
|
||||
"""Convert quotas class object to dict and display."""
|
||||
|
||||
new_quota_classes = []
|
||||
for quota_k, quota_v in sorted(quota_classes.to_dict().items()):
|
||||
if isinstance(quota_v, dict):
|
||||
quota_v = '\n'.join(
|
||||
['%s = %s' % (k, v) for k, v in sorted(quota_v.items())])
|
||||
new_quota_classes.append((quota_k, quota_v))
|
||||
|
||||
return new_quota_classes
|
||||
|
||||
|
||||
class ShowQuotaClasses(command.ShowOne):
|
||||
_description = "Shows Quota classes."
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowQuotaClasses, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'class_name',
|
||||
metavar='<class_name>',
|
||||
help='Name of quota class to list the quotas for.')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
result = client.quota_classes.get(parsed_args.class_name)
|
||||
quota_classes = quota_class_set_pretty_show(result)
|
||||
return zip(*sorted(quota_classes))
|
||||
|
||||
|
||||
class UpdateQuotaClasses(command.ShowOne):
|
||||
_description = "Update the quotas for a quota class (Admin only)."
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(UpdateQuotaClasses, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'class_name',
|
||||
metavar='<class_name>',
|
||||
help='Name of quota class to set the quotas for.')
|
||||
parser.add_argument(
|
||||
'--plans',
|
||||
metavar='<plans>',
|
||||
type=int,
|
||||
default=None,
|
||||
help='New value for the "plans" quota.')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
class_name = parsed_args.class_name
|
||||
data = {
|
||||
"plans": parsed_args.plans,
|
||||
}
|
||||
result = client.quota_classes.update(class_name, data)
|
||||
quota_classes = quota_class_set_pretty_show(result)
|
||||
|
||||
return zip(*sorted(quota_classes))
|
||||
108
karborclient/osc/v1/quotas.py
Normal file
108
karborclient/osc/v1/quotas.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 quotas action implementations"""
|
||||
|
||||
from osc_lib.command import command
|
||||
|
||||
|
||||
def quota_set_pretty_show(quotas):
|
||||
"""Convert quotas object to dict and display."""
|
||||
|
||||
new_quotas = []
|
||||
for quota_k, quota_v in sorted(quotas.to_dict().items()):
|
||||
if isinstance(quota_v, dict):
|
||||
quota_v = '\n'.join(
|
||||
['%s = %s' % (k, v) for k, v in sorted(quota_v.items())])
|
||||
new_quotas.append((quota_k, quota_v))
|
||||
|
||||
return new_quotas
|
||||
|
||||
|
||||
class ShowQuotas(command.ShowOne):
|
||||
_description = "Shows Quotas"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowQuotas, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--tenant',
|
||||
metavar='<tenant>',
|
||||
default=None,
|
||||
help='ID of tenant to list the quotas for.')
|
||||
parser.add_argument(
|
||||
'--detail',
|
||||
action='store_true',
|
||||
help='Optional flag to indicate whether to show quota in detail. '
|
||||
'Default false.')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
project_id = parsed_args.tenant or client.http_client.get_project_id()
|
||||
kwargs = {
|
||||
"project_id": project_id,
|
||||
"detail": parsed_args.detail,
|
||||
}
|
||||
result = client.quotas.get(**kwargs)
|
||||
quotas = quota_set_pretty_show(result)
|
||||
return zip(*sorted(quotas))
|
||||
|
||||
|
||||
class ShowDefaultQuotas(command.ShowOne):
|
||||
_description = "Shows default Quotas"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowDefaultQuotas, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--tenant',
|
||||
metavar='<tenant>',
|
||||
default=None,
|
||||
help='ID of tenant to list the quotas for.')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
project_id = parsed_args.tenant or client.http_client.get_project_id()
|
||||
|
||||
result = client.quotas.defaults(project_id)
|
||||
quotas = quota_set_pretty_show(result)
|
||||
|
||||
return zip(*sorted(quotas))
|
||||
|
||||
|
||||
class UpdateQuotas(command.ShowOne):
|
||||
_description = "Updates Quotas"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(UpdateQuotas, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'tenant',
|
||||
metavar='<tenant>',
|
||||
help='ID of tenant to set the quotas for.')
|
||||
parser.add_argument(
|
||||
'--plans',
|
||||
metavar='<plans>',
|
||||
type=int,
|
||||
default=None,
|
||||
help='New value for the "plans" quota.')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
project_id = parsed_args.tenant
|
||||
data = {
|
||||
"plans": parsed_args.plans,
|
||||
}
|
||||
result = client.quotas.update(project_id, data)
|
||||
quotas = quota_set_pretty_show(result)
|
||||
|
||||
return zip(*sorted(quotas))
|
||||
214
karborclient/osc/v1/restores.py
Normal file
214
karborclient/osc/v1/restores.py
Normal file
@@ -0,0 +1,214 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 restore action implementations"""
|
||||
|
||||
import functools
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils as osc_utils
|
||||
from oslo_log import log as logging
|
||||
|
||||
from karborclient.common.apiclient import exceptions
|
||||
from karborclient.i18n import _
|
||||
from karborclient import utils
|
||||
|
||||
|
||||
def format_restore(restore_info):
|
||||
for key in ('parameters', 'resources_status',
|
||||
'resources_reason'):
|
||||
if key not in restore_info:
|
||||
continue
|
||||
restore_info[key] = jsonutils.dumps(restore_info[key],
|
||||
indent=2, sort_keys=True)
|
||||
restore_info.pop("links", None)
|
||||
|
||||
|
||||
class ListRestores(command.Lister):
|
||||
_description = _("List restores.")
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListRestores")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListRestores, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--all-projects',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Include all projects (admin only)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--status',
|
||||
metavar='<status>',
|
||||
help=_('Filter results by status'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--marker',
|
||||
metavar='<restore>',
|
||||
help=_('The last restore ID of the previous page'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
type=int,
|
||||
metavar='<num-restores>',
|
||||
help=_('Maximum number of restores to display'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sort',
|
||||
metavar="<key>[:<direction>]",
|
||||
default=None,
|
||||
help=_("Sort output by selected keys and directions(asc or desc), "
|
||||
"multiple keys and directions can be "
|
||||
"specified separated by comma"),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--project',
|
||||
metavar='<project>',
|
||||
help=_('Filter results by a project(admin only)')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
data_protection_client = self.app.client_manager.data_protection
|
||||
all_projects = bool(parsed_args.project) or parsed_args.all_projects
|
||||
|
||||
search_opts = {
|
||||
'all_tenants': all_projects,
|
||||
'project_id': parsed_args.project,
|
||||
'status': parsed_args.status,
|
||||
}
|
||||
|
||||
data = data_protection_client.restores.list(
|
||||
search_opts=search_opts, marker=parsed_args.marker,
|
||||
limit=parsed_args.limit, sort=parsed_args.sort)
|
||||
|
||||
column_headers = ['Id', 'Project id', 'Provider id', 'Checkpoint id',
|
||||
'Restore target', 'Parameters', 'Status']
|
||||
|
||||
json_dumps = functools.partial(jsonutils.dumps,
|
||||
indent=2,
|
||||
sort_keys=True)
|
||||
formatters = {
|
||||
"Parameters": json_dumps,
|
||||
}
|
||||
return (column_headers,
|
||||
(osc_utils.get_item_properties(
|
||||
s, column_headers, formatters=formatters,
|
||||
) for s in data))
|
||||
|
||||
|
||||
class ShowRestore(command.ShowOne):
|
||||
_description = "Shows restore details"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowRestore, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'restore',
|
||||
metavar="<restore>",
|
||||
help=_('The UUID of the restore.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
restore = osc_utils.find_resource(client.restores, parsed_args.restore)
|
||||
|
||||
format_restore(restore._info)
|
||||
return zip(*sorted(restore._info.items()))
|
||||
|
||||
|
||||
class CreateRestore(command.ShowOne):
|
||||
_description = "Creates a restore"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreateRestore, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'provider_id',
|
||||
metavar='<provider_id>',
|
||||
help=_('The UUID of the provider.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'checkpoint_id',
|
||||
metavar='<checkpoint_id>',
|
||||
help=_('The UUID of the checkpoint.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--restore_target',
|
||||
metavar='<restore_target>',
|
||||
help=_('The target of the restore operation.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--restore_username',
|
||||
metavar='<restore_username>',
|
||||
default=None,
|
||||
help=_('Username to restore target.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--restore_password',
|
||||
metavar='<restore_password>',
|
||||
default=None,
|
||||
help=_('Password to restore target.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--parameters-json',
|
||||
type=str,
|
||||
dest='parameters_json',
|
||||
metavar='<parameters>',
|
||||
default=None,
|
||||
help=_('Restore parameters in json format.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--parameters',
|
||||
action='append',
|
||||
metavar='resource_type=<type>[,resource_id=<id>,key=val,...]',
|
||||
default=[],
|
||||
help=_("Restore parameters, may be specified multiple times. "
|
||||
"resource_type: type of resource to apply parameters. "
|
||||
"resource_id: limit the parameters to a specific resource. "
|
||||
"Other keys and values: according to provider\'s "
|
||||
"restore schema.")
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
if not uuidutils.is_uuid_like(parsed_args.provider_id):
|
||||
raise exceptions.CommandError(
|
||||
"Invalid provider id provided.")
|
||||
if not uuidutils.is_uuid_like(parsed_args.checkpoint_id):
|
||||
raise exceptions.CommandError(
|
||||
"Invalid checkpoint id provided.")
|
||||
|
||||
restore_parameters = utils.extract_parameters(parsed_args)
|
||||
restore_auth = None
|
||||
if parsed_args.restore_target is not None:
|
||||
if parsed_args.restore_username is None:
|
||||
raise exceptions.CommandError(
|
||||
"Must specify username for restore_target.")
|
||||
if parsed_args.restore_password is None:
|
||||
raise exceptions.CommandError(
|
||||
"Must specify password for restore_target.")
|
||||
restore_auth = {
|
||||
'type': 'password',
|
||||
'username': parsed_args.restore_username,
|
||||
'password': parsed_args.restore_password,
|
||||
}
|
||||
restore = client.restores.create(parsed_args.provider_id,
|
||||
parsed_args.checkpoint_id,
|
||||
parsed_args.restore_target,
|
||||
restore_parameters, restore_auth)
|
||||
format_restore(restore._info)
|
||||
return zip(*sorted(restore._info.items()))
|
||||
222
karborclient/osc/v1/scheduled_operations.py
Normal file
222
karborclient/osc/v1/scheduled_operations.py
Normal file
@@ -0,0 +1,222 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 scheduled_operations action implementations"""
|
||||
|
||||
import functools
|
||||
import six
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils as osc_utils
|
||||
from oslo_log import log as logging
|
||||
|
||||
from karborclient.common.apiclient import exceptions
|
||||
from karborclient.i18n import _
|
||||
|
||||
|
||||
def format_scheduledoperation(scheduledoperation_info):
|
||||
for key in ('operation_definition', ):
|
||||
if key not in scheduledoperation_info:
|
||||
continue
|
||||
scheduledoperation_info[key] = jsonutils.dumps(
|
||||
scheduledoperation_info[key], indent=2, sort_keys=True)
|
||||
scheduledoperation_info.pop("links", None)
|
||||
|
||||
|
||||
class ListScheduledOperations(command.Lister):
|
||||
_description = _("List scheduled_operations.")
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListScheduledOperations")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListScheduledOperations, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--all-projects',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Shows details for all tenants. Admin only.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
metavar='<name>',
|
||||
help=_('Filters results by a name. Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--operation_type',
|
||||
metavar='<operation_type>',
|
||||
default=None,
|
||||
help=_('Filters results by a type. Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--trigger_id',
|
||||
metavar='<trigger_id>',
|
||||
default=None,
|
||||
help=_('Filters results by a trigger id. Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--operation_definition',
|
||||
metavar='<operation_definition>',
|
||||
default=None,
|
||||
help=_('Filters results by a operation definition. Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--marker',
|
||||
metavar='<scheduled_operations>',
|
||||
help=_('The last scheduled_operations ID of the previous page'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
type=int,
|
||||
metavar='<num-scheduled_operations>',
|
||||
help=_('Maximum number of scheduled_operations to display'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sort',
|
||||
metavar="<key>[:<direction>]",
|
||||
default=None,
|
||||
help=_("Sort output by selected keys and directions(asc or desc) "
|
||||
"(default: name:asc), multiple keys and directions can be "
|
||||
"specified separated by comma"),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--project',
|
||||
metavar='<project>',
|
||||
help=_('Filter results by a project(admin only)')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
data_protection_client = self.app.client_manager.data_protection
|
||||
all_projects = bool(parsed_args.project) or parsed_args.all_projects
|
||||
|
||||
search_opts = {
|
||||
'all_tenants': all_projects,
|
||||
'project_id': parsed_args.project,
|
||||
'name': parsed_args.name,
|
||||
'operation_type': parsed_args.operation_type,
|
||||
'trigger_id': parsed_args.trigger_id,
|
||||
'operation_definition': parsed_args.operation_definition,
|
||||
}
|
||||
|
||||
data = data_protection_client.scheduled_operations.list(
|
||||
search_opts=search_opts, marker=parsed_args.marker,
|
||||
limit=parsed_args.limit, sort=parsed_args.sort)
|
||||
|
||||
column_headers = ['Id', 'Name', 'Operation Type', 'Trigger Id',
|
||||
'Operation Definition']
|
||||
|
||||
json_dumps = functools.partial(jsonutils.dumps,
|
||||
indent=2,
|
||||
sort_keys=True)
|
||||
formatters = {
|
||||
"Operation Definition": json_dumps,
|
||||
}
|
||||
return (column_headers,
|
||||
list(osc_utils.get_item_properties(
|
||||
s, column_headers, formatters=formatters,
|
||||
) for s in data))
|
||||
|
||||
|
||||
class ShowScheduledOperation(command.ShowOne):
|
||||
_description = "Shows scheduled_operation details"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowScheduledOperation, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'scheduledoperation',
|
||||
metavar="<scheduledoperation>",
|
||||
help=_('The UUID of the scheduledoperation.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
so = osc_utils.find_resource(client.scheduled_operations,
|
||||
parsed_args.scheduledoperation)
|
||||
|
||||
format_scheduledoperation(so._info)
|
||||
return zip(*sorted(six.iteritems(so._info)))
|
||||
|
||||
|
||||
class CreateScheduledOperation(command.ShowOne):
|
||||
_description = "Creates a scheduled operation"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreateScheduledOperation, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'name',
|
||||
metavar='<name>',
|
||||
help=_('The name of the scheduled operation.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'operation_type',
|
||||
metavar='<operation_type>',
|
||||
help=_('Operation Type of scheduled operation.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'trigger_id',
|
||||
metavar='<trigger_id>',
|
||||
help=_('Trigger id of scheduled operation.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'operation_definition',
|
||||
metavar='<key=value,key=value>',
|
||||
help=_('Operation definition of scheduled operation.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
if not uuidutils.is_uuid_like(parsed_args.trigger_id):
|
||||
raise exceptions.CommandError(
|
||||
"Invalid trigger id provided.")
|
||||
so = client.scheduled_operations.create(
|
||||
parsed_args.name, parsed_args.operation_type,
|
||||
parsed_args.trigger_id, parsed_args.operation_definition)
|
||||
|
||||
format_scheduledoperation(so._info)
|
||||
return zip(*sorted(six.iteritems(so._info)))
|
||||
|
||||
|
||||
class DeleteScheduledOperation(command.Command):
|
||||
_description = "Delete scheduled operation"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(DeleteScheduledOperation, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'scheduledoperation',
|
||||
metavar='<scheduledoperation>',
|
||||
nargs="+",
|
||||
help=_('ID of scheduled operation.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
failure_count = 0
|
||||
for so_id in parsed_args.scheduledoperation:
|
||||
try:
|
||||
so = osc_utils.find_resource(client.scheduled_operations,
|
||||
so_id)
|
||||
client.scheduled_operations.delete(so.id)
|
||||
except exceptions.NotFound:
|
||||
failure_count += 1
|
||||
print("Failed to delete '%s'; scheduled operation "
|
||||
"not found" % so_id)
|
||||
if failure_count == len(parsed_args.scheduledoperation):
|
||||
raise exceptions.CommandError(
|
||||
"Unable to find and delete any of the "
|
||||
"specified scheduled operation.")
|
||||
99
karborclient/osc/v1/services.py
Normal file
99
karborclient/osc/v1/services.py
Normal file
@@ -0,0 +1,99 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 os-services action implementations"""
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils as osc_utils
|
||||
from oslo_log import log as logging
|
||||
|
||||
from karborclient.i18n import _
|
||||
|
||||
|
||||
class ListServices(command.Lister):
|
||||
_description = _("List services.")
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListServices")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListServices, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--host',
|
||||
metavar='<host>',
|
||||
help=_('Filter results by host'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--binary',
|
||||
metavar='<binary>',
|
||||
help=_('Filter results by binary'),
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
data_protection_client = self.app.client_manager.data_protection
|
||||
data = data_protection_client.services.list(
|
||||
host=parsed_args.host,
|
||||
binary=parsed_args.binary
|
||||
)
|
||||
|
||||
column_headers = ["Id", "Binary", "Host", "Status", "State",
|
||||
"Updated_at", "Disabled Reason"]
|
||||
return (column_headers,
|
||||
(osc_utils.get_item_properties(
|
||||
s, column_headers
|
||||
) for s in data))
|
||||
|
||||
|
||||
class EnableService(command.ShowOne):
|
||||
_description = _('Enable service')
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(EnableService, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'service_id',
|
||||
metavar='<service_id>',
|
||||
help=_('The ID of the service.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
service = client.services.enable(parsed_args.service_id)
|
||||
return zip(*sorted(service._info.items()))
|
||||
|
||||
|
||||
class DisableService(command.ShowOne):
|
||||
_description = _('Disable service')
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(DisableService, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'service_id',
|
||||
metavar='<service_id>',
|
||||
help=_('The ID of the service.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--reason',
|
||||
metavar='<reason>',
|
||||
help=_('Reason for disabling the service.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
if parsed_args.reason:
|
||||
service = client.services.disable_log_reason(
|
||||
parsed_args.service_id, parsed_args.reason)
|
||||
else:
|
||||
service = client.services.disable(parsed_args.service_id)
|
||||
return zip(*sorted(service._info.items()))
|
||||
229
karborclient/osc/v1/triggers.py
Normal file
229
karborclient/osc/v1/triggers.py
Normal file
@@ -0,0 +1,229 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 triggers action implementations"""
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils as osc_utils
|
||||
from oslo_log import log as logging
|
||||
|
||||
from karborclient.common.apiclient import exceptions
|
||||
from karborclient.i18n import _
|
||||
from karborclient import utils
|
||||
|
||||
|
||||
class ListTriggers(command.Lister):
|
||||
_description = _("List triggers.")
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListTriggers")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListTriggers, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--all-projects',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Shows details for all tenants. Admin only.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--name',
|
||||
metavar='<name>',
|
||||
default=None,
|
||||
help=_('Filters results by a name. Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--type',
|
||||
metavar='<type>',
|
||||
default=None,
|
||||
help=_('Filters results by a type. Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--properties',
|
||||
metavar='<properties>',
|
||||
default=None,
|
||||
help=_('Filters results by a properties. Default=None.'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--marker',
|
||||
metavar='<trigger>',
|
||||
help=_('The last trigger ID of the previous page'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
type=int,
|
||||
metavar='<num-triggers>',
|
||||
help=_('Maximum number of triggers to display'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sort',
|
||||
metavar="<key>[:<direction>]",
|
||||
default=None,
|
||||
help=_("Sort output by selected keys and directions(asc or desc) "
|
||||
"(default: name:asc), multiple keys and directions can be "
|
||||
"specified separated by comma"),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--project',
|
||||
metavar='<project>',
|
||||
help=_('Display information from single tenant (Admin only).')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
data_protection_client = self.app.client_manager.data_protection
|
||||
all_projects = bool(parsed_args.project) or parsed_args.all_projects
|
||||
|
||||
search_opts = {
|
||||
'all_tenants': all_projects,
|
||||
'project_id': parsed_args.project,
|
||||
'name': parsed_args.name,
|
||||
'type': parsed_args.type,
|
||||
'properties': parsed_args.properties,
|
||||
}
|
||||
|
||||
data = data_protection_client.triggers.list(
|
||||
search_opts=search_opts, marker=parsed_args.marker,
|
||||
limit=parsed_args.limit, sort=parsed_args.sort)
|
||||
|
||||
column_headers = ['Id', 'Name', 'Type', 'Properties']
|
||||
|
||||
return (column_headers,
|
||||
(osc_utils.get_item_properties(
|
||||
s, column_headers
|
||||
) for s in data))
|
||||
|
||||
|
||||
class ShowTrigger(command.ShowOne):
|
||||
_description = "Shows trigger details"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowTrigger, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'trigger',
|
||||
metavar="<trigger>",
|
||||
help=_('The UUID of the trigger.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
trigger = osc_utils.find_resource(client.triggers, parsed_args.trigger)
|
||||
|
||||
trigger._info.pop("links", None)
|
||||
return zip(*sorted(trigger._info.items()))
|
||||
|
||||
|
||||
class CreateTrigger(command.ShowOne):
|
||||
_description = "Creates a trigger"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreateTrigger, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'name',
|
||||
metavar='<name>',
|
||||
help=_('The name of the trigger.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'type',
|
||||
metavar='<type>',
|
||||
help=_('Type of trigger.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'properties',
|
||||
metavar='<key=value,key=value>',
|
||||
help=_('Properties of trigger.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
trigger = client.triggers.create(parsed_args.name, parsed_args.type,
|
||||
parsed_args.properties)
|
||||
|
||||
trigger._info.pop("links", None)
|
||||
return zip(*sorted(trigger._info.items()))
|
||||
|
||||
|
||||
class UpdateTrigger(command.ShowOne):
|
||||
_description = "Update a trigger"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(UpdateTrigger, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
"trigger_id",
|
||||
metavar="<TRIGGER ID>",
|
||||
help=_("Id of trigger to update.")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--name",
|
||||
metavar="<name>",
|
||||
help=_("A name to which the trigger will be renamed.")
|
||||
)
|
||||
parser.add_argument(
|
||||
"--properties",
|
||||
metavar="<key=value,key=value>",
|
||||
help=_("Properties of trigger which will be updated.")
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
data = {}
|
||||
if parsed_args.name is not None:
|
||||
data['name'] = parsed_args.name
|
||||
if parsed_args.properties is not None:
|
||||
trigger_properties = utils.extract_properties(parsed_args)
|
||||
data['properties'] = trigger_properties
|
||||
try:
|
||||
trigger = osc_utils.find_resource(client.triggers,
|
||||
parsed_args.trigger_id)
|
||||
trigger = client.triggers.update(trigger.id, data)
|
||||
except exceptions.NotFound:
|
||||
raise exceptions.CommandError(
|
||||
"Trigger %s not found" % parsed_args.trigger_id)
|
||||
else:
|
||||
trigger._info.pop("links", None)
|
||||
return zip(*sorted(trigger._info.items()))
|
||||
|
||||
|
||||
class DeleteTrigger(command.Command):
|
||||
_description = "Delete trigger"
|
||||
|
||||
log = logging.getLogger(__name__ + ".DeleteTrigger")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(DeleteTrigger, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'trigger',
|
||||
metavar='<trigger>',
|
||||
nargs="+",
|
||||
help=_('ID of trigger.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
failure_count = 0
|
||||
for trigger_id in parsed_args.trigger:
|
||||
try:
|
||||
trigger = osc_utils.find_resource(client.triggers, trigger_id)
|
||||
client.triggers.delete(trigger.id)
|
||||
except exceptions.NotFound:
|
||||
failure_count += 1
|
||||
self.log.error(
|
||||
"Failed to delete '{0}'; trigger not found".
|
||||
format(trigger_id))
|
||||
if failure_count == len(parsed_args.trigger):
|
||||
raise exceptions.CommandError(
|
||||
"Unable to find and delete any of the "
|
||||
"specified trigger.")
|
||||
184
karborclient/osc/v1/verifications.py
Normal file
184
karborclient/osc/v1/verifications.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# 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.
|
||||
|
||||
"""Data protection V1 verification action implementations"""
|
||||
|
||||
import functools
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from osc_lib.command import command
|
||||
from osc_lib import utils as osc_utils
|
||||
|
||||
from karborclient.common.apiclient import exceptions
|
||||
from karborclient.i18n import _
|
||||
from karborclient import utils
|
||||
|
||||
|
||||
def format_verification(verification_info):
|
||||
for key in ('parameters', 'resources_status',
|
||||
'resources_reason'):
|
||||
if key not in verification_info:
|
||||
continue
|
||||
verification_info[key] = jsonutils.dumps(verification_info[key],
|
||||
indent=2, sort_keys=True)
|
||||
verification_info.pop("links", None)
|
||||
|
||||
|
||||
class ListVerifications(command.Lister):
|
||||
_description = _("List verifications.")
|
||||
|
||||
log = logging.getLogger(__name__ + ".ListVerifications")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ListVerifications, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--all-projects',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Include all projects (admin only)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--status',
|
||||
metavar='<status>',
|
||||
help=_('Filter results by status'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--marker',
|
||||
metavar='<verification>',
|
||||
help=_('The last verification ID of the previous page'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
type=int,
|
||||
metavar='<num-verifications>',
|
||||
help=_('Maximum number of verifications to display'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sort',
|
||||
metavar="<key>[:<direction>]",
|
||||
default=None,
|
||||
help=_("Sort output by selected keys and directions(asc or desc), "
|
||||
"multiple keys and directions can be "
|
||||
"specified separated by comma"),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--project',
|
||||
metavar='<project>',
|
||||
help=_('Filter results by a project(admin only)')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
data_protection_client = self.app.client_manager.data_protection
|
||||
all_projects = bool(parsed_args.project) or parsed_args.all_projects
|
||||
|
||||
search_opts = {
|
||||
'all_tenants': all_projects,
|
||||
'project_id': parsed_args.project,
|
||||
'status': parsed_args.status,
|
||||
}
|
||||
|
||||
data = data_protection_client.verifications.list(
|
||||
search_opts=search_opts, marker=parsed_args.marker,
|
||||
limit=parsed_args.limit, sort=parsed_args.sort)
|
||||
|
||||
column_headers = ['Id', 'Project id', 'Provider id', 'Checkpoint id',
|
||||
'Parameters', 'Status']
|
||||
|
||||
json_dumps = functools.partial(jsonutils.dumps,
|
||||
indent=2,
|
||||
sort_keys=True)
|
||||
formatters = {
|
||||
"Parameters": json_dumps,
|
||||
}
|
||||
return (column_headers,
|
||||
(osc_utils.get_item_properties(
|
||||
s, column_headers, formatters=formatters,
|
||||
) for s in data))
|
||||
|
||||
|
||||
class ShowVerification(command.ShowOne):
|
||||
_description = "Shows verification details"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowVerification, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'verification',
|
||||
metavar="<verification>",
|
||||
help=_('The UUID of the verification.')
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
verification = osc_utils.find_resource(client.verifications,
|
||||
parsed_args.verification)
|
||||
|
||||
format_verification(verification._info)
|
||||
return zip(*sorted(verification._info.items()))
|
||||
|
||||
|
||||
class CreateVerification(command.ShowOne):
|
||||
_description = "Creates a verification"
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreateVerification, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'provider_id',
|
||||
metavar='<provider_id>',
|
||||
help=_('The UUID of the provider.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'checkpoint_id',
|
||||
metavar='<checkpoint_id>',
|
||||
help=_('The UUID of the checkpoint.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--parameters-json',
|
||||
type=str,
|
||||
dest='parameters_json',
|
||||
metavar='<parameters>',
|
||||
default=None,
|
||||
help=_('Verification parameters in json format.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--parameters',
|
||||
action='append',
|
||||
metavar='resource_type=<type>[,resource_id=<id>,key=val,...]',
|
||||
default=[],
|
||||
help=_("Verification parameters, may be specified multiple times. "
|
||||
"resource_type: type of resource to apply parameters. "
|
||||
"resource_id: limit the parameters to a specific resource. "
|
||||
"Other keys and values: according to provider\'s "
|
||||
"verification schema.")
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client_manager.data_protection
|
||||
if not uuidutils.is_uuid_like(parsed_args.provider_id):
|
||||
raise exceptions.CommandError(
|
||||
"Invalid provider id provided.")
|
||||
if not uuidutils.is_uuid_like(parsed_args.checkpoint_id):
|
||||
raise exceptions.CommandError(
|
||||
"Invalid checkpoint id provided.")
|
||||
|
||||
verification_parameters = utils.extract_parameters(parsed_args)
|
||||
verification = client.verifications.create(parsed_args.provider_id,
|
||||
parsed_args.checkpoint_id,
|
||||
verification_parameters)
|
||||
format_verification(verification._info)
|
||||
return zip(*sorted(verification._info.items()))
|
||||
@@ -11,48 +11,58 @@
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Command-line interface to the Smaug Project.
|
||||
Command-line interface to the karbor Project.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import sys
|
||||
|
||||
from keystoneclient.auth.identity.generic import password
|
||||
from keystoneclient.auth.identity.generic import token
|
||||
from keystoneclient.auth.identity import v3 as identity
|
||||
from keystoneclient import discover
|
||||
from keystoneclient import exceptions as ks_exc
|
||||
from keystoneclient import session as ksession
|
||||
from keystoneauth1 import discover
|
||||
from keystoneauth1 import exceptions as ks_exc
|
||||
from keystoneauth1.identity.generic import password
|
||||
from keystoneauth1.identity.generic import token
|
||||
from keystoneauth1 import loading
|
||||
from oslo_log import handlers
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import importutils
|
||||
|
||||
import six
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
import smaugclient
|
||||
from smaugclient import client as smaug_client
|
||||
from smaugclient.common import utils
|
||||
from smaugclient.openstack.common.apiclient import exceptions as exc
|
||||
import karborclient
|
||||
from karborclient import client as karbor_client
|
||||
from karborclient.common.apiclient import exceptions as exc
|
||||
from karborclient.common import utils
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SmaugShell(object):
|
||||
class KarborShell(object):
|
||||
|
||||
def _append_global_identity_args(self, parser):
|
||||
# Register the CLI arguments that have moved to the session object.
|
||||
ksession.Session.register_cli_options(parser)
|
||||
def _append_global_identity_args(self, parser, argv):
|
||||
loading.register_session_argparse_arguments(parser)
|
||||
# Peek into argv to see if os-auth-token (or the deprecated
|
||||
# os_auth_token) or the new os-token or the environment variable
|
||||
# OS_AUTH_TOKEN were given. In which case, the token auth plugin is
|
||||
# what the user wants. Else, we'll default to password.
|
||||
default_auth_plugin = 'password'
|
||||
token_opts = ['os-token', 'os-auth-token', 'os_auth-token']
|
||||
if argv and any(i in token_opts for i in argv):
|
||||
default_auth_plugin = 'token'
|
||||
loading.register_auth_argparse_arguments(
|
||||
parser, argv, default=default_auth_plugin)
|
||||
|
||||
identity.Password.register_argparse_arguments(parser)
|
||||
|
||||
def get_base_parser(self):
|
||||
def get_base_parser(self, argv):
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='smaug',
|
||||
prog='karbor',
|
||||
description=__doc__.strip(),
|
||||
epilog='See "smaug help COMMAND" '
|
||||
epilog='See "karbor help COMMAND" '
|
||||
'for help on a specific command.',
|
||||
add_help=False,
|
||||
formatter_class=HelpFormatter,
|
||||
@@ -65,13 +75,13 @@ class SmaugShell(object):
|
||||
|
||||
parser.add_argument('--version',
|
||||
action='version',
|
||||
version=smaugclient.__version__,
|
||||
version=karborclient.__version__,
|
||||
help="Show program's version number and exit.")
|
||||
|
||||
parser.add_argument('-d', '--debug',
|
||||
default=bool(utils.env('SMAUGCLIENT_DEBUG')),
|
||||
default=bool(utils.env('KARBORCLIENT_DEBUG')),
|
||||
action='store_true',
|
||||
help='Defaults to env[SMAUGCLIENT_DEBUG].')
|
||||
help='Defaults to env[KARBORCLIENT_DEBUG].')
|
||||
|
||||
parser.add_argument('-v', '--verbose',
|
||||
default=False, action="store_true",
|
||||
@@ -96,11 +106,11 @@ class SmaugShell(object):
|
||||
'API response, '
|
||||
'defaults to system socket timeout.')
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
parser.add_argument('--os_tenant_id',
|
||||
default=utils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID].')
|
||||
|
||||
parser.add_argument('--os-tenant-name',
|
||||
parser.add_argument('--os_tenant_name',
|
||||
default=utils.env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME].')
|
||||
|
||||
@@ -117,14 +127,14 @@ class SmaugShell(object):
|
||||
action='store_true',
|
||||
help="Do not contact keystone for a token. "
|
||||
"Defaults to env[OS_NO_CLIENT_AUTH].")
|
||||
parser.add_argument('--smaug-url',
|
||||
default=utils.env('SMAUG_URL'),
|
||||
help='Defaults to env[SMAUG_URL].')
|
||||
parser.add_argument('--karbor-url',
|
||||
default=utils.env('KARBOR_URL'),
|
||||
help='Defaults to env[KARBOR_URL].')
|
||||
|
||||
parser.add_argument('--smaug-api-version',
|
||||
parser.add_argument('--karbor-api-version',
|
||||
default=utils.env(
|
||||
'SMAUG_API_VERSION', default='1'),
|
||||
help='Defaults to env[SMAUG_API_VERSION] '
|
||||
'KARBOR_API_VERSION', default='1'),
|
||||
help='Defaults to env[KARBOR_API_VERSION] '
|
||||
'or 1.')
|
||||
|
||||
parser.add_argument('--os-service-type',
|
||||
@@ -136,36 +146,27 @@ class SmaugShell(object):
|
||||
help='Defaults to env[OS_ENDPOINT_TYPE].')
|
||||
|
||||
parser.add_argument('--include-password',
|
||||
default=bool(utils.env('SMAUG_INCLUDE_PASSWORD')),
|
||||
default=bool(utils.env('KARBOR_INCLUDE_PASSWORD')),
|
||||
action='store_true',
|
||||
help='Send os-username and os-password to smaug.')
|
||||
help='Send os-username and os-password to karbor.')
|
||||
|
||||
self._append_global_identity_args(parser)
|
||||
self._append_global_identity_args(parser, argv)
|
||||
|
||||
return parser
|
||||
|
||||
def get_subcommand_parser(self, version):
|
||||
parser = self.get_base_parser()
|
||||
def get_subcommand_parser(self, version, argv=None):
|
||||
parser = self.get_base_parser(argv)
|
||||
|
||||
self.subcommands = {}
|
||||
subparsers = parser.add_subparsers(metavar='<subcommand>')
|
||||
submodule = utils.import_versioned_module(version, 'shell')
|
||||
submodule = importutils.import_versioned_module(
|
||||
'karborclient', version, 'shell'
|
||||
)
|
||||
self._find_actions(subparsers, submodule)
|
||||
self._find_actions(subparsers, self)
|
||||
|
||||
self._add_bash_completion_subparser(subparsers)
|
||||
|
||||
return parser
|
||||
|
||||
def _add_bash_completion_subparser(self, subparsers):
|
||||
subparser = subparsers.add_parser(
|
||||
'bash_completion',
|
||||
add_help=False,
|
||||
formatter_class=HelpFormatter
|
||||
)
|
||||
self.subcommands['bash_completion'] = subparser
|
||||
subparser.set_defaults(func=self.do_bash_completion)
|
||||
|
||||
def _find_actions(self, subparsers, actions_module):
|
||||
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
|
||||
# I prefer to be hypen-separated instead of underscores.
|
||||
@@ -192,7 +193,7 @@ class SmaugShell(object):
|
||||
v2_auth_url = None
|
||||
v3_auth_url = None
|
||||
try:
|
||||
ks_discover = discover.Discover(session=session, auth_url=auth_url)
|
||||
ks_discover = discover.Discover(session=session, url=auth_url)
|
||||
v2_auth_url = ks_discover.url_for('2.0')
|
||||
v3_auth_url = ks_discover.url_for('3.0')
|
||||
except ks_exc.ClientException as e:
|
||||
@@ -284,16 +285,17 @@ class SmaugShell(object):
|
||||
|
||||
def main(self, argv):
|
||||
# Parse args once to find version
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
base_argv = copy.deepcopy(argv)
|
||||
parser = self.get_base_parser(argv)
|
||||
(options, args) = parser.parse_known_args(base_argv)
|
||||
self._setup_logging(options.debug)
|
||||
|
||||
# build available subcommands based on version
|
||||
api_version = options.smaug_api_version
|
||||
subcommand_parser = self.get_subcommand_parser(api_version)
|
||||
api_version = options.karbor_api_version
|
||||
subcommand_parser = self.get_subcommand_parser(api_version, argv)
|
||||
self.parser = subcommand_parser
|
||||
|
||||
keystone_session = None
|
||||
ks_session = None
|
||||
keystone_auth = None
|
||||
|
||||
# Handle top-level --help/-h before attempting to parse
|
||||
@@ -320,11 +322,11 @@ class SmaugShell(object):
|
||||
" env[OS_AUTH_TOKEN]")
|
||||
|
||||
if args.os_no_client_auth:
|
||||
if not args.smaug_url:
|
||||
if not args.karbor_url:
|
||||
raise exc.CommandError(
|
||||
"If you specify --os-no-client-auth"
|
||||
" you must also specify a Smaug API URL"
|
||||
" via either --smaug-url or env[SMAUG_URL]")
|
||||
" you must also specify a Karbor API URL"
|
||||
" via either --karbor-url or env[KARBOR_URL]")
|
||||
|
||||
else:
|
||||
# Tenant name or ID is needed to make keystoneclient retrieve a
|
||||
@@ -343,10 +345,10 @@ class SmaugShell(object):
|
||||
" either --os-auth-url or via"
|
||||
" env[OS_AUTH_URL]")
|
||||
|
||||
endpoint = args.smaug_url
|
||||
endpoint = args.karbor_url
|
||||
|
||||
if args.os_no_client_auth:
|
||||
# Authenticate through smaug, don't use session
|
||||
# Authenticate through karbor, don't use session
|
||||
kwargs = {
|
||||
'username': args.os_username,
|
||||
'password': args.os_password,
|
||||
@@ -361,12 +363,12 @@ class SmaugShell(object):
|
||||
kwargs['region_name'] = args.os_region_name
|
||||
else:
|
||||
# Create a keystone session and keystone auth
|
||||
keystone_session = ksession.Session.load_from_cli_options(args)
|
||||
ks_session = loading.load_session_from_argparse_arguments(args)
|
||||
project_id = args.os_project_id or args.os_tenant_id
|
||||
project_name = args.os_project_name or args.os_tenant_name
|
||||
|
||||
keystone_auth = self._get_keystone_auth(
|
||||
keystone_session,
|
||||
ks_session,
|
||||
args.os_auth_url,
|
||||
username=args.os_username,
|
||||
user_id=args.os_user_id,
|
||||
@@ -383,12 +385,12 @@ class SmaugShell(object):
|
||||
service_type = args.os_service_type or 'data-protect'
|
||||
|
||||
endpoint = keystone_auth.get_endpoint(
|
||||
keystone_session,
|
||||
ks_session,
|
||||
service_type=service_type,
|
||||
region_name=args.os_region_name)
|
||||
|
||||
kwargs = {
|
||||
'session': keystone_session,
|
||||
'session': ks_session,
|
||||
'auth': keystone_auth,
|
||||
'service_type': service_type,
|
||||
'endpoint_type': endpoint_type,
|
||||
@@ -398,9 +400,9 @@ class SmaugShell(object):
|
||||
if args.api_timeout:
|
||||
kwargs['timeout'] = args.api_timeout
|
||||
|
||||
client = smaug_client.Client(api_version, endpoint, **kwargs)
|
||||
self.cs = karbor_client.Client(api_version, endpoint, **kwargs)
|
||||
|
||||
args.func(client, args)
|
||||
args.func(self.cs, args)
|
||||
|
||||
def do_bash_completion(self, args):
|
||||
"""Prints all of the commands and options to stdout."""
|
||||
@@ -412,7 +414,6 @@ class SmaugShell(object):
|
||||
options.add(option)
|
||||
|
||||
commands.remove('bash-completion')
|
||||
commands.remove('bash_completion')
|
||||
print(' '.join(commands | options))
|
||||
|
||||
@utils.arg('command', metavar='<subcommand>', nargs='?',
|
||||
@@ -440,10 +441,10 @@ class HelpFormatter(argparse.HelpFormatter):
|
||||
|
||||
def main(args=sys.argv[1:]):
|
||||
try:
|
||||
SmaugShell().main(args)
|
||||
KarborShell().main(args)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print('... terminating smaug client', file=sys.stderr)
|
||||
print('... terminating karbor client', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
if '--debug' in args or '-d' in args:
|
||||
@@ -44,17 +44,20 @@ class TestResponse(requests.Response):
|
||||
self._text = None
|
||||
super(TestResponse, self)
|
||||
if isinstance(data, dict):
|
||||
self.status_code = data.get('status_code', None)
|
||||
self.headers = data.get('headers', None)
|
||||
self.status_code = data.get('status_code')
|
||||
self.headers = data.get('headers')
|
||||
self.reason = data.get('reason', '')
|
||||
# Fake the text attribute to streamline Response creation
|
||||
self._text = data.get('text', None)
|
||||
self._text = data.get('text')
|
||||
else:
|
||||
self.status_code = data
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__dict__ == other.__dict__
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._text
|
||||
1
karborclient/tests/unit/osc/v1/__init__.py
Normal file
1
karborclient/tests/unit/osc/v1/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__author__ = 'c00179918'
|
||||
29
karborclient/tests/unit/osc/v1/fakes.py
Normal file
29
karborclient/tests/unit/osc/v1/fakes.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2015 Mirantis Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
import mock
|
||||
from osc_lib.tests import utils
|
||||
|
||||
|
||||
class TestDataProtection(utils.TestCommand):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDataProtection, self).setUp()
|
||||
|
||||
self.app.client_manager.data_protection = mock.Mock()
|
||||
self.app.client_manager.network = mock.Mock()
|
||||
self.app.client_manager.compute = mock.Mock()
|
||||
self.app.client_manager.volume = mock.Mock()
|
||||
254
karborclient/tests/unit/osc/v1/test_checkpoints.py
Normal file
254
karborclient/tests/unit/osc/v1/test_checkpoints.py
Normal file
@@ -0,0 +1,254 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from karborclient.osc.v1 import checkpoints as osc_checkpoints
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import checkpoints
|
||||
|
||||
|
||||
CHECKPOINT_INFO = {
|
||||
"id": "dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
|
||||
"project_id": "e486a2f49695423ca9c47e589b948108",
|
||||
"status": "available",
|
||||
"protection_plan": {
|
||||
"id": "3523a271-68aa-42f5-b9ba-56e5200a2ebb",
|
||||
"name": "My application",
|
||||
"provider_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
|
||||
"resources": [{
|
||||
"id": "99777fdd-8a5b-45ab-ba2c-52420008103f",
|
||||
"type": "OS::Glance::Image",
|
||||
"name": "cirros-0.3.4-x86_64-uec"}]
|
||||
},
|
||||
"resource_graph": jsonutils.dumps(
|
||||
"[{'0x0': ['OS::Glance::Image', "
|
||||
"'99777fdd-8a5b-45ab-ba2c-52420008103f', "
|
||||
"'cirros-0.3.4-x86_64-uec']}, [[['0x0']]]]"
|
||||
),
|
||||
}
|
||||
|
||||
CHECKPOINT_INFO_2 = {
|
||||
"id": "a6fd95fe-0892-43b2-ad3c-e56f3a1b86b8",
|
||||
"project_id": "79b35e99a6a541b3bcede40f590d6878",
|
||||
"status": "available",
|
||||
"protection_plan": {
|
||||
"id": "3b47fd5d-21f9-4e63-8409-0acb1bffc038",
|
||||
"name": "My application",
|
||||
"provider_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
|
||||
"resources": [{
|
||||
"id": "99777fdd-8a5b-45ab-ba2c-52420008103f",
|
||||
"type": "OS::Glance::Image",
|
||||
"name": "cirros-0.3.4-x86_64-uec"}]
|
||||
},
|
||||
"resource_graph": jsonutils.dumps(
|
||||
"[{'0x0': ['OS::Glance::Image', "
|
||||
"'99777fdd-8a5b-45ab-ba2c-52420008103f', "
|
||||
"'cirros-0.3.4-x86_64-uec']}, [[['0x0']]]]"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class TestCheckpoints(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestCheckpoints, self).setUp()
|
||||
cm = self.app.client_manager
|
||||
self.checkpoints_mock = cm.data_protection.checkpoints
|
||||
self.checkpoints_mock.reset_mock()
|
||||
|
||||
|
||||
class TestListCheckpoints(TestCheckpoints):
|
||||
def setUp(self):
|
||||
super(TestListCheckpoints, self).setUp()
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_checkpoints.ListCheckpoints(self.app, None)
|
||||
|
||||
def test_checkpoints_list(self):
|
||||
self.checkpoints_mock.list.return_value = [checkpoints.Checkpoint(
|
||||
None, copy.deepcopy(CHECKPOINT_INFO))]
|
||||
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9']
|
||||
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
['Id', 'Project id', 'Status', 'Protection plan', 'Metadata',
|
||||
'Created at'])
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
expected_data = [(
|
||||
"dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
|
||||
"e486a2f49695423ca9c47e589b948108",
|
||||
"available",
|
||||
"Name: %(name)s\nId: %(id)s" % {
|
||||
"id": "3523a271-68aa-42f5-b9ba-56e5200a2ebb",
|
||||
"name": "My application",
|
||||
},
|
||||
'',
|
||||
'')]
|
||||
self.assertEqual(expected_data, list(data))
|
||||
|
||||
def test_checkpoints_list_with_all_projects(self):
|
||||
self.checkpoints_mock.list.return_value = [checkpoints.Checkpoint(
|
||||
None, copy.deepcopy(CHECKPOINT_INFO)), checkpoints.Checkpoint(
|
||||
None, copy.deepcopy(CHECKPOINT_INFO_2))]
|
||||
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9', '--all-projects']
|
||||
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
|
||||
('all_projects', True)]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
expected_columns = (
|
||||
['Id', 'Project id', 'Status', 'Protection plan', 'Metadata',
|
||||
'Created at'])
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
expected_data = [(
|
||||
"dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
|
||||
"e486a2f49695423ca9c47e589b948108",
|
||||
"available",
|
||||
"Name: %(name)s\nId: %(id)s" % {
|
||||
"id": "3523a271-68aa-42f5-b9ba-56e5200a2ebb",
|
||||
"name": "My application",
|
||||
},
|
||||
'',
|
||||
''), (
|
||||
"a6fd95fe-0892-43b2-ad3c-e56f3a1b86b8",
|
||||
"79b35e99a6a541b3bcede40f590d6878",
|
||||
"available",
|
||||
"Name: %(name)s\nId: %(id)s" % {
|
||||
"id": "3b47fd5d-21f9-4e63-8409-0acb1bffc038",
|
||||
"name": "My application",
|
||||
},
|
||||
'',
|
||||
'')
|
||||
]
|
||||
self.assertEqual(expected_data, list(data))
|
||||
|
||||
|
||||
class TestCreateCheckpoint(TestCheckpoints):
|
||||
def setUp(self):
|
||||
super(TestCreateCheckpoint, self).setUp()
|
||||
self.checkpoints_mock.create.return_value = checkpoints.Checkpoint(
|
||||
None, copy.deepcopy(CHECKPOINT_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_checkpoints.CreateCheckpoint(self.app, None)
|
||||
|
||||
def test_checkpoint_create(self):
|
||||
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'3523a271-68aa-42f5-b9ba-56e5200a2ebb']
|
||||
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
|
||||
('plan_id', '3523a271-68aa-42f5-b9ba-56e5200a2ebb')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.checkpoints_mock.create.assert_called_once_with(
|
||||
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'3523a271-68aa-42f5-b9ba-56e5200a2ebb',
|
||||
None)
|
||||
|
||||
|
||||
class TestShowCheckpoint(TestCheckpoints):
|
||||
def setUp(self):
|
||||
super(TestShowCheckpoint, self).setUp()
|
||||
self.checkpoints_mock.get.return_value = checkpoints.Checkpoint(
|
||||
None, copy.deepcopy(CHECKPOINT_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_checkpoints.ShowCheckpoint(self.app, None)
|
||||
|
||||
def test_checkpoint_show(self):
|
||||
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3']
|
||||
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
|
||||
('checkpoint_id',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.checkpoints_mock.get.assert_called_once_with(
|
||||
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3')
|
||||
|
||||
|
||||
class TestDeleteCheckpoint(TestCheckpoints):
|
||||
def setUp(self):
|
||||
super(TestDeleteCheckpoint, self).setUp()
|
||||
self.checkpoints_mock.get.return_value = checkpoints.Checkpoint(
|
||||
None, copy.deepcopy(CHECKPOINT_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_checkpoints.DeleteCheckpoint(self.app, None)
|
||||
|
||||
def test_checkpoint_delete(self):
|
||||
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3']
|
||||
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
|
||||
('checkpoint',
|
||||
['dcb20606-ad71-40a3-80e4-ef0fafdad0c3'])]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.checkpoints_mock.delete.assert_called_once_with(
|
||||
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3')
|
||||
|
||||
|
||||
class TestResetCheckpointState(TestCheckpoints):
|
||||
def setUp(self):
|
||||
super(TestResetCheckpointState, self).setUp()
|
||||
self.cmd = osc_checkpoints.ResetCheckpointState(self.app, None)
|
||||
|
||||
def test_reset_checkpoint_with_default_state(self):
|
||||
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3']
|
||||
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
|
||||
('checkpoint',
|
||||
['dcb20606-ad71-40a3-80e4-ef0fafdad0c3']),
|
||||
('state', 'error')]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.checkpoints_mock.reset_state.assert_called_once_with(
|
||||
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3',
|
||||
'error')
|
||||
|
||||
def test_reset_checkpoint(self):
|
||||
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3',
|
||||
'--available']
|
||||
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
|
||||
('checkpoint',
|
||||
['dcb20606-ad71-40a3-80e4-ef0fafdad0c3']),
|
||||
('state', 'available')]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.checkpoints_mock.reset_state.assert_called_once_with(
|
||||
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3',
|
||||
'available')
|
||||
128
karborclient/tests/unit/osc/v1/test_operation_logs.py
Normal file
128
karborclient/tests/unit/osc/v1/test_operation_logs.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
from karborclient.osc.v1 import operation_logs as osc_operation_logs
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import operation_logs
|
||||
|
||||
|
||||
OPERATIONLOG_INFO = {
|
||||
"id": "22b82aa7-9179-4c71-bba2-caf5c0e68db7",
|
||||
"project_id": "e486a2f49695423ca9c47e589b948108",
|
||||
"operation_type": "protect",
|
||||
"checkpoint_id": "dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
|
||||
"plan_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
|
||||
"provider_id": "23902b02-5666-4ee6-8dfe-962ac09c3994",
|
||||
"restore_id": None,
|
||||
"scheduled_operation_id": "23902b02-5666-4ee6-8dfe-962ac09c3991",
|
||||
"started_at": "2015-08-27T09:50:58-05:00",
|
||||
"ended_at": "2015-08-27T10:50:58-05:00",
|
||||
"status": "protecting",
|
||||
"error_info": "Could not access bank",
|
||||
"extra_info": None
|
||||
}
|
||||
|
||||
|
||||
class TestOperationLogs(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestOperationLogs, self).setUp()
|
||||
self.operation_logs_mock = (
|
||||
self.app.client_manager.data_protection.operation_logs)
|
||||
self.operation_logs_mock.reset_mock()
|
||||
|
||||
|
||||
class TestListOperationLogs(TestOperationLogs):
|
||||
def setUp(self):
|
||||
super(TestListOperationLogs, self).setUp()
|
||||
self.operation_logs_mock.list.return_value = [
|
||||
operation_logs.OperationLog(None,
|
||||
copy.deepcopy(OPERATIONLOG_INFO))]
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_operation_logs.ListOperationLogs(self.app, None)
|
||||
|
||||
def test_operation_logs_list(self):
|
||||
arglist = ['--status', 'success']
|
||||
verifylist = [('status', 'success')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
['Id', 'Operation Type', 'Checkpoint id', 'Plan Id',
|
||||
'Provider id', 'Restore Id', 'Scheduled Operation Id',
|
||||
'Status', 'Started At', 'Ended At', 'Error Info',
|
||||
'Extra Info'])
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
expected_data = [("22b82aa7-9179-4c71-bba2-caf5c0e68db7",
|
||||
"protect",
|
||||
"dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
|
||||
"cf56bd3e-97a7-4078-b6d5-f36246333fd9",
|
||||
"23902b02-5666-4ee6-8dfe-962ac09c3994",
|
||||
None,
|
||||
"23902b02-5666-4ee6-8dfe-962ac09c3991",
|
||||
"protecting",
|
||||
"2015-08-27T09:50:58-05:00",
|
||||
"2015-08-27T10:50:58-05:00",
|
||||
"Could not access bank",
|
||||
None)]
|
||||
self.assertEqual(expected_data, list(data))
|
||||
|
||||
|
||||
class TestShowOperationLog(TestOperationLogs):
|
||||
def setUp(self):
|
||||
super(TestShowOperationLog, self).setUp()
|
||||
self._oplog_info = copy.deepcopy(OPERATIONLOG_INFO)
|
||||
self.operation_logs_mock.get.return_value = (
|
||||
operation_logs.OperationLog(None, self._oplog_info))
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_operation_logs.ShowOperationLog(self.app, None)
|
||||
|
||||
def test_operation_log_show(self):
|
||||
arglist = ['22b82aa7-9179-4c71-bba2-caf5c0e68db7']
|
||||
verifylist = [('operation_log',
|
||||
'22b82aa7-9179-4c71-bba2-caf5c0e68db7')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
'checkpoint_id', 'ended_at', 'error_info', 'extra_info',
|
||||
'id', 'operation_type', 'plan_id', 'project_id',
|
||||
'provider_id', 'restore_id', 'scheduled_operation_id',
|
||||
'started_at', 'status')
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
self.assertEqual(self._oplog_info['checkpoint_id'], data[0])
|
||||
self.assertEqual(self._oplog_info['ended_at'], data[1])
|
||||
self.assertEqual(self._oplog_info['error_info'], data[2])
|
||||
self.assertEqual(self._oplog_info['extra_info'], data[3])
|
||||
self.assertEqual(self._oplog_info['id'], data[4])
|
||||
self.assertEqual(self._oplog_info['operation_type'], data[5])
|
||||
self.assertEqual(self._oplog_info['plan_id'], data[6])
|
||||
self.assertEqual(self._oplog_info['project_id'], data[7])
|
||||
self.assertEqual(self._oplog_info['provider_id'], data[8])
|
||||
self.assertEqual(self._oplog_info['restore_id'], data[9])
|
||||
self.assertEqual(self._oplog_info['scheduled_operation_id'], data[10])
|
||||
self.assertEqual(self._oplog_info['started_at'], data[11])
|
||||
self.assertEqual(self._oplog_info['status'], data[12])
|
||||
183
karborclient/tests/unit/osc/v1/test_plans.py
Normal file
183
karborclient/tests/unit/osc/v1/test_plans.py
Normal file
@@ -0,0 +1,183 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
from karborclient.osc.v1 import plans as osc_plans
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import plans
|
||||
|
||||
|
||||
PLAN_INFO = {
|
||||
"status": "suspended",
|
||||
"provider_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
|
||||
"description": "",
|
||||
"parameters": {},
|
||||
"id": "204c825e-eb2f-4609-95ab-70b3caa43ac8",
|
||||
"resources": [{
|
||||
'type': 'OS::Cinder::Volume',
|
||||
'id': '71bfe64a-e0b9-4a91-9e15-a7fc9ab31b14',
|
||||
'name': 'testsinglevolume'}],
|
||||
"name": "OS Volume protection plan."
|
||||
}
|
||||
|
||||
|
||||
class TestPlans(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestPlans, self).setUp()
|
||||
self.plans_mock = self.app.client_manager.data_protection.plans
|
||||
self.plans_mock.reset_mock()
|
||||
|
||||
|
||||
class TestListPlans(TestPlans):
|
||||
def setUp(self):
|
||||
super(TestListPlans, self).setUp()
|
||||
self.plans_mock.list.return_value = [plans.Plan(
|
||||
None, copy.deepcopy(PLAN_INFO))]
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_plans.ListPlans(self.app, None)
|
||||
|
||||
def test_plans_list(self):
|
||||
arglist = ['--status', 'suspended']
|
||||
verifylist = [('status', 'suspended')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
['Id', 'Name', 'Description', 'Provider id', 'Status'])
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
expected_data = [("204c825e-eb2f-4609-95ab-70b3caa43ac8",
|
||||
"OS Volume protection plan.",
|
||||
"",
|
||||
"cf56bd3e-97a7-4078-b6d5-f36246333fd9",
|
||||
"suspended")]
|
||||
self.assertEqual(expected_data, list(data))
|
||||
|
||||
|
||||
class TestCreatePlan(TestPlans):
|
||||
def setUp(self):
|
||||
super(TestCreatePlan, self).setUp()
|
||||
self.plans_mock.create.return_value = plans.Plan(
|
||||
None, copy.deepcopy(PLAN_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_plans.CreatePlan(self.app, None)
|
||||
|
||||
def test_plan_create(self):
|
||||
arglist = ['OS Volume protection plan.',
|
||||
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
"'71bfe64a-e0b9-4a91-9e15-a7fc9ab31b14'="
|
||||
"'OS::Cinder::Volume'='testsinglevolume'"]
|
||||
verifylist = [('name', 'OS Volume protection plan.'),
|
||||
('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
|
||||
('resources', "'71bfe64a-e0b9-4a91-9e15-a7fc9ab31b14'="
|
||||
"'OS::Cinder::Volume'='testsinglevolume'")]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.plans_mock.create.assert_called_once_with(
|
||||
'OS Volume protection plan.',
|
||||
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
[{'id': "'71bfe64a-e0b9-4a91-9e15-a7fc9ab31b14'",
|
||||
'type': "'OS::Cinder::Volume'",
|
||||
'name': "'testsinglevolume'"}],
|
||||
{}, description=None)
|
||||
|
||||
|
||||
class TestUpdatePlan(TestPlans):
|
||||
def setUp(self):
|
||||
super(TestUpdatePlan, self).setUp()
|
||||
self.plans_mock.get.return_value = plans.Plan(
|
||||
None, copy.deepcopy(PLAN_INFO))
|
||||
self.plans_mock.update.return_value = plans.Plan(
|
||||
None, copy.deepcopy(PLAN_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_plans.UpdatePlan(self.app, None)
|
||||
|
||||
def test_plan_update(self):
|
||||
arglist = ['204c825e-eb2f-4609-95ab-70b3caa43ac8',
|
||||
'--status', 'started']
|
||||
verifylist = [('plan_id', '204c825e-eb2f-4609-95ab-70b3caa43ac8'),
|
||||
('status', 'started')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.plans_mock.update.assert_called_once_with(
|
||||
'204c825e-eb2f-4609-95ab-70b3caa43ac8',
|
||||
{'status': 'started'})
|
||||
|
||||
|
||||
class TestDeletePlan(TestPlans):
|
||||
def setUp(self):
|
||||
super(TestDeletePlan, self).setUp()
|
||||
self.plans_mock.get.return_value = plans.Plan(
|
||||
None, copy.deepcopy(PLAN_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_plans.DeletePlan(self.app, None)
|
||||
|
||||
def test_plan_delete(self):
|
||||
arglist = ['204c825e-eb2f-4609-95ab-70b3caa43ac8']
|
||||
verifylist = [('plan', ['204c825e-eb2f-4609-95ab-70b3caa43ac8'])]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.plans_mock.delete.assert_called_once_with(
|
||||
'204c825e-eb2f-4609-95ab-70b3caa43ac8')
|
||||
|
||||
|
||||
class TestShowPlan(TestPlans):
|
||||
def setUp(self):
|
||||
super(TestShowPlan, self).setUp()
|
||||
self._plan_info = copy.deepcopy(PLAN_INFO)
|
||||
self.plans_mock.get.return_value = plans.Plan(
|
||||
None, self._plan_info)
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_plans.ShowPlan(self.app, None)
|
||||
|
||||
def test_plan_show(self):
|
||||
arglist = ['204c825e-eb2f-4609-95ab-70b3caa43ac8']
|
||||
verifylist = [('plan', '204c825e-eb2f-4609-95ab-70b3caa43ac8')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
'description', 'id', 'name', 'parameters', 'provider_id',
|
||||
'resources', 'status')
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
self.assertEqual(self._plan_info['description'], data[0])
|
||||
self.assertEqual(self._plan_info['id'], data[1])
|
||||
self.assertEqual(self._plan_info['name'], data[2])
|
||||
self.assertEqual(self._plan_info['parameters'], data[3])
|
||||
self.assertEqual(self._plan_info['provider_id'], data[4])
|
||||
self.assertEqual(self._plan_info['resources'], data[5])
|
||||
self.assertEqual(self._plan_info['status'], data[6])
|
||||
163
karborclient/tests/unit/osc/v1/test_protectables.py
Normal file
163
karborclient/tests/unit/osc/v1/test_protectables.py
Normal file
@@ -0,0 +1,163 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
from karborclient.osc.v1 import protectables as osc_protectables
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import protectables
|
||||
|
||||
|
||||
PROTECTABLE_LIST_INFO = {
|
||||
"protectable_type": [
|
||||
"OS::Keystone::Project",
|
||||
"OS::Cinder::Volume",
|
||||
"OS::Glance::Image",
|
||||
"OS::Nova::Server"
|
||||
]
|
||||
}
|
||||
|
||||
PROTECTABLE_SHOW_INFO = {
|
||||
"name": "OS::Nova::Server",
|
||||
"dependent_types": [
|
||||
"OS::Cinder::Volume",
|
||||
"OS::Glance::Image"
|
||||
]
|
||||
}
|
||||
|
||||
PROTECTABLE_INSTANCE_LIST_INFO = {
|
||||
"id": "25336116-f38e-4c22-81ad-e9b7bd71ba51",
|
||||
"type": "OS::Cinder::Volume",
|
||||
"name": "System volume",
|
||||
"extra_info": {
|
||||
"availability_zone": "az1"
|
||||
}
|
||||
}
|
||||
|
||||
PROTECTABLE_INSTANCE_SHOW_INFO = {
|
||||
"id": "cb4ef2ff-10f5-46c9-bce4-cf7a49c65a01",
|
||||
"type": "OS::Nova::Server",
|
||||
"name": "My VM",
|
||||
"dependent_resources": [{
|
||||
"id": "99777fdd-8a5b-45ab-ba2c-52420008103f",
|
||||
"type": "OS::Glance::Image",
|
||||
"name": "cirros-0.3.4-x86_64-uec"}]
|
||||
}
|
||||
|
||||
|
||||
class TestProtectables(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestProtectables, self).setUp()
|
||||
cm = self.app.client_manager
|
||||
self.protectables_mock = cm.data_protection.protectables
|
||||
self.protectables_mock.reset_mock()
|
||||
|
||||
|
||||
class TestListProtectables(TestProtectables):
|
||||
def setUp(self):
|
||||
super(TestListProtectables, self).setUp()
|
||||
self.protectables_mock.list.return_value = [protectables.Protectable(
|
||||
None, copy.deepcopy(PROTECTABLE_LIST_INFO))]
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_protectables.ListProtectables(self.app, None)
|
||||
|
||||
def test_protectables_list(self):
|
||||
arglist = []
|
||||
verifylist = []
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
['Protectable type'])
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
expected_data = [(['OS::Keystone::Project',
|
||||
'OS::Cinder::Volume',
|
||||
'OS::Glance::Image',
|
||||
'OS::Nova::Server'],)]
|
||||
self.assertEqual(expected_data, list(data))
|
||||
|
||||
|
||||
class TestShowProtectable(TestProtectables):
|
||||
def setUp(self):
|
||||
super(TestShowProtectable, self).setUp()
|
||||
self.protectables_mock.get.return_value = protectables.Protectable(
|
||||
None, copy.deepcopy(PROTECTABLE_SHOW_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_protectables.ShowProtectable(self.app, None)
|
||||
|
||||
def test_protectable_show(self):
|
||||
arglist = ['OS::Nova::Server']
|
||||
verifylist = [('protectable_type', 'OS::Nova::Server')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.protectables_mock.get.assert_called_once_with(
|
||||
'OS::Nova::Server')
|
||||
|
||||
|
||||
class TestListProtectableInstances(TestProtectables):
|
||||
def setUp(self):
|
||||
super(TestListProtectableInstances, self).setUp()
|
||||
pm = self.protectables_mock
|
||||
pm.list_instances.return_value = [protectables.Instances(
|
||||
None, copy.deepcopy(PROTECTABLE_INSTANCE_LIST_INFO)), ]
|
||||
# Command to test
|
||||
self.cmd = osc_protectables.ListProtectableInstances(self.app, None)
|
||||
|
||||
def test_protectable_instances_list(self):
|
||||
arglist = ['OS::Cinder::Volume']
|
||||
verifylist = [('protectable_type', 'OS::Cinder::Volume')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.protectables_mock.list_instances.assert_called_once_with(
|
||||
'OS::Cinder::Volume', limit=None, marker=None,
|
||||
search_opts={'type': None, 'parameters': None},
|
||||
sort=None)
|
||||
|
||||
|
||||
class TestShowProtectableInstance(TestProtectables):
|
||||
def setUp(self):
|
||||
super(TestShowProtectableInstance, self).setUp()
|
||||
pm = self.protectables_mock
|
||||
pm.get_instance.return_value = protectables.Instances(
|
||||
None, copy.deepcopy(PROTECTABLE_INSTANCE_SHOW_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_protectables.ShowProtectableInstance(self.app, None)
|
||||
|
||||
def test_protectable_instance_show(self):
|
||||
arglist = ['OS::Nova::Server', 'cb4ef2ff-10f5-46c9-bce4-cf7a49c65a01']
|
||||
verifylist = [('protectable_type', 'OS::Nova::Server'),
|
||||
('protectable_id',
|
||||
'cb4ef2ff-10f5-46c9-bce4-cf7a49c65a01')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.protectables_mock.get_instance.assert_called_once_with(
|
||||
'OS::Nova::Server', 'cb4ef2ff-10f5-46c9-bce4-cf7a49c65a01',
|
||||
search_opts={'parameters': None})
|
||||
144
karborclient/tests/unit/osc/v1/test_providers.py
Normal file
144
karborclient/tests/unit/osc/v1/test_providers.py
Normal file
@@ -0,0 +1,144 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
from karborclient.osc.v1 import providers as osc_providers
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import providers
|
||||
|
||||
|
||||
PROVIDER_INFO = {
|
||||
"id": "2220f8b1-975d-4621-a872-fa9afb43cb6c",
|
||||
"name": "OS Infra Provider",
|
||||
"description": "provider description",
|
||||
"extended_info_schema": {
|
||||
"options_schema": {
|
||||
"OS::Cinder::Volume": {
|
||||
"required": [
|
||||
"backup_mode"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backup_mode": {
|
||||
"default": "auto",
|
||||
"enum": [
|
||||
"full",
|
||||
"incremental",
|
||||
"auto"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "The backup mode.",
|
||||
"title": "Backup Mode"
|
||||
}
|
||||
},
|
||||
"title": "Cinder Protection Options"
|
||||
}
|
||||
},
|
||||
"saved_info_schema": {
|
||||
"OS::Cinder::Volume": {
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name for this backup.",
|
||||
"title": "Name"
|
||||
}
|
||||
},
|
||||
"title": "Cinder Protection Saved Info"
|
||||
}
|
||||
},
|
||||
"restore_schema": {
|
||||
"OS::Cinder::Volume": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"restore_name": {
|
||||
"type": "string",
|
||||
"description": "The name of the restored volume.",
|
||||
"title": "Restore Name"
|
||||
}
|
||||
},
|
||||
"title": "Cinder Protection Restore"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestProviders(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestProviders, self).setUp()
|
||||
self.providers_mock = self.app.client_manager.data_protection.providers
|
||||
self.providers_mock.reset_mock()
|
||||
|
||||
|
||||
class TestListProviders(TestProviders):
|
||||
def setUp(self):
|
||||
super(TestListProviders, self).setUp()
|
||||
self.providers_mock.list.return_value = [providers.Provider(
|
||||
None, copy.deepcopy(PROVIDER_INFO))]
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_providers.ListProviders(self.app, None)
|
||||
|
||||
def test_providers_list(self):
|
||||
arglist = ['--name', 'OS Infra Provider']
|
||||
verifylist = [('name', 'OS Infra Provider')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
['Id', 'Name', 'Description'])
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
expected_data = [("2220f8b1-975d-4621-a872-fa9afb43cb6c",
|
||||
"OS Infra Provider",
|
||||
"provider description")]
|
||||
self.assertEqual(expected_data, list(data))
|
||||
|
||||
|
||||
class TestShowProvider(TestProviders):
|
||||
def setUp(self):
|
||||
super(TestShowProvider, self).setUp()
|
||||
self._provider_info = copy.deepcopy(PROVIDER_INFO)
|
||||
self.providers_mock.get.return_value = providers.Provider(
|
||||
None, self._provider_info)
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_providers.ShowProvider(self.app, None)
|
||||
|
||||
def test_provider_show(self):
|
||||
arglist = ['2220f8b1-975d-4621-a872-fa9afb43cb6c']
|
||||
verifylist = [('provider', '2220f8b1-975d-4621-a872-fa9afb43cb6c')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
'description', 'extended_info_schema', 'id', 'name')
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
self.assertEqual(self._provider_info['description'], data[0])
|
||||
self.assertEqual(self._provider_info['extended_info_schema'], data[1])
|
||||
self.assertEqual(self._provider_info['id'], data[2])
|
||||
self.assertEqual(self._provider_info['name'], data[3])
|
||||
77
karborclient/tests/unit/osc/v1/test_quota_classes.py
Normal file
77
karborclient/tests/unit/osc/v1/test_quota_classes.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
from karborclient.osc.v1 import quota_classes as osc_quota_classes
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import quota_classes
|
||||
|
||||
|
||||
QUOTA_CLASSES_INFO = {
|
||||
"id": "default",
|
||||
"plans": "40"
|
||||
}
|
||||
|
||||
|
||||
class TestQuotaClasses(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestQuotaClasses, self).setUp()
|
||||
self.quotas_mock = (
|
||||
self.app.client_manager.data_protection.quota_classes)
|
||||
self.quotas_mock.reset_mock()
|
||||
|
||||
|
||||
class TestUpdateQuotaClasses(TestQuotaClasses):
|
||||
def setUp(self):
|
||||
super(TestUpdateQuotaClasses, self).setUp()
|
||||
self.quotas_mock.update.return_value = quota_classes.QuotaClass(
|
||||
None, copy.deepcopy(QUOTA_CLASSES_INFO))
|
||||
self.cmd = osc_quota_classes.UpdateQuotaClasses(self.app, None)
|
||||
|
||||
def test_quota_classes_update(self):
|
||||
arglist = ['--plans',
|
||||
'40', 'default']
|
||||
verifylist = [('plans', 40),
|
||||
('class_name',
|
||||
'default')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
self.quotas_mock.update.assert_called_once_with(
|
||||
'default',
|
||||
{'plans': 40})
|
||||
|
||||
|
||||
class TestShowQuotaClasses(TestQuotaClasses):
|
||||
def setUp(self):
|
||||
super(TestShowQuotaClasses, self).setUp()
|
||||
self._quota_info = copy.deepcopy(QUOTA_CLASSES_INFO)
|
||||
self.quotas_mock.get.return_value = quota_classes.QuotaClass(
|
||||
None, copy.deepcopy(QUOTA_CLASSES_INFO))
|
||||
|
||||
self.cmd = osc_quota_classes.ShowQuotaClasses(self.app, None)
|
||||
|
||||
def test_quota_classes_show(self):
|
||||
arglist = ['default']
|
||||
verifylist = [('class_name', 'default')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.assertEqual(('id', 'plans'), columns)
|
||||
self.assertEqual(('default', '40'),
|
||||
data)
|
||||
76
karborclient/tests/unit/osc/v1/test_quotas.py
Normal file
76
karborclient/tests/unit/osc/v1/test_quotas.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
from karborclient.osc.v1 import quotas as osc_quotas
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import quotas
|
||||
|
||||
|
||||
QUOTA_INFO = {
|
||||
"id": "73f74f90a1754bd7ad658afb3272323f",
|
||||
"plans": "40"
|
||||
}
|
||||
|
||||
|
||||
class TestQuotas(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestQuotas, self).setUp()
|
||||
self.quotas_mock = self.app.client_manager.data_protection.quotas
|
||||
self.quotas_mock.reset_mock()
|
||||
|
||||
|
||||
class TestUpdateQuotas(TestQuotas):
|
||||
def setUp(self):
|
||||
super(TestUpdateQuotas, self).setUp()
|
||||
self.quotas_mock.update.return_value = quotas.Quota(
|
||||
None, copy.deepcopy(QUOTA_INFO))
|
||||
self.cmd = osc_quotas.UpdateQuotas(self.app, None)
|
||||
|
||||
def test_quotas_update(self):
|
||||
arglist = ['--plans',
|
||||
'40', '73f74f90a1754bd7ad658afb3272323f']
|
||||
verifylist = [('plans', 40),
|
||||
('tenant',
|
||||
'73f74f90a1754bd7ad658afb3272323f')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
self.quotas_mock.update.assert_called_once_with(
|
||||
'73f74f90a1754bd7ad658afb3272323f',
|
||||
{'plans': 40})
|
||||
|
||||
|
||||
class TestShowQuotas(TestQuotas):
|
||||
def setUp(self):
|
||||
super(TestShowQuotas, self).setUp()
|
||||
self._quota_info = copy.deepcopy(QUOTA_INFO)
|
||||
self.quotas_mock.get.return_value = quotas.Quota(
|
||||
None, copy.deepcopy(QUOTA_INFO))
|
||||
|
||||
self.cmd = osc_quotas.ShowQuotas(self.app, None)
|
||||
|
||||
def test_quota_show(self):
|
||||
arglist = []
|
||||
verifylist = []
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
self.assertEqual(('id', 'plans'), columns)
|
||||
self.assertEqual(('73f74f90a1754bd7ad658afb3272323f', '40'),
|
||||
data)
|
||||
139
karborclient/tests/unit/osc/v1/test_restores.py
Normal file
139
karborclient/tests/unit/osc/v1/test_restores.py
Normal file
@@ -0,0 +1,139 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from karborclient.osc.v1 import restores as osc_restores
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import restores
|
||||
|
||||
|
||||
RESTORE_INFO = {
|
||||
"id": "22b82aa7-9179-4c71-bba2-caf5c0e68db7",
|
||||
"project_id": "e486a2f49695423ca9c47e589b948108",
|
||||
"provider_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
|
||||
"checkpoint_id": "dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
|
||||
"restore_target": "",
|
||||
"parameters": {},
|
||||
"restore_auth": {},
|
||||
"resources_status": {},
|
||||
"resources_reason": {},
|
||||
"status": "success"
|
||||
}
|
||||
|
||||
|
||||
class TestRestores(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestRestores, self).setUp()
|
||||
self.restores_mock = self.app.client_manager.data_protection.restores
|
||||
self.restores_mock.reset_mock()
|
||||
|
||||
|
||||
class TestListRestores(TestRestores):
|
||||
def setUp(self):
|
||||
super(TestListRestores, self).setUp()
|
||||
self.restores_mock.list.return_value = [restores.Restore(
|
||||
None, copy.deepcopy(RESTORE_INFO))]
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_restores.ListRestores(self.app, None)
|
||||
|
||||
def test_restores_list(self):
|
||||
arglist = ['--status', 'success']
|
||||
verifylist = [('status', 'success')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
['Id', 'Project id', 'Provider id', 'Checkpoint id',
|
||||
'Restore target', 'Parameters', 'Status'])
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
expected_data = [("22b82aa7-9179-4c71-bba2-caf5c0e68db7",
|
||||
"e486a2f49695423ca9c47e589b948108",
|
||||
"cf56bd3e-97a7-4078-b6d5-f36246333fd9",
|
||||
"dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
|
||||
"",
|
||||
jsonutils.dumps({}),
|
||||
"success")]
|
||||
self.assertEqual(expected_data, list(data))
|
||||
|
||||
|
||||
class TestCreateRestore(TestRestores):
|
||||
def setUp(self):
|
||||
super(TestCreateRestore, self).setUp()
|
||||
self.restores_mock.create.return_value = restores.Restore(
|
||||
None, copy.deepcopy(RESTORE_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_restores.CreateRestore(self.app, None)
|
||||
|
||||
def test_restore_create(self):
|
||||
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3']
|
||||
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
|
||||
('checkpoint_id',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.restores_mock.create.assert_called_once_with(
|
||||
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3',
|
||||
None, {}, None)
|
||||
|
||||
|
||||
class TestShowRestore(TestRestores):
|
||||
def setUp(self):
|
||||
super(TestShowRestore, self).setUp()
|
||||
self._restore_info = copy.deepcopy(RESTORE_INFO)
|
||||
self.restores_mock.get.return_value = restores.Restore(
|
||||
None, self._restore_info)
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_restores.ShowRestore(self.app, None)
|
||||
|
||||
def test_restore_show(self):
|
||||
arglist = ['22b82aa7-9179-4c71-bba2-caf5c0e68db7']
|
||||
verifylist = [('restore', '22b82aa7-9179-4c71-bba2-caf5c0e68db7')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
'checkpoint_id', 'id', 'parameters', 'project_id',
|
||||
'provider_id', 'resources_reason', 'resources_status',
|
||||
'restore_auth', 'restore_target', 'status')
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
self.assertEqual(self._restore_info['checkpoint_id'], data[0])
|
||||
self.assertEqual(self._restore_info['id'], data[1])
|
||||
self.assertEqual(self._restore_info['parameters'], data[2])
|
||||
self.assertEqual(self._restore_info['project_id'], data[3])
|
||||
self.assertEqual(self._restore_info['provider_id'], data[4])
|
||||
self.assertEqual(self._restore_info['resources_reason'], data[5])
|
||||
self.assertEqual(self._restore_info['resources_status'], data[6])
|
||||
self.assertEqual(self._restore_info['restore_auth'], data[7])
|
||||
self.assertEqual(self._restore_info['restore_target'], data[8])
|
||||
self.assertEqual(self._restore_info['status'], data[9])
|
||||
173
karborclient/tests/unit/osc/v1/test_scheduledoperations.py
Normal file
173
karborclient/tests/unit/osc/v1/test_scheduledoperations.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
import json
|
||||
|
||||
from karborclient.osc.v1 import scheduled_operations as osc_so
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import scheduled_operations
|
||||
|
||||
|
||||
SCHEDULEDOPERATION_INFO = {
|
||||
"id": "1a2c0c3d-f402-4cd8-b5db-82e85cb51fad",
|
||||
"name": "My scheduled operation",
|
||||
"description": "It will run everyday",
|
||||
"operation_type": "protect",
|
||||
"trigger_id": "23902b02-5666-4ee6-8dfe-962ac09c3995",
|
||||
"operation_definition": {
|
||||
"provider_id": "2a9ce1f3-cc1a-4516-9435-0ebb13caa399",
|
||||
"plan_id": "2a9ce1f3-cc1a-4516-9435-0ebb13caa398"
|
||||
},
|
||||
"enabled": 1
|
||||
}
|
||||
|
||||
|
||||
class TestScheduledOperations(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestScheduledOperations, self).setUp()
|
||||
self.so_mock = self.app.client_manager.data_protection.\
|
||||
scheduled_operations
|
||||
self.so_mock.reset_mock()
|
||||
|
||||
|
||||
class TestListScheduledOperations(TestScheduledOperations):
|
||||
def setUp(self):
|
||||
super(TestListScheduledOperations, self).setUp()
|
||||
self.so_mock.list.return_value = [
|
||||
scheduled_operations.ScheduledOperation(
|
||||
None, copy.deepcopy(SCHEDULEDOPERATION_INFO))
|
||||
]
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_so.ListScheduledOperations(self.app, None)
|
||||
|
||||
def test_scheduled_operations_list(self):
|
||||
arglist = ['--name', 'My scheduled operation']
|
||||
verifylist = [('name', 'My scheduled operation')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
['Id', 'Name', 'Operation Type', 'Trigger Id',
|
||||
'Operation Definition'])
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
operation_definition = {
|
||||
"provider_id": "2a9ce1f3-cc1a-4516-9435-0ebb13caa399",
|
||||
"plan_id": "2a9ce1f3-cc1a-4516-9435-0ebb13caa398"
|
||||
}
|
||||
|
||||
# Check that data is correct
|
||||
expected_data = [("1a2c0c3d-f402-4cd8-b5db-82e85cb51fad",
|
||||
"My scheduled operation",
|
||||
"protect",
|
||||
"23902b02-5666-4ee6-8dfe-962ac09c3995",
|
||||
json.dumps(operation_definition,
|
||||
indent=2, sort_keys=True)
|
||||
)]
|
||||
self.assertEqual(expected_data, data)
|
||||
|
||||
|
||||
class TestCreateScheduledOperation(TestScheduledOperations):
|
||||
def setUp(self):
|
||||
super(TestCreateScheduledOperation, self).setUp()
|
||||
self.so_mock.create.return_value = scheduled_operations.\
|
||||
ScheduledOperation(None, copy.deepcopy(SCHEDULEDOPERATION_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_so.CreateScheduledOperation(self.app, None)
|
||||
|
||||
def test_scheduled_operation_create(self):
|
||||
arglist = ['My scheduled operation',
|
||||
'protect',
|
||||
"23902b02-5666-4ee6-8dfe-962ac09c3995",
|
||||
"'provider_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa399,"
|
||||
"plan_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa398'"]
|
||||
verifylist = [('name', 'My scheduled operation'),
|
||||
('operation_type', 'protect'),
|
||||
('trigger_id', "23902b02-5666-4ee6-8dfe-962ac09c3995"),
|
||||
('operation_definition',
|
||||
"'provider_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa399,"
|
||||
"plan_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa398'")]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.so_mock.create.assert_called_once_with(
|
||||
'My scheduled operation',
|
||||
'protect',
|
||||
'23902b02-5666-4ee6-8dfe-962ac09c3995',
|
||||
"'provider_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa399,"
|
||||
"plan_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa398'")
|
||||
|
||||
|
||||
class TestDeleteScheduledOperation(TestScheduledOperations):
|
||||
def setUp(self):
|
||||
super(TestDeleteScheduledOperation, self).setUp()
|
||||
self.so_mock.get.return_value = scheduled_operations.\
|
||||
ScheduledOperation(None, copy.deepcopy(SCHEDULEDOPERATION_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_so.DeleteScheduledOperation(self.app, None)
|
||||
|
||||
def test_scheduled_operation_delete(self):
|
||||
arglist = ['1a2c0c3d-f402-4cd8-b5db-82e85cb51fad']
|
||||
verifylist = [('scheduledoperation',
|
||||
['1a2c0c3d-f402-4cd8-b5db-82e85cb51fad'])]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.so_mock.delete.assert_called_once_with(
|
||||
'1a2c0c3d-f402-4cd8-b5db-82e85cb51fad')
|
||||
|
||||
|
||||
class TestShowScheduledOperation(TestScheduledOperations):
|
||||
def setUp(self):
|
||||
super(TestShowScheduledOperation, self).setUp()
|
||||
self._schedop_info = copy.deepcopy(SCHEDULEDOPERATION_INFO)
|
||||
self.so_mock.get.return_value = scheduled_operations.\
|
||||
ScheduledOperation(None, self._schedop_info)
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_so.ShowScheduledOperation(self.app, None)
|
||||
|
||||
def test_scheduled_operation_show(self):
|
||||
arglist = ['1a2c0c3d-f402-4cd8-b5db-82e85cb51fad']
|
||||
verifylist = [('scheduledoperation',
|
||||
'1a2c0c3d-f402-4cd8-b5db-82e85cb51fad')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
'description', 'enabled', 'id', 'name', 'operation_definition',
|
||||
'operation_type', 'trigger_id')
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
self.assertEqual(self._schedop_info['description'], data[0])
|
||||
self.assertEqual(self._schedop_info['enabled'], data[1])
|
||||
self.assertEqual(self._schedop_info['id'], data[2])
|
||||
self.assertEqual(self._schedop_info['name'], data[3])
|
||||
self.assertEqual(self._schedop_info['operation_definition'], data[4])
|
||||
self.assertEqual(self._schedop_info['operation_type'], data[5])
|
||||
self.assertEqual(self._schedop_info['trigger_id'], data[6])
|
||||
101
karborclient/tests/unit/osc/v1/test_services.py
Normal file
101
karborclient/tests/unit/osc/v1/test_services.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# 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.
|
||||
import copy
|
||||
|
||||
from karborclient.osc.v1 import services as osc_services
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import services
|
||||
|
||||
SERVICE_INFO = {
|
||||
"status": "enabled",
|
||||
"binary": "karbor-operationengine",
|
||||
"state": "up",
|
||||
"updated_at": "2017-10-25T07:06:58.000000",
|
||||
"host": "fake_host",
|
||||
"disabled_reason": None,
|
||||
"id": 1
|
||||
}
|
||||
|
||||
|
||||
class TestServices(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestServices, self).setUp()
|
||||
self.services_mock = self.app.client_manager.data_protection.services
|
||||
self.services_mock.reset_mock()
|
||||
|
||||
|
||||
class TestListServices(TestServices):
|
||||
def setUp(self):
|
||||
super(TestListServices, self).setUp()
|
||||
self.services_mock.list.return_value = [
|
||||
services.Service(None, copy.deepcopy(SERVICE_INFO))]
|
||||
self.cmd = osc_services.ListServices(self.app, None)
|
||||
|
||||
def test_services_list(self):
|
||||
arg_list = ['--host', 'fake_host']
|
||||
verify_list = [('host', 'fake_host')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arg_list, verify_list)
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
expected_columns = (["Id", "Binary", "Host", "Status", "State",
|
||||
"Updated_at", "Disabled Reason"])
|
||||
self.assertEqual(expected_columns, columns)
|
||||
expected_data = [(1,
|
||||
"karbor-operationengine",
|
||||
"fake_host",
|
||||
"enabled",
|
||||
"up",
|
||||
"2017-10-25T07:06:58.000000",
|
||||
None
|
||||
)]
|
||||
self.assertEqual(expected_data, list(data))
|
||||
|
||||
|
||||
class TestEnableService(TestServices):
|
||||
def setUp(self):
|
||||
super(TestEnableService, self).setUp()
|
||||
self.services_mock.enable.return_value = services.Service(
|
||||
None, copy.deepcopy(SERVICE_INFO))
|
||||
self.cmd = osc_services.EnableService(self.app, None)
|
||||
|
||||
def test_enable_service(self):
|
||||
arg_list = ['1']
|
||||
verify_list = [('service_id', '1')]
|
||||
parsed_args = self.check_parser(self.cmd, arg_list, verify_list)
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.services_mock.enable.assert_called_once_with('1')
|
||||
|
||||
|
||||
class TestDisableService(TestServices):
|
||||
def setUp(self):
|
||||
super(TestDisableService, self).setUp()
|
||||
self.services_mock.disable.return_value = services.Service(
|
||||
None, copy.deepcopy(SERVICE_INFO))
|
||||
self.services_mock.disable_log_reason.return_value = services.Service(
|
||||
None, copy.deepcopy(SERVICE_INFO))
|
||||
self.cmd = osc_services.DisableService(self.app, None)
|
||||
|
||||
def test_disable_service(self):
|
||||
arg_list = ['1']
|
||||
verify_list = [('service_id', '1')]
|
||||
parsed_args = self.check_parser(self.cmd, arg_list, verify_list)
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.services_mock.disable.assert_called_once_with('1')
|
||||
|
||||
def test_disable_service_with_reason(self):
|
||||
arg_list = ['1', '--reason', 'fake_reason']
|
||||
verify_list = [('service_id', '1'), ('reason', 'fake_reason')]
|
||||
parsed_args = self.check_parser(self.cmd, arg_list, verify_list)
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.services_mock.disable_log_reason.assert_called_once_with(
|
||||
'1', 'fake_reason')
|
||||
189
karborclient/tests/unit/osc/v1/test_triggers.py
Normal file
189
karborclient/tests/unit/osc/v1/test_triggers.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
from karborclient.osc.v1 import triggers as osc_triggers
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import triggers
|
||||
|
||||
|
||||
TRIGGER_INFO = {
|
||||
"id": "2a9ce1f3-cc1a-4516-9435-0ebb13caa398",
|
||||
"name": "My backup trigger",
|
||||
"type": "time",
|
||||
"properties": {
|
||||
"format": "calendar",
|
||||
"pattern": "BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT",
|
||||
"start_time": "2015-12-17T08:30:00",
|
||||
"end_time": "2016-03-17T08:30:00",
|
||||
"window": "3600"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestTriggers(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestTriggers, self).setUp()
|
||||
self.triggers_mock = self.app.client_manager.data_protection.triggers
|
||||
self.triggers_mock.reset_mock()
|
||||
|
||||
|
||||
class TestListTriggers(TestTriggers):
|
||||
def setUp(self):
|
||||
super(TestListTriggers, self).setUp()
|
||||
self.triggers_mock.list.return_value = [triggers.Trigger(
|
||||
None, copy.deepcopy(TRIGGER_INFO))]
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_triggers.ListTriggers(self.app, None)
|
||||
|
||||
def test_triggers_list(self):
|
||||
arglist = ['--name', 'My backup trigger']
|
||||
verifylist = [('name', 'My backup trigger')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
['Id', 'Name', 'Type', 'Properties'])
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
expected_data = [("2a9ce1f3-cc1a-4516-9435-0ebb13caa398",
|
||||
"My backup trigger",
|
||||
"time",
|
||||
{"format": "calendar",
|
||||
"pattern": "BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT", # noqa
|
||||
"start_time": "2015-12-17T08:30:00",
|
||||
"end_time": "2016-03-17T08:30:00",
|
||||
"window": "3600"})]
|
||||
self.assertEqual(expected_data, list(data))
|
||||
|
||||
|
||||
class TestCreateTrigger(TestTriggers):
|
||||
def setUp(self):
|
||||
super(TestCreateTrigger, self).setUp()
|
||||
self.triggers_mock.create.return_value = triggers.Trigger(
|
||||
None, copy.deepcopy(TRIGGER_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_triggers.CreateTrigger(self.app, None)
|
||||
|
||||
def test_trigger_create(self):
|
||||
arglist = ['My backup trigger',
|
||||
'time',
|
||||
"'format'='calendar',"
|
||||
"'pattern'='BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT'," # noqa
|
||||
"'start_time'='2015-12-17T08:30:00',"
|
||||
"'end_time'='2016-03-17T08:30:00',"
|
||||
"'window'='3600'"]
|
||||
verifylist = [('name', 'My backup trigger'),
|
||||
('type', 'time'),
|
||||
('properties', "'format'='calendar',"
|
||||
"'pattern'='BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT'," # noqa
|
||||
"'start_time'='2015-12-17T08:30:00',"
|
||||
"'end_time'='2016-03-17T08:30:00',"
|
||||
"'window'='3600'")]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.triggers_mock.create.assert_called_once_with(
|
||||
'My backup trigger',
|
||||
'time',
|
||||
"'format'='calendar',"
|
||||
"'pattern'='BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT'," # noqa
|
||||
"'start_time'='2015-12-17T08:30:00',"
|
||||
"'end_time'='2016-03-17T08:30:00',"
|
||||
"'window'='3600'")
|
||||
|
||||
|
||||
class TestUpdateTrigger(TestTriggers):
|
||||
def setUp(self):
|
||||
super(TestUpdateTrigger, self).setUp()
|
||||
self.triggers_mock.get.return_value = triggers.Trigger(
|
||||
None, copy.deepcopy(TRIGGER_INFO))
|
||||
self.triggers_mock.update.return_value = triggers.Trigger(
|
||||
None, copy.deepcopy(TRIGGER_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_triggers.UpdateTrigger(self.app, None)
|
||||
|
||||
def test_trigger_update(self):
|
||||
arglist = ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398',
|
||||
'--name', 'My backup trigger']
|
||||
verifylist = [('trigger_id', '2a9ce1f3-cc1a-4516-9435-0ebb13caa398'),
|
||||
('name', 'My backup trigger')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.triggers_mock.update.assert_called_once_with(
|
||||
'2a9ce1f3-cc1a-4516-9435-0ebb13caa398',
|
||||
{'name': 'My backup trigger'})
|
||||
|
||||
|
||||
class TestDeleteTrigger(TestTriggers):
|
||||
def setUp(self):
|
||||
super(TestDeleteTrigger, self).setUp()
|
||||
self.triggers_mock.get.return_value = triggers.Trigger(
|
||||
None, copy.deepcopy(TRIGGER_INFO))
|
||||
# Command to test
|
||||
self.cmd = osc_triggers.DeleteTrigger(self.app, None)
|
||||
|
||||
def test_trigger_delete(self):
|
||||
arglist = ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398']
|
||||
verifylist = [('trigger', ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398'])]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that correct arguments were passed
|
||||
self.triggers_mock.delete.assert_called_once_with(
|
||||
'2a9ce1f3-cc1a-4516-9435-0ebb13caa398')
|
||||
|
||||
|
||||
class TestShowTrigger(TestTriggers):
|
||||
def setUp(self):
|
||||
super(TestShowTrigger, self).setUp()
|
||||
self._trigger_info = copy.deepcopy(TRIGGER_INFO)
|
||||
self.triggers_mock.get.return_value = triggers.Trigger(
|
||||
None, self._trigger_info)
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_triggers.ShowTrigger(self.app, None)
|
||||
|
||||
def test_trigger_show(self):
|
||||
arglist = ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398']
|
||||
verifylist = [('trigger', '2a9ce1f3-cc1a-4516-9435-0ebb13caa398')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
# Check that columns are correct
|
||||
expected_columns = (
|
||||
'id', 'name', 'properties', 'type')
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
self.assertEqual(self._trigger_info['id'], data[0])
|
||||
self.assertEqual(self._trigger_info['name'], data[1])
|
||||
self.assertEqual(self._trigger_info['properties'], data[2])
|
||||
self.assertEqual(self._trigger_info['type'], data[3])
|
||||
130
karborclient/tests/unit/osc/v1/test_verifications.py
Normal file
130
karborclient/tests/unit/osc/v1/test_verifications.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from karborclient.osc.v1 import verifications as osc_verifications
|
||||
from karborclient.tests.unit.osc.v1 import fakes
|
||||
from karborclient.v1 import verifications
|
||||
|
||||
|
||||
VERIFICATION_INFO = {
|
||||
"id": "22b82aa7-9179-4c71-bba2-caf5c0e68db7",
|
||||
"project_id": "e486a2f49695423ca9c47e589b948108",
|
||||
"provider_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
|
||||
"checkpoint_id": "dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
|
||||
"parameters": {},
|
||||
"resources_status": {},
|
||||
"resources_reason": {},
|
||||
"status": "success"
|
||||
}
|
||||
|
||||
|
||||
class TestVerifications(fakes.TestDataProtection):
|
||||
def setUp(self):
|
||||
super(TestVerifications, self).setUp()
|
||||
self.verifications_mock = (
|
||||
self.app.client_manager.data_protection.verifications)
|
||||
self.verifications_mock.reset_mock()
|
||||
|
||||
|
||||
class TestListVerifications(TestVerifications):
|
||||
def setUp(self):
|
||||
super(TestListVerifications, self).setUp()
|
||||
self.verifications_mock.list.return_value = (
|
||||
[verifications.Verification(
|
||||
None, copy.deepcopy(VERIFICATION_INFO))])
|
||||
|
||||
# Command to test
|
||||
self.cmd = osc_verifications.ListVerifications(self.app, None)
|
||||
|
||||
def test_verifications_list(self):
|
||||
arglist = ['--status', 'success']
|
||||
verifylist = [('status', 'success')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
expected_columns = (
|
||||
['Id', 'Project id', 'Provider id', 'Checkpoint id',
|
||||
'Parameters', 'Status'])
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
# Check that data is correct
|
||||
expected_data = [("22b82aa7-9179-4c71-bba2-caf5c0e68db7",
|
||||
"e486a2f49695423ca9c47e589b948108",
|
||||
"cf56bd3e-97a7-4078-b6d5-f36246333fd9",
|
||||
"dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
|
||||
jsonutils.dumps({}),
|
||||
"success")]
|
||||
self.assertEqual(expected_data, list(data))
|
||||
|
||||
|
||||
class TestCreateVerification(TestVerifications):
|
||||
def setUp(self):
|
||||
super(TestCreateVerification, self).setUp()
|
||||
self.verifications_mock.create.return_value = (
|
||||
verifications.Verification(
|
||||
None, copy.deepcopy(VERIFICATION_INFO)))
|
||||
self.cmd = osc_verifications.CreateVerification(self.app, None)
|
||||
|
||||
def test_verification_create(self):
|
||||
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3']
|
||||
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
|
||||
('checkpoint_id',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
self.verifications_mock.create.assert_called_once_with(
|
||||
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
|
||||
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3', {})
|
||||
|
||||
|
||||
class TestShowVerification(TestVerifications):
|
||||
def setUp(self):
|
||||
super(TestShowVerification, self).setUp()
|
||||
self._verification_info = copy.deepcopy(VERIFICATION_INFO)
|
||||
self.verifications_mock.get.return_value = (
|
||||
verifications.Verification(None, self._verification_info))
|
||||
|
||||
self.cmd = osc_verifications.ShowVerification(self.app, None)
|
||||
|
||||
def test_verification_show(self):
|
||||
arglist = ['22b82aa7-9179-4c71-bba2-caf5c0e68db7']
|
||||
verifylist = [('verification', '22b82aa7-9179-4c71-bba2-caf5c0e68db7')]
|
||||
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
columns, data = self.cmd.take_action(parsed_args)
|
||||
|
||||
expected_columns = (
|
||||
'checkpoint_id', 'id', 'parameters', 'project_id',
|
||||
'provider_id', 'resources_reason', 'resources_status',
|
||||
'status')
|
||||
self.assertEqual(expected_columns, columns)
|
||||
|
||||
self.assertEqual(self._verification_info['checkpoint_id'], data[0])
|
||||
self.assertEqual(self._verification_info['id'], data[1])
|
||||
self.assertEqual(self._verification_info['parameters'], data[2])
|
||||
self.assertEqual(self._verification_info['project_id'], data[3])
|
||||
self.assertEqual(self._verification_info['provider_id'], data[4])
|
||||
self.assertEqual(self._verification_info['resources_reason'], data[5])
|
||||
self.assertEqual(self._verification_info['resources_status'], data[6])
|
||||
self.assertEqual(self._verification_info['status'], data[7])
|
||||
@@ -16,12 +16,12 @@ import socket
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from smaugclient.common import http
|
||||
from smaugclient.openstack.common.apiclient import exceptions as exc
|
||||
from smaugclient.tests.unit import fakes
|
||||
from karborclient.common.apiclient import exceptions as exc
|
||||
from karborclient.common import http
|
||||
from karborclient.tests.unit import fakes
|
||||
|
||||
|
||||
@mock.patch('smaugclient.common.http.requests.request')
|
||||
@mock.patch('karborclient.common.http.requests.request')
|
||||
class HttpClientTest(testtools.TestCase):
|
||||
|
||||
# Patch os.environ to avoid required auth info.
|
||||
@@ -29,7 +29,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
super(HttpClientTest, self).setUp()
|
||||
|
||||
def test_http_raw_request(self, mock_request):
|
||||
headers = {'User-Agent': 'python-smaugclient'}
|
||||
headers = {'User-Agent': 'python-karborclient'}
|
||||
mock_request.return_value = \
|
||||
fakes.FakeHTTPResponse(
|
||||
200, 'OK',
|
||||
@@ -71,15 +71,15 @@ class HttpClientTest(testtools.TestCase):
|
||||
mock_request.assert_has_calls([
|
||||
mock.call('GET', 'http://example.com:8082',
|
||||
allow_redirects=False,
|
||||
headers={'User-Agent': 'python-smaugclient'}),
|
||||
headers={'User-Agent': 'python-karborclient'}),
|
||||
mock.call('GET', 'http://example.com:8082',
|
||||
allow_redirects=False,
|
||||
headers={'User-Agent': 'python-smaugclient',
|
||||
headers={'User-Agent': 'python-karborclient',
|
||||
'X-Auth-Key': 'pass',
|
||||
'X-Auth-User': 'user'}),
|
||||
mock.call('GET', 'http://example.com:8082',
|
||||
allow_redirects=False,
|
||||
headers={'User-Agent': 'python-smaugclient',
|
||||
headers={'User-Agent': 'python-karborclient',
|
||||
'X-Auth-Token': 'abcd1234'})
|
||||
])
|
||||
|
||||
@@ -101,7 +101,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
'GET', 'http://example.com:8082',
|
||||
allow_redirects=False,
|
||||
headers={'X-Region-Name': 'RegionOne',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
|
||||
def test_http_json_request(self, mock_request):
|
||||
# Record a 200
|
||||
@@ -119,7 +119,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
'GET', 'http://example.com:8082',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
|
||||
def test_http_json_request_argument_passed_to_requests(self, mock_request):
|
||||
"""Check that we have sent the proper arguments to requests."""
|
||||
@@ -147,7 +147,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
data='"text"',
|
||||
headers={'Content-Type': 'application/json',
|
||||
'X-Auth-Url': 'http://AUTH_URL',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
|
||||
def test_http_json_request_w_req_body(self, mock_request):
|
||||
# Record a 200
|
||||
@@ -166,7 +166,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
data='"test-body"',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
|
||||
def test_http_json_request_non_json_resp_cont_type(self, mock_request):
|
||||
# Record a 200
|
||||
@@ -184,7 +184,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
'GET', 'http://example.com:8082', data='"test-data"',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
|
||||
def test_http_json_request_invalid_json(self, mock_request):
|
||||
# Record a 200
|
||||
@@ -202,7 +202,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
'GET', 'http://example.com:8082',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
|
||||
def test_http_manual_redirect_delete(self, mock_request):
|
||||
mock_request.side_effect = [
|
||||
@@ -223,11 +223,11 @@ class HttpClientTest(testtools.TestCase):
|
||||
mock.call('DELETE', 'http://example.com:8082/foo',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'}),
|
||||
'User-Agent': 'python-karborclient'}),
|
||||
mock.call('DELETE', 'http://example.com:8082/foo/bar',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
])
|
||||
|
||||
def test_http_manual_redirect_post(self, mock_request):
|
||||
@@ -249,11 +249,11 @@ class HttpClientTest(testtools.TestCase):
|
||||
mock.call('POST', 'http://example.com:8082/foo',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'}),
|
||||
'User-Agent': 'python-karborclient'}),
|
||||
mock.call('POST', 'http://example.com:8082/foo/bar',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
])
|
||||
|
||||
def test_http_manual_redirect_put(self, mock_request):
|
||||
@@ -275,11 +275,11 @@ class HttpClientTest(testtools.TestCase):
|
||||
mock.call('PUT', 'http://example.com:8082/foo',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'}),
|
||||
'User-Agent': 'python-karborclient'}),
|
||||
mock.call('PUT', 'http://example.com:8082/foo/bar',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
])
|
||||
|
||||
def test_http_manual_redirect_prohibited(self, mock_request):
|
||||
@@ -295,7 +295,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
'DELETE', 'http://example.com:8082/foo',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
|
||||
def test_http_manual_redirect_error_without_location(self, mock_request):
|
||||
mock_request.return_value = \
|
||||
@@ -310,7 +310,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
'DELETE', 'http://example.com:8082/foo',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
|
||||
def test_http_json_request_redirect(self, mock_request):
|
||||
# Record the 302
|
||||
@@ -333,11 +333,11 @@ class HttpClientTest(testtools.TestCase):
|
||||
mock.call('GET', 'http://example.com:8082',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'}),
|
||||
'User-Agent': 'python-karborclient'}),
|
||||
mock.call('GET', 'http://example.com:8082',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
])
|
||||
|
||||
def test_http_404_json_request(self, mock_request):
|
||||
@@ -356,7 +356,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
'GET', 'http://example.com:8082',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
|
||||
def test_http_300_json_request(self, mock_request):
|
||||
mock_request.return_value = \
|
||||
@@ -374,10 +374,10 @@ class HttpClientTest(testtools.TestCase):
|
||||
'GET', 'http://example.com:8082',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'})
|
||||
'User-Agent': 'python-karborclient'})
|
||||
|
||||
def test_fake_json_request(self, mock_request):
|
||||
headers = {'User-Agent': 'python-smaugclient'}
|
||||
headers = {'User-Agent': 'python-karborclient'}
|
||||
mock_request.side_effect = [socket.gaierror]
|
||||
|
||||
client = http.HTTPClient('fake://example.com:8082')
|
||||
@@ -388,7 +388,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
headers=headers)
|
||||
|
||||
def test_http_request_socket_error(self, mock_request):
|
||||
headers = {'User-Agent': 'python-smaugclient'}
|
||||
headers = {'User-Agent': 'python-karborclient'}
|
||||
mock_request.side_effect = [socket.gaierror]
|
||||
|
||||
client = http.HTTPClient('http://example.com:8082')
|
||||
@@ -399,7 +399,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
headers=headers)
|
||||
|
||||
def test_http_request_socket_timeout(self, mock_request):
|
||||
headers = {'User-Agent': 'python-smaugclient'}
|
||||
headers = {'User-Agent': 'python-karborclient'}
|
||||
mock_request.side_effect = [socket.timeout]
|
||||
|
||||
client = http.HTTPClient('http://example.com:8082')
|
||||
@@ -424,7 +424,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
'GET', 'http://example.com:8082',
|
||||
allow_redirects=False,
|
||||
headers={'Content-Type': 'application/json',
|
||||
'User-Agent': 'python-smaugclient'},
|
||||
'User-Agent': 'python-karborclient'},
|
||||
timeout=float(123))
|
||||
|
||||
def test_get_system_ca_file(self, mock_request):
|
||||
@@ -445,7 +445,7 @@ class HttpClientTest(testtools.TestCase):
|
||||
client = http.HTTPClient('https://foo', cacert="NOWHERE")
|
||||
self.assertEqual("NOWHERE", client.verify_cert)
|
||||
|
||||
with mock.patch('smaugclient.common.http.get_system_ca_file') as gsf:
|
||||
with mock.patch('karborclient.common.http.get_system_ca_file') as gsf:
|
||||
gsf.return_value = "SOMEWHERE"
|
||||
client = http.HTTPClient('https://foo')
|
||||
self.assertEqual("SOMEWHERE", client.verify_cert)
|
||||
@@ -11,13 +11,13 @@
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Tests for `smaugclient` module.
|
||||
Tests for `karborclient` module.
|
||||
"""
|
||||
|
||||
from smaugclient.tests.unit import base
|
||||
from karborclient.tests.unit import base
|
||||
|
||||
|
||||
class TestSmaugclient(base.TestCaseShell):
|
||||
class TestKarborclient(base.TestCaseShell):
|
||||
|
||||
def test_something(self):
|
||||
pass
|
||||
@@ -15,17 +15,17 @@ import re
|
||||
import sys
|
||||
|
||||
import fixtures
|
||||
from keystoneclient import fixture
|
||||
from keystoneclient.fixture import v2 as ks_v2_fixture
|
||||
from keystoneauth1 import fixture
|
||||
from keystoneauth1.fixture import v2 as ks_v2_fixture
|
||||
import mock
|
||||
from oslo_log import handlers
|
||||
from oslo_log import log
|
||||
import six
|
||||
from testtools import matchers
|
||||
|
||||
from smaugclient.openstack.common.apiclient import exceptions
|
||||
import smaugclient.shell
|
||||
from smaugclient.tests.unit import base
|
||||
from karborclient.common.apiclient import exceptions
|
||||
import karborclient.shell
|
||||
from karborclient.tests.unit import base
|
||||
|
||||
|
||||
FAKE_ENV = {'OS_USERNAME': 'username',
|
||||
@@ -51,7 +51,7 @@ def _create_ver_list(versions):
|
||||
|
||||
class TestArgs(object):
|
||||
package_version = ''
|
||||
smaug_repo_url = 'http://127.0.0.1'
|
||||
karbor_repo_url = 'http://127.0.0.1'
|
||||
exists_action = ''
|
||||
is_public = False
|
||||
categories = []
|
||||
@@ -78,7 +78,7 @@ class ShellCommandTest(ShellTest):
|
||||
def get_auth_endpoint(bound_self, args):
|
||||
return ('test', {})
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'smaugclient.shell.SmaugShell._get_endpoint_and_kwargs',
|
||||
'karborclient.shell.KarborShell._get_endpoint_and_kwargs',
|
||||
get_auth_endpoint))
|
||||
self.client = mock.MagicMock()
|
||||
|
||||
@@ -99,7 +99,7 @@ class ShellCommandTest(ShellTest):
|
||||
try:
|
||||
sys.stdout = six.StringIO()
|
||||
sys.stderr = six.StringIO()
|
||||
_shell = smaugclient.shell.SmaugShell()
|
||||
_shell = karborclient.shell.KarborShell()
|
||||
_shell.main(argstr.split())
|
||||
except SystemExit:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
@@ -133,9 +133,9 @@ class ShellCommandTest(ShellTest):
|
||||
|
||||
def test_help(self):
|
||||
required = [
|
||||
'.*?^usage: smaug',
|
||||
'.*?^\s+plan-create\s+Create a plan.',
|
||||
'.*?^See "smaug help COMMAND" for help on a specific command',
|
||||
'.*?^usage: karbor',
|
||||
'.*?^\s+plan-create\s+Creates a plan.',
|
||||
'.*?^See "karbor help COMMAND" for help on a specific command',
|
||||
]
|
||||
stdout, stderr = self.shell('help')
|
||||
for r in required:
|
||||
@@ -144,8 +144,8 @@ class ShellCommandTest(ShellTest):
|
||||
|
||||
def test_help_on_subcommand(self):
|
||||
required = [
|
||||
'.*?^usage: smaug plan-create',
|
||||
'.*?^Create a plan.',
|
||||
'.*?^usage: karbor plan-create',
|
||||
'.*?^Creates a plan.',
|
||||
]
|
||||
stdout, stderr = self.shell('help plan-create')
|
||||
for r in required:
|
||||
@@ -154,9 +154,9 @@ class ShellCommandTest(ShellTest):
|
||||
|
||||
def test_help_no_options(self):
|
||||
required = [
|
||||
'.*?^usage: smaug',
|
||||
'.*?^\s+plan-create\s+Create a plan',
|
||||
'.*?^See "smaug help COMMAND" for help on a specific command',
|
||||
'.*?^usage: karbor',
|
||||
'.*?^\s+plan-create\s+Creates a plan',
|
||||
'.*?^See "karbor help COMMAND" for help on a specific command',
|
||||
]
|
||||
stdout, stderr = self.shell('')
|
||||
for r in required:
|
||||
0
karborclient/tests/unit/v1/__init__.py
Normal file
0
karborclient/tests/unit/v1/__init__.py
Normal file
@@ -15,10 +15,10 @@ try:
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
from smaugclient.common import http as base_client
|
||||
from smaugclient.tests.unit import base
|
||||
from smaugclient.tests.unit import fakes
|
||||
from smaugclient.v1 import client
|
||||
from karborclient.common import http as base_client
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit import fakes
|
||||
from karborclient.v1 import client
|
||||
|
||||
|
||||
REQUEST_ID = 'req-test-request-id'
|
||||
@@ -37,12 +37,13 @@ class FakeClient(fakes.FakeClient, client.Client):
|
||||
'project_id': PROJECT_ID,
|
||||
}
|
||||
client.Client.__init__(self, 'http://endpoint', **kwargs)
|
||||
self.client = FakeHTTPClient(**kwargs)
|
||||
self.client = self.http_client
|
||||
|
||||
|
||||
class FakeHTTPClient(base_client.HTTPClient):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, endpoint, **kwargs):
|
||||
super(FakeHTTPClient, self)
|
||||
self.username = 'username'
|
||||
self.password = 'password'
|
||||
self.auth_url = 'auth_url'
|
||||
@@ -50,6 +51,9 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
self.management_url = 'http://10.0.2.15:8776/v1/fake'
|
||||
self.osapi_max_limit = 1000
|
||||
self.marker = None
|
||||
self.project_id = 'project_id'
|
||||
self.auth_token = 'auth_token'
|
||||
self.region_name = 'region_name'
|
||||
|
||||
def _cs_request(self, url, method, **kwargs):
|
||||
# Check that certain things are called correctly
|
||||
@@ -82,7 +86,7 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
(method, url, callback))
|
||||
|
||||
# Note the call
|
||||
self.callstack.append((method, url, kwargs.get('body', None)))
|
||||
self.callstack.append((method, url, kwargs.get('body')))
|
||||
status, headers, body = getattr(self, callback)(**kwargs)
|
||||
# add fake request-id header
|
||||
headers['x-openstack-request-id'] = REQUEST_ID
|
||||
@@ -92,3 +96,27 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
"headers": headers,
|
||||
})
|
||||
return r, body
|
||||
|
||||
def json_request(self, method, url, **kwargs):
|
||||
return self._cs_request(url, method, **kwargs)
|
||||
|
||||
def get_providers_1234_checkpoints(self, **kwargs):
|
||||
return 200, {}, {"checkpoints": []}
|
||||
|
||||
def get_plans(self, **kwargs):
|
||||
return 200, {}, {"plans": []}
|
||||
|
||||
def get_operation_logs(self, **kwargs):
|
||||
return 200, {}, {"operation_logs": []}
|
||||
|
||||
def get_restores(self, **kwargs):
|
||||
return 200, {}, {"restores": []}
|
||||
|
||||
def get_scheduled_operations(self, **kwargs):
|
||||
return 200, {}, {"operations": []}
|
||||
|
||||
def get_triggers(self, **kwargs):
|
||||
return 200, {}, {"triggers": []}
|
||||
|
||||
def get_verifications(self, **kwargs):
|
||||
return 200, {}, {"verifications": []}
|
||||
@@ -12,19 +12,20 @@
|
||||
|
||||
import mock
|
||||
|
||||
from smaugclient.tests.unit import base
|
||||
from smaugclient.tests.unit.v1 import fakes
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'checkpoint': {}})
|
||||
|
||||
FAKE_PROVIDER_ID = "2220f8b1-975d-4621-a872-fa9afb43cb6c"
|
||||
FAKE_PLAN_ID = "3330f8b1-975d-4621-a872-fa9afb43cb6c"
|
||||
FAKE_CHECKPOINT_ID = "e4381b1a-905e-4fec-8104-b4419ccaf963"
|
||||
|
||||
|
||||
class CheckpointsTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_checkpoints(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.checkpoints.list(provider_id=FAKE_PROVIDER_ID)
|
||||
@@ -33,7 +34,17 @@ class CheckpointsTest(base.TestCaseShell):
|
||||
'/providers/{provider_id}/checkpoints'.format(
|
||||
provider_id=FAKE_PROVIDER_ID), headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_checkpoints_with_all_tenants(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.checkpoints.list(provider_id=FAKE_PROVIDER_ID,
|
||||
search_opts={'all_tenants': 1})
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/providers/{provider_id}/checkpoints?all_tenants=1'.format(
|
||||
provider_id=FAKE_PROVIDER_ID), headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_get_checkpoint(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.checkpoints.get(FAKE_PROVIDER_ID, '1')
|
||||
@@ -42,7 +53,7 @@ class CheckpointsTest(base.TestCaseShell):
|
||||
'/providers/{provider_id}/checkpoints/1'.format(
|
||||
provider_id=FAKE_PROVIDER_ID), headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.raw_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.raw_request')
|
||||
def test_delete_checkpoint(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.checkpoints.delete(FAKE_PROVIDER_ID, '1')
|
||||
@@ -51,7 +62,7 @@ class CheckpointsTest(base.TestCaseShell):
|
||||
'/providers/{provider_id}/checkpoints/1'.format(
|
||||
provider_id=FAKE_PROVIDER_ID), headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_checkpoints_with_marker_limit(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.checkpoints.list(provider_id=FAKE_PROVIDER_ID,
|
||||
@@ -62,7 +73,7 @@ class CheckpointsTest(base.TestCaseShell):
|
||||
'checkpoints?limit=2&marker=1234'.format(
|
||||
provider_id=FAKE_PROVIDER_ID), headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_checkpoints_with_sort_key_dir(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.checkpoints.list(provider_id=FAKE_PROVIDER_ID,
|
||||
@@ -78,7 +89,7 @@ class CheckpointsTest(base.TestCaseShell):
|
||||
cs.checkpoints.list, FAKE_PROVIDER_ID,
|
||||
sort_key='invalid', sort_dir='asc')
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_create_checkpoint(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.checkpoints.create(FAKE_PROVIDER_ID, FAKE_PLAN_ID)
|
||||
@@ -88,5 +99,19 @@ class CheckpointsTest(base.TestCaseShell):
|
||||
'checkpoints'.format(
|
||||
provider_id=FAKE_PROVIDER_ID),
|
||||
data={
|
||||
'checkpoint': {'plan_id': FAKE_PLAN_ID}},
|
||||
'checkpoint': {'plan_id': FAKE_PLAN_ID, 'extra-info': None}},
|
||||
headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_reset_checkpoint_state(self, mock_request):
|
||||
mock_request.return_value = ({}, {})
|
||||
cs.checkpoints.reset_state(
|
||||
FAKE_PROVIDER_ID, FAKE_CHECKPOINT_ID, 'error')
|
||||
mock_request.assert_called_with(
|
||||
'PUT',
|
||||
'/providers/{provider_id}/checkpoints/{checkpoint_id}'.format(
|
||||
provider_id=FAKE_PROVIDER_ID,
|
||||
checkpoint_id=FAKE_CHECKPOINT_ID
|
||||
),
|
||||
data={'os-resetState': {'state': 'error'}},
|
||||
headers={})
|
||||
79
karborclient/tests/unit/v1/test_operation_logs.py
Normal file
79
karborclient/tests/unit/v1/test_operation_logs.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'operation_log': {}})
|
||||
|
||||
|
||||
class OperationLogsTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_operation_logs(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.operation_logs.list()
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/operation_logs', headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_operation_logs_with_all_tenants(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.operation_logs.list(search_opts={'all_tenants': 1})
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/operation_logs?all_tenants=1', headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_operation_logs_with_marker_limit(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.operation_logs.list(marker=1234, limit=2)
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/operation_logs?limit=2&marker=1234', headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_operation_logs_with_sort_key_dir(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.operation_logs.list(sort_key='id', sort_dir='asc')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/operation_logs?'
|
||||
'sort_dir=asc&sort_key=id', headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_operation_logs_with_invalid_sort_key(self, mock_request):
|
||||
self.assertRaises(ValueError,
|
||||
cs.operation_logs.list,
|
||||
sort_key='invalid', sort_dir='asc')
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_operation_log(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.operation_logs.get('1')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/operation_logs/1',
|
||||
headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_operation_log_with_headers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.operation_logs.get('1', session_id='fake_session_id')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/operation_logs/1',
|
||||
headers={'X-Configuration-Session': 'fake_session_id'})
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
import mock
|
||||
|
||||
from smaugclient.tests.unit import base
|
||||
from smaugclient.tests.unit.v1 import fakes
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'plan': {'name': 'fake_name'}})
|
||||
@@ -21,7 +21,7 @@ mock_request_return = ({}, {'plan': {'name': 'fake_name'}})
|
||||
|
||||
class PlansTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_plans_with_marker_limit(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.plans.list(marker=1234, limit=2)
|
||||
@@ -30,7 +30,7 @@ class PlansTest(base.TestCaseShell):
|
||||
'/plans?limit=2&marker=1234'.format(
|
||||
project_id=fakes.PROJECT_ID), headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_plans_with_sort_key_dir(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.plans.list(sort_key='id', sort_dir='asc')
|
||||
@@ -40,15 +40,15 @@ class PlansTest(base.TestCaseShell):
|
||||
'sort_dir=asc&sort_key=id'.format(
|
||||
project_id=fakes.PROJECT_ID), headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_plans_with_invalid_sort_key(self, mock_request):
|
||||
self.assertRaises(ValueError,
|
||||
cs.plans.list, sort_key='invalid', sort_dir='asc')
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_create_plan(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.plans.create('Plan name', 'provider_id', '', "")
|
||||
cs.plans.create('Plan name', 'provider_id', '', "", '')
|
||||
mock_request.assert_called_with(
|
||||
'POST',
|
||||
'/plans',
|
||||
@@ -56,10 +56,11 @@ class PlansTest(base.TestCaseShell):
|
||||
'plan': {'provider_id': 'provider_id',
|
||||
'name': 'Plan name',
|
||||
'resources': '',
|
||||
'parameters': ''}},
|
||||
'parameters': '',
|
||||
'description': ''}},
|
||||
headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.raw_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.raw_request')
|
||||
def test_delete_plan(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.plans.delete('1')
|
||||
@@ -68,7 +69,7 @@ class PlansTest(base.TestCaseShell):
|
||||
'/plans/1',
|
||||
headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_create_update(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.plans.update('1', {'name': 'Test name.'})
|
||||
@@ -77,7 +78,7 @@ class PlansTest(base.TestCaseShell):
|
||||
'/plans/1',
|
||||
data={'plan': {'name': 'Test name.'}}, headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_plan(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.plans.get('1')
|
||||
@@ -86,7 +87,7 @@ class PlansTest(base.TestCaseShell):
|
||||
'/plans/1',
|
||||
headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_plan_with_headers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.plans.get('1', session_id='fake_session_id')
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
import mock
|
||||
|
||||
from smaugclient.tests.unit import base
|
||||
from smaugclient.tests.unit.v1 import fakes
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'protectable_type': {}})
|
||||
@@ -23,7 +23,7 @@ mock_instance_request_return = ({}, {'instance': {}})
|
||||
|
||||
class ProtectablesTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_protectables(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.protectables.list()
|
||||
@@ -31,7 +31,7 @@ class ProtectablesTest(base.TestCaseShell):
|
||||
'GET',
|
||||
'/protectables', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_get_protectables(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.protectables.get('OS::Cinder::Volume')
|
||||
@@ -39,7 +39,7 @@ class ProtectablesTest(base.TestCaseShell):
|
||||
'GET',
|
||||
'/protectables/OS::Cinder::Volume', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_get_protectables_instance(self, mock_request):
|
||||
mock_request.return_value = mock_instance_request_return
|
||||
cs.protectables.get_instance('OS::Cinder::Volume', '1')
|
||||
@@ -48,7 +48,7 @@ class ProtectablesTest(base.TestCaseShell):
|
||||
'/protectables/OS::Cinder::Volume/'
|
||||
'instances/1', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_protectables_instances(self, mock_request):
|
||||
mock_request.return_value = mock_instances_request_return
|
||||
cs.protectables.list_instances('OS::Cinder::Volume')
|
||||
@@ -57,7 +57,7 @@ class ProtectablesTest(base.TestCaseShell):
|
||||
'/protectables/OS::Cinder::Volume/'
|
||||
'instances', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_protectables_instances_with_marker_limit(self, mock_request):
|
||||
mock_request.return_value = mock_instances_request_return
|
||||
cs.protectables.list_instances('OS::Cinder::Volume',
|
||||
@@ -67,7 +67,7 @@ class ProtectablesTest(base.TestCaseShell):
|
||||
'/protectables/OS::Cinder::Volume/'
|
||||
'instances?limit=2&marker=1234', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_protectables_instances_with_sort_key_dir(self, mock_request):
|
||||
mock_request.return_value = mock_instances_request_return
|
||||
cs.protectables.list_instances('OS::Cinder::Volume',
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
import mock
|
||||
|
||||
from smaugclient.tests.unit import base
|
||||
from smaugclient.tests.unit.v1 import fakes
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'provider': {}})
|
||||
@@ -21,7 +21,7 @@ mock_request_return = ({}, {'provider': {}})
|
||||
|
||||
class ProvidersTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_get_providers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.providers.get('2220f8b1-975d-4621-a872-fa9afb43cb6c')
|
||||
@@ -30,7 +30,7 @@ class ProvidersTest(base.TestCaseShell):
|
||||
'/providers/'
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_providers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.providers.list()
|
||||
@@ -38,7 +38,7 @@ class ProvidersTest(base.TestCaseShell):
|
||||
'GET',
|
||||
'/providers', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_providers_with_marker_limit(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.providers.list(marker=1234, limit=2)
|
||||
@@ -46,7 +46,7 @@ class ProvidersTest(base.TestCaseShell):
|
||||
'GET',
|
||||
'/providers?limit=2&marker=1234', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_providers_with_sort_key_dir(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.providers.list(sort_key='id', sort_dir='asc')
|
||||
58
karborclient/tests/unit/v1/test_quota_classes.py
Normal file
58
karborclient/tests/unit/v1/test_quota_classes.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'quota_class': {'plans': 50}})
|
||||
|
||||
|
||||
class QuotaClassesTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_quota_class_update(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.quota_classes.update('default', {'plans': 50})
|
||||
mock_request.assert_called_with(
|
||||
'PUT',
|
||||
'/quota_classes/default',
|
||||
data={'quota_class': {'plans': 50}}, headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_quota_class_update_with_none(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.quota_classes.update('default', {'plans': None})
|
||||
mock_request.assert_called_with(
|
||||
'PUT',
|
||||
'/quota_classes/default',
|
||||
data={'quota_class': {'plans': 50}}, headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_quota_class(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.quota_classes.get('default')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/quota_classes/default',
|
||||
headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_quota_class_with_headers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.quota_classes.get('default', session_id='fake_session_id')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/quota_classes/default',
|
||||
headers={'X-Configuration-Session': 'fake_session_id'})
|
||||
88
karborclient/tests/unit/v1/test_quotas.py
Normal file
88
karborclient/tests/unit/v1/test_quotas.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'quota': {'plans': 50}})
|
||||
|
||||
|
||||
class QuotasTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_quota_update(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.quotas.update(fakes.PROJECT_ID, {'plans': 50})
|
||||
mock_request.assert_called_with(
|
||||
'PUT',
|
||||
'/quotas/{project_id}'.format(project_id=fakes.PROJECT_ID),
|
||||
data={'quota': {'plans': 50}}, headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_quota_update_with_none(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.quotas.update(fakes.PROJECT_ID, {'plans': None})
|
||||
mock_request.assert_called_with(
|
||||
'PUT',
|
||||
'/quotas/{project_id}'.format(project_id=fakes.PROJECT_ID),
|
||||
data={'quota': {'plans': 50}}, headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_quota(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.quotas.get(fakes.PROJECT_ID, detail=False)
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/quotas/{project_id}'.format(project_id=fakes.PROJECT_ID),
|
||||
headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_quota_with_headers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.quotas.get(fakes.PROJECT_ID, False, session_id='fake_session_id')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/quotas/{project_id}'.format(project_id=fakes.PROJECT_ID),
|
||||
headers={'X-Configuration-Session': 'fake_session_id'})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_quota_with_detail(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.quotas.get(fakes.PROJECT_ID, detail=True)
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/quotas/{project_id}/detail'.format(
|
||||
project_id=fakes.PROJECT_ID),
|
||||
headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_quota_with_default(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.quotas.defaults(fakes.PROJECT_ID)
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/quotas/{project_id}/defaults'.format(
|
||||
project_id=fakes.PROJECT_ID),
|
||||
headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_quota_default_with_headers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.quotas.defaults(fakes.PROJECT_ID, session_id='fake_session_id')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/quotas/{project_id}/defaults'.format(
|
||||
project_id=fakes.PROJECT_ID),
|
||||
headers={'X-Configuration-Session': 'fake_session_id'})
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
import mock
|
||||
|
||||
from smaugclient.tests.unit import base
|
||||
from smaugclient.tests.unit.v1 import fakes
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'restore': {}})
|
||||
@@ -21,7 +21,7 @@ mock_request_return = ({}, {'restore': {}})
|
||||
|
||||
class RestoresTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_restores_with_marker_limit(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.restores.list(marker=1234, limit=2)
|
||||
@@ -29,7 +29,7 @@ class RestoresTest(base.TestCaseShell):
|
||||
'GET',
|
||||
'/restores?limit=2&marker=1234', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_restores_with_sort_key_dir(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.restores.list(sort_key='id', sort_dir='asc')
|
||||
@@ -38,18 +38,20 @@ class RestoresTest(base.TestCaseShell):
|
||||
'/restores?'
|
||||
'sort_dir=asc&sort_key=id', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_plans_with_invalid_sort_key(self, mock_request):
|
||||
self.assertRaises(ValueError,
|
||||
cs.restores.list, sort_key='invalid', sort_dir='asc')
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_create_restore(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.restores.create('586cc6ce-e286-40bd-b2b5-dd32694d9944',
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
'192.168.1.2:35357/v2.0',
|
||||
'{"username": "admin"}')
|
||||
'{}',
|
||||
'{"type": "password", "username": "admin", '
|
||||
'"password": "test"}')
|
||||
mock_request.assert_called_with(
|
||||
'POST',
|
||||
'/restores',
|
||||
@@ -57,12 +59,14 @@ class RestoresTest(base.TestCaseShell):
|
||||
'restore':
|
||||
{
|
||||
'checkpoint_id': '2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
'parameters': '{"username": "admin"}',
|
||||
'parameters': '{}',
|
||||
'provider_id': '586cc6ce-e286-40bd-b2b5-dd32694d9944',
|
||||
'restore_target': '192.168.1.2:35357/v2.0'
|
||||
'restore_target': '192.168.1.2:35357/v2.0',
|
||||
'restore_auth': '{"type": "password", "username": '
|
||||
'"admin", "password": "test"}'
|
||||
}}, headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_restore(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.restores.get('1')
|
||||
@@ -71,7 +75,7 @@ class RestoresTest(base.TestCaseShell):
|
||||
'/restores/1',
|
||||
headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_restore_with_headers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.restores.get('1', session_id='fake_session_id')
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
import mock
|
||||
|
||||
from smaugclient.tests.unit import base
|
||||
from smaugclient.tests.unit.v1 import fakes
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'scheduled_operation': {'name': 'fake_name'}})
|
||||
@@ -21,7 +21,7 @@ mock_request_return = ({}, {'scheduled_operation': {'name': 'fake_name'}})
|
||||
|
||||
class ScheduledOperationsTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_scheduled_operations_with_marker_limit(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.scheduled_operations.list(marker=1234, limit=2)
|
||||
@@ -29,7 +29,7 @@ class ScheduledOperationsTest(base.TestCaseShell):
|
||||
'GET',
|
||||
'/scheduled_operations?limit=2&marker=1234', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list__scheduled_operations_with_sort_key_dir(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.scheduled_operations.list(sort_key='id', sort_dir='asc')
|
||||
@@ -38,14 +38,14 @@ class ScheduledOperationsTest(base.TestCaseShell):
|
||||
'/scheduled_operations?'
|
||||
'sort_dir=asc&sort_key=id', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_scheduled_operations_with_invalid_sort_key(self,
|
||||
mock_request):
|
||||
self.assertRaises(ValueError,
|
||||
cs.scheduled_operations.list, sort_key='invalid',
|
||||
sort_dir='asc')
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_create_scheduled_operation(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.scheduled_operations.create(
|
||||
@@ -63,7 +63,7 @@ class ScheduledOperationsTest(base.TestCaseShell):
|
||||
'operation_definition': 'operation_definition'}},
|
||||
headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.raw_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.raw_request')
|
||||
def test_delete_scheduled_operation(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.scheduled_operations.delete('1')
|
||||
@@ -72,7 +72,7 @@ class ScheduledOperationsTest(base.TestCaseShell):
|
||||
'/scheduled_operations/1',
|
||||
headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_scheduled_operation(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.scheduled_operations.get('1')
|
||||
@@ -81,7 +81,7 @@ class ScheduledOperationsTest(base.TestCaseShell):
|
||||
'/scheduled_operations/1',
|
||||
headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_scheduled_operation_with_headers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.scheduled_operations.get('1', session_id='fake_session_id')
|
||||
104
karborclient/tests/unit/v1/test_services.py
Normal file
104
karborclient/tests/unit/v1/test_services.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# 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.
|
||||
import mock
|
||||
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'service': {}})
|
||||
|
||||
|
||||
class ServicesTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_services(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.services.list()
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/os-services',
|
||||
headers={}
|
||||
)
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_services_with_host(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.services.list(host='fake_host')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/os-services?host=fake_host',
|
||||
headers={}
|
||||
)
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_services_with_binary(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.services.list(binary='fake_binary')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/os-services?binary=fake_binary',
|
||||
headers={}
|
||||
)
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_services_with_host_and_binary(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.services.list(host='fake_host', binary='fake_binary')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/os-services?binary=fake_binary&host=fake_host',
|
||||
headers={}
|
||||
)
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_enable_service(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
body = {
|
||||
'status': 'enabled'
|
||||
}
|
||||
cs.services.enable('1')
|
||||
mock_request.assert_called_with(
|
||||
'PUT',
|
||||
'/os-services/1',
|
||||
data=body,
|
||||
headers={}
|
||||
)
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_disable_service(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
body = {
|
||||
'status': 'disabled'
|
||||
}
|
||||
cs.services.disable('1')
|
||||
mock_request.assert_called_with(
|
||||
'PUT',
|
||||
'/os-services/1',
|
||||
data=body,
|
||||
headers={}
|
||||
)
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_disable_service_with_reason(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
body = {
|
||||
'status': 'disabled',
|
||||
'disabled_reason': 'fake_reason'
|
||||
}
|
||||
cs.services.disable_log_reason('1', 'fake_reason')
|
||||
mock_request.assert_called_with(
|
||||
'PUT',
|
||||
'/os-services/1',
|
||||
data=body,
|
||||
headers={}
|
||||
)
|
||||
137
karborclient/tests/unit/v1/test_shell.py
Normal file
137
karborclient/tests/unit/v1/test_shell.py
Normal file
@@ -0,0 +1,137 @@
|
||||
# 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.
|
||||
|
||||
import fixtures
|
||||
import mock
|
||||
|
||||
from karborclient import shell
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
FAKE_PROVIDER_ID = '1234'
|
||||
FAKE_ENDPOINT = 'http://127.0.0.1/identity'
|
||||
|
||||
|
||||
class ShellFixture(fixtures.Fixture):
|
||||
def setUp(self):
|
||||
super(ShellFixture, self).setUp()
|
||||
self.shell = shell.KarborShell()
|
||||
|
||||
def tearDown(self):
|
||||
# For some method like test_image_meta_bad_action we are
|
||||
# testing a SystemExit to be thrown and object self.shell has
|
||||
# no time to get instantiated which is OK in this case, so
|
||||
# we make sure the method is there before launching it.
|
||||
if hasattr(self.shell, 'cs'):
|
||||
self.shell.cs.clear_callstack()
|
||||
super(ShellFixture, self).tearDown()
|
||||
|
||||
|
||||
class ShellTest(base.TestCaseShell):
|
||||
|
||||
FAKE_ENV = {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'project_id',
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0',
|
||||
'OS_AUTH_TOKEN': 'fake_token'
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
"""Run before each test."""
|
||||
super(ShellTest, self).setUp()
|
||||
for var in self.FAKE_ENV:
|
||||
self.useFixture(fixtures.EnvironmentVariable(
|
||||
var, self.FAKE_ENV[var]))
|
||||
self.shell = self.useFixture(ShellFixture()).shell
|
||||
|
||||
get_endpoint = mock.MagicMock()
|
||||
get_endpoint.return_value = FAKE_ENDPOINT
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'keystoneauth1.identity.generic.token.Token.get_endpoint',
|
||||
get_endpoint))
|
||||
self.useFixture(fixtures.MonkeyPatch('karborclient.client.Client',
|
||||
fakes.FakeClient))
|
||||
self.useFixture(fixtures.MonkeyPatch(
|
||||
'karborclient.common.http._construct_http_client',
|
||||
fakes.FakeHTTPClient))
|
||||
|
||||
def run_command(self, cmd):
|
||||
if not isinstance(cmd, list):
|
||||
cmd = cmd.split()
|
||||
self.shell.main(cmd)
|
||||
|
||||
def assert_called(self, method, url, body=None, **kwargs):
|
||||
return self.shell.cs.assert_called(method, url, body, **kwargs)
|
||||
|
||||
def test_checkpoint_list_with_all_tenants(self):
|
||||
self.run_command(
|
||||
'checkpoint-list ' + FAKE_PROVIDER_ID + ' --all-tenants 1')
|
||||
|
||||
self.assert_called('GET',
|
||||
'/providers/1234/'
|
||||
'checkpoints?all_tenants=1')
|
||||
|
||||
def test_checkpoint_list_with_all(self):
|
||||
self.run_command(
|
||||
'checkpoint-list ' + FAKE_PROVIDER_ID + ' --all')
|
||||
self.assert_called('GET',
|
||||
'/providers/1234/'
|
||||
'checkpoints?all_tenants=1')
|
||||
|
||||
def test_plan_list_with_all_tenants(self):
|
||||
self.run_command('plan-list --all-tenants 1')
|
||||
self.assert_called('GET', '/plans?all_tenants=1')
|
||||
|
||||
def test_plan_list_with_all(self):
|
||||
self.run_command('plan-list --all')
|
||||
self.assert_called('GET', '/plans?all_tenants=1')
|
||||
|
||||
def test_resotre_list_with_all_tenants(self):
|
||||
self.run_command('restore-list --all-tenants 1')
|
||||
self.assert_called('GET', '/restores?all_tenants=1')
|
||||
|
||||
def test_resotre_list_with_all(self):
|
||||
self.run_command('restore-list --all')
|
||||
self.assert_called('GET', '/restores?all_tenants=1')
|
||||
|
||||
def test_verification_list_with_all_tenants(self):
|
||||
self.run_command('verification-list --all-tenants 1')
|
||||
self.assert_called('GET', '/verifications?all_tenants=1')
|
||||
|
||||
def test_verification_list_with_all(self):
|
||||
self.run_command('verification-list --all')
|
||||
self.assert_called('GET', '/verifications?all_tenants=1')
|
||||
|
||||
def test_trigger_list_with_all_tenants(self):
|
||||
self.run_command('trigger-list --all-tenants 1')
|
||||
self.assert_called('GET', '/triggers?all_tenants=1')
|
||||
|
||||
def test_trigger_list_with_all(self):
|
||||
self.run_command('trigger-list --all')
|
||||
self.assert_called('GET', '/triggers?all_tenants=1')
|
||||
|
||||
def test_scheduledoperation_list_with_all_tenants(self):
|
||||
self.run_command('scheduledoperation-list --all-tenants 1')
|
||||
self.assert_called('GET', '/scheduled_operations?all_tenants=1')
|
||||
|
||||
def test_scheduledoperation_list_with_all(self):
|
||||
self.run_command('scheduledoperation-list --all')
|
||||
self.assert_called('GET', '/scheduled_operations?all_tenants=1')
|
||||
|
||||
def test_operationlog_list_with_all_tenants(self):
|
||||
self.run_command('operationlog-list --all-tenants 1')
|
||||
self.assert_called('GET', '/operation_logs?all_tenants=1')
|
||||
|
||||
def test_operationlog_list_with_all(self):
|
||||
self.run_command('operationlog-list --all')
|
||||
self.assert_called('GET', '/operation_logs?all_tenants=1')
|
||||
@@ -12,8 +12,9 @@
|
||||
|
||||
import mock
|
||||
|
||||
from smaugclient.tests.unit import base
|
||||
from smaugclient.tests.unit.v1 import fakes
|
||||
from karborclient.common.apiclient import exceptions
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'trigger_info': {'name': 'fake_name'}})
|
||||
@@ -21,7 +22,23 @@ mock_request_return = ({}, {'trigger_info': {'name': 'fake_name'}})
|
||||
|
||||
class TriggersTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_triggers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.triggers.list()
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/triggers', headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_triggers_with_all_tenants(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.triggers.list(search_opts={'all_tenants': 1})
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/triggers?all_tenants=1', headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_triggers_with_marker_limit(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.triggers.list(marker=1234, limit=2)
|
||||
@@ -29,7 +46,7 @@ class TriggersTest(base.TestCaseShell):
|
||||
'GET',
|
||||
'/triggers?limit=2&marker=1234', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_triggers_with_sort_key_dir(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.triggers.list(sort_key='id', sort_dir='asc')
|
||||
@@ -38,25 +55,30 @@ class TriggersTest(base.TestCaseShell):
|
||||
'/triggers?'
|
||||
'sort_dir=asc&sort_key=id', headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_triggers_with_invalid_sort_key(self, mock_request):
|
||||
self.assertRaises(ValueError,
|
||||
cs.triggers.list, sort_key='invalid', sort_dir='asc')
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_create_trigger(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.triggers.create('name', 'time', 'properties')
|
||||
cs.triggers.create('name', 'time', {})
|
||||
mock_request.assert_called_with(
|
||||
'POST',
|
||||
'/triggers',
|
||||
data={
|
||||
'trigger_info': {'name': 'name',
|
||||
'type': 'time',
|
||||
'properties': 'properties'}},
|
||||
'properties': {}}},
|
||||
headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.raw_request')
|
||||
def test_create_trigger_with_invalid_window(self):
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
cs.triggers.create,
|
||||
'name', 'time', {'window': 'fake'})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.raw_request')
|
||||
def test_delete_trigger(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.triggers.delete('1')
|
||||
@@ -65,8 +87,8 @@ class TriggersTest(base.TestCaseShell):
|
||||
'/triggers/1',
|
||||
headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
def test_show_plan(self, mock_request):
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_trigger(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.triggers.get('1')
|
||||
mock_request.assert_called_with(
|
||||
@@ -74,7 +96,7 @@ class TriggersTest(base.TestCaseShell):
|
||||
'/triggers/1',
|
||||
headers={})
|
||||
|
||||
@mock.patch('smaugclient.common.http.HTTPClient.json_request')
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_trigger_with_headers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.triggers.get('1', session_id='fake_session_id')
|
||||
@@ -82,3 +104,24 @@ class TriggersTest(base.TestCaseShell):
|
||||
'GET',
|
||||
'/triggers/1',
|
||||
headers={'X-Configuration-Session': 'fake_session_id'})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_update_trigger(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
trigger_id = '123'
|
||||
data = {"name": "My Trigger",
|
||||
"properties": {"pattern": "0 10 * * *", "format": "crontab"}}
|
||||
body = {"trigger_info": data}
|
||||
cs.triggers.update(trigger_id, data)
|
||||
mock_request.assert_called_with(
|
||||
'PUT',
|
||||
'/triggers/123',
|
||||
data=body,
|
||||
headers={}
|
||||
)
|
||||
|
||||
def test_update_trigger_with_invalid_window(self):
|
||||
trigger_id = '123'
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
cs.triggers.update,
|
||||
trigger_id, {'properties': {'window': 'fake'}})
|
||||
80
karborclient/tests/unit/v1/test_verifications.py
Normal file
80
karborclient/tests/unit/v1/test_verifications.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from karborclient.tests.unit import base
|
||||
from karborclient.tests.unit.v1 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
mock_request_return = ({}, {'verification': {}})
|
||||
|
||||
|
||||
class VerificationsTest(base.TestCaseShell):
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_verifications_with_marker_limit(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.verifications.list(marker=1234, limit=2)
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/verifications?limit=2&marker=1234', headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_verifications_with_sort_key_dir(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.verifications.list(sort_key='id', sort_dir='asc')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/verifications?'
|
||||
'sort_dir=asc&sort_key=id', headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_list_verifications_with_invalid_sort_key(self, mock_request):
|
||||
self.assertRaises(ValueError,
|
||||
cs.verifications.list,
|
||||
sort_key='invalid', sort_dir='asc')
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_create_verification(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.verifications.create('586cc6ce-e286-40bd-b2b5-dd32694d9944',
|
||||
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
'{}')
|
||||
mock_request.assert_called_with(
|
||||
'POST',
|
||||
'/verifications',
|
||||
data={
|
||||
'verification':
|
||||
{
|
||||
'checkpoint_id': '2220f8b1-975d-4621-a872-fa9afb43cb6c',
|
||||
'parameters': '{}',
|
||||
'provider_id': '586cc6ce-e286-40bd-b2b5-dd32694d9944'
|
||||
}}, headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_verification(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.verifications.get('1')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/verifications/1',
|
||||
headers={})
|
||||
|
||||
@mock.patch('karborclient.common.http.HTTPClient.json_request')
|
||||
def test_show_verification_with_headers(self, mock_request):
|
||||
mock_request.return_value = mock_request_return
|
||||
cs.verifications.get('1', session_id='fake_session_id')
|
||||
mock_request.assert_called_with(
|
||||
'GET',
|
||||
'/verifications/1',
|
||||
headers={'X-Configuration-Session': 'fake_session_id'})
|
||||
146
karborclient/utils.py
Normal file
146
karborclient/utils.py
Normal file
@@ -0,0 +1,146 @@
|
||||
# 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.
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from karborclient.common.apiclient import exceptions
|
||||
|
||||
|
||||
def extract_resources(args):
|
||||
resources = []
|
||||
for data in args.resources.split(','):
|
||||
if '=' in data and len(data.split('=')) in [3, 4]:
|
||||
resource = dict(zip(['id', 'type', 'name', 'extra_info'],
|
||||
data.split('=')))
|
||||
if resource.get('extra_info'):
|
||||
resource['extra_info'] = jsonutils.loads(
|
||||
resource.get('extra_info'))
|
||||
else:
|
||||
raise exceptions.CommandError(
|
||||
"Unable to parse parameter resources. "
|
||||
"The keys of resource are id , type, name and "
|
||||
"extra_info. The extra_info field is optional.")
|
||||
resources.append(resource)
|
||||
return resources
|
||||
|
||||
|
||||
def check_resources(cs, resources):
|
||||
# check the resource whether it is available
|
||||
for resource in resources:
|
||||
try:
|
||||
instance = cs.protectables.get_instance(
|
||||
resource["type"], resource["id"])
|
||||
except exceptions.NotFound:
|
||||
raise exceptions.CommandError(
|
||||
"The resource: %s can not be found." % resource["id"])
|
||||
else:
|
||||
if instance is None:
|
||||
raise exceptions.CommandError(
|
||||
"The resource: %s is invalid." % resource["id"])
|
||||
|
||||
|
||||
def extract_parameters(args):
|
||||
if all((args.parameters, args.parameters_json)):
|
||||
raise exceptions.CommandError(
|
||||
"Must provide parameters or parameters-json, not both")
|
||||
if not any((args.parameters, args.parameters_json)):
|
||||
return {}
|
||||
|
||||
if args.parameters_json:
|
||||
return jsonutils.loads(args.parameters_json)
|
||||
parameters = {}
|
||||
for resource_params in args.parameters:
|
||||
resource_type = None
|
||||
resource_id = None
|
||||
parameter = {}
|
||||
for param_kv in resource_params.split(','):
|
||||
try:
|
||||
key, value = param_kv.split('=')
|
||||
except Exception:
|
||||
raise exceptions.CommandError(
|
||||
'parameters must be in the form: key1=val1,key2=val2,...'
|
||||
)
|
||||
if key == "resource_type":
|
||||
resource_type = value
|
||||
elif key == "resource_id":
|
||||
if not uuidutils.is_uuid_like(value):
|
||||
raise exceptions.CommandError('resource_id must be a uuid')
|
||||
resource_id = value
|
||||
else:
|
||||
parameter[key] = value
|
||||
if resource_type is None:
|
||||
raise exceptions.CommandError(
|
||||
'Must specify resource_type for parameters'
|
||||
)
|
||||
if resource_id is None:
|
||||
resource_key = resource_type
|
||||
else:
|
||||
resource_key = "%s#%s" % (resource_type, resource_id)
|
||||
parameters[resource_key] = parameter
|
||||
|
||||
return parameters
|
||||
|
||||
|
||||
def extract_instances_parameters(args):
|
||||
parameters = {}
|
||||
for parameter in args.parameters:
|
||||
if '=' in parameter:
|
||||
(key, value) = parameter.split('=', 1)
|
||||
else:
|
||||
key = parameter
|
||||
value = None
|
||||
|
||||
parameters[key] = value
|
||||
return parameters
|
||||
|
||||
|
||||
def extract_extra_info(args):
|
||||
checkpoint_extra_info = {}
|
||||
for data in args.extra_info:
|
||||
# unset doesn't require a val, so we have the if/else
|
||||
if '=' in data:
|
||||
(key, value) = data.split('=', 1)
|
||||
else:
|
||||
key = data
|
||||
value = None
|
||||
|
||||
checkpoint_extra_info[key] = value
|
||||
return checkpoint_extra_info
|
||||
|
||||
|
||||
def extract_properties(args):
|
||||
properties = {}
|
||||
if args.properties is None:
|
||||
return properties
|
||||
for data in args.properties.split(','):
|
||||
if '=' in data:
|
||||
(resource_key, resource_value) = data.split('=', 1)
|
||||
else:
|
||||
raise exceptions.CommandError(
|
||||
"Unable to parse parameter properties.")
|
||||
|
||||
properties[resource_key] = resource_value
|
||||
return properties
|
||||
|
||||
|
||||
def extract_operation_definition(args):
|
||||
operation_definition = {}
|
||||
for data in args.operation_definition.split(','):
|
||||
if '=' in data:
|
||||
(resource_key, resource_value) = data.split('=', 1)
|
||||
else:
|
||||
raise exceptions.CommandError(
|
||||
"Unable to parse parameter operation_definition.")
|
||||
|
||||
operation_definition[resource_key] = resource_value
|
||||
return operation_definition
|
||||
0
karborclient/v1/__init__.py
Normal file
0
karborclient/v1/__init__.py
Normal file
@@ -12,26 +12,46 @@
|
||||
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from smaugclient.common import base
|
||||
from karborclient.common import base
|
||||
|
||||
|
||||
class Checkpoint(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Checkpoint %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
def get(self):
|
||||
self.set_loaded(True)
|
||||
if not hasattr(self.manager, 'get'):
|
||||
return
|
||||
plan = self.protection_plan
|
||||
if plan is not None:
|
||||
provider_id = plan.get("provider_id")
|
||||
new = self.manager.get(provider_id, self.id)
|
||||
if new:
|
||||
self._add_details(new._info)
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
class CheckpointManager(base.ManagerWithFind):
|
||||
resource_class = Checkpoint
|
||||
|
||||
def create(self, provider_id, plan_id):
|
||||
body = {'checkpoint': {'plan_id': plan_id}}
|
||||
def create(self, provider_id, plan_id, checkpoint_extra_info=None):
|
||||
body = {'checkpoint': {'plan_id': plan_id,
|
||||
'extra-info': checkpoint_extra_info}}
|
||||
url = "/providers/{provider_id}/" \
|
||||
"checkpoints" .format(provider_id=provider_id)
|
||||
return self._create(url, body, 'checkpoint')
|
||||
|
||||
def reset_state(self, provider_id, checkpoint_id, state):
|
||||
body = {'os-resetState': {'state': state}}
|
||||
return self.update(provider_id, checkpoint_id, body)
|
||||
|
||||
def update(self, provider_id, checkpoint_id, values):
|
||||
url = '/providers/{provider_id}/checkpoints/{checkpoint_id}'.format(
|
||||
provider_id=provider_id, checkpoint_id=checkpoint_id)
|
||||
return self._update(url, values)
|
||||
|
||||
def delete(self, provider_id, checkpoint_id):
|
||||
path = '/providers/{provider_id}/checkpoints/' \
|
||||
'{checkpoint_id}'.format(provider_id=provider_id,
|
||||
@@ -12,18 +12,23 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from smaugclient.common import http
|
||||
from smaugclient.v1 import checkpoints
|
||||
from smaugclient.v1 import plans
|
||||
from smaugclient.v1 import protectables
|
||||
from smaugclient.v1 import providers
|
||||
from smaugclient.v1 import restores
|
||||
from smaugclient.v1 import scheduled_operations
|
||||
from smaugclient.v1 import triggers
|
||||
from karborclient.common import http
|
||||
from karborclient.v1 import checkpoints
|
||||
from karborclient.v1 import operation_logs
|
||||
from karborclient.v1 import plans
|
||||
from karborclient.v1 import protectables
|
||||
from karborclient.v1 import providers
|
||||
from karborclient.v1 import quota_classes
|
||||
from karborclient.v1 import quotas
|
||||
from karborclient.v1 import restores
|
||||
from karborclient.v1 import scheduled_operations
|
||||
from karborclient.v1 import services
|
||||
from karborclient.v1 import triggers
|
||||
from karborclient.v1 import verifications
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""Client for the Smaug v1 API.
|
||||
"""Client for the karbor v1 API.
|
||||
|
||||
:param string endpoint: A user-supplied endpoint URL for the service.
|
||||
:param string token: Token for authentication.
|
||||
@@ -32,7 +37,7 @@ class Client(object):
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize a new client for the Smaug v1 API."""
|
||||
"""Initialize a new client for the karbor v1 API."""
|
||||
self.http_client = http._construct_http_client(*args, **kwargs)
|
||||
self.plans = plans.PlanManager(self.http_client)
|
||||
self.restores = restores.RestoreManager(self.http_client)
|
||||
@@ -42,3 +47,10 @@ class Client(object):
|
||||
self.triggers = triggers.TriggerManager(self.http_client)
|
||||
self.scheduled_operations = \
|
||||
scheduled_operations.ScheduledOperationManager(self.http_client)
|
||||
self.operation_logs = \
|
||||
operation_logs.OperationLogManager(self.http_client)
|
||||
self.verifications = verifications.VerificationManager(
|
||||
self.http_client)
|
||||
self.services = services.ServiceManager(self.http_client)
|
||||
self.quotas = quotas.QuotaManager(self.http_client)
|
||||
self.quota_classes = quota_classes.QuotaClassManager(self.http_client)
|
||||
44
karborclient/v1/operation_logs.py
Normal file
44
karborclient/v1/operation_logs.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
|
||||
from karborclient.common import base
|
||||
|
||||
|
||||
class OperationLog(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<OperationLog %s>" % self._info
|
||||
|
||||
|
||||
class OperationLogManager(base.ManagerWithFind):
|
||||
resource_class = OperationLog
|
||||
|
||||
def get(self, operation_log_id, session_id=None):
|
||||
if session_id:
|
||||
headers = {'X-Configuration-Session': session_id}
|
||||
else:
|
||||
headers = {}
|
||||
url = "/operation_logs/{operation_log_id}".format(
|
||||
operation_log_id=operation_log_id)
|
||||
return self._get(url, response_key="operation_log", headers=headers)
|
||||
|
||||
def list(self, detailed=False, search_opts=None, marker=None, limit=None,
|
||||
sort_key=None, sort_dir=None, sort=None):
|
||||
"""Lists all operation_logs.
|
||||
|
||||
"""
|
||||
resource_type = "operation_logs"
|
||||
url = self._build_list_url(
|
||||
resource_type, detailed=detailed,
|
||||
search_opts=search_opts, marker=marker,
|
||||
limit=limit, sort_key=sort_key,
|
||||
sort_dir=sort_dir, sort=sort)
|
||||
return self._list(url, 'operation_logs')
|
||||
@@ -10,22 +10,21 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from smaugclient.common import base
|
||||
from karborclient.common import base
|
||||
|
||||
|
||||
class Plan(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Plan %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
|
||||
|
||||
class PlanManager(base.ManagerWithFind):
|
||||
resource_class = Plan
|
||||
|
||||
def create(self, name, provider_id, resources, parameters):
|
||||
def create(self, name, provider_id, resources, parameters,
|
||||
description=None):
|
||||
body = {'plan': {'name': name,
|
||||
'description': description,
|
||||
'provider_id': provider_id,
|
||||
'resources': resources,
|
||||
'parameters': parameters
|
||||
@@ -12,24 +12,18 @@
|
||||
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from smaugclient.common import base
|
||||
from karborclient.common import base
|
||||
|
||||
|
||||
class Protectable(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Protectable %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
|
||||
|
||||
class Instances(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Instances %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
|
||||
|
||||
class ProtectableManager(base.ManagerWithFind):
|
||||
resource_class = Protectable
|
||||
@@ -77,14 +71,27 @@ class ProtectableManager(base.ManagerWithFind):
|
||||
sort_dir=sort_dir, sort=sort)
|
||||
return self._list(url, response_key='instances', obj_class=Instances)
|
||||
|
||||
def get_instance(self, type, id, session_id=None):
|
||||
def get_instance(self, type, id, search_opts=None, session_id=None):
|
||||
if session_id:
|
||||
headers = {'X-Configuration-Session': session_id}
|
||||
else:
|
||||
headers = {}
|
||||
url = "/protectables/{protectable_type}/" \
|
||||
"instances/{protectable_id}".format(protectable_type=type,
|
||||
protectable_id=id)
|
||||
|
||||
if search_opts is None:
|
||||
search_opts = {}
|
||||
query_params = {}
|
||||
for key, val in search_opts.items():
|
||||
if val:
|
||||
query_params[key] = val
|
||||
query_string = ""
|
||||
if query_params:
|
||||
params = sorted(query_params.items(), key=lambda x: x[0])
|
||||
query_string = "?%s" % parse.urlencode(params)
|
||||
|
||||
url = ("/protectables/{protectable_type}/instances/"
|
||||
"{protectable_id}{query_string}").format(
|
||||
protectable_type=type, protectable_id=id,
|
||||
query_string=query_string)
|
||||
return self._get(url, response_key="instance", headers=headers)
|
||||
|
||||
def _build_instances_list_url(self, protectable_type,
|
||||
@@ -10,16 +10,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from smaugclient.common import base
|
||||
from karborclient.common import base
|
||||
|
||||
|
||||
class Provider(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Provider %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
|
||||
|
||||
class ProviderManager(base.ManagerWithFind):
|
||||
resource_class = Provider
|
||||
45
karborclient/v1/quota_classes.py
Normal file
45
karborclient/v1/quota_classes.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# 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.
|
||||
|
||||
from karborclient.common import base
|
||||
|
||||
|
||||
class QuotaClass(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<QuotaClass %s>" % self._info
|
||||
|
||||
|
||||
class QuotaClassManager(base.ManagerWithFind):
|
||||
resource_class = QuotaClass
|
||||
|
||||
def list(self):
|
||||
pass
|
||||
|
||||
def update(self, class_name, data):
|
||||
|
||||
if "plans" in data and data["plans"] is None:
|
||||
data["plans"] = 50
|
||||
|
||||
body = {"quota_class": data}
|
||||
|
||||
return self._update('/quota_classes/{class_name}'
|
||||
.format(class_name=class_name),
|
||||
body, "quota_class")
|
||||
|
||||
def get(self, class_name, session_id=None):
|
||||
if session_id:
|
||||
headers = {'X-Configuration-Session': session_id}
|
||||
else:
|
||||
headers = {}
|
||||
url = "/quota_classes/{class_name}".format(
|
||||
class_name=class_name)
|
||||
return self._get(url, response_key="quota_class", headers=headers)
|
||||
58
karborclient/v1/quotas.py
Normal file
58
karborclient/v1/quotas.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# 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.
|
||||
|
||||
from karborclient.common import base
|
||||
|
||||
|
||||
class Quota(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Quota %s>" % self._info
|
||||
|
||||
|
||||
class QuotaManager(base.ManagerWithFind):
|
||||
resource_class = Quota
|
||||
|
||||
def list(self):
|
||||
pass
|
||||
|
||||
def update(self, project_id, data):
|
||||
|
||||
if "plans" in data and data["plans"] is None:
|
||||
data["plans"] = 50
|
||||
|
||||
body = {"quota": data}
|
||||
|
||||
return self._update('/quotas/{project_id}'
|
||||
.format(project_id=project_id),
|
||||
body, "quota")
|
||||
|
||||
def get(self, project_id, detail, session_id=None):
|
||||
if session_id:
|
||||
headers = {'X-Configuration-Session': session_id}
|
||||
else:
|
||||
headers = {}
|
||||
base_url = "/quotas/{project_id}".format(
|
||||
project_id=project_id)
|
||||
if detail:
|
||||
url = base_url + '/detail'
|
||||
else:
|
||||
url = base_url
|
||||
return self._get(url, response_key="quota", headers=headers)
|
||||
|
||||
def defaults(self, project_id, session_id=None):
|
||||
if session_id:
|
||||
headers = {'X-Configuration-Session': session_id}
|
||||
else:
|
||||
headers = {}
|
||||
url = "/quotas/{project_id}/defaults".format(
|
||||
project_id=project_id)
|
||||
return self._get(url, response_key="quota", headers=headers)
|
||||
@@ -10,35 +10,31 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from smaugclient.common import base
|
||||
from karborclient.common import base
|
||||
|
||||
|
||||
class Restore(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Restore %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
|
||||
|
||||
class RestoreManager(base.ManagerWithFind):
|
||||
resource_class = Restore
|
||||
|
||||
def create(self, provider_id, checkpoint_id, restore_target, parameters):
|
||||
body = {'restore': {'provider_id': provider_id,
|
||||
'checkpoint_id': checkpoint_id,
|
||||
'restore_target': restore_target,
|
||||
'parameters': parameters,
|
||||
}
|
||||
}
|
||||
def create(self, provider_id, checkpoint_id, restore_target, parameters,
|
||||
restore_auth):
|
||||
body = {
|
||||
'restore': {
|
||||
'provider_id': provider_id,
|
||||
'checkpoint_id': checkpoint_id,
|
||||
'restore_target': restore_target,
|
||||
'restore_auth': restore_auth,
|
||||
'parameters': parameters,
|
||||
}
|
||||
}
|
||||
url = "/restores"
|
||||
return self._create(url, body, 'restore')
|
||||
|
||||
def delete(self, restore_id):
|
||||
path = '/restores/{restore_id}'.format(
|
||||
restore_id=restore_id)
|
||||
return self._delete(path)
|
||||
|
||||
def get(self, restore_id, session_id=None):
|
||||
if session_id:
|
||||
headers = {'X-Configuration-Session': session_id}
|
||||
@@ -10,16 +10,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from smaugclient.common import base
|
||||
from karborclient.common import base
|
||||
|
||||
|
||||
class ScheduledOperation(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<ScheduledOperation %s>" % self._info
|
||||
|
||||
def data(self, **kwargs):
|
||||
return self.manager.data(self, **kwargs)
|
||||
|
||||
|
||||
class ScheduledOperationManager(base.ManagerWithFind):
|
||||
resource_class = ScheduledOperation
|
||||
64
karborclient/v1/services.py
Normal file
64
karborclient/v1/services.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# 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.
|
||||
|
||||
from karborclient.common import base
|
||||
|
||||
|
||||
class Service(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Service %s>" % self._info
|
||||
|
||||
|
||||
class ServiceManager(base.ManagerWithFind):
|
||||
resource_class = Service
|
||||
|
||||
def enable(self, service_id):
|
||||
"""Enable the service specified by the service ID
|
||||
|
||||
:param service_id: The ID of the service to enable.
|
||||
"""
|
||||
body = {
|
||||
'status': 'enabled'
|
||||
}
|
||||
return self._update('/os-services/%s' % service_id, body, "service")
|
||||
|
||||
def disable(self, service_id):
|
||||
"""Disable the service specified by the service ID.
|
||||
|
||||
:param service_id: The ID of the service to disable.
|
||||
"""
|
||||
body = {
|
||||
'status': 'disabled'
|
||||
}
|
||||
return self._update('/os-services/%s' % service_id, body, "service")
|
||||
|
||||
def disable_log_reason(self, service_id, reason):
|
||||
"""Disable the service with a reason.
|
||||
|
||||
:param service_id: The ID of the service to disable.
|
||||
:param reason: The reason for disabling a service.
|
||||
"""
|
||||
body = {
|
||||
'status': 'disabled',
|
||||
'disabled_reason': reason
|
||||
}
|
||||
return self._update("/os-services/%s" % service_id, body, "service")
|
||||
|
||||
def list(self, host=None, binary=None):
|
||||
"""Lists all services."""
|
||||
search_opts = {
|
||||
'host': host,
|
||||
'binary': binary
|
||||
}
|
||||
resource_type = "os-services"
|
||||
url = self._build_list_url(resource_type, search_opts=search_opts)
|
||||
return self._list(url, 'services')
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user