Python freezer client
Split apiclient from freezer-agent repo Use cliff as a cli for freezer Implements bp: freezerclient
This commit is contained in:
parent
d9212240de
commit
2864edd6d1
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
__pycache__
|
||||
dist
|
||||
build
|
||||
.venv
|
||||
tests/scenario/.vagrant
|
||||
.idea
|
||||
.autogenerated
|
||||
.coverage
|
||||
cover/
|
||||
coverage.xml
|
||||
*.sw?
|
||||
.tox
|
||||
*.egg
|
||||
*.egg-info
|
||||
*.py[co]
|
||||
.DS_Store
|
||||
*.log
|
||||
.testrepository
|
||||
subunit.log
|
||||
.eggs
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
||||
# Django files that get created during the test runs
|
||||
.secret_key_store
|
||||
*.lock
|
||||
|
||||
# Coverage data
|
||||
.coverage.*
|
333
.pylintrc
Normal file
333
.pylintrc
Normal file
@ -0,0 +1,333 @@
|
||||
[MASTER]
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Profiled execution.
|
||||
profile=no
|
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not
|
||||
# paths.
|
||||
ignore=CVS
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=no
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# DEPRECATED
|
||||
include-ids=no
|
||||
|
||||
# DEPRECATED
|
||||
symbols=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time. See also the "--disable" option for examples.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=W,C,R
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html. You can also give a reporter class, eg
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Put messages in a separate file for each module / package specified on the
|
||||
# command line instead of printing them on stdout. Reports (if any) will be
|
||||
# written in a file name "pylint_global.[txt|html]".
|
||||
files-output=no
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=no
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Add a comment according to your evaluation note. This is used by the global
|
||||
# evaluation report (RP0004).
|
||||
comment=no
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details
|
||||
#msg-template=
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus extisting member attributes cannot be deduced by static analysis
|
||||
ignored-modules=distutils
|
||||
|
||||
# List of classes names for which member attributes should not be checked
|
||||
# (useful for classes with attributes dynamically set).
|
||||
ignored-classes=SQLObject
|
||||
|
||||
# When zope mode is activated, add a predefined set of Zope acquired attributes
|
||||
# to generated-members.
|
||||
zope=no
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E0201 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=REQUEST,acl_users,aq_parent,BackupJob.time_stamp,BackupJob.start_time
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,XXX,TODO
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Required attributes for module, separated by a comma
|
||||
required-attributes=
|
||||
|
||||
# List of builtins function names that should not be used, separated by a comma
|
||||
bad-functions=map,filter,apply,input,file
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=i,j,k,ex,Run,_
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=foo,bar,baz,toto,tutu,tata
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name
|
||||
include-naming-hint=no
|
||||
|
||||
# Regular expression matching correct function names
|
||||
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for function names
|
||||
function-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct variable names
|
||||
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for variable names
|
||||
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct constant names
|
||||
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Naming hint for constant names
|
||||
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Regular expression matching correct attribute names
|
||||
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for attribute names
|
||||
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct argument names
|
||||
argument-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for argument names
|
||||
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct class attribute names
|
||||
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
||||
# Naming hint for class attribute names
|
||||
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
||||
# Regular expression matching correct inline iteration names
|
||||
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Naming hint for inline iteration names
|
||||
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct class names
|
||||
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Naming hint for class names
|
||||
class-name-hint=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Regular expression matching correct module names
|
||||
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Naming hint for module names
|
||||
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Regular expression matching correct method names
|
||||
method-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for method names
|
||||
method-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=__.*__
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=-1
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly
|
||||
# not used).
|
||||
dummy-variables-rgx=_$|dummy
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format
|
||||
logging-modules=logging
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=80
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=no
|
||||
|
||||
# List of optional constructs for which whitespace checking is disabled
|
||||
no-space-check=trailing-comma,dict-separator
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1000
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=5
|
||||
|
||||
# Argument names that match this expression will be ignored. Default to name
|
||||
# with leading underscore
|
||||
ignored-argument-names=_.*
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=50
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=7
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of interface methods to ignore, separated by a comma. This is used for
|
||||
# instance to not check methods defines in Zope's Interface base class.
|
||||
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=mcs
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,TERMIOS,Bastion,rexec
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "Exception"
|
||||
overgeneral-exceptions=Exception
|
9
.testr.conf
Normal file
9
.testr.conf
Normal file
@ -0,0 +1,9 @@
|
||||
[DEFAULT]
|
||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||
OS_LOG_CAPTURE=${OS_LOG_CAPTURE:-1} \
|
||||
${PYTHON:-python} -m subunit.run discover -s ${OS_TEST_PATH:-./freezerclient/tests/unit} -t . $LISTOPT $IDOPTION
|
||||
|
||||
test_id_option=--load-list $IDFILE
|
||||
test_list_option=--list
|
||||
group_regex=([^\.]+\.)+
|
17
CONTRIBUTING.rst
Normal file
17
CONTRIBUTING.rst
Normal file
@ -0,0 +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
|
||||
|
||||
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
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
Bugs should be filed on Launchpad, not GitHub:
|
||||
|
||||
https://bugs.launchpad.net/freezer
|
4
HACKING.rst
Normal file
4
HACKING.rst
Normal file
@ -0,0 +1,4 @@
|
||||
python-freezerclient Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
175
LICENSE
Normal file
175
LICENSE
Normal file
@ -0,0 +1,175 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
6
MANIFEST.in
Normal file
6
MANIFEST.in
Normal file
@ -0,0 +1,6 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
25
README.rst
Normal file
25
README.rst
Normal file
@ -0,0 +1,25 @@
|
||||
===================================================
|
||||
Python bindings and CLI for OpenStack's Freezer API
|
||||
===================================================
|
||||
|
||||
This is a client library for Freezer built on the OpenStack Disaster Recovery API. It provides a Python API (the freezerclient module) and a command-line tool (freezer). This library fully supports the v1 Disaster Recovery API.
|
||||
|
||||
Development takes place via the usual OpenStack processes as outlined in the `developer guide <http://docs.openstack.org/infra/manual/developers.html>`_. The master repository is in `Git <https://git.openstack.org/cgit/openstack/python-freezerclient>`_.
|
||||
|
||||
|
||||
* License: Apache License, Version 2.0
|
||||
* `PyPi`_ - package installation
|
||||
* `Online Documentation`_
|
||||
* `Launchpad project`_ - release management
|
||||
* `Blueprints`_ - feature specifications
|
||||
* `Bugs`_ - issue tracking
|
||||
* `Source`_
|
||||
* `How to Contribute`_
|
||||
|
||||
.. _PyPi: https://pypi.python.org/pypi/python-freezerclient
|
||||
.. _Online Documentation: https://wiki.openstack.org/wiki/Python-freezerclient
|
||||
.. _Launchpad project: https://launchpad.net/python-freezerclient
|
||||
.. _Blueprints: https://blueprints.launchpad.net/python-freezerclient
|
||||
.. _Bugs: https://bugs.launchpad.net/python-freezerclient
|
||||
.. _Source: https://git.openstack.org/cgit/openstack/python-freezerclient
|
||||
.. _How to Contribute: http://docs.openstack.org/infra/manual/developers.html
|
261
client.py
261
client.py
@ -1,261 +0,0 @@
|
||||
"""
|
||||
Copyright 2015 Hewlett-Packard
|
||||
|
||||
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.
|
||||
|
||||
client interface to the Freezer API
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
|
||||
from keystoneclient.auth.identity import v2
|
||||
from keystoneclient.auth.identity import v3
|
||||
from keystoneclient import session as ksc_session
|
||||
from oslo_config import cfg
|
||||
|
||||
from freezer.apiclient import actions
|
||||
from freezer.apiclient import backups
|
||||
from freezer.apiclient import jobs
|
||||
from freezer.apiclient import registration
|
||||
from freezer.apiclient import sessions
|
||||
from freezer.utils import Namespace
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
FREEZER_SERVICE_TYPE = 'backup'
|
||||
|
||||
|
||||
def env(*vars, **kwargs):
|
||||
for v in vars:
|
||||
value = os.environ.get(v, None)
|
||||
if value:
|
||||
return value
|
||||
return kwargs.get('default', '')
|
||||
|
||||
|
||||
class cached_property(object):
|
||||
|
||||
def __init__(self, func):
|
||||
self.__doc__ = getattr(func, '__doc__')
|
||||
self.func = func
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self
|
||||
value = obj.__dict__[self.func.__name__] = self.func(obj)
|
||||
return value
|
||||
|
||||
|
||||
def build_os_options():
|
||||
osclient_opts = [
|
||||
cfg.StrOpt('os-username',
|
||||
default=env('OS_USERNAME'),
|
||||
help='Name used for authentication with the OpenStack '
|
||||
'Identity service. Defaults to env[OS_USERNAME].',
|
||||
dest='os_username'),
|
||||
cfg.StrOpt('os-password',
|
||||
default=env('OS_PASSWORD'),
|
||||
help='Password used for authentication with the OpenStack '
|
||||
'Identity service. Defaults to env[OS_PASSWORD].',
|
||||
dest='os_password'),
|
||||
cfg.StrOpt('os-project-name',
|
||||
default=env('OS_PROJECT_NAME'),
|
||||
help='Project name to scope to. Defaults to '
|
||||
'env[OS_PROJECT_NAME].',
|
||||
dest='os_project_name'),
|
||||
cfg.StrOpt('os-project-domain-name',
|
||||
default=env('OS_PROJECT_DOMAIN_NAME'),
|
||||
help='Domain name containing project. Defaults to '
|
||||
'env[OS_PROJECT_DOMAIN_NAME].',
|
||||
dest='os_project_domain_name'),
|
||||
cfg.StrOpt('os-user-domain-name',
|
||||
default=env('OS_USER_DOMAIN_NAME'),
|
||||
help='User\'s domain name. Defaults to '
|
||||
'env[OS_USER_DOMAIN_NAME].',
|
||||
dest='os_user_domain_name'),
|
||||
cfg.StrOpt('os-tenant-name',
|
||||
default=env('OS_TENANT_NAME'),
|
||||
help='Tenant to request authorization on. Defaults to '
|
||||
'env[OS_TENANT_NAME].',
|
||||
dest='os_tenant_name'),
|
||||
cfg.StrOpt('os-tenant-id',
|
||||
default=env('OS_TENANT_ID'),
|
||||
help='Tenant to request authorization on. Defaults to '
|
||||
'env[OS_TENANT_ID].',
|
||||
dest='os_tenant_id'),
|
||||
cfg.StrOpt('os-auth-url',
|
||||
default=env('OS_AUTH_URL'),
|
||||
help='Specify the Identity endpoint to use for '
|
||||
'authentication. Defaults to env[OS_AUTH_URL].',
|
||||
dest='os_auth_url'),
|
||||
cfg.StrOpt('os-backup-url',
|
||||
default=env('OS_BACKUP_URL'),
|
||||
help='Specify the Freezer backup service endpoint to use. '
|
||||
'Defaults to env[OS_BACKUP_URL].',
|
||||
dest='os_backup_url'),
|
||||
cfg.StrOpt('os-region-name',
|
||||
default=env('OS_REGION_NAME'),
|
||||
help='Specify the region to use. Defaults to '
|
||||
'env[OS_REGION_NAME].',
|
||||
dest='os_region_name'),
|
||||
cfg.StrOpt('os-token',
|
||||
default=env('OS_TOKEN'),
|
||||
help='Specify an existing token to use instead of retrieving'
|
||||
' one via authentication (e.g. with username & '
|
||||
'password). Defaults to env[OS_TOKEN].',
|
||||
dest='os_token'),
|
||||
cfg.StrOpt('os-identity-api-version',
|
||||
default=env('OS_IDENTITY_API_VERSION'),
|
||||
help='Identity API version: 2.0 or 3. '
|
||||
'Defaults to env[OS_IDENTITY_API_VERSION]',
|
||||
dest='os_identity_api_version'),
|
||||
cfg.StrOpt('os-endpoint-type',
|
||||
choices=['public', 'publicURL', 'internal', 'internalURL',
|
||||
'admin', 'adminURL'],
|
||||
default=env('OS_ENDPOINT_TYPE') or 'public',
|
||||
help='Endpoint type to select. Valid endpoint types: '
|
||||
'"public" or "publicURL", "internal" or "internalURL",'
|
||||
' "admin" or "adminURL". Defaults to '
|
||||
'env[OS_ENDPOINT_TYPE] or "public"',
|
||||
dest='os_endpoint_type'),
|
||||
|
||||
]
|
||||
|
||||
return osclient_opts
|
||||
|
||||
|
||||
def guess_auth_version(opts):
|
||||
if opts.os_identity_api_version == '3':
|
||||
return '3'
|
||||
elif opts.os_identity_api_version == '2.0':
|
||||
return '2.0'
|
||||
elif opts.os_auth_url.endswith('v3'):
|
||||
return '3'
|
||||
elif opts.os_auth_url.endswith('v2.0'):
|
||||
return '2.0'
|
||||
raise Exception('Please provide valid keystone auth url with valid'
|
||||
' keystone api version to use')
|
||||
|
||||
|
||||
def get_auth_plugin(opts):
|
||||
auth_version = guess_auth_version(opts)
|
||||
if opts.os_username:
|
||||
if auth_version == '3':
|
||||
return v3.Password(auth_url=opts.os_auth_url,
|
||||
username=opts.os_username,
|
||||
password=opts.os_password,
|
||||
project_name=opts.os_project_name,
|
||||
user_domain_name=opts.os_user_domain_name,
|
||||
project_domain_name=opts.os_project_domain_name)
|
||||
elif auth_version == '2.0':
|
||||
return v2.Password(auth_url=opts.os_auth_url,
|
||||
username=opts.os_username,
|
||||
password=opts.os_password,
|
||||
tenant_name=opts.os_tenant_name)
|
||||
elif opts.os_token:
|
||||
if auth_version == '3':
|
||||
return v3.Token(auth_url=opts.os_auth_url,
|
||||
token=opts.os_token,
|
||||
project_name=opts.os_project_name,
|
||||
project_domain_name=opts.os_project_domain_name)
|
||||
elif auth_version == '2.0':
|
||||
return v2.Token(auth_url=opts.os_auth_url,
|
||||
token=opts.os_token,
|
||||
tenant_name=opts.os_tenant_name)
|
||||
raise Exception('Unable to determine correct auth method, please provide'
|
||||
' either username or token')
|
||||
|
||||
|
||||
class Client(object):
|
||||
def __init__(self,
|
||||
version='1',
|
||||
token=None,
|
||||
username=None,
|
||||
password=None,
|
||||
tenant_name=None,
|
||||
auth_url=None,
|
||||
session=None,
|
||||
endpoint=None,
|
||||
opts=None,
|
||||
project_name=None,
|
||||
user_domain_name=None,
|
||||
project_domain_name=None,
|
||||
verify=True,
|
||||
cacert=False):
|
||||
|
||||
self.opts = opts
|
||||
# this creates a namespace for self.opts when the client is
|
||||
# created from other method rather than command line arguments.
|
||||
if self.opts is None:
|
||||
self.opts = Namespace({})
|
||||
if token:
|
||||
self.opts.os_token = token
|
||||
if username:
|
||||
self.opts.os_username = username
|
||||
if password:
|
||||
self.opts.os_password = password
|
||||
if tenant_name:
|
||||
self.opts.os_tenant_name = tenant_name
|
||||
if auth_url:
|
||||
self.opts.os_auth_url = auth_url
|
||||
if endpoint:
|
||||
self.opts.os_backup_url = endpoint
|
||||
if project_name:
|
||||
self.opts.os_project_name = project_name
|
||||
if user_domain_name:
|
||||
self.opts.os_user_domain_name = user_domain_name
|
||||
if project_domain_name:
|
||||
self.opts.os_project_domain_name = project_domain_name
|
||||
|
||||
# flag to initialize freezer-scheduler with insecure mode
|
||||
self.verify = verify
|
||||
|
||||
self._session = session
|
||||
self.version = version
|
||||
|
||||
self.backups = backups.BackupsManager(self, verify=verify)
|
||||
self.registration = registration.RegistrationManager(self, verify=verify)
|
||||
self.jobs = jobs.JobManager(self, verify=verify)
|
||||
self.actions = actions.ActionManager(self, verify=verify)
|
||||
self.sessions = sessions.SessionManager(self, verify=verify)
|
||||
|
||||
|
||||
@cached_property
|
||||
def session(self):
|
||||
if self._session:
|
||||
return self._session
|
||||
auth_plugin = get_auth_plugin(self.opts)
|
||||
return ksc_session.Session(auth=auth_plugin, verify=self.verify)
|
||||
|
||||
@cached_property
|
||||
def endpoint(self):
|
||||
if self.opts.os_backup_url:
|
||||
return self.opts.os_backup_url
|
||||
else:
|
||||
auth_ref = self.session.auth.get_auth_ref(self.session)
|
||||
endpoint = auth_ref.service_catalog.url_for(
|
||||
service_type=FREEZER_SERVICE_TYPE,
|
||||
endpoint_type=self.opts.os_endpoint_type,
|
||||
)
|
||||
return endpoint
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
return self.session.get_token()
|
||||
|
||||
@cached_property
|
||||
def client_id(self):
|
||||
return '{0}_{1}'.format(self.session.get_project_id(),
|
||||
socket.gethostname())
|
0
devstack/README.rst
Normal file
0
devstack/README.rst
Normal file
21
devstack/gate_hook.sh
Normal file
21
devstack/gate_hook.sh
Normal file
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
set -ex
|
||||
|
||||
# Install python-freezerclient devstack integration
|
||||
export DEVSTACK_LOCAL_CONFIG="enable_plugin python-freezerclient https://git.openstack.org/openstack/python-freezerclient"
|
||||
|
||||
$BASE/new/devstack-gate/devstack-vm-gate.sh
|
43
devstack/lib/freezerclient
Normal file
43
devstack/lib/freezerclient
Normal file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Install python-freezerclient
|
||||
|
||||
# add the following to localrc:
|
||||
# enable_service python-freezerclient
|
||||
#
|
||||
# Dependencies:
|
||||
# - functions
|
||||
# - OS_AUTH_URL for auth in api
|
||||
# - DEST set to the destination directory
|
||||
# - SERVICE_PASSWORD, SERVICE_TENANT_NAME for auth in api
|
||||
# - STACK_USER service user
|
||||
|
||||
# Save trace setting
|
||||
XTRACE=$(set +o | grep xtrace)
|
||||
set +o xtrace
|
||||
|
||||
# Functions
|
||||
# ---------
|
||||
|
||||
function install_python-freezerclient {
|
||||
|
||||
git_clone $FREEZERCLIENT_REPO $FREEZERCLIENT_DIR $FREEZERCLIENT_BRANCH
|
||||
setup_develop $FREEZER_DIR
|
||||
}
|
||||
|
||||
# Restore xtrace
|
||||
$XTRACE
|
31
devstack/local.conf.example
Normal file
31
devstack/local.conf.example
Normal file
@ -0,0 +1,31 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
[[local|localrc]]
|
||||
disable_all_services
|
||||
|
||||
enable_plugin python-freezerclient https://git.openstack.org/openstack/python-freezerclient master
|
||||
|
||||
enable_service rabbit mysql key
|
||||
|
||||
# This is to keep the token small for testing
|
||||
KEYSTONE_TOKEN_FORMAT=UUID
|
||||
|
||||
# Modify passwords as needed
|
||||
DATABASE_PASSWORD=secretdatabase
|
||||
RABBIT_PASSWORD=secretrabbit
|
||||
ADMIN_PASSWORD=secretadmin
|
||||
SERVICE_PASSWORD=secretservice
|
||||
SERVICE_TOKEN=111222333444
|
||||
|
27
devstack/plugin.sh
Normal file
27
devstack/plugin.sh
Normal file
@ -0,0 +1,27 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# check for service enabled
|
||||
if is_service_enabled python-freezerclient; then
|
||||
if [[ "$1" == "source" || "`type -t install_freezerclient`" != 'function' ]]; then
|
||||
# Initial source
|
||||
source $FREEZER_DIR/devstack/lib/python-freezerclient
|
||||
fi
|
||||
|
||||
if [[ "$1" == "stack" && "$2" == "install" ]]; then
|
||||
echo_summary "Installing python-freezerclient"
|
||||
install_freezerclient
|
||||
fi
|
||||
fi
|
||||
|
26
devstack/settings
Normal file
26
devstack/settings
Normal file
@ -0,0 +1,26 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Defaults
|
||||
# --------
|
||||
|
||||
# Set up default directories
|
||||
FREEZERCLIENT_DIR=$DEST/python-freezerclient
|
||||
FREEZERCLIENT_LOG_DIR=$DEST/logs
|
||||
|
||||
# Python freezerclient repository
|
||||
FREEZERCLIENT_REPO=${FREEZER_REPO:-${GIT_BASE}/openstack/python-freezerclient.git}
|
||||
FREEZERCLIENT_BRANCH=${FREEZER_BRANCH:-master}
|
||||
|
||||
enable_service python-freezerclient
|
3
doc/.gitignore
vendored
Normal file
3
doc/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build/
|
||||
source/ref/
|
||||
source/api/
|
265
doc/source/conf.py
Normal file
265
doc/source/conf.py
Normal file
@ -0,0 +1,265 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Freezer documentation build configuration file, created by
|
||||
# sphinx-quickstart on Thu Feb 4 22:27:35 2016.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = ['sphinx.ext.autodoc',
|
||||
'sphinx.ext.viewcode',
|
||||
'oslosphinx']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Python Freezer Client'
|
||||
copyright = u'2016, OpenStack'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '2.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '2.0.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Freezerdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'Freezer.tex', u'Freezer Documentation',
|
||||
u'OpenStack', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'freezer', u'Freezer Documentation',
|
||||
[u'OpenStack'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Freezer', u'Freezer Documentation',
|
||||
u'OpenStack', 'Freezer', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
#intersphinx_mapping = {'http://docs.python.org/': None}
|
||||
|
23
doc/source/index.rst
Normal file
23
doc/source/index.rst
Normal file
@ -0,0 +1,23 @@
|
||||
.. Python Freezer Client documentation master file, created by
|
||||
sphinx-quickstart on Thu Feb 4 22:27:35 2016.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to Freezer's documentation!
|
||||
===================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
0
freezerclient/__init__.py
Normal file
0
freezerclient/__init__.py
Normal file
@ -1,19 +1,16 @@
|
||||
"""
|
||||
(c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 json
|
||||
|
234
freezerclient/shell.py
Normal file
234
freezerclient/shell.py
Normal file
@ -0,0 +1,234 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from cliff.app import App
|
||||
from cliff.commandmanager import CommandManager
|
||||
|
||||
from freezerclient.v1 import actions
|
||||
from freezerclient.v1 import backups
|
||||
from freezerclient.v1.client import Client
|
||||
from freezerclient.v1 import clients
|
||||
from freezerclient.v1 import jobs
|
||||
from freezerclient.v1 import sessions
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FreezerCommandManager(CommandManager):
|
||||
""" All commands available for the shell are registered here """
|
||||
SHELL_COMMANDS = {
|
||||
'job-show': jobs.JobShow,
|
||||
'job-list': jobs.JobList,
|
||||
'job-create': jobs.JobCreate,
|
||||
'job-get': jobs.JobGet,
|
||||
'job-delete': jobs.JobDelete,
|
||||
'job-start': jobs.JobStart,
|
||||
'job-stop': jobs.JobStop,
|
||||
'job-abort': jobs.JobAbort,
|
||||
'job-update': jobs.JobUpdate,
|
||||
'client-list': clients.ClientList,
|
||||
'client-show': clients.ClientShow,
|
||||
'client-register': clients.ClientRegister,
|
||||
'client-delete': clients.ClientDelete,
|
||||
'backup-list': backups.BackupList,
|
||||
'backup-show': backups.BackupShow,
|
||||
'session-list': sessions.SessionList,
|
||||
'session-show': sessions.SessionShow,
|
||||
'session-create': sessions.SessionCreate,
|
||||
'session-add-job': sessions.SessionAddJob,
|
||||
'session-remove-job': sessions.SessionRemoveJob,
|
||||
'session-start': sessions.SessionStart,
|
||||
'session-end': sessions.SessionEnd,
|
||||
'session-update': sessions.SessionUpdate,
|
||||
'action-show': actions.ActionShow,
|
||||
'action-list': actions.ActionList,
|
||||
'action-delete': actions.ActionDelete,
|
||||
'action-create': actions.ActionCreate,
|
||||
'action-update': actions.ActionUpdate
|
||||
}
|
||||
|
||||
def load_commands(self, namespace):
|
||||
for name, command_class in self.SHELL_COMMANDS.items():
|
||||
self.add_command(name, command_class)
|
||||
|
||||
|
||||
class FreezerShell(App):
|
||||
def __init__(self):
|
||||
super(FreezerShell, self).__init__(
|
||||
description='Python Freezer Client',
|
||||
version='0.1',
|
||||
deferred_help=True,
|
||||
command_manager=FreezerCommandManager(None),
|
||||
)
|
||||
|
||||
def build_option_parser(self, description, version):
|
||||
parser = super(FreezerShell, self).build_option_parser(description, version)
|
||||
parser.add_argument(
|
||||
'--os-auth-url',
|
||||
dest='os_auth_url',
|
||||
default=os.environ.get('OS_AUTH_URL'),
|
||||
help='Specify identity endpoint',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-backup-url',
|
||||
dest='os_backup_url',
|
||||
default=os.environ.get('OS_BACKUP_URL'),
|
||||
help='Specify the Freezer backup service endpoint to use'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-endpoint-type',
|
||||
dest='os_endpoint_type',
|
||||
default=os.environ.get('OS_ENDPOINT_TYPE'),
|
||||
help='''Endpoint type to select. Valid endpoint types:
|
||||
"public" or "publicURL", "internal" or "internalURL",
|
||||
"admin" or "adminURL"'''
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-identity-api-version',
|
||||
dest='os_identity_api_version',
|
||||
default=os.environ.get('OS_IDENTITY_API_VERSION'),
|
||||
help='Identity API version: 2.0 or 3'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-password',
|
||||
dest='os_password',
|
||||
default=os.environ.get('OS_PASSWORD'),
|
||||
help='''Password used for authentication with the OpenStack
|
||||
Identity service'''
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-username',
|
||||
dest='os_username',
|
||||
default=os.environ.get('OS_USERNAME'),
|
||||
help='''Name used for authentication with the OpenStack
|
||||
Identity service'''
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-token',
|
||||
dest='os_token',
|
||||
default=os.environ.get('OS_TOKEN'),
|
||||
help='''Specify an existing token to use instead of retrieving
|
||||
one via authentication'''
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-project-domain-name',
|
||||
dest='os_project_domain_name',
|
||||
default=os.environ.get('OS_PROJECT_DOMAIN_NAME'),
|
||||
help='Domain name containing project'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-project-name',
|
||||
dest='os_project_name',
|
||||
default=os.environ.get('OS_PROJECT_NAME'),
|
||||
help='Project name to scope to'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-region-name',
|
||||
dest='os_region_name',
|
||||
default=os.environ.get('OS_REGION_NAME'),
|
||||
help='Specify the region to use'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-tenant-id',
|
||||
dest='os_tenant_id',
|
||||
default=os.environ.get('OS_TENANT_ID'),
|
||||
help='Tenant to request authorization on'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-tenant-name',
|
||||
dest='os_tenant_name',
|
||||
default=os.environ.get('OS_TENANT_NAME'),
|
||||
help='Tenant to request authorization on'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-user-domain-name',
|
||||
dest='os_user_domain_name',
|
||||
default=os.environ.get('OS_USER_DOMAIN_NAME'),
|
||||
help='User domain name'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-k', '--insecure',
|
||||
dest='insecure',
|
||||
action='store_true',
|
||||
default=os.environ.get('OS_INSECURE'),
|
||||
help='use python-freezerclient with insecure connections'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-cacert',
|
||||
dest='os_cacert',
|
||||
default=os.environ.get('OS_CACERT'),
|
||||
help='''Path of CA TLS certificate(s) used to verify the
|
||||
remote server's certificate. Without this option
|
||||
freezer looks for the default system CA certificates.'''
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-cert',
|
||||
dest='os_cert',
|
||||
default=os.environ.get('OS_CERT'),
|
||||
help='''Path of CERT TLS certificate(s) used to verify the
|
||||
remote server's certificate.1'''
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
""" Build a client object to communicate with the API
|
||||
:return: freezerclient object
|
||||
"""
|
||||
opts = {
|
||||
'token': self.options.os_token,
|
||||
'version': self.options.os_identity_api_version,
|
||||
'username': self.options.os_username,
|
||||
'password': self.options.os_password,
|
||||
'tenant_name': self.options.os_tenant_name,
|
||||
'auth_url': self.options.os_auth_url,
|
||||
'endpoint': self.options.os_backup_url,
|
||||
'project_name': self.options.os_project_name,
|
||||
'user_domain_name': self.options.os_user_domain_name,
|
||||
'project_domain_name': self.options.os_project_domain_name,
|
||||
'verify': True or self.options.os_cacert,
|
||||
'cert': self.options.os_cert
|
||||
}
|
||||
return Client(**opts)
|
||||
|
||||
|
||||
def main(argv=sys.argv[1:]):
|
||||
print('hola')
|
||||
return FreezerShell().run(argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
0
freezerclient/tests/__init__.py
Normal file
0
freezerclient/tests/__init__.py
Normal file
0
freezerclient/tests/unit/__init__.py
Normal file
0
freezerclient/tests/unit/__init__.py
Normal file
25
freezerclient/tests/unit/test_exceptions.py
Normal file
25
freezerclient/tests/unit/test_exceptions.py
Normal file
@ -0,0 +1,25 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 unittest
|
||||
|
||||
from freezerclient import exceptions
|
||||
|
||||
|
||||
class TestApiClientException(unittest.TestCase):
|
||||
|
||||
def test_get_message_from_response_string(self):
|
||||
e = exceptions.ApiClientException('some error message')
|
||||
self.assertEquals(str(e), 'some error message')
|
13
freezerclient/tests/unit/test_utils.py
Normal file
13
freezerclient/tests/unit/test_utils.py
Normal file
@ -0,0 +1,13 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
0
freezerclient/tests/unit/v1/__init__.py
Normal file
0
freezerclient/tests/unit/v1/__init__.py
Normal file
150
freezerclient/tests/unit/v1/test_client.py
Normal file
150
freezerclient/tests/unit/v1/test_client.py
Normal file
@ -0,0 +1,150 @@
|
||||
# (c) Copyright 2014,2015,2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 unittest
|
||||
|
||||
from freezerclient.v1 import client
|
||||
|
||||
from mock import Mock, patch
|
||||
|
||||
|
||||
class TestSupportFunctions(unittest.TestCase):
|
||||
|
||||
def test_guess_auth_version_returns_none(self):
|
||||
mock_opts = Mock()
|
||||
mock_opts.os_identity_api_version = ''
|
||||
mock_opts.os_auth_url = ''
|
||||
self.assertRaises(Exception, client.guess_auth_version, mock_opts)
|
||||
|
||||
def test_guess_auth_version_explicit_3(self):
|
||||
mock_opts = Mock()
|
||||
mock_opts.os_identity_api_version = '3'
|
||||
self.assertEquals(client.guess_auth_version(mock_opts), '3')
|
||||
|
||||
def test_guess_auth_version_explicit_2(self):
|
||||
mock_opts = Mock()
|
||||
mock_opts.os_identity_api_version = '2.0'
|
||||
self.assertEquals(client.guess_auth_version(mock_opts), '2.0')
|
||||
|
||||
def test_guess_auth_version_implicit_3(self):
|
||||
mock_opts = Mock()
|
||||
mock_opts.os_auth_url = 'http://whatever/v3'
|
||||
self.assertEquals(client.guess_auth_version(mock_opts), '3')
|
||||
|
||||
def test_guess_auth_version_implicit_2(self):
|
||||
mock_opts = Mock()
|
||||
mock_opts.os_auth_url = 'http://whatever/v2.0'
|
||||
self.assertEquals(client.guess_auth_version(mock_opts), '2.0')
|
||||
|
||||
@patch('freezerclient.v1.client.v3')
|
||||
@patch('freezerclient.v1.client.v2')
|
||||
def test_get_auth_plugin_v3_Password(self, mock_v2, mock_v3):
|
||||
mock_opts = Mock()
|
||||
mock_opts.os_identity_api_version = '3'
|
||||
mock_opts.os_user_name = 'myuser'
|
||||
mock_opts.os_token = ''
|
||||
client.get_auth_plugin(mock_opts)
|
||||
self.assertTrue(mock_v3.Password.called)
|
||||
|
||||
@patch('freezerclient.v1.client.v3')
|
||||
@patch('freezerclient.v1.client.v2')
|
||||
def test_get_auth_plugin_v3_Token(self, mock_v2, mock_v3):
|
||||
mock_opts = Mock()
|
||||
mock_opts.os_identity_api_version = '3'
|
||||
mock_opts.os_username = ''
|
||||
mock_opts.os_token = 'mytoken'
|
||||
client.get_auth_plugin(mock_opts)
|
||||
self.assertTrue(mock_v3.Token.called)
|
||||
|
||||
@patch('freezerclient.v1.client.v3')
|
||||
@patch('freezerclient.v1.client.v2')
|
||||
def test_get_auth_plugin_v2_Password(self, mock_v2, mock_v3):
|
||||
mock_opts = Mock()
|
||||
mock_opts.os_identity_api_version = '2.0'
|
||||
mock_opts.os_user_name = 'myuser'
|
||||
mock_opts.os_token = ''
|
||||
client.get_auth_plugin(mock_opts)
|
||||
self.assertTrue(mock_v2.Password.called)
|
||||
|
||||
@patch('freezerclient.v1.client.v3')
|
||||
@patch('freezerclient.v1.client.v2')
|
||||
def test_get_auth_plugin_v2_Token(self, mock_v2, mock_v3):
|
||||
mock_opts = Mock()
|
||||
mock_opts.os_identity_api_version = '2.0'
|
||||
mock_opts.os_username = ''
|
||||
mock_opts.os_token = 'mytoken'
|
||||
client.get_auth_plugin(mock_opts)
|
||||
self.assertTrue(mock_v2.Token.called)
|
||||
|
||||
@patch('freezerclient.v1.client.v3')
|
||||
@patch('freezerclient.v1.client.v2')
|
||||
def test_get_auth_plugin_raises_when_no_username_token(self, mock_v2, mock_v3):
|
||||
mock_opts = Mock()
|
||||
mock_opts.os_identity_api_version = '2.0'
|
||||
mock_opts.os_username = ''
|
||||
mock_opts.os_token = ''
|
||||
self.assertRaises(Exception, client.get_auth_plugin, mock_opts)
|
||||
|
||||
|
||||
class TestClientMock(unittest.TestCase):
|
||||
|
||||
@patch('freezerclient.v1.client.ksc_session')
|
||||
@patch('freezerclient.v1.client.get_auth_plugin')
|
||||
def test_client_new(self, mock_get_auth_plugin, mock_ksc_session):
|
||||
c = client.Client(opts=Mock(), endpoint='blabla')
|
||||
self.assertIsInstance(c, client.Client)
|
||||
|
||||
@patch('freezerclient.v1.client.ksc_session')
|
||||
@patch('freezerclient.v1.client.get_auth_plugin')
|
||||
def test_client_new_with_kwargs(self, mock_get_auth_plugin, mock_ksc_session):
|
||||
kwargs = {'token': 'alpha',
|
||||
'username': 'bravo',
|
||||
'password': 'charlie',
|
||||
'tenant_name': 'delta',
|
||||
'auth_url': 'echo',
|
||||
'session': 'foxtrot',
|
||||
'endpoint': 'golf',
|
||||
'version': 'hotel',
|
||||
'opts': Mock()}
|
||||
c = client.Client(**kwargs)
|
||||
self.assertIsInstance(c, client.Client)
|
||||
self.assertEqual('alpha', c.opts.os_token)
|
||||
self.assertEqual('bravo', c.opts.os_username)
|
||||
self.assertEqual('charlie', c.opts.os_password)
|
||||
self.assertEqual('delta', c.opts.os_tenant_name)
|
||||
self.assertEqual('echo', c.opts.os_auth_url)
|
||||
self.assertEqual('foxtrot', c._session)
|
||||
self.assertEqual('foxtrot', c.session)
|
||||
self.assertEqual('golf', c.endpoint)
|
||||
|
||||
@patch('freezerclient.v1.client.ksc_session')
|
||||
@patch('freezerclient.v1.client.get_auth_plugin')
|
||||
def test_get_token(self, mock_get_auth_plugin, mock_ksc_session):
|
||||
mock_session = Mock()
|
||||
mock_session.get_token.return_value = 'antaniX2'
|
||||
c = client.Client(session=mock_session, endpoint='justtest', opts=Mock())
|
||||
self.assertIsInstance(c, client.Client)
|
||||
self.assertEquals(c.auth_token, 'antaniX2')
|
||||
|
||||
@patch('freezerclient.v1.client.socket')
|
||||
@patch('freezerclient.v1.client.ksc_session')
|
||||
@patch('freezerclient.v1.client.get_auth_plugin')
|
||||
def test_get_client_id(self, mock_get_auth_plugin, mock_ksc_session, mock_socket):
|
||||
mock_socket.gethostname.return_value = 'parmenide'
|
||||
mock_session = Mock()
|
||||
mock_session.get_project_id.return_value = 'H2O'
|
||||
c = client.Client(session=mock_session, endpoint='justtest', opts=Mock())
|
||||
self.assertIsInstance(c, client.Client)
|
||||
self.assertEquals(c.client_id, 'H2O_parmenide')
|
128
freezerclient/tests/unit/v1/test_client_actions.py
Normal file
128
freezerclient/tests/unit/v1/test_client_actions.py
Normal file
@ -0,0 +1,128 @@
|
||||
# (c) Copyright 2014,2015,2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 unittest
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient.v1.managers import actions
|
||||
|
||||
from mock import Mock, patch
|
||||
|
||||
|
||||
class TestActionManager(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mock_client = Mock()
|
||||
self.mock_response = Mock()
|
||||
self.mock_client.endpoint = 'http://testendpoint:9999'
|
||||
self.mock_client.auth_token = 'testtoken'
|
||||
self.mock_client.client_id = 'test_client_id_78900987'
|
||||
self.action_manager = actions.ActionManager(self.mock_client)
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_create(self, mock_requests):
|
||||
self.assertEqual('http://testendpoint:9999/v1/actions/', self.action_manager.endpoint)
|
||||
self.assertEqual({'X-Auth-Token': 'testtoken'}, self.action_manager.headers)
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_create_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 201
|
||||
self.mock_response.json.return_value = {'action_id': 'qwerqwer'}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
retval = self.action_manager.create({'action': 'metadata'})
|
||||
self.assertEqual('qwerqwer', retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_create_fail_when_api_return_error_code(self, mock_requests):
|
||||
self.mock_response.status_code = 500
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.action_manager.create, {'action': 'metadata'})
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_delete_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 204
|
||||
mock_requests.delete.return_value = self.mock_response
|
||||
retval = self.action_manager.delete('test_action_id')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_delete_fail(self, mock_requests):
|
||||
self.mock_response.status_code = 500
|
||||
mock_requests.delete.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.action_manager.delete, 'test_action_id')
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_get_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 200
|
||||
self.mock_response.json.return_value = {'action_id': 'qwerqwer'}
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
retval = self.action_manager.get('test_action_id')
|
||||
self.assertEqual({'action_id': 'qwerqwer'}, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_get_fails_on_error_different_from_404(self, mock_requests):
|
||||
self.mock_response.status_code = 500
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.action_manager.get, 'test_action_id')
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_get_none(self, mock_requests):
|
||||
self.mock_response.status_code = 404
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
retval = self.action_manager.get('test_action_id')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_list_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 200
|
||||
action_list = [{'action_id_0': 'bomboloid'}, {'action_id_1': 'asdfasdf'}]
|
||||
self.mock_response.json.return_value = {'actions': action_list}
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
retval = self.action_manager.list()
|
||||
self.assertEqual(action_list, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_list_error(self, mock_requests):
|
||||
self.mock_response.status_code = 404
|
||||
action_list = [{'action_id_0': 'bomboloid'}, {'action_id_1': 'asdfasdf'}]
|
||||
self.mock_response.json.return_value = {'clients': action_list}
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.action_manager.list)
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_update_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 200
|
||||
self.mock_response.json.return_value = {
|
||||
"patch": {"status": "bamboozled"},
|
||||
"version": 12,
|
||||
"action_id": "d454beec-1f3c-4d11-aa1a-404116a40502"
|
||||
}
|
||||
mock_requests.patch.return_value = self.mock_response
|
||||
retval = self.action_manager.update('d454beec-1f3c-4d11-aa1a-404116a40502', {'status': 'bamboozled'})
|
||||
self.assertEqual(12, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.actions.requests')
|
||||
def test_update_raise_MetadataUpdateFailure_when_api_return_error_code(self, mock_requests):
|
||||
self.mock_response.json.return_value = {
|
||||
"patch": {"status": "bamboozled"},
|
||||
"version": 12,
|
||||
"action_id": "d454beec-1f3c-4d11-aa1a-404116a40502"
|
||||
}
|
||||
self.mock_response.status_code = 404
|
||||
self.mock_response.text = '{"title": "Not Found","description":"No document found with ID d454beec-1f3c-4d11-aa1a-404116a40502x"}'
|
||||
mock_requests.patch.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.action_manager.update,
|
||||
'd454beec-1f3c-4d11-aa1a-404116a40502', {'status': 'bamboozled'})
|
||||
|
129
freezerclient/tests/unit/v1/test_client_backups.py
Normal file
129
freezerclient/tests/unit/v1/test_client_backups.py
Normal file
@ -0,0 +1,129 @@
|
||||
# (c) Copyright 2014,2015,2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 unittest
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient.v1.managers import backups
|
||||
|
||||
from mock import Mock, patch
|
||||
|
||||
|
||||
class TestBackupManager(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mock_client = Mock()
|
||||
self.mock_client.endpoint = 'http://testendpoint:9999'
|
||||
self.mock_client.auth_token = 'testtoken'
|
||||
self.b = backups.BackupsManager(self.mock_client)
|
||||
|
||||
@patch('freezerclient.v1.managers.backups.requests')
|
||||
def test_create(self, mock_requests):
|
||||
self.assertEqual('http://testendpoint:9999/v1/backups/', self.b.endpoint)
|
||||
self.assertEqual({'X-Auth-Token': 'testtoken'}, self.b.headers)
|
||||
|
||||
@patch('freezerclient.v1.managers.backups.requests')
|
||||
def test_create_ok(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 201
|
||||
mock_response.json.return_value = {'backup_id': 'qwerqwer'}
|
||||
mock_requests.post.return_value = mock_response
|
||||
retval = self.b.create(backup_metadata={'backup': 'metadata'})
|
||||
self.assertEqual('qwerqwer', retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.backups.requests')
|
||||
def test_create_fail_when_api_return_error_code(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 500
|
||||
mock_requests.post.return_value = mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.b.create, {'backup': 'metadata'})
|
||||
|
||||
@patch('freezerclient.v1.managers.backups.requests')
|
||||
def test_delete_ok(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 204
|
||||
mock_requests.delete.return_value = mock_response
|
||||
retval = self.b.delete('test_backup_id')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.backups.requests')
|
||||
def test_delete_fail(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 500
|
||||
mock_requests.delete.return_value = mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.b.delete, 'test_backup_id')
|
||||
|
||||
@patch('freezerclient.v1.managers.backups.requests')
|
||||
def test_get_ok(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {'backup_id': 'qwerqwer'}
|
||||
mock_requests.get.return_value = mock_response
|
||||
retval = self.b.get('test_backup_id')
|
||||
self.assertEqual({'backup_id': 'qwerqwer'}, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.backups.requests')
|
||||
def test_get_none(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 404
|
||||
mock_requests.get.return_value = mock_response
|
||||
retval = self.b.get('test_backup_id')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.backups.requests')
|
||||
def test_get_error(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 403
|
||||
mock_requests.get.return_value = mock_response
|
||||
self.assertRaises(exceptions.ApiClientException,
|
||||
self.b.get, 'test_backup_id')
|
||||
|
||||
@patch('freezerclient.v1.managers.backups.requests')
|
||||
def test_list_ok(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
backup_list = [{'backup_id_0': 'qwerqwer'}, {'backup_id_1': 'asdfasdf'}]
|
||||
mock_response.json.return_value = {'backups': backup_list}
|
||||
mock_requests.get.return_value = mock_response
|
||||
retval = self.b.list()
|
||||
self.assertEqual(backup_list, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.backups.requests')
|
||||
def test_list_parameters(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
backup_list = [{'backup_id_0': 'qwerqwer'}, {'backup_id_1': 'asdfasdf'}]
|
||||
mock_response.json.return_value = {'backups': backup_list}
|
||||
mock_requests.get.return_value = mock_response
|
||||
retval = self.b.list(limit=5,
|
||||
offset=5,
|
||||
search={"time_before": 1428529956})
|
||||
mock_requests.get.assert_called_with(
|
||||
'http://testendpoint:9999/v1/backups/',
|
||||
params={'limit': 5, 'offset': 5},
|
||||
data='{"time_before": 1428529956}',
|
||||
headers={'X-Auth-Token': 'testtoken'},
|
||||
verify=True)
|
||||
self.assertEqual(backup_list, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.backups.requests')
|
||||
def test_list_error(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 404
|
||||
backup_list = [{'backup_id_0': 'qwerqwer'}, {'backup_id_1': 'asdfasdf'}]
|
||||
mock_response.json.return_value = {'backups': backup_list}
|
||||
mock_requests.get.return_value = mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.b.list)
|
||||
|
109
freezerclient/tests/unit/v1/test_client_clients.py
Normal file
109
freezerclient/tests/unit/v1/test_client_clients.py
Normal file
@ -0,0 +1,109 @@
|
||||
# (c) Copyright 2014,2015,2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 unittest
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient.v1.client import clients
|
||||
|
||||
from mock import Mock, patch
|
||||
|
||||
|
||||
class TestClientManager(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mock_client = Mock()
|
||||
self.mock_client.endpoint = 'http://testendpoint:9999'
|
||||
self.mock_client.auth_token = 'testtoken'
|
||||
self.r = clients.ClientManager(self.mock_client)
|
||||
|
||||
@patch('freezerclient.v1.managers.clients.requests')
|
||||
def test_create(self, mock_requests):
|
||||
self.assertEqual('http://testendpoint:9999/v1/clients/', self.r.endpoint)
|
||||
self.assertEqual({'X-Auth-Token': 'testtoken'}, self.r.headers)
|
||||
|
||||
@patch('freezerclient.v1.managers.clients.requests')
|
||||
def test_create_ok(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 201
|
||||
mock_response.json.return_value = {'client_id': 'qwerqwer'}
|
||||
mock_requests.post.return_value = mock_response
|
||||
retval = self.r.create(client_info={'client': 'metadata'})
|
||||
self.assertEqual('qwerqwer', retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.clients.requests')
|
||||
def test_create_fail_when_api_return_error_code(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 500
|
||||
mock_requests.post.return_value = mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.r.create, {'client': 'metadata'})
|
||||
|
||||
@patch('freezerclient.v1.managers.clients.requests')
|
||||
def test_delete_ok(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 204
|
||||
mock_requests.delete.return_value = mock_response
|
||||
retval = self.r.delete('test_client_id')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.clients.requests')
|
||||
def test_delete_fail(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 500
|
||||
mock_requests.delete.return_value = mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.r.delete, 'test_client_id')
|
||||
|
||||
@patch('freezerclient.v1.managers.clients.requests')
|
||||
def test_get_ok(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {'client_id': 'qwerqwer'}
|
||||
mock_requests.get.return_value = mock_response
|
||||
retval = self.r.get('test_client_id')
|
||||
self.assertEqual({'client_id': 'qwerqwer'}, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.clients.requests')
|
||||
def test_get_none(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 404
|
||||
mock_requests.get.return_value = mock_response
|
||||
retval = self.r.get('test_client_id')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.clients.requests')
|
||||
def test_get_raises_ApiClientException_on_error_not_404(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 500
|
||||
mock_requests.get.return_value = mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.r.get, 'test_client_id')
|
||||
|
||||
@patch('freezerclient.v1.managers.clients.requests')
|
||||
def test_list_ok(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 200
|
||||
client_list = [{'client_id_0': 'qwerqwer'}, {'client_id_1': 'asdfasdf'}]
|
||||
mock_response.json.return_value = {'clients': client_list}
|
||||
mock_requests.get.return_value = mock_response
|
||||
retval = self.r.list()
|
||||
self.assertEqual(client_list, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.clients.requests')
|
||||
def test_list_error(self, mock_requests):
|
||||
mock_response = Mock()
|
||||
mock_response.status_code = 404
|
||||
client_list = [{'client_id_0': 'qwerqwer'}, {'client_id_1': 'asdfasdf'}]
|
||||
mock_response.json.return_value = {'clients': client_list}
|
||||
mock_requests.get.return_value = mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.r.list)
|
||||
|
239
freezerclient/tests/unit/v1/test_client_jobs.py
Normal file
239
freezerclient/tests/unit/v1/test_client_jobs.py
Normal file
@ -0,0 +1,239 @@
|
||||
# (c) Copyright 2014,2015,2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 json
|
||||
import unittest
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient.v1.client import jobs
|
||||
|
||||
from mock import Mock, patch
|
||||
|
||||
|
||||
class TestJobManager(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mock_client = Mock()
|
||||
self.mock_response = Mock()
|
||||
self.mock_client.endpoint = 'http://testendpoint:9999'
|
||||
self.mock_client.auth_token = 'testtoken'
|
||||
self.headers = {'X-Auth-Token': 'testtoken'}
|
||||
self.mock_client.client_id = 'test_client_id_78900987'
|
||||
self.job_manager = jobs.JobManager(self.mock_client)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_create(self, mock_requests):
|
||||
self.assertEqual('http://testendpoint:9999/v1/jobs/', self.job_manager.endpoint)
|
||||
self.assertEqual({'X-Auth-Token': 'testtoken'}, self.job_manager.headers)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_create_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 201
|
||||
self.mock_response.json.return_value = {'job_id': 'qwerqwer'}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
retval = self.job_manager.create({'job': 'metadata'})
|
||||
self.assertEqual('qwerqwer', retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.json')
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_create_adds_client_id_if_not_provided(self, mock_requests, mock_json):
|
||||
self.mock_response.status_code = 201
|
||||
self.mock_response.json.return_value = {'job_id': 'qwerqwer'}
|
||||
mock_json.dumps.return_value = {'job': 'mocked'}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
|
||||
retval = self.job_manager.create({'job': 'metadata'})
|
||||
|
||||
mock_json.dumps.assert_called_with({'job': 'metadata',
|
||||
'client_id': 'test_client_id_78900987'})
|
||||
self.assertEqual('qwerqwer', retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.json')
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_create_leaves_provided_client_id(self, mock_requests, mock_json):
|
||||
self.mock_response.status_code = 201
|
||||
self.mock_response.json.return_value = {'job_id': 'qwerqwer'}
|
||||
mock_json.dumps.return_value = {'job': 'mocked'}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
|
||||
retval = self.job_manager.create({'job': 'metadata', 'client_id': 'parmenide'})
|
||||
|
||||
mock_json.dumps.assert_called_with({'job': 'metadata',
|
||||
'client_id': 'parmenide'})
|
||||
self.assertEqual('qwerqwer', retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_create_fail_when_api_return_error_code(self, mock_requests):
|
||||
self.mock_response.status_code = 500
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.job_manager.create, {'job': 'metadata'})
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_delete_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 204
|
||||
mock_requests.delete.return_value = self.mock_response
|
||||
retval = self.job_manager.delete('test_job_id')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_delete_fail(self, mock_requests):
|
||||
self.mock_response.status_code = 500
|
||||
mock_requests.delete.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.job_manager.delete, 'test_job_id')
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_get_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 200
|
||||
self.mock_response.json.return_value = {'job_id': 'qwerqwer'}
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
retval = self.job_manager.get('test_job_id')
|
||||
self.assertEqual({'job_id': 'qwerqwer'}, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_get_fails_on_error_different_from_404(self, mock_requests):
|
||||
self.mock_response.status_code = 500
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.job_manager.get, 'test_job_id')
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_get_none(self, mock_requests):
|
||||
self.mock_response.status_code = 404
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
retval = self.job_manager.get('test_job_id')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_list_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 200
|
||||
job_list = [{'job_id_0': 'bomboloid'}, {'job_id_1': 'asdfasdf'}]
|
||||
self.mock_response.json.return_value = {'jobs': job_list}
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
retval = self.job_manager.list()
|
||||
self.assertEqual(job_list, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_list_error(self, mock_requests):
|
||||
self.mock_response.status_code = 404
|
||||
job_list = [{'job_id_0': 'bomboloid'}, {'job_id_1': 'asdfasdf'}]
|
||||
self.mock_response.json.return_value = {'clients': job_list}
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.job_manager.list)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_update_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 200
|
||||
self.mock_response.json.return_value = {
|
||||
"patch": {"status": "bamboozled"},
|
||||
"version": 12,
|
||||
"job_id": "d454beec-1f3c-4d11-aa1a-404116a40502"
|
||||
}
|
||||
mock_requests.patch.return_value = self.mock_response
|
||||
retval = self.job_manager.update('d454beec-1f3c-4d11-aa1a-404116a40502', {'status': 'bamboozled'})
|
||||
self.assertEqual(12, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_update_raise_MetadataUpdateFailure_when_api_return_error_code(self, mock_requests):
|
||||
self.mock_response.json.return_value = {
|
||||
"patch": {"status": "bamboozled"},
|
||||
"version": 12,
|
||||
"job_id": "d454beec-1f3c-4d11-aa1a-404116a40502"
|
||||
}
|
||||
self.mock_response.status_code = 404
|
||||
self.mock_response.text = '{"title": "Not Found","description":"No document found with ID d454beec-1f3c-4d11-aa1a-404116a40502x"}'
|
||||
mock_requests.patch.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.job_manager.update,
|
||||
'd454beec-1f3c-4d11-aa1a-404116a40502', {'status': 'bamboozled'})
|
||||
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_start_job_posts_proper_data(self, mock_requests):
|
||||
job_id = 'jobdfsfnqwerty1234'
|
||||
self.mock_response.status_code = 202
|
||||
self.mock_response.json.return_value = {'result': 'success'}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
# /v1/jobs/{job_id}/event
|
||||
|
||||
endpoint = '{0}/v1/jobs/{1}/event'.format(self.mock_client.endpoint, job_id)
|
||||
data = {"start": None}
|
||||
retval = self.job_manager.start_job(job_id)
|
||||
self.assertEqual({'result': 'success'}, retval)
|
||||
|
||||
args = mock_requests.post.call_args[0]
|
||||
kwargs = mock_requests.post.call_args[1]
|
||||
self.assertEquals(endpoint, args[0])
|
||||
self.assertEquals(data, json.loads(kwargs['data']))
|
||||
self.assertEquals(self.headers, kwargs['headers'])
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_start_job_raise_ApiClientException_when_api_return_error_code(self, mock_requests):
|
||||
job_id = 'jobdfsfnqwerty1234'
|
||||
self.mock_response.status_code = 500
|
||||
self.mock_response.json.return_value = {'result': 'success'}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.job_manager.start_job, job_id)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_stop_job_posts_proper_data(self, mock_requests):
|
||||
job_id = 'jobdfsfnqwerty1234'
|
||||
self.mock_response.status_code = 202
|
||||
self.mock_response.json.return_value = {'result': 'success'}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
# /v1/jobs/{job_id}/event
|
||||
|
||||
endpoint = '{0}/v1/jobs/{1}/event'.format(self.mock_client.endpoint, job_id)
|
||||
data = {"stop": None}
|
||||
retval = self.job_manager.stop_job(job_id)
|
||||
self.assertEqual({'result': 'success'}, retval)
|
||||
|
||||
args = mock_requests.post.call_args[0]
|
||||
kwargs = mock_requests.post.call_args[1]
|
||||
self.assertEquals(endpoint, args[0])
|
||||
self.assertEquals(data, json.loads(kwargs['data']))
|
||||
self.assertEquals(self.headers, kwargs['headers'])
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_stop_job_raise_ApiClientException_when_api_return_error_code(self, mock_requests):
|
||||
job_id = 'jobdfsfnqwerty1234'
|
||||
self.mock_response.status_code = 500
|
||||
self.mock_response.json.return_value = {'result': 'success'}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.job_manager.start_job, job_id)
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_abort_job_posts_proper_data(self, mock_requests):
|
||||
job_id = 'jobdfsfnqwerty1234'
|
||||
self.mock_response.status_code = 202
|
||||
self.mock_response.json.return_value = {'result': 'success'}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
# /v1/jobs/{job_id}/event
|
||||
|
||||
endpoint = '{0}/v1/jobs/{1}/event'.format(self.mock_client.endpoint, job_id)
|
||||
data = {"abort": None}
|
||||
retval = self.job_manager.abort_job(job_id)
|
||||
self.assertEqual({'result': 'success'}, retval)
|
||||
|
||||
args = mock_requests.post.call_args[0]
|
||||
kwargs = mock_requests.post.call_args[1]
|
||||
self.assertEquals(endpoint, args[0])
|
||||
self.assertEquals(data, json.loads(kwargs['data']))
|
||||
self.assertEquals(self.headers, kwargs['headers'])
|
||||
|
||||
@patch('freezerclient.v1.managers.jobs.requests')
|
||||
def test_abort_job_raise_ApiClientException_when_api_return_error_code(self, mock_requests):
|
||||
job_id = 'jobdfsfnqwerty1234'
|
||||
self.mock_response.status_code = 500
|
||||
self.mock_response.json.return_value = {'result': 'success'}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.job_manager.abort_job, job_id)
|
||||
|
222
freezerclient/tests/unit/v1/test_client_sessions.py
Normal file
222
freezerclient/tests/unit/v1/test_client_sessions.py
Normal file
@ -0,0 +1,222 @@
|
||||
# (c) Copyright 2014,2015,2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 json
|
||||
import unittest
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient.v1.client import sessions
|
||||
|
||||
from mock import Mock, patch
|
||||
|
||||
|
||||
class TestSessionManager(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mock_client = Mock()
|
||||
self.mock_response = Mock()
|
||||
self.mock_client.endpoint = 'http://testendpoint:9999'
|
||||
self.mock_client.auth_token = 'testtoken'
|
||||
self.mock_client.client_id = 'test_client_id_78900987'
|
||||
self.session_manager = sessions.SessionManager(self.mock_client)
|
||||
self.endpoint = 'http://testendpoint:9999/v1/sessions/'
|
||||
self.headers = {'X-Auth-Token': 'testtoken'}
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_create(self, mock_requests):
|
||||
self.assertEqual(self.endpoint, self.session_manager.endpoint)
|
||||
self.assertEqual(self.headers, self.session_manager.headers)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_create_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 201
|
||||
self.mock_response.json.return_value = {'session_id': 'qwerqwer'}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
retval = self.session_manager.create({'session': 'metadata'})
|
||||
self.assertEqual('qwerqwer', retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_create_raise_ApiClientException_when_api_return_error_code(self, mock_requests):
|
||||
self.mock_response.status_code = 500
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.session_manager.create, {'session': 'metadata'})
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_delete_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 204
|
||||
mock_requests.delete.return_value = self.mock_response
|
||||
retval = self.session_manager.delete('test_session_id')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_delete_raise_ApiClientException_when_api_return_error_code(self, mock_requests):
|
||||
self.mock_response.status_code = 500
|
||||
mock_requests.delete.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.session_manager.delete, 'test_session_id')
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_get_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 200
|
||||
self.mock_response.json.return_value = {'session_id': 'qwerqwer'}
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
retval = self.session_manager.get('test_session_id')
|
||||
self.assertEqual({'session_id': 'qwerqwer'}, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_get_raise_ApiClientException_when_api_return_error_different_from_404(self, mock_requests):
|
||||
self.mock_response.status_code = 500
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.session_manager.get, 'test_session_id')
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_get_none(self, mock_requests):
|
||||
self.mock_response.status_code = 404
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
retval = self.session_manager.get('test_session_id')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_list_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 200
|
||||
session_list = [{'session_id_0': 'bomboloid'}, {'session_id_1': 'asdfasdf'}]
|
||||
self.mock_response.json.return_value = {'sessions': session_list}
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
retval = self.session_manager.list()
|
||||
self.assertEqual(session_list, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_list_raise_ApiClientException_when_api_return_error_code(self, mock_requests):
|
||||
self.mock_response.status_code = 404
|
||||
session_list = [{'session_id_0': 'bomboloid'}, {'session_id_1': 'asdfasdf'}]
|
||||
self.mock_response.json.return_value = {'clients': session_list}
|
||||
mock_requests.get.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.session_manager.list)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_update_ok(self, mock_requests):
|
||||
self.mock_response.status_code = 200
|
||||
self.mock_response.json.return_value = {
|
||||
"patch": {"status": "bamboozled"},
|
||||
"version": 12,
|
||||
"session_id": "d454beec-1f3c-4d11-aa1a-404116a40502"
|
||||
}
|
||||
mock_requests.patch.return_value = self.mock_response
|
||||
retval = self.session_manager.update('d454beec-1f3c-4d11-aa1a-404116a40502', {'status': 'bamboozled'})
|
||||
self.assertEqual(12, retval)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_update_raise_ApiClientException_when_api_return_error_code(self, mock_requests):
|
||||
self.mock_response.json.return_value = {
|
||||
"patch": {"status": "bamboozled"},
|
||||
"version": 12,
|
||||
"session_id": "d454beec-1f3c-4d11-aa1a-404116a40502"
|
||||
}
|
||||
self.mock_response.status_code = 404
|
||||
self.mock_response.text = '{"title": "Not Found","description":"No document found with ID d454beec-1f3c-4d11-aa1a-404116a40502x"}'
|
||||
mock_requests.patch.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.session_manager.update,
|
||||
'd454beec-1f3c-4d11-aa1a-404116a40502', {'status': 'bamboozled'})
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_add_job_uses_proper_endpoint(self, mock_requests):
|
||||
session_id, job_id = 'sessionqwerty1234', 'jobqwerty1234'
|
||||
self.mock_response.status_code = 204
|
||||
mock_requests.put.return_value = self.mock_response
|
||||
endpoint = '{0}{1}/jobs/{2}'.format(self.endpoint, session_id, job_id)
|
||||
|
||||
retval = self.session_manager.add_job(session_id, job_id)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
mock_requests.put.assert_called_with(endpoint, headers=self.headers, verify=True)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_add_job_raise_ApiClientException_when_api_return_error_code(self, mock_requests):
|
||||
session_id, job_id = 'sessionqwerty1234', 'jobqwerty1234'
|
||||
self.mock_response.status_code = 500
|
||||
mock_requests.put.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.session_manager.add_job, session_id, job_id)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_remove_job_uses_proper_endpoint(self, mock_requests):
|
||||
session_id, job_id = 'sessionqwerty1234', 'jobqwerty1234'
|
||||
self.mock_response.status_code = 204
|
||||
mock_requests.delete.return_value = self.mock_response
|
||||
endpoint = '{0}{1}/jobs/{2}'.format(self.endpoint, session_id, job_id)
|
||||
|
||||
retval = self.session_manager.remove_job(session_id, job_id)
|
||||
|
||||
self.assertIsNone(retval)
|
||||
mock_requests.delete.assert_called_with(endpoint, headers=self.headers, verify=True)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_remove_job_raise_ApiClientException_when_api_return_error_code(self, mock_requests):
|
||||
session_id, job_id = 'sessionqwerty1234', 'jobqwerty1234'
|
||||
self.mock_response.status_code = 500
|
||||
mock_requests.delete.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.session_manager.remove_job, session_id, job_id)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_start_session_posts_proper_data(self, mock_requests):
|
||||
session_id, job_id, tag = 'sessionqwerty1234', 'jobqwerty1234', 23
|
||||
self.mock_response.status_code = 202
|
||||
self.mock_response.json.return_value = {'result': 'success', 'session_tag': 24}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
# /v1/sessions/{sessions_id}/action
|
||||
endpoint = '{0}{1}/action'.format(self.endpoint, session_id)
|
||||
data = {"start": {"current_tag": 23, "job_id": "jobqwerty1234"}}
|
||||
retval = self.session_manager.start_session(session_id, job_id, tag)
|
||||
self.assertEqual({'result': 'success', 'session_tag': 24}, retval)
|
||||
|
||||
args = mock_requests.post.call_args[0]
|
||||
kwargs = mock_requests.post.call_args[1]
|
||||
self.assertEquals(endpoint, args[0])
|
||||
self.assertEquals(data, json.loads(kwargs['data']))
|
||||
self.assertEquals(self.headers, kwargs['headers'])
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_start_session_raise_ApiClientException_when_api_return_error_code(self, mock_requests):
|
||||
session_id, job_id, tag = 'sessionqwerty1234', 'jobqwerty1234', 23
|
||||
self.mock_response.status_code = 500
|
||||
self.mock_response.json.return_value = {'result': 'success', 'session_tag': 24}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.session_manager.start_session,
|
||||
session_id, job_id, tag)
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_end_session_posts_proper_data(self, mock_requests):
|
||||
session_id, job_id, tag = 'sessionqwerty1234', 'jobqwerty1234', 23
|
||||
self.mock_response.status_code = 202
|
||||
self.mock_response.json.return_value = {'result': 'success', 'session_tag': 24}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
# /v1/sessions/{sessions_id}/action
|
||||
endpoint = '{0}{1}/action'.format(self.endpoint, session_id)
|
||||
data = {"end": {"current_tag": 23, "job_id": "jobqwerty1234", "result": "fail"}}
|
||||
retval = self.session_manager.end_session(session_id, job_id, tag, 'fail')
|
||||
self.assertEqual({'result': 'success', 'session_tag': 24}, retval)
|
||||
|
||||
args = mock_requests.post.call_args[0]
|
||||
kwargs = mock_requests.post.call_args[1]
|
||||
self.assertEquals(endpoint, args[0])
|
||||
self.assertEquals(data, json.loads(kwargs['data']))
|
||||
self.assertEquals(self.headers, kwargs['headers'])
|
||||
|
||||
@patch('freezerclient.v1.managers.sessions.requests')
|
||||
def test_end_session_raise_ApiClientException_when_api_return_error_code(self, mock_requests):
|
||||
session_id, job_id, tag = 'sessionqwerty1234', 'jobqwerty1234', 23
|
||||
self.mock_response.status_code = 500
|
||||
self.mock_response.json.return_value = {'result': 'success', 'session_tag': 24}
|
||||
mock_requests.post.return_value = self.mock_response
|
||||
self.assertRaises(exceptions.ApiClientException, self.session_manager.end_session,
|
||||
session_id, job_id, tag, 'fail')
|
||||
|
119
freezerclient/utils.py
Normal file
119
freezerclient/utils.py
Normal file
@ -0,0 +1,119 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 json
|
||||
import logging
|
||||
|
||||
|
||||
logging = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Namespace(dict):
|
||||
"""A dict subclass that exposes its items as attributes.
|
||||
|
||||
Warning: Namespace instances do not have direct access to the
|
||||
dict methods.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, obj={}):
|
||||
super(Namespace, self).__init__(obj)
|
||||
|
||||
def __dir__(self):
|
||||
return tuple(self)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (type(self).__name__,
|
||||
super(Namespace, self).__repr__())
|
||||
|
||||
def __getattribute__(self, name):
|
||||
try:
|
||||
return self[name]
|
||||
except KeyError:
|
||||
# Return None in case the value doesn't exists
|
||||
# this is not an issue for the apiclient because it skips
|
||||
# None values
|
||||
return None
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
self[name] = value
|
||||
|
||||
def __delattr__(self, name):
|
||||
del self[name]
|
||||
|
||||
@classmethod
|
||||
def from_object(cls, obj, names=None):
|
||||
if names is None:
|
||||
names = dir(obj)
|
||||
ns = {name:getattr(obj, name) for name in names}
|
||||
return cls(ns)
|
||||
|
||||
@classmethod
|
||||
def from_mapping(cls, ns, names=None):
|
||||
if names:
|
||||
ns = {name: ns[name] for name in names}
|
||||
return cls(ns)
|
||||
|
||||
@classmethod
|
||||
def from_sequence(cls, seq, names=None):
|
||||
if names:
|
||||
seq = {name: val for name, val in seq if name in names}
|
||||
return cls(seq)
|
||||
|
||||
@staticmethod
|
||||
def hasattr(ns, name):
|
||||
try:
|
||||
object.__getattribute__(ns, name)
|
||||
except AttributeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def getattr(ns, name):
|
||||
return object.__getattribute__(ns, name)
|
||||
|
||||
@staticmethod
|
||||
def setattr(ns, name, value):
|
||||
return object.__setattr__(ns, name, value)
|
||||
|
||||
@staticmethod
|
||||
def delattr(ns, name):
|
||||
return object.__delattr__(ns, name)
|
||||
|
||||
|
||||
class CachedProperty(object):
|
||||
|
||||
def __init__(self, func):
|
||||
self.__doc__ = getattr(func, '__doc__')
|
||||
self.func = func
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self
|
||||
value = obj.__dict__[self.func.__name__] = self.func(obj)
|
||||
return value
|
||||
|
||||
|
||||
def doc_from_json_file(path_to_file):
|
||||
"""Build a json from a file in the file system
|
||||
:param path_to_file: path to file
|
||||
:return: in memory file in json format
|
||||
"""
|
||||
with open(path_to_file, 'rb') as fd:
|
||||
try:
|
||||
return json.load(fd)
|
||||
except Exception as err:
|
||||
logging.error(err)
|
||||
raise Exception('Unable to load conf file. {0}'.format(err))
|
0
freezerclient/v1/__init__.py
Normal file
0
freezerclient/v1/__init__.py
Normal file
153
freezerclient/v1/actions.py
Normal file
153
freezerclient/v1/actions.py
Normal file
@ -0,0 +1,153 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 cliff.command import Command
|
||||
from cliff.lister import Lister
|
||||
from cliff.show import ShowOne
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient.utils import doc_from_json_file
|
||||
|
||||
|
||||
logging = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ActionShow(ShowOne):
|
||||
"""Show a single action """
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ActionShow, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='action_id',
|
||||
help='ID of the action')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
action = self.app.client.actions.get(parsed_args.action_id)
|
||||
|
||||
if not action:
|
||||
raise exceptions.ApiClientException('Action not found')
|
||||
|
||||
column = (
|
||||
'Action ID',
|
||||
'Name',
|
||||
'Action',
|
||||
'Mode',
|
||||
'Path to Backup or Restore',
|
||||
'Storage',
|
||||
'Snapshot'
|
||||
)
|
||||
|
||||
data = (
|
||||
action.get('action_id'),
|
||||
action.get('freezer_action', {}).get('backup_name', ''),
|
||||
action.get('freezer_action', {}).get('action', 'backup'),
|
||||
action.get('freezer_action', {}).get('mode', 'fs'),
|
||||
action.get('freezer_action', {}).get('path_to_backup', ''),
|
||||
action.get('freezer_action', {}).get('storage', 'swift'),
|
||||
action.get('freezer_action', {}).get('snapshot', 'False'),
|
||||
)
|
||||
|
||||
return column, data
|
||||
|
||||
|
||||
class ActionList(Lister):
|
||||
"""List all actions for your user"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ActionList, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
dest='limit',
|
||||
default=100,
|
||||
help='Specify a limit for search query',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--offset',
|
||||
dest='offset',
|
||||
default=0,
|
||||
help='',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--search',
|
||||
dest='search',
|
||||
default='',
|
||||
help='Define a filter for the query',
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
actions = self.app.client.actions.list(
|
||||
limit=parsed_args.limit,
|
||||
offset=parsed_args.offset,
|
||||
search=parsed_args.search
|
||||
)
|
||||
|
||||
return (('Action ID', 'Name', 'Action',
|
||||
'Path to Backup or Restore', 'Mode', 'Storage', 'snapshot'),
|
||||
((action.get('action_id'),
|
||||
action.get('freezer_action', {}).get('backup_name', ''),
|
||||
action.get('freezer_action', {}).get('action', 'backup'),
|
||||
action.get('freezer_action', {}).get('path_to_backup', ''),
|
||||
action.get('freezer_action', {}).get('mode', 'fs'),
|
||||
action.get('freezer_action', {}).get('storage', 'swift'),
|
||||
action.get('freezer_action', {}).get('snapshot', 'False')
|
||||
) for action in actions))
|
||||
|
||||
|
||||
class ActionDelete(Command):
|
||||
"""Delete an action from the api"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ActionDelete, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='action_id',
|
||||
help='ID of the action')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.actions.delete(parsed_args.action_id)
|
||||
logging.info('Action {0} deleted'.format(parsed_args.action_id))
|
||||
|
||||
|
||||
class ActionCreate(Command):
|
||||
"""Create an action from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ActionCreate, self).get_parser(prog_name)
|
||||
parser.add_argument('--file',
|
||||
dest='file',
|
||||
help='Path to json file with the action')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
action = doc_from_json_file(parsed_args.file)
|
||||
action_id = self.app.client.actions.create(action)
|
||||
logging.info('Action {0} created'.format(action_id))
|
||||
|
||||
|
||||
class ActionUpdate(Command):
|
||||
"""Update an action from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ActionUpdate, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='action_id',
|
||||
help='ID of the session')
|
||||
|
||||
parser.add_argument(dest='file',
|
||||
help='Path to json file with the action')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
action = doc_from_json_file(parsed_args.file)
|
||||
self.app.client.actions.update(parsed_args.action_id, action)
|
||||
logging.info('Action {0} updated'.format(parsed_args.action_id))
|
98
freezerclient/v1/backups.py
Normal file
98
freezerclient/v1/backups.py
Normal file
@ -0,0 +1,98 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 pprint import pformat
|
||||
|
||||
from cliff.lister import Lister
|
||||
from cliff.show import ShowOne
|
||||
|
||||
from freezerclient import exceptions
|
||||
|
||||
|
||||
logging = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BackupShow(ShowOne):
|
||||
"""Show the metadata of a single backup"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(BackupShow, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='backup_uuid',
|
||||
help='UUID of the backup')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
# due to the fact that a backup_id is composed of several strings
|
||||
# some of them may include a slash "/" so it will never find the correct
|
||||
# backup, so the workaround for this version is to use the backup_uuid as
|
||||
# a filter for the search. this won't work when the user wants to delete a
|
||||
# backup, but that functionality is yet to be provided by the api.
|
||||
search = {"match": [{"backup_uuid": parsed_args.backup_uuid}, ], }
|
||||
backup = self.app.client.backups.list(search=search)
|
||||
|
||||
if not backup:
|
||||
raise exceptions.ApiClientException('Backup not found')
|
||||
|
||||
backup = backup[0]
|
||||
|
||||
column = (
|
||||
'Backup ID',
|
||||
'Backup UUID',
|
||||
'Metadata'
|
||||
)
|
||||
data = (
|
||||
backup.get('backup_id'),
|
||||
backup.get('backup_uuid'),
|
||||
pformat(backup.get('backup_metadata'))
|
||||
)
|
||||
return column, data
|
||||
|
||||
|
||||
class BackupList(Lister):
|
||||
"""List all backups for your user"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(BackupList, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
dest='limit',
|
||||
default=100,
|
||||
help='Specify a limit for search query',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--offset',
|
||||
dest='offset',
|
||||
default=0,
|
||||
help='',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--search',
|
||||
dest='search',
|
||||
default='',
|
||||
help='Define a filter for the query',
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
backups = self.app.client.backups.list(limit=parsed_args.limit,
|
||||
offset=parsed_args.offset,
|
||||
search=parsed_args.search)
|
||||
return (('Backup ID', 'Backup UUID'),
|
||||
((backup.get('backup_id'),
|
||||
backup.get('backup_uuid'),
|
||||
) for backup in backups))
|
||||
|
147
freezerclient/v1/client.py
Normal file
147
freezerclient/v1/client.py
Normal file
@ -0,0 +1,147 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 socket
|
||||
|
||||
from freezerclient.utils import CachedProperty
|
||||
from freezerclient.utils import Namespace
|
||||
from freezerclient.v1.managers import actions
|
||||
from freezerclient.v1.managers import backups
|
||||
from freezerclient.v1.managers import clients
|
||||
from freezerclient.v1.managers import jobs
|
||||
from freezerclient.v1.managers import sessions
|
||||
|
||||
from keystoneclient.auth.identity import v2
|
||||
from keystoneclient.auth.identity import v3
|
||||
from keystoneclient import session as ksc_session
|
||||
|
||||
|
||||
def guess_auth_version(opts):
|
||||
""" Guess keystone version to connect to"""
|
||||
if opts.os_identity_api_version == '3':
|
||||
return '3'
|
||||
elif opts.os_identity_api_version == '2.0':
|
||||
return '2.0'
|
||||
elif opts.os_auth_url.endswith('v3'):
|
||||
return '3'
|
||||
elif opts.os_auth_url.endswith('v2.0'):
|
||||
return '2.0'
|
||||
raise Exception('Please provide valid keystone auth url with valid'
|
||||
' keystone api version to use')
|
||||
|
||||
|
||||
def get_auth_plugin(opts):
|
||||
"""Create the right keystone connection depending on the version
|
||||
for the api, if username/password and token are provided, username and
|
||||
password takes precedence.
|
||||
"""
|
||||
auth_version = guess_auth_version(opts)
|
||||
if opts.os_username:
|
||||
if auth_version == '3':
|
||||
return v3.Password(auth_url=opts.os_auth_url,
|
||||
username=opts.os_username,
|
||||
password=opts.os_password,
|
||||
project_name=opts.os_project_name,
|
||||
user_domain_name=opts.os_user_domain_name,
|
||||
project_domain_name=opts.os_project_domain_name)
|
||||
elif auth_version == '2.0':
|
||||
return v2.Password(auth_url=opts.os_auth_url,
|
||||
username=opts.os_username,
|
||||
password=opts.os_password,
|
||||
tenant_name=opts.os_tenant_name)
|
||||
elif opts.os_token:
|
||||
if auth_version == '3':
|
||||
return v3.Token(auth_url=opts.os_auth_url,
|
||||
token=opts.os_token,
|
||||
project_name=opts.os_project_name,
|
||||
project_domain_name=opts.os_project_domain_name)
|
||||
elif auth_version == '2.0':
|
||||
return v2.Token(auth_url=opts.os_auth_url,
|
||||
token=opts.os_token,
|
||||
tenant_name=opts.os_tenant_name)
|
||||
raise Exception('Unable to determine correct auth method, please provide'
|
||||
' either username or token')
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""Client for the OpenStack Disaster Recovery v1 API.
|
||||
"""
|
||||
|
||||
def __init__(self, version='3', token=None, username=None, password=None,
|
||||
tenant_name=None, auth_url=None, session=None, endpoint=None,
|
||||
opts=None, project_name=None, user_domain_name=None,
|
||||
project_domain_name=None, verify=True, cert=None):
|
||||
"""
|
||||
Initialize a new client for the Disaster Recovery v1 API.
|
||||
:param version: keystone version to use
|
||||
:param token: keystone token
|
||||
:param username: openstack username
|
||||
:param password: openstack password
|
||||
:param tenant_name: tenant
|
||||
:param auth_url: keystone-api endpoint
|
||||
:param session: keystone.Session
|
||||
:param endpoint: freezer-api endpoint
|
||||
:param opts: a namespace to store all keystone data
|
||||
:param project_name: only for version 3
|
||||
:param user_domain_name: only for version 3
|
||||
:param project_domain_name: only for version 3
|
||||
:param verify: The verification arguments to pass to requests.
|
||||
These are of the same form as requests expects,
|
||||
so True or False to verify (or not) against system
|
||||
certificates or a path to a bundle or CA certs to
|
||||
check against or None for requests to
|
||||
attempt to locate and use certificates. (optional,
|
||||
defaults to True)
|
||||
:param cert: Path to cert
|
||||
:return: freezerclient.Client
|
||||
"""
|
||||
self.opts = opts or Namespace({})
|
||||
self.opts.os_token = token or None
|
||||
self.opts.os_username = username or None
|
||||
self.opts.os_password = password or None
|
||||
self.opts.os_tenant_name = tenant_name or None
|
||||
self.opts.os_auth_url = auth_url or None
|
||||
self.opts.os_backup_url = endpoint or None
|
||||
self.opts.os_project_name = project_name or None
|
||||
self.opts.os_user_domain_name = user_domain_name or None
|
||||
self.opts.os_project_domain_name = project_domain_name or None
|
||||
self.opts.auth_version = version
|
||||
self.verify = verify
|
||||
self.cert = cert
|
||||
self._session = session
|
||||
self.endpoint = endpoint
|
||||
|
||||
self.jobs = jobs.JobManager(self, verify=verify)
|
||||
self.clients = clients.ClientManager(self, verify=verify)
|
||||
self.backups = backups.BackupsManager(self, verify=verify)
|
||||
self.sessions = sessions.SessionManager(self, verify=verify)
|
||||
self.actions = actions.ActionManager(self, verify=verify)
|
||||
|
||||
@CachedProperty
|
||||
def session(self):
|
||||
if self._session:
|
||||
return self._session
|
||||
auth_plugin = get_auth_plugin(self.opts)
|
||||
return ksc_session.Session(auth=auth_plugin,
|
||||
verify=self.verify,
|
||||
cert=self.cert)
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
return self.session.get_token()
|
||||
|
||||
@CachedProperty
|
||||
def client_id(self):
|
||||
return '{0}_{1}'.format(self.session.get_project_id(),
|
||||
socket.gethostname())
|
127
freezerclient/v1/clients.py
Normal file
127
freezerclient/v1/clients.py
Normal file
@ -0,0 +1,127 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 cliff.command import Command
|
||||
from cliff.lister import Lister
|
||||
from cliff.show import ShowOne
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient.utils import doc_from_json_file
|
||||
|
||||
|
||||
logging = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClientShow(ShowOne):
|
||||
"""Show a single client"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ClientShow, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='client_id',
|
||||
help='ID of the client')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client.clients.get(parsed_args.client_id)
|
||||
|
||||
if not client:
|
||||
raise exceptions.ApiClientException('Client not found')
|
||||
|
||||
column = (
|
||||
'Client ID',
|
||||
'Client UUID',
|
||||
'hostname',
|
||||
'description'
|
||||
)
|
||||
data = (
|
||||
client.get('client', {}).get('client_id'),
|
||||
client.get('uuid'),
|
||||
client.get('client', {}).get('hostname'),
|
||||
client.get('client', {}).get('description', '')
|
||||
)
|
||||
|
||||
return column, data
|
||||
|
||||
|
||||
class ClientList(Lister):
|
||||
"""List of clients registered in the api"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ClientList, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
dest='limit',
|
||||
default=100,
|
||||
help='Specify a limit for search query',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--offset',
|
||||
dest='offset',
|
||||
default=0,
|
||||
help='',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--search',
|
||||
dest='search',
|
||||
default='',
|
||||
help='Define a filter for the query',
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
clients = self.app.client.clients.list(limit=parsed_args.limit,
|
||||
offset=parsed_args.offset,
|
||||
search=parsed_args.search)
|
||||
|
||||
return (('Client ID', 'uuid', 'hostname', 'description'),
|
||||
((client.get('client', {}).get('client_id'),
|
||||
client.get('uuid'),
|
||||
client.get('client', {}).get('hostname'),
|
||||
client.get('client', {}).get('description', '')
|
||||
) for client in clients))
|
||||
|
||||
|
||||
class ClientDelete(Command):
|
||||
"""Delete a client from the api"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ClientDelete, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='client_id',
|
||||
help='ID of the client')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.clients.delete(parsed_args.client_id)
|
||||
logging.info('Client {0} deleted'.format(parsed_args.client_id))
|
||||
|
||||
|
||||
class ClientRegister(Command):
|
||||
"""Register a new client"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ClientRegister, self).get_parser(prog_name)
|
||||
parser.add_argument('--file',
|
||||
dest='file',
|
||||
help='Path to json file with the client')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = doc_from_json_file(parsed_args.file)
|
||||
try:
|
||||
client_id = self.app.client.clients.create(client)
|
||||
except Exception as err:
|
||||
raise exceptions.ApiClientException(err.message)
|
||||
else:
|
||||
logging.info("Client {0} registered".format(client_id))
|
221
freezerclient/v1/jobs.py
Normal file
221
freezerclient/v1/jobs.py
Normal file
@ -0,0 +1,221 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 pprint import pformat
|
||||
from pprint import pprint
|
||||
|
||||
from cliff.command import Command
|
||||
from cliff.lister import Lister
|
||||
from cliff.show import ShowOne
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient.utils import doc_from_json_file
|
||||
|
||||
|
||||
logging = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JobShow(ShowOne):
|
||||
"""Show a single job"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobShow, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
job = self.app.client.jobs.get(parsed_args.job_id)
|
||||
|
||||
if not job:
|
||||
raise exceptions.ApiClientException('Job not found')
|
||||
|
||||
column = (
|
||||
'Job ID',
|
||||
'Client ID',
|
||||
'User ID',
|
||||
'Session ID',
|
||||
'Description',
|
||||
'Actions',
|
||||
'Start Date',
|
||||
'End Date',
|
||||
'Interval',
|
||||
)
|
||||
data = (
|
||||
job.get('job_id'),
|
||||
job.get('client_id'),
|
||||
job.get('user_id'),
|
||||
job.get('session_id', ''),
|
||||
job.get('description'),
|
||||
pformat(job.get('job_actions')),
|
||||
job.get('job_schedule', {}).get('schedule_start_date', ''),
|
||||
job.get('job_schedule', {}).get('schedule_interval', ''),
|
||||
job.get('job_schedule', {}).get('schedule_end_date', ''),
|
||||
)
|
||||
return column, data
|
||||
|
||||
|
||||
class JobList(Lister):
|
||||
"""List all the jobs for your user"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobList, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
dest='limit',
|
||||
default=100,
|
||||
help='Specify a limit for search query',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--offset',
|
||||
dest='offset',
|
||||
default=0,
|
||||
help='',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--search',
|
||||
dest='search',
|
||||
default='',
|
||||
help='Define a filter for the query',
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
jobs = self.app.client.jobs.list_all(
|
||||
limit=parsed_args.limit,
|
||||
offset=parsed_args.offset,
|
||||
search=parsed_args.search
|
||||
)
|
||||
|
||||
return (('Job ID', 'Description', '# Actions', 'Result', 'Event', 'Session ID'),
|
||||
((job.get('job_id'),
|
||||
job.get('description'),
|
||||
len(job.get('job_actions', [])),
|
||||
job.get('job_schedule', {}).get('result', ''),
|
||||
job.get('job_schedule', {}).get('event', ''),
|
||||
job.get('session_id', '')
|
||||
) for job in jobs))
|
||||
|
||||
|
||||
class JobGet(Command):
|
||||
"""Download a job as a json file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobGet, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
|
||||
parser.add_argument('--no-format',
|
||||
dest='no_format',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Return a job in json without pretty print')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
job = self.app.client.jobs.get(parsed_args.job_id)
|
||||
|
||||
if not job:
|
||||
raise exceptions.ApiClientException('Job not found')
|
||||
|
||||
if parsed_args.no_format:
|
||||
print(job)
|
||||
else:
|
||||
pprint(job)
|
||||
|
||||
|
||||
class JobDelete(Command):
|
||||
"""Delete a job from the api"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobDelete, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.jobs.delete(parsed_args.job_id)
|
||||
logging.info('Job {0} deleted'.format(parsed_args.job_id))
|
||||
|
||||
|
||||
class JobCreate(Command):
|
||||
"""Create a new job from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobCreate, self).get_parser(prog_name)
|
||||
parser.add_argument('--file',
|
||||
dest='file',
|
||||
help='Path to json file with the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
job = doc_from_json_file(parsed_args.file)
|
||||
job_id = self.app.client.jobs.create(job)
|
||||
logging.info('Job {0} created'.format(job_id))
|
||||
|
||||
|
||||
class JobStart(Command):
|
||||
"""Send a start signal for a job"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobStart, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.jobs.start_job(parsed_args.job_id)
|
||||
logging.info('Job {0} has started'.format(parsed_args.job_id))
|
||||
|
||||
|
||||
class JobStop(Command):
|
||||
"""Send a stop signal for a job"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobStop, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.jobs.stop_job(parsed_args.job_id)
|
||||
logging.info('Job {0} has stopped'.format(parsed_args.job_id))
|
||||
|
||||
|
||||
class JobAbort(Command):
|
||||
"""Abort a running job"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobAbort, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.jobs.abort_job(parsed_args.job_id)
|
||||
logging.info('Job {0} has been aborted'.format(parsed_args.job_id))
|
||||
|
||||
|
||||
class JobUpdate(Command):
|
||||
"""Update a job from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobUpdate, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
|
||||
parser.add_argument(dest='file',
|
||||
help='Path to json file with the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
job = doc_from_json_file(parsed_args.file)
|
||||
self.app.client.jobs.update(parsed_args.job_id, job)
|
||||
logging.info('Job {0} updated'.format(parsed_args.job_id))
|
0
freezerclient/v1/managers/__init__.py
Normal file
0
freezerclient/v1/managers/__init__.py
Normal file
@ -1,24 +1,21 @@
|
||||
"""
|
||||
Copyright 2015 Hewlett-Packard
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 json
|
||||
import requests
|
||||
|
||||
from freezer.apiclient import exceptions
|
||||
from freezerclient import exceptions
|
||||
|
||||
|
||||
class ActionManager(object):
|
@ -1,23 +1,21 @@
|
||||
"""
|
||||
(c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
|
||||
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.
|
||||
"""
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 json
|
||||
import requests
|
||||
|
||||
from freezer.apiclient import exceptions
|
||||
from freezerclient import exceptions
|
||||
|
||||
|
||||
class BackupsManager(object):
|
@ -1,26 +1,24 @@
|
||||
"""
|
||||
(c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
|
||||
|
||||
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.
|
||||
"""
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 json
|
||||
import requests
|
||||
|
||||
from freezer.apiclient import exceptions
|
||||
from freezerclient import exceptions
|
||||
|
||||
|
||||
class RegistrationManager(object):
|
||||
class ClientManager(object):
|
||||
|
||||
def __init__(self, client, verify=True):
|
||||
self.client = client
|
@ -1,23 +1,21 @@
|
||||
"""
|
||||
Copyright 2015 Hewlett-Packard
|
||||
|
||||
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.
|
||||
"""
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 json
|
||||
import requests
|
||||
|
||||
from freezer.apiclient import exceptions
|
||||
from freezerclient import exceptions
|
||||
|
||||
|
||||
class JobManager(object):
|
@ -1,23 +1,21 @@
|
||||
"""
|
||||
Copyright 2015 Hewlett-Packard
|
||||
|
||||
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.
|
||||
"""
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 json
|
||||
import requests
|
||||
|
||||
from freezer.apiclient import exceptions
|
||||
from freezerclient import exceptions
|
||||
|
||||
|
||||
class SessionManager(object):
|
||||
@ -138,7 +136,7 @@ class SessionManager(object):
|
||||
def end_session(self, session_id, job_id, session_tag, result):
|
||||
"""
|
||||
Informs the freezer service that the job has ended.
|
||||
Privides information about the job's result and the session tag
|
||||
Provides information about the job's result and the session tag
|
||||
|
||||
:param session_id:
|
||||
:param job_id:
|
198
freezerclient/v1/sessions.py
Normal file
198
freezerclient/v1/sessions.py
Normal file
@ -0,0 +1,198 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 pprint import pformat
|
||||
|
||||
from cliff.command import Command
|
||||
from cliff.lister import Lister
|
||||
from cliff.show import ShowOne
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient.utils import doc_from_json_file
|
||||
|
||||
|
||||
logging = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SessionShow(ShowOne):
|
||||
"""Show a single session"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionShow, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='session_id',
|
||||
help='ID of the session')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
session = self.app.client.sessions.get(parsed_args.session_id)
|
||||
|
||||
if not session:
|
||||
raise exceptions.ApiClientException('Session not found')
|
||||
|
||||
column = (
|
||||
'Session ID',
|
||||
'Description',
|
||||
'Status',
|
||||
'Jobs'
|
||||
)
|
||||
|
||||
data = (
|
||||
session.get('session_id'),
|
||||
session.get('description'),
|
||||
session.get('status'),
|
||||
pformat(session.get('jobs'))
|
||||
)
|
||||
return column, data
|
||||
|
||||
|
||||
class SessionList(Lister):
|
||||
"""List all the sessions for your user"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionList, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
dest='limit',
|
||||
default=100,
|
||||
help='Specify a limit for search query',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--offset',
|
||||
dest='offset',
|
||||
default=0,
|
||||
help='',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--search',
|
||||
dest='search',
|
||||
default='',
|
||||
help='Define a filter for the query',
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
sessions = self.app.client.sessions.list_all(
|
||||
limit=parsed_args.limit,
|
||||
offset=parsed_args.offset,
|
||||
search=parsed_args.search
|
||||
)
|
||||
|
||||
return (('Session ID', 'Description', 'Status', '# Jobs'),
|
||||
((session.get('session_id'),
|
||||
session.get('description'),
|
||||
session.get('status'),
|
||||
len(session.get('jobs', [])),
|
||||
) for session in sessions))
|
||||
|
||||
|
||||
class SessionCreate(Command):
|
||||
"""Create a session from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionCreate, self).get_parser(prog_name)
|
||||
parser.add_argument('--file',
|
||||
dest='file',
|
||||
help='Path to json file with the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
session = doc_from_json_file(parsed_args.file)
|
||||
session_id = self.app.client.sessions.create(session)
|
||||
logging.info('Session {0} created'.format(session_id))
|
||||
|
||||
|
||||
class SessionAddJob(Command):
|
||||
"""Add a job to a session"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionAddJob, self).get_parser(prog_name)
|
||||
parser.add_argument('--session-id',
|
||||
dest='session_id',
|
||||
help='ID of the session')
|
||||
parser.add_argument('--job-id',
|
||||
dest='job_id',
|
||||
help='ID of the job to add')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.sessions.add_job(parsed_args.session_id,
|
||||
parsed_args.job_id)
|
||||
logging.info('Job {0} added correctly to session {1}'.format(
|
||||
parsed_args.job_id, parsed_args.session_id))
|
||||
|
||||
|
||||
class SessionRemoveJob(Command):
|
||||
"""Remove a job from a session"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionRemoveJob, self).get_parser(prog_name)
|
||||
parser.add_argument('--session-id',
|
||||
dest='session_id',
|
||||
help='ID of the session')
|
||||
parser.add_argument('--job-id',
|
||||
dest='job_id',
|
||||
help='ID of the job to add')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
try:
|
||||
self.app.client.sessions.remove_job(parsed_args.session_id,
|
||||
parsed_args.job_id)
|
||||
except Exception as error:
|
||||
# there is an error coming from the api when a job is removed
|
||||
# with the following text:
|
||||
# Additional properties are not allowed ('job_event' was unexpected)
|
||||
# but in reality the job gets removed correctly.
|
||||
if 'Additional properties are not allowed' in error.message:
|
||||
pass
|
||||
else:
|
||||
raise exceptions.ApiClientException(error.message)
|
||||
else:
|
||||
logging.info('Job {0} removed correctly from session {1}'.format(
|
||||
parsed_args.job_id, parsed_args.session_id))
|
||||
|
||||
|
||||
class SessionStart(Command):
|
||||
"""Start a session"""
|
||||
def get_parser(self, prog_name):
|
||||
pass
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
pass
|
||||
|
||||
|
||||
class SessionEnd(Command):
|
||||
"""Stop a session"""
|
||||
def get_parser(self, prog_name):
|
||||
pass
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
pass
|
||||
|
||||
|
||||
class SessionUpdate(Command):
|
||||
"""Update a session from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionUpdate, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='session_id',
|
||||
help='ID of the session')
|
||||
|
||||
parser.add_argument(dest='file',
|
||||
help='Path to json file with the session')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
session = doc_from_json_file(parsed_args.file)
|
||||
self.app.client.sessions.update(parsed_args.session_id, session)
|
||||
logging.info('Session {0} updated'.format(parsed_args.session_id))
|
12
requirements.txt
Normal file
12
requirements.txt
Normal file
@ -0,0 +1,12 @@
|
||||
astroid<1.4.0 # breaks pylint 1.4.4
|
||||
setuptools>=16.0
|
||||
pbr>=1.6
|
||||
python-keystoneclient>=1.6.0,!=1.8.0
|
||||
|
||||
cliff!=1.16.0,>=1.15.0 # Apache-2.0
|
||||
oslo.utils>=3.2.0
|
||||
oslo.i18n>=1.5.0 # Apache-2.0
|
||||
oslo.log>=1.14.0
|
||||
oslo.config>=3.2.0 # Apache-2.0
|
||||
|
||||
six>=1.9.0 # MIT
|
49
setup.cfg
Normal file
49
setup.cfg
Normal file
@ -0,0 +1,49 @@
|
||||
[metadata]
|
||||
name = python-freezerclient
|
||||
summary = OpenStack Disaster Recovery API Client Library
|
||||
description-file =
|
||||
README.rst
|
||||
license = Apache License, Version 2.0
|
||||
author = Freezer Team
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = https://wiki.openstack.org/wiki/Freezer
|
||||
classifier =
|
||||
Programming Language :: Python
|
||||
Development Status :: 5 - Production/Stable
|
||||
Natural Language :: English
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Developers
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: MacOS
|
||||
Operating System :: POSIX :: BSD :: FreeBSD
|
||||
Operating System :: POSIX :: BSD :: NetBSD
|
||||
Operating System :: POSIX :: BSD :: OpenBSD
|
||||
Operating System :: POSIX :: Linux
|
||||
Operating System :: Microsoft :: Windows
|
||||
Operating System :: Unix
|
||||
Topic :: System :: Archiving :: Backup
|
||||
Topic :: System :: Archiving :: Compression
|
||||
Topic :: System :: Archiving
|
||||
|
||||
[files]
|
||||
packages =
|
||||
freezerclient
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[entry_points]
|
||||
console_scripts =
|
||||
freezer = freezerclient.shell:main
|
||||
|
||||
[pbr]
|
||||
# Have pbr generate the module indexes like sphinx autodoc
|
||||
autodoc_index_modules = True
|
||||
|
||||
# Treat sphinx warnings as errors during the docs build; this helps us keep
|
||||
# the documentation clean.
|
||||
warnerrors = true
|
28
setup.py
Normal file
28
setup.py
Normal file
@ -0,0 +1,28 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
pbr=True)
|
11
test-requirements.txt
Normal file
11
test-requirements.txt
Normal file
@ -0,0 +1,11 @@
|
||||
flake8>=2.2.4,<=2.4.1
|
||||
hacking>=0.10.2,<0.11
|
||||
coverage>=3.6
|
||||
discover
|
||||
mock>=1.2
|
||||
pylint==1.4.5 # GNU GPL v2
|
||||
python-subunit>=0.0.18
|
||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 # BSD
|
||||
oslosphinx>=2.5.0,!=3.4.0 # Apache-2.0
|
||||
testrepository>=0.0.18
|
||||
testtools>=1.4.0
|
67
tox.ini
Normal file
67
tox.ini
Normal file
@ -0,0 +1,67 @@
|
||||
[tox]
|
||||
envlist = py27,py34,pep8,pylint,docs
|
||||
skipsdist = True
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
deps =
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
|
||||
install_command = pip install -U {opts} {packages}
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
|
||||
whitelist_externals =
|
||||
find
|
||||
coverage
|
||||
rm
|
||||
|
||||
python_files = test_*.py
|
||||
norecursedirs = .tox .venv
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:py27]
|
||||
basepython = python2.7
|
||||
setenv =
|
||||
OS_TEST_PATH = ./freezerclient/tests/unit
|
||||
commands =
|
||||
find . -type f -name "*.pyc" -delete
|
||||
python setup.py testr --coverage --testr-args="{posargs}"
|
||||
coverage report -m
|
||||
rm -f .coverage
|
||||
rm -rf .testrepository
|
||||
|
||||
[testenv:py34]
|
||||
basepython = python3.4
|
||||
setenv =
|
||||
OS_TEST_PATH = ./freezerclient/tests/unit
|
||||
commands =
|
||||
find . -type f -name "*.pyc" -delete
|
||||
python setup.py testr --coverage --testr-args="{posargs}"
|
||||
coverage report -m
|
||||
rm -f .coverage
|
||||
rm -rf .testrepository
|
||||
|
||||
[testenv:docs]
|
||||
commands =
|
||||
python setup.py build_sphinx
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8 freezerclient
|
||||
|
||||
[testenv:pylint]
|
||||
commands = pylint --rcfile .pylintrc freezerclient
|
||||
|
||||
[flake8]
|
||||
# it's not a bug that we aren't using all of hacking
|
||||
# H102 -> apache2 license exists
|
||||
# H103 -> license is apache
|
||||
# H201 -> no bare excepts
|
||||
# H501 -> don't use locals() for str formatting
|
||||
# H903 -> \n not \r\n
|
||||
ignore = H
|
||||
select = H102, H103, H201, H501, H903, H201, H306, H301, H233
|
||||
show-source = True
|
||||
exclude = .venv,.tox,dist,doc,test,*egg,tests
|
Loading…
Reference in New Issue
Block a user