Compare commits

..

146 Commits

Author SHA1 Message Date
Zuul
1419988ace Merge "Add unit test for quotas" 2019-11-09 07:28:19 +00:00
liushuai
21addfcca0 Add unit test for quotas
Change-Id: Id0d6ee5142687f64cbf448a8983683b19eb4b1f6
2019-11-07 23:44:37 +08:00
liushuai
56681c318e Add unit test for triggers
Change-Id: I3b6aaead255b51c742e8602d09b4b3f7f152ec6c
2019-11-07 17:34:58 +08:00
liushuai
5bd1b2feeb Add unit test for operation logs
Change-Id: I812ccf4ac1e5ab3f3d2402bcf9a9dd5b88781513
2019-11-05 22:44:18 +08:00
liushuai
05139e97e8 optional argument should have default values
Change-Id: I5ff5447f8ceec0a8ff25d46208a59eb2d6c8e307
Closes-Bug: #1844488
2019-09-23 17:29:28 +08:00
Zuul
c4e27f2e6d Merge "Add Python 3 Train unit tests" 2019-09-09 12:09:39 +00:00
jacky06
1a98ae3101 Replace git.openstack.org URLs with opendev.org URLs
Change-Id: Ia80a351665da5428d3c7c4cb518ecf0afc2ef8c1
2019-08-24 10:52:37 +08:00
chenke
4571dcb492 Switch to the new canonical constraints URL on master
Reference:
1. http://lists.openstack.org/pipermail/openstack-discuss/2019-May/006478.html
2. https://github.com/openstack/nova/blob/master/tox.ini#L17

Change-Id: Ie02a59eaee3807432c111e301163e87ab5afe2bd
2019-07-03 15:55:53 +08:00
Corey Bryant
9cd38596cd Add Python 3 Train unit tests
This is a mechanically generated patch to ensure unit testing is in place
for all of the Tested Runtimes for Train.

See the Train python3-updates goal document for details:
https://governance.openstack.org/tc/goals/train/python3-updates.html

Change-Id: Ia67bc92c85694a6be8eea65b9c4bb661ecc13b36
Story: #2005924
Task: #34214
2019-06-24 15:16:41 -04:00
Jiao Pengju
17f75a9c00 Fix listing with --all error
When executing command "karbor xxx-list --all", it will raise
error as 'error: ambiguous option: --all could match --all-tenants,
--all_tenants'. The reason is we have both '--all-tenants' and
'--all_tenants' in the specify operations, '--all' matches two
args, so it can not work, but when using '--all-' or '--all_',
it return the correct result. We should fix it, so we remove the
arg '--all_tenants' which is not in the help info.
Story: 2005874
Task: 33686

Change-Id: Iafa70c35594af732435122ebd50c114fd7f0b9df
2019-06-16 12:07:15 +08:00
OpenDev Sysadmins
6a5f46615c OpenDev Migration Patch
This commit was bulk generated and pushed by the OpenDev sysadmins
as a part of the Git hosting and code review systems migration
detailed in these mailing list posts:

http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003603.html
http://lists.openstack.org/pipermail/openstack-discuss/2019-April/004920.html

Attempts have been made to correct repository namespaces and
hostnames based on simple pattern matching, but it's possible some
were updated incorrectly or missed entirely. Please reach out to us
via the contact information listed at https://opendev.org/ with any
questions you may have.
2019-04-19 19:41:49 +00:00
Zuul
036ec8746a Merge "Update json module to jsonutils" 2019-03-21 09:05:28 +00:00
cao.yuan
56474b54df Update json module to jsonutils
oslo project provide jsonutils, and karborclient use it in many place[1],
this PS to update the remained json module to oslo jsonutils for
consistency.

[1]: https://github.com/openstack/python-karborclient/search?utf8=%E2%9C%93&q=jsonutils&type=

Change-Id: I8c2c0383eac12a5562f205640d6c8c7d062266b1
2019-02-25 20:15:46 +08:00
ZhongShengping
998e72a03a add python 3.7 unit test job
This is a mechanically generated patch to add a unit test job running
under Python 3.7.

See ML discussion here [1] for context.

[1] http://lists.openstack.org/pipermail/openstack-dev/2018-October/135626.html

Change-Id: I207e95619a8f4e8948f0d71404738a32daa7b5ba
Story: #2004073
Task: #27421
2019-02-19 17:06:02 +08:00
98k
7d93f625d9 Add doc/requirements.txt to docs tox environment
Without these dependencies, the releasenotes build does not actually
work.

Change-Id: Ie38200dafb86dbf4cc604ae837fc04f42c47b399
2019-01-09 17:46:52 +00:00
Zuul
3216f64d14 Merge "Add Python 3.6 classifier to setup.cfg" 2018-12-21 01:27:52 +00:00
Zuul
e5440f809d Merge "Convert trigger window from string to integer" 2018-12-10 06:26:49 +00:00
YUHAN
d45538cf41 Convert trigger window from string to integer
Change-Id: Iccdaea4b4e5aedd2548e865576eb718643e41cf9
2018-12-10 12:56:18 +08:00
98k
ce7b872c26 Change openstack-dev to openstack-discuss
Mailinglists have been updated. Openstack-discuss replaces openstack-dev.

Change-Id: I7fd74af8edb78fd95f59162f1282f1434c522cac
2018-12-04 07:38:17 +00:00
Zuul
a0a7b4a24b Merge "Add osc support to update plan description" 2018-12-04 07:17:46 +00:00
liushuai
c2e7441444 Add osc support to update plan description
Change-Id: Ibc661594342a89cc5f89084972a4fb9844da1a84
2018-12-04 13:56:51 +08:00
Zuul
5cf1f0d246 Merge "Unsubmitted name field shoud be ignored" 2018-12-04 05:48:27 +00:00
Zuul
c992876f96 Merge "Add support to update plan description" 2018-12-04 05:45:40 +00:00
Zuul
bc9ac570bf Merge "Add osc support to reset checkpoint state" 2018-12-03 08:53:48 +00:00
Jiao Pengju
048b3a0bd9 Add support to reset checkpoint state
This patch added clinet support for doing
checkpoint state reset.
Implements: bp checkpoint-status-reset

Change-Id: Id34501bd4d43c6ae0e9d0d789be7e92581cbff8c
2018-12-03 14:14:50 +08:00
liushuai
58234ab51c Add support to update plan description
Change-Id: I048e970cd449e0e51bbfc3a97e325afd0fa73d5c
2018-12-02 23:19:04 +08:00
Jiao Pengju
1eb26df991 Add osc support to reset checkpoint state
This patch added osc clinet support for doing
checkpoint state reset.
Implements: bp checkpoint-status-reset

Change-Id: If7c2ae3563ff0959c4c59f2b23a8c7c9ea11e196
2018-12-02 22:35:47 +08:00
qingszhao
47d15a74a3 Add Python 3.6 classifier to setup.cfg
Change-Id: Ibc1b7be0b55756b8768b58b04b0898b76aba8427
2018-11-30 06:56:08 +00:00
liushuai
597e452dbc Unsubmitted name field shoud be ignored
Closes-Bug: #1805815

Change-Id: Ife1a13b7b145eff7a8e8e8471bac0ee43c195c68
2018-11-30 00:11:01 +08:00
liushuai
262799e3c0 Convert trigger window from string to integer
Closes-Bug: #1805755

Change-Id: Ib2da9cb008fad3f9d686a1409c83d98b7daebe68
2018-11-29 11:21:42 +08:00
Zuul
f3c117e17c Merge "Add osc all tenants support for checkpoint listing" 2018-11-19 03:36:42 +00:00
Jiao Pengju
e3ed8939b8 Add osc all tenants support for checkpoint listing
Change-Id: I19c5461f28425377918ebc3faeaf5a7340eaead8
Implements: bp checkpoint-all-tenants
2018-11-18 21:00:08 +08:00
Jiao Pengju
38b2b847c8 Add all tenants support for checkpoint listing
Change-Id: Iffcc68efad6a218faa9a6d6d53ff1f7b833ed13e
Implements: bp checkpoint-all-tenants
2018-11-18 20:05:52 +08:00
Jiao Pengju
2fe9422e04 Limit the operation type for scheduledoperation
Now karbor only support two types of scheduledoperation,
but the client do not show and limit the values. So the
end users can type any string to execute the command of
'scheduledoperation create', but the server returns error
, this will make users confused, and they still not know
the right value. This patch will limit the operation type
in 'protect' and 'retention_protect'.

Change-Id: Ic1110124472ac455f988bb25254feeb4417caf1a
2018-11-09 12:38:52 +08:00
Zuul
b0011487e2 Merge "Use templates for cover and lower-constraints" 2018-10-10 01:03:12 +00:00
Chen
db689c650f Remove PyPI downloads
According to official site,
https://packaging.python.org/guides/analyzing-pypi-package-downloads/
PyPI package download statistics is no longer maintained and thus
should be removed.

Change-Id: I0cdc7bdfa4a8c36d57a2c0f5391a7e8c53925fed
2018-10-06 02:45:59 +00:00
Andreas Jaeger
1c2079ef71 Use templates for cover and lower-constraints
Small cleanups:

* Use openstack-tox-cover template, this runs the cover job
  in the check queue only. Remove individual cover jobs.
* Use openstack-lower-constraints-jobs template, remove individual
  jobs.
* Sort list of templates

Change-Id: I1d4176ff6a3c53c447f9d118008d3d51ac455b88
2018-09-29 19:09:06 +02:00
Nguyen Hai
038e2f6984 add python 3.6 unit test job
This is a mechanically generated patch to add a unit test job running
under Python 3.6 as part of the python3-first goal.

See the python3-first goal document for details:
https://governance.openstack.org/tc/goals/stein/python3-first.html

Change-Id: Ie2624ea979e19abe3feae68d8315c54fd9a9f7ff
Story: #2002586
Task: #24303
2018-08-22 15:08:13 +09:00
Nguyen Hai
334ef1ec33 switch documentation job to new PTI
This is a mechanically generated patch to switch the documentation
jobs to use the new PTI versions of the jobs as part of the
python3-first goal.

See the python3-first goal document for details:
https://governance.openstack.org/tc/goals/stein/python3-first.html

Change-Id: I4e3d217ba4d67fd227d532ac2ce6a36f36d68815
Story: #2002586
Task: #24303
2018-08-22 15:08:12 +09:00
Nguyen Hai
4455105df5 import zuul job settings from project-config
This is a mechanically generated patch to complete step 1 of moving
the zuul job settings out of project-config and into each project
repository.

Because there will be a separate patch on each branch, the branch
specifiers for branch-specific jobs have been removed.

Because this patch is generated by a script, there may be some
cosmetic changes to the layout of the YAML file(s) as the contents are
normalized.

See the python3-first goal document for details:
https://governance.openstack.org/tc/goals/stein/python3-first.html

Change-Id: Ic6f174b987bd6a32af097926951bac75cb15f6ea
Story: #2002586
Task: #24303
2018-08-22 15:08:11 +09:00
Doug Hellmann
7aca76b32b fix tox python3 overrides
We want to default to running all tox environments under python 3, so
set the basepython value in each environment.

We do not want to specify a minor version number, because we do not
want to have to update the file every time we upgrade python.

We do not want to set the override once in testenv, because that
breaks the more specific versions used in default environments like
py35 and py36.

Change-Id: I8b2f1a62f8c9ca04d6c63f4a4ad22ec445bad88b
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
2018-06-06 17:58:17 -04:00
Zuul
a614c845b2 Merge "Follow the new PTI for document build" 2018-04-21 07:32:13 +00:00
Zuul
8929cad166 Merge "add lower-constraints job" 2018-04-21 07:28:52 +00:00
melissaml
103d4dac05 Trivial: Update pypi url to new url
Pypi url changed from [1] to [2]

[1] https://pypi.python.org/pypi/<package>
[2] https://pypi.org/project/<package>

Change-Id: I7e141db7a05cdfb02dac362b14789c2bee51e184
2018-04-21 06:22:13 +08:00
Doug Hellmann
0fe7b7c087 add lower-constraints job
Create a tox environment for running the unit tests against the lower
bounds of the dependencies.

Create a lower-constraints.txt to be used to enforce the lower bounds
in those tests.

Add openstack-tox-lower-constraints job to the zuul configuration.

See http://lists.openstack.org/pipermail/openstack-dev/2018-March/128352.html
for more details.

Change-Id: I71c76a9173aaa7429b766225f19819715a9ccf64
Depends-On: https://review.openstack.org/555034
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
2018-04-20 16:22:33 -04:00
Nguyen Hai
01d1aa0da9 Follow the new PTI for document build
- For compliance with the Project Testing Interface as described in:
https://governance.openstack.org/tc/reference/project-testing-interface.html
http://lists.openstack.org/pipermail/openstack-dev/2017-December/125710.html
- Remove the '[build_sphinx]' as described in:
http://lists.openstack.org/pipermail/openstack-dev/2018-March/128594.html
- This patch also changes minor mistakes in docs.

Change-Id: Iec58d57d59b98c6086910e532e16f1e417815c95
2018-04-12 12:33:29 +09:00
Zuul
71034945a3 Merge "Updated from global requirements" 2018-04-12 02:02:41 +00:00
Zuul
1658f71943 Merge "Update links in README" 2018-04-11 08:18:40 +00:00
OpenStack Proposal Bot
e7e720ca1d Updated from global requirements
Change-Id: I866a1aeb9dbd8c8e1ca502b23c8e4993b232b64e
2018-03-23 01:46:55 +00:00
melissaml
c3b5d573ad Update links in README
Change the outdated links to the latest links in README

Change-Id: If90de4139843539d890948d225eda64a249501b1
2018-03-11 03:04:46 +08:00
Zuul
2dd285549d Merge "Updated from global requirements" 2018-01-31 08:12:23 +00:00
Zuul
c14a1db371 Merge "Change home-page url for karborclient" 2018-01-30 02:06:31 +00:00
Zuul
26d570239d Merge "Add 'rm -f .testrepository/times.dbm' command in testenv" 2018-01-29 01:50:13 +00:00
OpenStack Proposal Bot
63c9890f21 Updated from global requirements
Change-Id: Ib644818e987386a6509ae4884b1dbc0d8418082c
2018-01-27 18:31:25 +00:00
Zuul
86fd679be3 Merge "Updated from global requirements" 2018-01-26 06:17:42 +00:00
OpenStack Proposal Bot
604007cbd3 Updated from global requirements
Change-Id: I21577f0c7cb5c622e13b13cd5e35781391d90319
2018-01-24 02:19:37 +00:00
Yuanbin.Chen
84d19ef13c Delete tox.ini functional when functional test is real.
This patch delete tox.ini functional, the
"/karborclient/tests/functional" is not exist.

Change-Id: I29b40bac7aff8e082e36a1575dc442370911088a
Signed-off-by: Yuanbin.Chen <cybing4@gmail.com>
2018-01-18 23:23:12 +08:00
chenpengzi
4c47bd020f Change home-page url for karborclient
Change-Id: I302b984078cb25b368b4aa5d47bd6f8ea7595d72
2018-01-10 13:03:10 +00:00
OpenStack Proposal Bot
4a5fe7dbc9 Updated from global requirements
Change-Id: I724d191cf50842c274a86bb53841abe28597bc13
2017-12-21 00:43:00 +00:00
Zuul
2d1ac6e744 Merge "Fixed non-ascii in README.txt" 2017-12-19 08:27:56 +00:00
Zuul
f93ac0b598 Merge "Avoid tox_install.sh for constraints support" 2017-12-15 07:32:54 +00:00
Thomas Goirand
194195a97a Fixed non-ascii in README.txt
Running "LANG=C python setup.py install" just fails because of a single
non-ascii char in README.txt. This patch fixes that.

Change-Id: I6840da97cf41891f75afebe06d00e0d3e3849a3d
2017-12-07 22:37:04 +01:00
Zuul
9cf5a90b5c Merge "Updated from global requirements" 2017-12-05 08:11:10 +00:00
chenying
a4cec6cb18 Add OSC commands for quota classes API
Change-Id: I9286d53d307d7b7b58340102967fc30bf87252a7
Implements: blueprint support-quotas-in-karbor
2017-12-05 15:43:53 +08:00
OpenStack Proposal Bot
b8726f4bf5 Updated from global requirements
Change-Id: I68301a8bd7c988d507740f4f306642cc4d462d6b
2017-12-05 03:32:01 +00:00
Zuul
7e084ac5c4 Merge "Add OSC commands for quota API" 2017-12-04 08:27:17 +00:00
chenying
8cf80d3041 Add OSC commands for quota API
Change-Id: I3b7afc0df984b015fb7a8b5488d447c470bd2a12
Implements: blueprint support-quotas-in-karbor
2017-12-04 14:54:12 +08:00
Andreas Jaeger
0b41772728 Avoid tox_install.sh for constraints support
We do not need tox_install.sh, pip can handle constraints itself
and install the project correctly. Thus update tox.ini and remove
the now obsolete tools/tox_install.sh file.

This follows https://review.openstack.org/#/c/508061 to remove
tools/tox_install.sh.

Change-Id: I28ba0904389699268d4b256f76fbfa3a6792eda0
2017-12-02 17:01:28 +00:00
Zuul
a09807472d Merge "Add commands for quota class API" 2017-11-29 12:15:32 +00:00
Zuul
f84d1c9b20 Merge "Add commands for quota API" 2017-11-29 12:12:01 +00:00
OpenStack Proposal Bot
9d4a1612c5 Updated from global requirements
Change-Id: I05e64b5f5542a722c9ede204b6c1f4e771ff1b88
2017-11-16 11:24:03 +00:00
chenying
3a22b3fcdd Add commands for quota class API
Change-Id: Id1710f986d7ff1943fe3841cfd9837d2316419e0
Implements: blueprint support-quotas-in-karbor
2017-11-14 19:38:42 +08:00
chenying
6c11094dc6 Add commands for quota API
Change-Id: Iac73809780012509ffa4883def68e37c7dc98bd4
Implements: blueprint support-quotas-in-karbor
2017-11-14 16:44:29 +08:00
Zuul
478ebe71ed Merge "Add OSC plugin for the service management API" 2017-10-27 10:17:33 +00:00
Zuul
a4a5088ff0 Merge "Add commands for service management API" 2017-10-26 07:51:42 +00:00
Jiao Pengju
87058d3e24 Add OSC plugin for the service management API
Depends-On: I3e9a62327653ea1dc9b5807f50f250c739c1566d
Change-Id: I49825e80935b357a06b0074c024390cd2ed3a9b9
Implements: blueprint karbor-service-management
2017-10-26 11:18:44 +08:00
Jiao Pengju
aba2875905 Add commands for service management API
Change-Id: I3e9a62327653ea1dc9b5807f50f250c739c1566d
Implements: blueprint karbor-service-management
2017-10-25 19:49:12 +08:00
chenying
50927b5c38 Add OSC plugin for the verification API
Change-Id: I2fbd064a058c7dcaff6e223a3e2c87bb16cdbaa0
Implements: blueprint support-verify-the-checkpoint-api
2017-10-25 15:00:18 +08:00
chenying
f13c3faef4 Add commands for verification API
Change-Id: I6b7075220bce16af9e162f2e42feb10ffd073169
Implements: blueprint support-verify-the-checkpoint-api
2017-10-25 11:15:08 +08:00
yushangbin
e11f2e8006 Add 'rm -f .testrepository/times.dbm' command in testenv
Running py2* post py3* tests results in error. Add
'rm -f .testrepository/times.dbm' command in testenv to
resolve this.

Closes-Bug: #1565928
Change-Id: I111d82a1509af03aa2e141ee3fd9b2ff9e223215
2017-10-13 08:50:31 +08:00
Jenkins
dd2e3ff3a3 Merge "Updated from global requirements" 2017-09-21 06:50:26 +00:00
OpenStack Proposal Bot
346c241876 Updated from global requirements
Change-Id: Ie9b8946e704e2ef85e73ef6d6c91a9537f2d4b83
2017-09-21 03:49:23 +00:00
lihaijing
1bfd6d0ee4 Delete bash_completion in subcommand
There are two "completion" in the subcommand table: bash-completion
and bash_completion. but "bash_completion" is not in help information
and it is repeated with "bash-completion", so delete it.

Change-Id: I18da874681ea00c18d72e164dc55aeea9d40731d
Closes-Bug: #1670123
2017-08-23 14:55:29 +08:00
Jenkins
7a7f708282 Merge "Updated from global requirements" 2017-08-20 08:48:26 +00:00
Jenkins
ea42b2ab3e Merge "Fix OSC scheduledoperations commands formatting" 2017-08-20 08:41:20 +00:00
OpenStack Proposal Bot
996cc1625f Updated from global requirements
Change-Id: I6a1540e5bd7966f887dbb0f968676f7d9bef2f10
2017-08-18 04:51:27 +00:00
chenying
a7de5b981e Fix OSC scheduledoperations commands formatting
Change-Id: I7eacb653ed8549c453fdca6598447bd9b5f93cc8
2017-08-17 09:47:58 +08:00
chenying
982a8c361d Fix OSC restore commands formatting
Change-Id: Ia85065631eef1f3d96df007e240df04362f18754
2017-08-16 21:10:35 +08:00
Yuval Brik
855cbe5cbb Fix OSC provider show formatting
Change-Id: I5c2c8283dd367ccdbf268b6abc38899a565fe94b
2017-08-15 10:16:19 +03:00
Yuval Brik
41d93cdca0 Fix OSC protectable show formatting
Change-Id: I353a9e2938335e1de3d1b2902a780039e03e5b7c
2017-08-15 10:15:58 +03:00
Yuval Brik
888008676c Convert DOS newlines to Unix newlines
Change-Id: If05dce63dadecd89de58350173fc000055e63ca5
2017-08-15 10:15:33 +03:00
Yuval Brik
c14aea8079 Fix OSC protectable instance formatting
Change-Id: Ib06abd01682c7194547332582c64b4c7ae7f5a05
2017-08-15 10:14:50 +03:00
Yuval Brik
e1f7540bac Use a copy of global literal for each test
If global is used in OSC tests, it might be altered, and then the test
result depends on concurrency and order.
Use a copy of the global literal for deterministic results.

Change-Id: I177cc4345c5ed194f2c36d80acc53db113f814e4
2017-08-15 10:13:10 +03:00
Jenkins
6a53851650 Merge "Fix OSC checkpoint show and create formatting" 2017-08-14 10:27:11 +00:00
Jenkins
84e4ff3733 Merge "Checkpoint list: show only plan name and id" 2017-08-14 10:25:50 +00:00
Yuval Brik
e774b2178c Fix OSC plan show, update, and create formatting
Change-Id: I0b7377506e38bf31f45e5bb5ad75f423a344af62
2017-08-14 11:22:05 +03:00
Yuval Brik
e3d987a2ce Fix OSC checkpoint show and create formatting
Change-Id: Iecbda83be307c30a432cc40a448429dc81a1245e
2017-08-14 11:11:03 +03:00
Yuval Brik
9b77c76ed0 Checkpoint list: show only plan name and id
No need to list the entire plan for each checkpoint in the checkpoint
list commands. Show only the name and id.

Change-Id: I50a7e65e8fe810e43e762a4355c7976c9c4eca96
2017-08-14 11:09:30 +03:00
OpenStack Proposal Bot
aa5d56495f Updated from global requirements
Change-Id: I8d9b3a48335b7eeb71464de54ed8adca7dbdc8c6
2017-07-27 20:32:31 +00:00
Yuval Brik
d5842f6196 Docs: arrange guides
Change-Id: I956d0137f3c1507d58aac0d9b22b6737473e55be
2017-07-27 12:10:38 +03:00
Jenkins
423e4d5b48 Merge "Add operation log API cmd to karborclient" 2017-07-27 06:23:16 +00:00
OpenStack Proposal Bot
30ee5f4829 Updated from global requirements
Change-Id: If26478dac87f9bc018e71c8ac8f39662aa37aeb2
2017-07-23 13:51:53 +00:00
OpenStack Proposal Bot
4839860b73 Updated from global requirements
Change-Id: Id0331e836c7530a6fce73a909675a0972b52236a
2017-07-22 16:38:24 +00:00
Hangdong Zhang
28ab39e6fb Update URLs in documentation
Update URLs according to OpenStack document migration.
BTW: Do some optimization as well (http -> https)

Change-Id: Ie0a5e08a0611715c6cb775b875b4fcae3b622780
2017-07-20 15:57:42 +08:00
Akihiro Motoki
a907bc2a34 Fix wrong entry points which breaks OSC gate
Change-Id: Ia7ed435b397a1ef82a0b8133c5009e1045ee3816
2017-07-19 12:42:56 +00:00
chenying
cb0b8b0515 Add operation log API cmd to karborclient
Change-Id: I097ec3424b47939ff00f417bedb186305de39a62
blueprint: operation-log-api
2017-07-18 15:42:05 +08:00
yushangbin
05c98f7a20 Fix warning in doc generating
Doc generating shows warning "Title overline too short", this patch
fixes that.

Change-Id: I7dff4f0e4425fcf99a3da690ede4c4659cb46222
2017-07-17 15:44:52 +08:00
rajat29
2d6a97e84a Update URLs in documents according to document migration
Change-Id: Ifd15ac7b3d1ef0b4ab450f797169b83991e8e250
2017-07-14 15:50:38 +05:30
Jenkins
7bb1cc2c7a Merge "Remove unused None from dict.get()" 2017-07-13 17:53:54 +00:00
Jenkins
b56f70e7fd Merge "Replace six.iteritems() with .items()" 2017-07-13 12:13:25 +00:00
sudhir_agarwal
cd7d456491 Remove unused None from dict.get()
Since the default value is None when can't get a key from a dict,
So there is no need to use dict.get('key', None).

Change-Id: I9f2ea40e13a63992170149936ae4e80b8969c023
2017-07-05 19:09:03 +05:30
sudhir_agarwal
eb70a9d460 Replace six.iteritems() with .items()
1.As mentioned in [1], we should avoid using six.iteritems to achieve
iterators. We can use dict.items instead, as it will return iterators
in PY3 as well. And dict.items/keys will more readable.
2.In py2, the performance about list should be negligible, see the
link [2].
[1] https://wiki.openstack.org/wiki/Python3
[2] http://lists.openstack.org/pipermail/openstack-dev/2015-June/066391.html

Change-Id: Ib752ad4c3aed525c4bea9dbd5710172ddbaf193b
2017-07-05 18:26:01 +05:30
Jeremy Liu
5eab398150 Add OpenStackClient plugin for scheduledoperations
This patch adds data protection support to the python-openstackclient
through a plugin for scheduledoperations.

Co-Authored-By: Spencer Yu <yushb@gohighsec.com>
Change-Id: Id2712e3a081e0fd2b69cad093d1a6b26b644e967
Partially-Implements: blueprint karbor-support-python-openstackclient
2017-07-05 17:29:00 +08:00
Jenkins
1a7243354e Merge "Add OpenStackClient plugin for checkpoint" 2017-07-05 09:20:48 +00:00
Jenkins
5ded9c1f19 Merge "Drop MANIFEST.in - it's not needed by pbr" 2017-07-05 09:14:23 +00:00
yushangbin
2fc5904648 Add OpenStackClient plugin for checkpoint
This patch adds dataprotection support to the python-openstackclient
through a plugin for checkpoint.

Partially-Implements: blueprint karbor-support-python-openstackclient
Change-Id: I5c4c927c63ac39538d5565a4167f5836e7127170
2017-07-05 16:52:41 +08:00
Jenkins
71e029c629 Merge "Add OpenStackClient plugin for triggers" 2017-07-05 08:43:06 +00:00
yushangbin
289370ab38 Add OpenStackClient plugin for triggers
This patch adds data protection support to the python-openstackclient
through a plugin for trigger.

Change-Id: I1a19340f47cbb1713ee8da1b238ea18ea5f3bc92
Partially-Implements: blueprint karbor-support-python-openstackclient
2017-07-05 16:02:49 +08:00
Jeremy Liu
795fa730a8 Fix bug in plan deletion when using osc plugin for karbor
Change-Id: Idb5620506dd800428407031eddd525e68b93f414
2017-07-05 15:58:43 +08:00
liyanhang
3f91adc2d8 Drop MANIFEST.in - it's not needed by pbr
This patch removes `MANIFEST.in` file as pbr generates a
sensible manifest from git files and some standard files
and it removes the need for an explicit `MANIFEST.in` file.

Change-Id: I9b3d5b6ca56766389b0a8c69325bbd4f391112b5
2017-07-05 14:14:17 +08:00
yushangbin
1b3b860565 Add OpenStackClient plugin for protectable
This patch adds dataprotection support to the python-openstackclient
through a plugin for protectable.

Partially-Implements: blueprint karbor-support-python-openstackclient
Change-Id: I088919edd7287602baaa9176b3af9d2995e5c116
2017-07-04 21:47:19 +08:00
Jenkins
d358465dcf Merge "Add OpenStackClient plugin for provider" 2017-07-04 11:01:57 +00:00
Jeremy Liu
b11e847b52 Add OpenStackClient plugin for provider
This patch adds data protection support to the python-openstackclient
through a plugin for provider.

Co-Authored-By: Spencer Yu <yushb@gohighsec.com>
Change-Id: I54d731f29c7f27c3b020a0de741d6efe0b062c8b
Partially-Implements: blueprint karbor-support-python-openstackclient
2017-07-04 07:10:30 +00:00
Jeremy Liu
a82f43ae23 Add unit tests for showing a plan/restore
Change-Id: Ibc1e578c9de6bec026411cfc34ea4a6dc83b43f7
2017-07-04 15:03:58 +08:00
Jeremy Liu
9936200bf8 Update spec to match what we've registered in openstackclient
Change-Id: Idacccaa8d61b47311691456a6f2fdf152b244e66
2017-07-04 10:09:40 +08:00
Jenkins
75a7e6276f Merge "Enable coverage report in console output" 2017-07-02 08:37:55 +00:00
OpenStack Proposal Bot
7b2a2e8c92 Updated from global requirements
Change-Id: I502d11c6165b197aace610d192f2028d3005cec9
2017-06-29 21:03:48 +00:00
Kiran_totad
22d5a7ec38 Remove old pep8 ignores that are no longer necessary
Change-Id: Ie2595cd59dd48c58b6afb8852f5743212ca61848
2017-06-29 13:34:35 +05:30
Kiran_totad
6f2feab60d switch to openstackdocstheme
Change-Id: Ie5a86285db70f6b5be237e9c4c84a26b60bd13be
2017-06-29 06:23:52 +00:00
Jenkins
3233c39c52 Merge "Fix help message for plan command." 2017-06-29 03:20:33 +00:00
yushangbin
7eb818c0ee Add OpenStackClient plugin for restore
This patch adds dataprotection support to the python-openstackclient
through a plugin for restore.

Partially-Implements: blueprint karbor-support-python-openstackclient
Change-Id: I48aa68af1dbf199b6de3a648abdf977b6201bf48
2017-06-28 15:05:48 +08:00
Jenkins
b658068fa6 Merge "Updated from global requirements" 2017-06-28 06:34:59 +00:00
Jenkins
378a7a38f9 Merge "Fix a bug for plan delete test." 2017-06-28 06:34:30 +00:00
OpenStack Proposal Bot
1f907c95b8 Updated from global requirements
Change-Id: I49feca2eb4cf9ddd5455003aa3f5d2de22d098df
2017-06-27 12:21:33 +00:00
yushangbin
1bf9f74604 Fix help message for plan command.
Change-Id: I54d96690b45f4bed630892f1f4181540fa48849b
2017-06-27 18:04:18 +08:00
Jeremy Liu
f898a169cb Use 'project' instead of 'tenant' when switching to openstackclient command
We tend to use 'project' rather than 'tenant' when switching to openstackclient
command, such as:

  openstack role add --user <user> --project <project> <role>

Change-Id: I661f1d7ca3ea229ce03376b43867af260943de23
2017-06-27 17:50:36 +08:00
yushangbin
2800c5f1c2 Fix a bug for plan delete test.
Change-Id: Ie8527c2f7d70d54e5ea9ee09e6eeb7515c625437
2017-06-27 14:24:57 +08:00
Yuval Brik
2d1d7624e8 Enable translation of python-karborclient
Change-Id: I39bed8fbf905ba5cdbfdaadc4b7db863aa38924f
2017-06-23 10:25:36 +03:00
chenying
995fe11dbe Add plan commands for OpenStackClinet plugin in karbor
Change-Id: I8e10fc1775cd07271ff5651e104b6b8b26743be3
Partially-Implements: karbor-support-python-openstackclient
2017-06-22 15:12:29 +08:00
OpenStack Proposal Bot
956f0207e7 Updated from global requirements
Change-Id: I124d8200bc0d57416395ff14a1b4ffeeaee90c0f
2017-06-14 16:43:21 +00:00
Jenkins
61a46a6752 Merge "Add OpenStackClient plugin and plan list" 2017-06-14 13:11:13 +00:00
chenying
0d9611ba9b Add OpenStackClient plugin and plan list
This patch adds dataprotection support to the python-openstackclient
through a plugin.

The support can be demonstrated through the implementation of
the karbor command plan-list which is now:
    openstack dataprotection plan list

Partially-Implements: karbor-support-python-openstackclient
Change-Id: I4dfac08fd2b04f9ac254d3aa8fdadc3a1691de0a
2017-06-13 18:58:42 +08:00
chenying
e3f50485fd Spec for openstack client support
This spec is intent on implementing karbor commands in the
python-karborclient repository as python-openstackclient plugins.

Change-Id: I06eea28a922997586e670fe33bf82a6da72cb4a2
Partially-Implements: karbor-support-python-openstackclient
2017-06-05 23:21:23 +08:00
Jenkins
cd75dda7bb Merge "Optimize the link address" 2017-06-01 02:18:21 +00:00
Jeremy Liu
1924aba63a Enable coverage report in console output
This will output coverage rate of every module in console.

Change-Id: If9e24293ae8b4d7293721c2fa49562462d84ce8a
2017-05-18 21:23:00 +08:00
M V P Nitesh
86b15d38b8 Optimize the link address
Use https instead of http to ensure the safety

Change-Id: Iebd272cac1691bbb89a72cc5f02b4667e85fbc8d
2017-04-11 12:41:46 +05:30
78 changed files with 5927 additions and 311 deletions

1
.gitignore vendored
View File

@@ -24,6 +24,7 @@ pip-log.txt
# Unit test / coverage reports
.coverage
cover
.tox
nosetests.xml
.testrepository

View File

@@ -1,4 +1,4 @@
[gerrit]
host=review.openstack.org
host=review.opendev.org
port=29418
project=openstack/python-karborclient.git

9
.zuul.yaml Normal file
View File

@@ -0,0 +1,9 @@
- project:
templates:
- check-requirements
- openstack-cover-jobs
- openstack-lower-constraints-jobs
- openstack-python-jobs
- openstack-python3-train-jobs
- openstackclient-plugin-jobs
- publish-openstack-docs-pti

View File

@@ -1,14 +1,14 @@
If you would like to contribute to the development of OpenStack, you must
follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
https://docs.openstack.org/infra/manual/developers.html
If you already have a good understanding of how the system works and your
OpenStack accounts are set up, you can skip to the development workflow
section of this documentation to learn how changes to OpenStack should be
submitted for review via the Gerrit tool:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
https://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.

View File

@@ -1,4 +1,4 @@
Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/

View File

@@ -1,6 +0,0 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

View File

@@ -2,26 +2,23 @@
Team and repository tags
========================
.. image:: https://governance.openstack.org/badges/python-karborclient.svg
:target: https://governance.openstack.org/reference/tags/index.html
.. image:: https://governance.openstack.org/tc/badges/python-karborclient.svg
:target: https://governance.openstack.org/tc/reference/tags/index.html
.. Change things from this point on
======
Karbor
======
.. image:: https://img.shields.io/pypi/v/python-karborclient.svg
:target: https://pypi.python.org/pypi/python-karborclient/
:target: https://pypi.org/project/python-karborclient/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/dm/python-karborclient.svg
:target: https://pypi.python.org/pypi/python-karborclient/
:alt: Downloads
Karbor Mission Statement
* Formalize Application Data Protection in OpenStack (APIs, Services, Plugins, )
* Formalize Application Data Protection in OpenStack (APIs, Services, Plugins, ...)
* Be able to protect Any Resource in OpenStack(as well as their dependencies)
* Allow Diversity of vendor solutions, capabilities and implementations
without compromising usability
@@ -34,13 +31,14 @@ Karbor Mission Statement
* `Specs`_
* `How to Contribute`_
.. _PyPi: https://pypi.python.org/pypi/python-karborclient
.. _PyPi: https://pypi.org/project/python-karborclient
.. _Launchpad project: https://launchpad.net/python-karborclient
.. _Blueprints: https://blueprints.launchpad.net/python-karborclient
.. _Bugs: https://bugs.launchpad.net/python-karborclient
.. _Source: https://git.openstack.org/cgit/openstack/python-karborclient
.. _Specs: http://docs.openstack.org/developer/karbor/specs/index.html
.. _How to Contribute: http://docs.openstack.org/infra/manual/developers.html
.. _Source: https://opendev.org/openstack/python-karborclient
.. _Specs: https://docs.openstack.org/karbor/latest/specs/index.html
.. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html
Python Karborclient
@@ -62,7 +60,7 @@ Project status, bugs, and blueprints are tracked on Launchpad:
Developer documentation can be found here:
http://docs.openstack.org/developer/karbor
https://docs.openstack.org/karbor/latest/
Additional resources are linked from the project wiki page:

5
doc/requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
openstackdocstheme>=1.18.1 # Apache-2.0

View File

@@ -23,7 +23,7 @@ sys.path.insert(0, os.path.abspath('../..'))
extensions = [
'sphinx.ext.autodoc',
#'sphinx.ext.intersphinx',
'oslosphinx'
'openstackdocstheme'
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
@@ -55,9 +55,15 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
# html_theme = '_theme'
html_theme = 'openstackdocs'
# html_static_path = ['static']
# openstackdocstheme options
repository_name = 'openstack/python-karborclient'
bug_project = 'python-karborclient'
bug_tag = ''
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project

View File

@@ -1,4 +0,0 @@
============
Contributing
============
.. include:: ../../CONTRIBUTING.rst

View File

@@ -0,0 +1,16 @@
============
Contributing
============
General Info
------------
.. include:: ../../../CONTRIBUTING.rst
Approved Specs
--------------
.. toctree::
:maxdepth: 1
../specs/index

View File

@@ -1,20 +1,20 @@
Welcome to karborclient's documentation!
========================================================
========================================
Contents:
Contents
--------
.. toctree::
:maxdepth: 2
:maxdepth: 1
readme
installation
usage
contributing
install/index
user/index
contributor/index
Indices and tables
==================
------------------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@@ -1 +1,5 @@
============
Introduction
============
.. include:: ../../README.rst

View File

@@ -0,0 +1,13 @@
Specs
=====
This section contains detailed specification documents for
different features inside Karbor Client.
Approved Specs
--------------
.. toctree::
:maxdepth: 1
karbor-support-in-python-openstackclient

View File

@@ -0,0 +1,166 @@
..
This work is licensed under a Creative Commons Attribution 3.0 Unported
License.
http://creativecommons.org/licenses/by/3.0/legalcode
========================================
Karbor support in python-openstackclient
========================================
Implement a new set of karbor commands as python-openstackclient plugins.
Launchpad Blueprint:
https://blueprints.launchpad.net/python-karborclient/+spec/karbor-support-python-openstackclient
Problem Description
===================
python-openstackclient is becoming the default command line client for many
OpenStack projects. Karbor would benefit from implementing all of its client
commands as a single python-openstackclient plugin implemented in the
python-karborclient repository.
Proposed Change
===============
The intent of this spec is to identify the commands to be implemented and
establish conventions for command and argument names. This spec is not
intended to be a full and correct specification of command and argument names.
The details can be left to the code reviews for the commands themselves.
The following conventions will be adopted for command names:
* As the ``OpenStackClient`` convention, the command name shall always take
the following form:
.. code-block:: bash
openstack [<global-options>] <object-1> <action> [<object-2>] \
[command-arguments]
As a example:
The following ``karbor`` commands about plan will be implemented for ``openstack``
initially suggesting these command names:
.. code-block:: bash
karbor plan-create <name> <provider_id> <resources>
openstack data protection plan create <name> <provider_id> <resources>
karbor plan-delete <plan>
openstack data protection plan delete <plan>
karbor plan-list
openstack data protection plan list
karbor plan-show <plan>
openstack data protection plan show <plan>
karbor plan-update <name> <resources> <status>
openstack data protection plan update <name> <resources> <status>
Configuration
-------------
None
Database
--------
None
Public API
----------
None
Public API Security
-------------------
None
Python API
----------
None
CLI (python-karborclient)
-------------------------
A new directory named osc will be created under /karborclient/osc
for the ``OpenStackClient`` plugin and the commands mentioned above.
Internal API
------------
None
Guest Agent
-----------
None
Alternatives
------------
None
Dashboard Impact (UX)
=====================
None
Implementation
==============
Assignee(s)
-----------
Primary assignee:
chenying
Milestones
----------
Work Items
----------
CLI commands as stated above.
Unit tests
Upgrade Implications
====================
None
Dependencies
============
python-openstackclient
osc-lib
Testing
=======
Unit tests will be located in: /karborclient/tests/unit/osc/
Documentation Impact
====================
OpenStack Client adoption list will be updated to include python-karborclient.
References
==========
https://docs.openstack.org/python-openstackclient/latest/
Appendix
========
None

View File

@@ -1,6 +1,6 @@
========
=====
Usage
========
=====
To use karborclient in a project::

View File

@@ -25,14 +25,10 @@ OpenStack Client interface. Handles the REST calls and responses.
# E0202: An attribute inherited from %s hide this method
# pylint: disable=E0202
try:
import simplejson as json
except ImportError:
import json
import time
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import importutils
import requests
@@ -132,7 +128,7 @@ class HTTPClient(object):
def serialize(self, kwargs):
if kwargs.get('json') is not None:
kwargs['headers']['Content-Type'] = 'application/json'
kwargs['data'] = json.dumps(kwargs['json'])
kwargs['data'] = jsonutils.dumps(kwargs['json'])
try:
del kwargs['json']
except KeyError:

View File

@@ -24,7 +24,7 @@ places where actual behavior differs from the spec.
# W0102: Dangerous default value %s as argument
# pylint: disable=W0102
import json
from oslo_serialization import jsonutils
import requests
import six
@@ -58,7 +58,7 @@ class TestResponse(requests.Response):
# Fake the text attribute to streamline Response creation
text = data.get('text', "")
if isinstance(text, (dict, list)):
self._content = json.dumps(text)
self._content = jsonutils.dumps(text)
default_headers = {
"Content-Type": "application/json",
}

View File

@@ -12,13 +12,13 @@
from __future__ import print_function
import json
import os
import sys
import six
import uuid
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
import prettytable
@@ -43,7 +43,7 @@ def env(*vars, **kwargs):
returns the default defined in kwargs.
"""
for v in vars:
value = os.environ.get(v, None)
value = os.environ.get(v)
if value:
return value
return kwargs.get('default', '')
@@ -138,7 +138,7 @@ def dict_prettyprint(val):
:param val: dict.
:return: formatted json string.
"""
return json.dumps(val, indent=2, sort_keys=True)
return jsonutils.dumps(val, indent=2, sort_keys=True)
def json_prettyprint(val):
@@ -147,7 +147,8 @@ def json_prettyprint(val):
:param val: json string.
:return: formatted json string.
"""
return val and json.dumps(json.loads(val), indent=2, sort_keys=True)
return val and jsonutils.dumps(jsonutils.loads(val),
indent=2, sort_keys=True)
def find_resource(manager, name_or_id, *args, **kwargs):

View File

@@ -12,7 +12,7 @@
"""oslo.i18n integration module.
See http://docs.openstack.org/developer/oslo.i18n/usage.html
See https://docs.openstack.org/oslo.i18n/latest/user/usage.html
"""

View File

View File

@@ -0,0 +1,56 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import logging
from osc_lib import utils
LOG = logging.getLogger(__name__)
DEFAULT_DATA_PROTECTION_API_VERSION = '1'
API_VERSION_OPTION = 'os_data_protection_api_version'
API_NAME = 'data_protection'
API_VERSIONS = {
'1': 'karborclient.v1.client.Client',
}
def make_client(instance):
"""Returns a data protection service client"""
data_protection_client = utils.get_client_class(
API_NAME,
instance._api_version[API_NAME],
API_VERSIONS)
LOG.debug('Instantiating data protection client: %s',
data_protection_client)
client = data_protection_client(
auth=instance.auth,
session=instance.session,
service_type="data-protect"
)
return client
def build_option_parser(parser):
"""Hook to add global options"""
parser.add_argument(
'--os-data-protection-api-version',
metavar='<data-protection-api-version>',
default=utils.env(
'OS_DATA_PROTECTION_API_VERSION',
default=DEFAULT_DATA_PROTECTION_API_VERSION),
help='Data protection API version, default=' +
DEFAULT_DATA_PROTECTION_API_VERSION +
' (Env: OS_DATA_PROTECTION_API_VERSION)')
return parser

View File

View File

@@ -0,0 +1,283 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 checkpoint action implementations"""
from osc_lib.command import command
from osc_lib import utils as osc_utils
from oslo_log import log as logging
from oslo_serialization import jsonutils
from karborclient.common.apiclient import exceptions
from karborclient.i18n import _
from karborclient import utils
def format_checkpoint(checkpoint_info):
if 'protection_plan' in checkpoint_info:
plan = checkpoint_info['protection_plan']
checkpoint_info['protection_plan'] = "Name: %s\nId: %s" % (
plan['name'], plan['id'])
if 'resource_graph' in checkpoint_info:
checkpoint_info['resource_graph'] = jsonutils.dumps(jsonutils.loads(
checkpoint_info['resource_graph']), indent=2, sort_keys=True)
checkpoint_info.pop("links", None)
class ListCheckpoints(command.Lister):
_description = _("List checkpoints.")
log = logging.getLogger(__name__ + ".ListCheckpoints")
def get_parser(self, prog_name):
parser = super(ListCheckpoints, self).get_parser(prog_name)
parser.add_argument(
'provider_id',
metavar='<provider_id>',
help=_('ID of provider.'),
)
parser.add_argument(
'--all-projects',
action='store_true',
default=False,
help=_('Include all projects (admin only)'),
)
parser.add_argument(
'--plan_id',
metavar='<plan_id>',
default=None,
help=_('Filters results by a plan ID. Default=None.'),
)
parser.add_argument(
'--start_date',
type=str,
metavar='<start_date>',
default=None,
help=_('Filters results by a start date("Y-m-d"). Default=None.'),
)
parser.add_argument(
'--end_date',
type=str,
metavar='<end_date>',
default=None,
help=_('Filters results by a end date("Y-m-d"). Default=None.'),
)
parser.add_argument(
'--project_id',
metavar='<project_id>',
default=None,
help=_('Filters results by a project ID. Default=None.'),
)
parser.add_argument(
'--marker',
metavar='<checkpoint>',
help=_('The last checkpoint ID of the previous page.'),
)
parser.add_argument(
'--limit',
type=int,
metavar='<num-checkpoints>',
help=_('Maximum number of checkpoints to display.'),
)
parser.add_argument(
'--sort',
metavar="<key>[:<direction>]",
default=None,
help=_("Sort output by selected keys and directions(asc or desc), "
"multiple keys and directions can be "
"specified separated by comma"),
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
data_protection_client = self.app.client_manager.data_protection
all_projects = bool(parsed_args.project_id) or parsed_args.all_projects
search_opts = {
'plan_id': parsed_args.plan_id,
'start_date': parsed_args.start_date,
'end_date': parsed_args.end_date,
'project_id': parsed_args.project_id,
'all_tenants': all_projects
}
data = data_protection_client.checkpoints.list(
provider_id=parsed_args.provider_id, search_opts=search_opts,
marker=parsed_args.marker, limit=parsed_args.limit,
sort=parsed_args.sort)
column_headers = ['Id', 'Project id', 'Status', 'Protection plan',
'Metadata', 'Created at']
def plan_formatter(plan):
return "Name: %s\nId: %s" % (plan['name'],
plan['id'])
formatters = {"Protection plan": plan_formatter}
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers, formatters=formatters
) for s in data))
class ShowCheckpoint(command.ShowOne):
_description = "Shows checkpoint details"
def get_parser(self, prog_name):
parser = super(ShowCheckpoint, self).get_parser(prog_name)
parser.add_argument(
'provider_id',
metavar="<provider_id>",
help=_('Id of provider.')
)
parser.add_argument(
'checkpoint_id',
metavar="<checkpoint_id>",
help=_('Id of checkpoint.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
checkpoint = client.checkpoints.get(parsed_args.provider_id,
parsed_args.checkpoint_id)
format_checkpoint(checkpoint._info)
return zip(*sorted(checkpoint._info.items()))
class CreateCheckpoint(command.ShowOne):
_description = "Creates a checkpoint"
def get_parser(self, prog_name):
parser = super(CreateCheckpoint, self).get_parser(prog_name)
parser.add_argument(
'provider_id',
metavar='<provider_id>',
help=_('ID of provider.')
)
parser.add_argument(
'plan_id',
metavar='<plan_id>',
help=_('ID of plan.')
)
parser.add_argument(
'--extra_info',
type=str,
nargs='*',
metavar='<key=value>',
default=None,
help=_('The extra info of a checkpoint.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
checkpoint_extra_info = None
if parsed_args.extra_info is not None:
checkpoint_extra_info = utils.extract_extra_info(parsed_args)
checkpoint = client.checkpoints.create(parsed_args.provider_id,
parsed_args.plan_id,
checkpoint_extra_info)
format_checkpoint(checkpoint._info)
return zip(*sorted(checkpoint._info.items()))
class DeleteCheckpoint(command.Command):
_description = "Delete checkpoint"
log = logging.getLogger(__name__ + ".DeleteCheckpoint")
def get_parser(self, prog_name):
parser = super(DeleteCheckpoint, self).get_parser(prog_name)
parser.add_argument(
'provider_id',
metavar='<provider_id>',
help=_('Id of provider.')
)
parser.add_argument(
'checkpoint',
metavar='<checkpoint>',
nargs="+",
help=_('Id of checkpoint.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
failure_count = 0
for checkpoint_id in parsed_args.checkpoint:
try:
client.checkpoints.delete(parsed_args.provider_id,
checkpoint_id)
except exceptions.NotFound:
failure_count += 1
self.log.error(
"Failed to delete '{0}'; checkpoint not found".
format(checkpoint_id))
if failure_count == len(parsed_args.checkpoint):
raise exceptions.CommandError(
"Unable to find and delete any of the "
"specified checkpoint.")
class ResetCheckpointState(command.Command):
_description = "Reset checkpoint state"
log = logging.getLogger(__name__ + ".ResetCheckpointState")
def get_parser(self, prog_name):
parser = super(ResetCheckpointState, self).get_parser(prog_name)
parser.add_argument(
'provider_id',
metavar='<provider_id>',
help=_('Id of provider.')
)
parser.add_argument(
'checkpoint',
metavar='<checkpoint>',
nargs="+",
help=_('Id of checkpoint.')
)
parser.add_argument(
'--available',
action='store_const', dest='state',
default='error', const='available',
help=_('Request the checkpoint be reset to "available" state '
'instead of "error" state(the default).'),
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
failure_count = 0
for checkpoint_id in parsed_args.checkpoint:
try:
client.checkpoints.reset_state(
parsed_args.provider_id, checkpoint_id, parsed_args.state)
except exceptions.NotFound:
failure_count += 1
self.log.error(
"Failed to reset state of '{0}'; checkpoint "
"not found".format(checkpoint_id))
except exceptions.Forbidden:
failure_count += 1
self.log.error(
"Failed to reset state of '{0}'; not "
"allowed".format(checkpoint_id))
except exceptions.BadRequest:
failure_count += 1
self.log.error(
"Failed to reset state of '{0}'; invalid input or "
"current checkpoint state".format(checkpoint_id))
if failure_count == len(parsed_args.checkpoint):
raise exceptions.CommandError(
"Unable to find or reset any of the specified "
"checkpoint's state.")

View File

@@ -0,0 +1,111 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 operation_log action implementations"""
from osc_lib.command import command
from osc_lib import utils as osc_utils
from oslo_log import log as logging
from karborclient.i18n import _
class ListOperationLogs(command.Lister):
_description = _("List operation_logs.")
log = logging.getLogger(__name__ + ".ListOperationLogs")
def get_parser(self, prog_name):
parser = super(ListOperationLogs, self).get_parser(prog_name)
parser.add_argument(
'--all-projects',
action='store_true',
default=False,
help=_('Include all projects (admin only)'),
)
parser.add_argument(
'--status',
metavar='<status>',
help=_('Filter results by status'),
)
parser.add_argument(
'--marker',
metavar='<operation_log>',
help=_('The last operation_log ID of the previous page'),
)
parser.add_argument(
'--limit',
type=int,
metavar='<num-operation_logs>',
help=_('Maximum number of operation_logs to display'),
)
parser.add_argument(
'--sort',
metavar="<key>[:<direction>]",
default=None,
help=_("Sort output by selected keys and directions(asc or desc), "
"multiple keys and directions can be "
"specified separated by comma"),
)
parser.add_argument(
'--project',
metavar='<project>',
help=_('Filter results by a project(admin only)')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
data_protection_client = self.app.client_manager.data_protection
all_projects = bool(parsed_args.project) or parsed_args.all_projects
search_opts = {
'all_tenants': all_projects,
'project_id': parsed_args.project,
'status': parsed_args.status,
}
data = data_protection_client.operation_logs.list(
search_opts=search_opts, marker=parsed_args.marker,
limit=parsed_args.limit, sort=parsed_args.sort)
column_headers = ['Id', 'Operation Type', 'Checkpoint id',
'Plan Id', 'Provider id', 'Restore Id',
'Scheduled Operation Id', 'Status',
'Started At', 'Ended At', 'Error Info',
'Extra Info']
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers
) for s in data))
class ShowOperationLog(command.ShowOne):
_description = "Shows operation_log details"
def get_parser(self, prog_name):
parser = super(ShowOperationLog, self).get_parser(prog_name)
parser.add_argument(
'operation_log',
metavar="<operation_log>",
help=_('The UUID of the operation_log.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
operation_log = osc_utils.find_resource(client.operation_logs,
parsed_args.operation_log)
operation_log._info.pop("links", None)
return zip(*sorted(operation_log._info.items()))

View File

@@ -0,0 +1,282 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 plan action implementations"""
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from osc_lib.command import command
from osc_lib import utils as osc_utils
from oslo_log import log as logging
from karborclient.common.apiclient import exceptions
from karborclient.i18n import _
from karborclient import utils
def format_plan(plan_info):
for key in ('resources', 'parameters'):
if key not in plan_info:
continue
plan_info[key] = jsonutils.dumps(plan_info[key],
indent=2, sort_keys=True)
plan_info.pop("links", None)
class ListPlans(command.Lister):
_description = _("List plans.")
log = logging.getLogger(__name__ + ".ListPlans")
def get_parser(self, prog_name):
parser = super(ListPlans, self).get_parser(prog_name)
parser.add_argument(
'--all-projects',
action='store_true',
default=False,
help=_('Include all projects (admin only)'),
)
parser.add_argument(
'--name',
metavar='<name>',
help=_('Filter results by plan name'),
)
parser.add_argument(
'--description',
metavar='<description>',
help=_('Filter results by plan description'),
)
parser.add_argument(
'--status',
metavar='<status>',
help=_('Filter results by status'),
)
parser.add_argument(
'--marker',
metavar='<plan>',
help=_('The last plan ID of the previous page'),
)
parser.add_argument(
'--limit',
type=int,
metavar='<num-plans>',
help=_('Maximum number of plans to display'),
)
parser.add_argument(
'--sort',
metavar="<key>[:<direction>]",
default=None,
help=_("Sort output by selected keys and directions(asc or desc) "
"(default: name:asc), multiple keys and directions can be "
"specified separated by comma"),
)
parser.add_argument(
'--project',
metavar='<project>',
help=_('Filter results by a project(admin only)')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
data_protection_client = self.app.client_manager.data_protection
all_projects = bool(parsed_args.project) or parsed_args.all_projects
search_opts = {
'all_tenants': all_projects,
'project_id': parsed_args.project,
'name': parsed_args.name,
'description': parsed_args.description,
'status': parsed_args.status,
}
data = data_protection_client.plans.list(
search_opts=search_opts, marker=parsed_args.marker,
limit=parsed_args.limit, sort=parsed_args.sort)
column_headers = ['Id', 'Name', 'Description', 'Provider id', 'Status']
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers
) for s in data))
class ShowPlan(command.ShowOne):
_description = "Shows plan details"
def get_parser(self, prog_name):
parser = super(ShowPlan, self).get_parser(prog_name)
parser.add_argument(
'plan',
metavar="<plan>",
help=_('The UUID of the plan.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
plan = osc_utils.find_resource(client.plans, parsed_args.plan)
format_plan(plan._info)
return zip(*sorted(plan._info.items()))
class CreatePlan(command.ShowOne):
_description = "Creates a plan"
def get_parser(self, prog_name):
parser = super(CreatePlan, self).get_parser(prog_name)
parser.add_argument(
'name',
metavar='<name>',
help=_('The name of the plan.')
)
parser.add_argument(
'provider_id',
metavar='<provider_id>',
help=_('The UUID of the provider.')
)
parser.add_argument(
'resources',
metavar='<id=type=name=extra_info,id=type=name=extra_info>',
help=_('Resource in list must be a dict when creating'
' a plan. The keys of resource are id ,type, name and '
'extra_info. The extra_info field is optional.')
)
parser.add_argument(
'--parameters-json',
type=str,
dest='parameters_json',
metavar='<parameters>',
default=None,
help=_('Plan parameters in json format.')
)
parser.add_argument(
'--parameters',
action='append',
metavar='resource_type=<type>[,resource_id=<id>,key=val,...]',
default=[],
help=_('Plan parameters, may be specified multiple times. '
'resource_type: type of resource to apply parameters. '
'resource_id: limit the parameters to a specific resource. '
'Other keys and values: according to provider\'s protect '
'schema.')
)
parser.add_argument(
'--description',
metavar='<description>',
help=_('The description of the plan.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
if not uuidutils.is_uuid_like(parsed_args.provider_id):
raise exceptions.CommandError(
"Invalid provider id provided.")
plan_resources = utils.extract_resources(parsed_args)
utils.check_resources(client, plan_resources)
plan_parameters = utils.extract_parameters(parsed_args)
plan = client.plans.create(parsed_args.name, parsed_args.provider_id,
plan_resources, plan_parameters,
description=parsed_args.description)
format_plan(plan._info)
return zip(*sorted(plan._info.items()))
class UpdatePlan(command.ShowOne):
_description = "Update a plan"
def get_parser(self, prog_name):
parser = super(UpdatePlan, self).get_parser(prog_name)
parser.add_argument(
"plan_id",
metavar="<PLAN ID>",
help=_("Id of plan to update.")
)
parser.add_argument(
"--name",
metavar="<name>",
help=_("A name to which the plan will be renamed.")
)
parser.add_argument(
"--description",
metavar="<description>",
help=_("Description to which the plan will be updated.")
)
parser.add_argument(
"--resources",
metavar="<id=type=name,id=type=name>",
help=_("Resources to which the plan will be updated.")
)
parser.add_argument(
"--status",
metavar="<suspended|started>",
help=_("status to which the plan will be updated.")
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
data = {}
if parsed_args.name is not None:
data['name'] = parsed_args.name
if parsed_args.description is not None:
data['description'] = parsed_args.description
if parsed_args.resources is not None:
plan_resources = utils.extract_resources(parsed_args)
data['resources'] = plan_resources
if parsed_args.status is not None:
data['status'] = parsed_args.status
try:
plan = osc_utils.find_resource(client.plans,
parsed_args.plan_id)
plan = client.plans.update(plan.id, data)
except exceptions.NotFound:
raise exceptions.CommandError(
"Plan %s not found" % parsed_args.plan_id)
else:
format_plan(plan._info)
return zip(*sorted(plan._info.items()))
class DeletePlan(command.Command):
_description = "Delete plan"
def get_parser(self, prog_name):
parser = super(DeletePlan, self).get_parser(prog_name)
parser.add_argument(
'plan',
metavar='<plan>',
nargs="+",
help=_('ID of plan.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
failure_count = 0
for plan_id in parsed_args.plan:
try:
plan = osc_utils.find_resource(client.plans, plan_id)
client.plans.delete(plan.id)
except exceptions.NotFound:
failure_count += 1
print("Failed to delete '{0}'; plan not "
"found".format(plan_id))
if failure_count == len(parsed_args.plan):
raise exceptions.CommandError(
"Unable to find and delete any of the "
"specified plan.")

View File

@@ -0,0 +1,198 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 protectables action implementations"""
import functools
from osc_lib.command import command
from osc_lib import utils as osc_utils
from oslo_log import log as logging
from oslo_serialization import jsonutils
from karborclient.i18n import _
from karborclient import utils
class ListProtectables(command.Lister):
_description = _("List protectable types.")
log = logging.getLogger(__name__ + ".ListProtectables")
def get_parser(self, prog_name):
parser = super(ListProtectables, self).get_parser(prog_name)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
data_protection_client = self.app.client_manager.data_protection
data = data_protection_client.protectables.list()
column_headers = ['Protectable type']
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers
) for s in data))
class ShowProtectable(command.ShowOne):
_description = "Shows protectable type details"
def get_parser(self, prog_name):
parser = super(ShowProtectable, self).get_parser(prog_name)
parser.add_argument(
'protectable_type',
metavar="<protectable_type>",
help=_('Protectable type.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
protectable = osc_utils.find_resource(client.protectables,
parsed_args.protectable_type)
protectable._info.pop("links", None)
if 'dependent_types' in protectable._info:
protectable._info['dependent_types'] = "\n".join(
protectable._info['dependent_types'])
return zip(*sorted(protectable._info.items()))
class ListProtectableInstances(command.Lister):
_description = _("List protectable instances.")
log = logging.getLogger(__name__ + ".ListProtectableInstances")
def get_parser(self, prog_name):
parser = super(ListProtectableInstances, self).get_parser(prog_name)
parser.add_argument(
'protectable_type',
metavar="<protectable_type>",
help=_('Type of protectable.')
)
parser.add_argument(
'--type',
metavar="<type>",
default=None,
help=_('Filters results by protectable type. Default=None.')
)
parser.add_argument(
'--marker',
metavar="<protectable_instance>",
default=None,
help=_('The last protectable instance ID of the previous page.')
)
parser.add_argument(
'--limit',
metavar="<num-protectable_instances>",
default=None,
help=_('Maximum number of protectable instances to display.')
)
parser.add_argument(
'--sort',
metavar="<key>[:<direction>]",
default=None,
help=_("Sort output by selected keys and directions(asc or desc), "
"multiple keys and directions can be "
"specified separated by comma"),
)
parser.add_argument(
'--parameters',
type=str,
nargs='*',
metavar="<key=value>",
default=None,
help=_('List instances by parameters key and value pair. '
'Default=None.')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
data_protection_client = self.app.client_manager.data_protection
search_opts = {
'type': parsed_args.type,
'parameters': (utils.extract_instances_parameters(parsed_args)
if parsed_args.parameters else None),
}
data = data_protection_client.protectables.list_instances(
parsed_args.protectable_type, search_opts=search_opts,
marker=parsed_args.marker, limit=parsed_args.limit,
sort=parsed_args.sort)
column_headers = ['Id', 'Type', 'Name', 'Dependent resources',
'Extra info']
json_dumps = functools.partial(jsonutils.dumps,
indent=2, sort_keys=True)
formatters = {
"Extra info": json_dumps,
"Dependent resources": json_dumps,
}
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers, formatters=formatters,
) for s in data))
class ShowProtectableInstance(command.ShowOne):
_description = "Shows protectable instance details"
def get_parser(self, prog_name):
parser = super(ShowProtectableInstance, self).get_parser(prog_name)
parser.add_argument(
'protectable_type',
metavar="<protectable_type>",
help=_('Protectable type.')
)
parser.add_argument(
'protectable_id',
metavar="<protectable_id>",
help=_('Protectable instance id.')
)
parser.add_argument(
'--parameters',
type=str,
nargs='*',
metavar="<key=value>",
default=None,
help=_('Show a instance by parameters key and value pair. '
'Default=None.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
search_opts = {
'parameters': (utils.extract_instances_parameters(parsed_args)
if parsed_args.parameters else None),
}
instance = client.protectables.get_instance(
parsed_args.protectable_type,
parsed_args.protectable_id,
search_opts=search_opts)
json_dumps = functools.partial(jsonutils.dumps,
indent=2, sort_keys=True)
instance._info.pop("links", None)
for key in ('extra_info', 'dependent_resources'):
if key not in instance._info:
continue
instance._info[key] = json_dumps(instance._info[key])
return zip(*sorted(instance._info.items()))

View File

@@ -0,0 +1,105 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 provider action implementations"""
import functools
from osc_lib.command import command
from osc_lib import utils as osc_utils
from oslo_log import log as logging
from oslo_serialization import jsonutils
from karborclient.i18n import _
class ListProviders(command.Lister):
_description = _("List providers.")
log = logging.getLogger(__name__ + ".ListProviders")
def get_parser(self, prog_name):
parser = super(ListProviders, self).get_parser(prog_name)
parser.add_argument(
'--name',
metavar='<name>',
help=_('Filters results by a name. Default=None.'),
)
parser.add_argument(
'--description',
metavar='<description>',
help=_('Filters results by a description. Default=None.'),
)
parser.add_argument(
'--marker',
metavar='<provider>',
help=_('The last provider ID of the previous page'),
)
parser.add_argument(
'--limit',
type=int,
metavar='<num-providers>',
help=_('Maximum number of providers to display'),
)
parser.add_argument(
'--sort',
metavar="<key>[:<direction>]",
default=None,
help=_("Sort output by selected keys and directions(asc or desc) "
"(default: name:asc), multiple keys and directions can be "
"specified separated by comma"),
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
data_protection_client = self.app.client_manager.data_protection
search_opts = {
'name': parsed_args.name,
'description': parsed_args.description,
}
data = data_protection_client.providers.list(
search_opts=search_opts, marker=parsed_args.marker,
limit=parsed_args.limit, sort=parsed_args.sort)
column_headers = ['Id', 'Name', 'Description']
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers
) for s in data))
class ShowProvider(command.ShowOne):
_description = "Shows provider details"
def get_parser(self, prog_name):
parser = super(ShowProvider, self).get_parser(prog_name)
parser.add_argument(
'provider',
metavar="<provider>",
help=_('The UUID of the provider.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
provider = osc_utils.find_resource(client.providers,
parsed_args.provider)
json_dumps = functools.partial(jsonutils.dumps,
indent=2, sort_keys=True)
provider._info.pop("links", None)
if 'extended_info_schema' in provider._info:
provider._info['extended_info_schema'] = json_dumps(
provider._info['extended_info_schema'])
return zip(*sorted(provider._info.items()))

View File

@@ -0,0 +1,75 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 quota classes action implementations"""
from osc_lib.command import command
def quota_class_set_pretty_show(quota_classes):
"""Convert quotas class object to dict and display."""
new_quota_classes = []
for quota_k, quota_v in sorted(quota_classes.to_dict().items()):
if isinstance(quota_v, dict):
quota_v = '\n'.join(
['%s = %s' % (k, v) for k, v in sorted(quota_v.items())])
new_quota_classes.append((quota_k, quota_v))
return new_quota_classes
class ShowQuotaClasses(command.ShowOne):
_description = "Shows Quota classes."
def get_parser(self, prog_name):
parser = super(ShowQuotaClasses, self).get_parser(prog_name)
parser.add_argument(
'class_name',
metavar='<class_name>',
help='Name of quota class to list the quotas for.')
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
result = client.quota_classes.get(parsed_args.class_name)
quota_classes = quota_class_set_pretty_show(result)
return zip(*sorted(quota_classes))
class UpdateQuotaClasses(command.ShowOne):
_description = "Update the quotas for a quota class (Admin only)."
def get_parser(self, prog_name):
parser = super(UpdateQuotaClasses, self).get_parser(prog_name)
parser.add_argument(
'class_name',
metavar='<class_name>',
help='Name of quota class to set the quotas for.')
parser.add_argument(
'--plans',
metavar='<plans>',
type=int,
default=None,
help='New value for the "plans" quota.')
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
class_name = parsed_args.class_name
data = {
"plans": parsed_args.plans,
}
result = client.quota_classes.update(class_name, data)
quota_classes = quota_class_set_pretty_show(result)
return zip(*sorted(quota_classes))

View File

@@ -0,0 +1,108 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 quotas action implementations"""
from osc_lib.command import command
def quota_set_pretty_show(quotas):
"""Convert quotas object to dict and display."""
new_quotas = []
for quota_k, quota_v in sorted(quotas.to_dict().items()):
if isinstance(quota_v, dict):
quota_v = '\n'.join(
['%s = %s' % (k, v) for k, v in sorted(quota_v.items())])
new_quotas.append((quota_k, quota_v))
return new_quotas
class ShowQuotas(command.ShowOne):
_description = "Shows Quotas"
def get_parser(self, prog_name):
parser = super(ShowQuotas, self).get_parser(prog_name)
parser.add_argument(
'--tenant',
metavar='<tenant>',
default=None,
help='ID of tenant to list the quotas for.')
parser.add_argument(
'--detail',
action='store_true',
help='Optional flag to indicate whether to show quota in detail. '
'Default false.')
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
project_id = parsed_args.tenant or client.http_client.get_project_id()
kwargs = {
"project_id": project_id,
"detail": parsed_args.detail,
}
result = client.quotas.get(**kwargs)
quotas = quota_set_pretty_show(result)
return zip(*sorted(quotas))
class ShowDefaultQuotas(command.ShowOne):
_description = "Shows default Quotas"
def get_parser(self, prog_name):
parser = super(ShowDefaultQuotas, self).get_parser(prog_name)
parser.add_argument(
'--tenant',
metavar='<tenant>',
default=None,
help='ID of tenant to list the quotas for.')
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
project_id = parsed_args.tenant or client.http_client.get_project_id()
result = client.quotas.defaults(project_id)
quotas = quota_set_pretty_show(result)
return zip(*sorted(quotas))
class UpdateQuotas(command.ShowOne):
_description = "Updates Quotas"
def get_parser(self, prog_name):
parser = super(UpdateQuotas, self).get_parser(prog_name)
parser.add_argument(
'tenant',
metavar='<tenant>',
help='ID of tenant to set the quotas for.')
parser.add_argument(
'--plans',
metavar='<plans>',
type=int,
default=None,
help='New value for the "plans" quota.')
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
project_id = parsed_args.tenant
data = {
"plans": parsed_args.plans,
}
result = client.quotas.update(project_id, data)
quotas = quota_set_pretty_show(result)
return zip(*sorted(quotas))

View File

@@ -0,0 +1,214 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 restore action implementations"""
import functools
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from osc_lib.command import command
from osc_lib import utils as osc_utils
from oslo_log import log as logging
from karborclient.common.apiclient import exceptions
from karborclient.i18n import _
from karborclient import utils
def format_restore(restore_info):
for key in ('parameters', 'resources_status',
'resources_reason'):
if key not in restore_info:
continue
restore_info[key] = jsonutils.dumps(restore_info[key],
indent=2, sort_keys=True)
restore_info.pop("links", None)
class ListRestores(command.Lister):
_description = _("List restores.")
log = logging.getLogger(__name__ + ".ListRestores")
def get_parser(self, prog_name):
parser = super(ListRestores, self).get_parser(prog_name)
parser.add_argument(
'--all-projects',
action='store_true',
default=False,
help=_('Include all projects (admin only)'),
)
parser.add_argument(
'--status',
metavar='<status>',
help=_('Filter results by status'),
)
parser.add_argument(
'--marker',
metavar='<restore>',
help=_('The last restore ID of the previous page'),
)
parser.add_argument(
'--limit',
type=int,
metavar='<num-restores>',
help=_('Maximum number of restores to display'),
)
parser.add_argument(
'--sort',
metavar="<key>[:<direction>]",
default=None,
help=_("Sort output by selected keys and directions(asc or desc), "
"multiple keys and directions can be "
"specified separated by comma"),
)
parser.add_argument(
'--project',
metavar='<project>',
help=_('Filter results by a project(admin only)')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
data_protection_client = self.app.client_manager.data_protection
all_projects = bool(parsed_args.project) or parsed_args.all_projects
search_opts = {
'all_tenants': all_projects,
'project_id': parsed_args.project,
'status': parsed_args.status,
}
data = data_protection_client.restores.list(
search_opts=search_opts, marker=parsed_args.marker,
limit=parsed_args.limit, sort=parsed_args.sort)
column_headers = ['Id', 'Project id', 'Provider id', 'Checkpoint id',
'Restore target', 'Parameters', 'Status']
json_dumps = functools.partial(jsonutils.dumps,
indent=2,
sort_keys=True)
formatters = {
"Parameters": json_dumps,
}
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers, formatters=formatters,
) for s in data))
class ShowRestore(command.ShowOne):
_description = "Shows restore details"
def get_parser(self, prog_name):
parser = super(ShowRestore, self).get_parser(prog_name)
parser.add_argument(
'restore',
metavar="<restore>",
help=_('The UUID of the restore.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
restore = osc_utils.find_resource(client.restores, parsed_args.restore)
format_restore(restore._info)
return zip(*sorted(restore._info.items()))
class CreateRestore(command.ShowOne):
_description = "Creates a restore"
def get_parser(self, prog_name):
parser = super(CreateRestore, self).get_parser(prog_name)
parser.add_argument(
'provider_id',
metavar='<provider_id>',
help=_('The UUID of the provider.')
)
parser.add_argument(
'checkpoint_id',
metavar='<checkpoint_id>',
help=_('The UUID of the checkpoint.')
)
parser.add_argument(
'--restore_target',
metavar='<restore_target>',
help=_('The target of the restore operation.')
)
parser.add_argument(
'--restore_username',
metavar='<restore_username>',
default=None,
help=_('Username to restore target.')
)
parser.add_argument(
'--restore_password',
metavar='<restore_password>',
default=None,
help=_('Password to restore target.')
)
parser.add_argument(
'--parameters-json',
type=str,
dest='parameters_json',
metavar='<parameters>',
default=None,
help=_('Restore parameters in json format.')
)
parser.add_argument(
'--parameters',
action='append',
metavar='resource_type=<type>[,resource_id=<id>,key=val,...]',
default=[],
help=_("Restore parameters, may be specified multiple times. "
"resource_type: type of resource to apply parameters. "
"resource_id: limit the parameters to a specific resource. "
"Other keys and values: according to provider\'s "
"restore schema.")
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
if not uuidutils.is_uuid_like(parsed_args.provider_id):
raise exceptions.CommandError(
"Invalid provider id provided.")
if not uuidutils.is_uuid_like(parsed_args.checkpoint_id):
raise exceptions.CommandError(
"Invalid checkpoint id provided.")
restore_parameters = utils.extract_parameters(parsed_args)
restore_auth = None
if parsed_args.restore_target is not None:
if parsed_args.restore_username is None:
raise exceptions.CommandError(
"Must specify username for restore_target.")
if parsed_args.restore_password is None:
raise exceptions.CommandError(
"Must specify password for restore_target.")
restore_auth = {
'type': 'password',
'username': parsed_args.restore_username,
'password': parsed_args.restore_password,
}
restore = client.restores.create(parsed_args.provider_id,
parsed_args.checkpoint_id,
parsed_args.restore_target,
restore_parameters, restore_auth)
format_restore(restore._info)
return zip(*sorted(restore._info.items()))

View File

@@ -0,0 +1,222 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 scheduled_operations action implementations"""
import functools
import six
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from osc_lib.command import command
from osc_lib import utils as osc_utils
from oslo_log import log as logging
from karborclient.common.apiclient import exceptions
from karborclient.i18n import _
def format_scheduledoperation(scheduledoperation_info):
for key in ('operation_definition', ):
if key not in scheduledoperation_info:
continue
scheduledoperation_info[key] = jsonutils.dumps(
scheduledoperation_info[key], indent=2, sort_keys=True)
scheduledoperation_info.pop("links", None)
class ListScheduledOperations(command.Lister):
_description = _("List scheduled_operations.")
log = logging.getLogger(__name__ + ".ListScheduledOperations")
def get_parser(self, prog_name):
parser = super(ListScheduledOperations, self).get_parser(prog_name)
parser.add_argument(
'--all-projects',
action='store_true',
default=False,
help=_('Shows details for all tenants. Admin only.'),
)
parser.add_argument(
'--name',
metavar='<name>',
help=_('Filters results by a name. Default=None.'),
)
parser.add_argument(
'--operation_type',
metavar='<operation_type>',
default=None,
help=_('Filters results by a type. Default=None.'),
)
parser.add_argument(
'--trigger_id',
metavar='<trigger_id>',
default=None,
help=_('Filters results by a trigger id. Default=None.'),
)
parser.add_argument(
'--operation_definition',
metavar='<operation_definition>',
default=None,
help=_('Filters results by a operation definition. Default=None.'),
)
parser.add_argument(
'--marker',
metavar='<scheduled_operations>',
help=_('The last scheduled_operations ID of the previous page'),
)
parser.add_argument(
'--limit',
type=int,
metavar='<num-scheduled_operations>',
help=_('Maximum number of scheduled_operations to display'),
)
parser.add_argument(
'--sort',
metavar="<key>[:<direction>]",
default=None,
help=_("Sort output by selected keys and directions(asc or desc) "
"(default: name:asc), multiple keys and directions can be "
"specified separated by comma"),
)
parser.add_argument(
'--project',
metavar='<project>',
help=_('Filter results by a project(admin only)')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
data_protection_client = self.app.client_manager.data_protection
all_projects = bool(parsed_args.project) or parsed_args.all_projects
search_opts = {
'all_tenants': all_projects,
'project_id': parsed_args.project,
'name': parsed_args.name,
'operation_type': parsed_args.operation_type,
'trigger_id': parsed_args.trigger_id,
'operation_definition': parsed_args.operation_definition,
}
data = data_protection_client.scheduled_operations.list(
search_opts=search_opts, marker=parsed_args.marker,
limit=parsed_args.limit, sort=parsed_args.sort)
column_headers = ['Id', 'Name', 'Operation Type', 'Trigger Id',
'Operation Definition']
json_dumps = functools.partial(jsonutils.dumps,
indent=2,
sort_keys=True)
formatters = {
"Operation Definition": json_dumps,
}
return (column_headers,
list(osc_utils.get_item_properties(
s, column_headers, formatters=formatters,
) for s in data))
class ShowScheduledOperation(command.ShowOne):
_description = "Shows scheduled_operation details"
def get_parser(self, prog_name):
parser = super(ShowScheduledOperation, self).get_parser(prog_name)
parser.add_argument(
'scheduledoperation',
metavar="<scheduledoperation>",
help=_('The UUID of the scheduledoperation.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
so = osc_utils.find_resource(client.scheduled_operations,
parsed_args.scheduledoperation)
format_scheduledoperation(so._info)
return zip(*sorted(six.iteritems(so._info)))
class CreateScheduledOperation(command.ShowOne):
_description = "Creates a scheduled operation"
def get_parser(self, prog_name):
parser = super(CreateScheduledOperation, self).get_parser(prog_name)
parser.add_argument(
'name',
metavar='<name>',
help=_('The name of the scheduled operation.')
)
parser.add_argument(
'operation_type',
metavar='<operation_type>',
help=_('Operation Type of scheduled operation.')
)
parser.add_argument(
'trigger_id',
metavar='<trigger_id>',
help=_('Trigger id of scheduled operation.')
)
parser.add_argument(
'operation_definition',
metavar='<key=value,key=value>',
help=_('Operation definition of scheduled operation.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
if not uuidutils.is_uuid_like(parsed_args.trigger_id):
raise exceptions.CommandError(
"Invalid trigger id provided.")
so = client.scheduled_operations.create(
parsed_args.name, parsed_args.operation_type,
parsed_args.trigger_id, parsed_args.operation_definition)
format_scheduledoperation(so._info)
return zip(*sorted(six.iteritems(so._info)))
class DeleteScheduledOperation(command.Command):
_description = "Delete scheduled operation"
def get_parser(self, prog_name):
parser = super(DeleteScheduledOperation, self).get_parser(prog_name)
parser.add_argument(
'scheduledoperation',
metavar='<scheduledoperation>',
nargs="+",
help=_('ID of scheduled operation.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
failure_count = 0
for so_id in parsed_args.scheduledoperation:
try:
so = osc_utils.find_resource(client.scheduled_operations,
so_id)
client.scheduled_operations.delete(so.id)
except exceptions.NotFound:
failure_count += 1
print("Failed to delete '%s'; scheduled operation "
"not found" % so_id)
if failure_count == len(parsed_args.scheduledoperation):
raise exceptions.CommandError(
"Unable to find and delete any of the "
"specified scheduled operation.")

View File

@@ -0,0 +1,99 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 os-services action implementations"""
from osc_lib.command import command
from osc_lib import utils as osc_utils
from oslo_log import log as logging
from karborclient.i18n import _
class ListServices(command.Lister):
_description = _("List services.")
log = logging.getLogger(__name__ + ".ListServices")
def get_parser(self, prog_name):
parser = super(ListServices, self).get_parser(prog_name)
parser.add_argument(
'--host',
metavar='<host>',
help=_('Filter results by host'),
)
parser.add_argument(
'--binary',
metavar='<binary>',
help=_('Filter results by binary'),
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
data_protection_client = self.app.client_manager.data_protection
data = data_protection_client.services.list(
host=parsed_args.host,
binary=parsed_args.binary
)
column_headers = ["Id", "Binary", "Host", "Status", "State",
"Updated_at", "Disabled Reason"]
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers
) for s in data))
class EnableService(command.ShowOne):
_description = _('Enable service')
def get_parser(self, prog_name):
parser = super(EnableService, self).get_parser(prog_name)
parser.add_argument(
'service_id',
metavar='<service_id>',
help=_('The ID of the service.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
service = client.services.enable(parsed_args.service_id)
return zip(*sorted(service._info.items()))
class DisableService(command.ShowOne):
_description = _('Disable service')
def get_parser(self, prog_name):
parser = super(DisableService, self).get_parser(prog_name)
parser.add_argument(
'service_id',
metavar='<service_id>',
help=_('The ID of the service.'),
)
parser.add_argument(
'--reason',
metavar='<reason>',
help=_('Reason for disabling the service.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
if parsed_args.reason:
service = client.services.disable_log_reason(
parsed_args.service_id, parsed_args.reason)
else:
service = client.services.disable(parsed_args.service_id)
return zip(*sorted(service._info.items()))

View File

@@ -0,0 +1,229 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 triggers action implementations"""
from osc_lib.command import command
from osc_lib import utils as osc_utils
from oslo_log import log as logging
from karborclient.common.apiclient import exceptions
from karborclient.i18n import _
from karborclient import utils
class ListTriggers(command.Lister):
_description = _("List triggers.")
log = logging.getLogger(__name__ + ".ListTriggers")
def get_parser(self, prog_name):
parser = super(ListTriggers, self).get_parser(prog_name)
parser.add_argument(
'--all-projects',
action='store_true',
default=False,
help=_('Shows details for all tenants. Admin only.'),
)
parser.add_argument(
'--name',
metavar='<name>',
default=None,
help=_('Filters results by a name. Default=None.'),
)
parser.add_argument(
'--type',
metavar='<type>',
default=None,
help=_('Filters results by a type. Default=None.'),
)
parser.add_argument(
'--properties',
metavar='<properties>',
default=None,
help=_('Filters results by a properties. Default=None.'),
)
parser.add_argument(
'--marker',
metavar='<trigger>',
help=_('The last trigger ID of the previous page'),
)
parser.add_argument(
'--limit',
type=int,
metavar='<num-triggers>',
help=_('Maximum number of triggers to display'),
)
parser.add_argument(
'--sort',
metavar="<key>[:<direction>]",
default=None,
help=_("Sort output by selected keys and directions(asc or desc) "
"(default: name:asc), multiple keys and directions can be "
"specified separated by comma"),
)
parser.add_argument(
'--project',
metavar='<project>',
help=_('Display information from single tenant (Admin only).')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
data_protection_client = self.app.client_manager.data_protection
all_projects = bool(parsed_args.project) or parsed_args.all_projects
search_opts = {
'all_tenants': all_projects,
'project_id': parsed_args.project,
'name': parsed_args.name,
'type': parsed_args.type,
'properties': parsed_args.properties,
}
data = data_protection_client.triggers.list(
search_opts=search_opts, marker=parsed_args.marker,
limit=parsed_args.limit, sort=parsed_args.sort)
column_headers = ['Id', 'Name', 'Type', 'Properties']
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers
) for s in data))
class ShowTrigger(command.ShowOne):
_description = "Shows trigger details"
def get_parser(self, prog_name):
parser = super(ShowTrigger, self).get_parser(prog_name)
parser.add_argument(
'trigger',
metavar="<trigger>",
help=_('The UUID of the trigger.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
trigger = osc_utils.find_resource(client.triggers, parsed_args.trigger)
trigger._info.pop("links", None)
return zip(*sorted(trigger._info.items()))
class CreateTrigger(command.ShowOne):
_description = "Creates a trigger"
def get_parser(self, prog_name):
parser = super(CreateTrigger, self).get_parser(prog_name)
parser.add_argument(
'name',
metavar='<name>',
help=_('The name of the trigger.')
)
parser.add_argument(
'type',
metavar='<type>',
help=_('Type of trigger.')
)
parser.add_argument(
'properties',
metavar='<key=value,key=value>',
help=_('Properties of trigger.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
trigger = client.triggers.create(parsed_args.name, parsed_args.type,
parsed_args.properties)
trigger._info.pop("links", None)
return zip(*sorted(trigger._info.items()))
class UpdateTrigger(command.ShowOne):
_description = "Update a trigger"
def get_parser(self, prog_name):
parser = super(UpdateTrigger, self).get_parser(prog_name)
parser.add_argument(
"trigger_id",
metavar="<TRIGGER ID>",
help=_("Id of trigger to update.")
)
parser.add_argument(
"--name",
metavar="<name>",
help=_("A name to which the trigger will be renamed.")
)
parser.add_argument(
"--properties",
metavar="<key=value,key=value>",
help=_("Properties of trigger which will be updated.")
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
data = {}
if parsed_args.name is not None:
data['name'] = parsed_args.name
if parsed_args.properties is not None:
trigger_properties = utils.extract_properties(parsed_args)
data['properties'] = trigger_properties
try:
trigger = osc_utils.find_resource(client.triggers,
parsed_args.trigger_id)
trigger = client.triggers.update(trigger.id, data)
except exceptions.NotFound:
raise exceptions.CommandError(
"Trigger %s not found" % parsed_args.trigger_id)
else:
trigger._info.pop("links", None)
return zip(*sorted(trigger._info.items()))
class DeleteTrigger(command.Command):
_description = "Delete trigger"
log = logging.getLogger(__name__ + ".DeleteTrigger")
def get_parser(self, prog_name):
parser = super(DeleteTrigger, self).get_parser(prog_name)
parser.add_argument(
'trigger',
metavar='<trigger>',
nargs="+",
help=_('ID of trigger.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
failure_count = 0
for trigger_id in parsed_args.trigger:
try:
trigger = osc_utils.find_resource(client.triggers, trigger_id)
client.triggers.delete(trigger.id)
except exceptions.NotFound:
failure_count += 1
self.log.error(
"Failed to delete '{0}'; trigger not found".
format(trigger_id))
if failure_count == len(parsed_args.trigger):
raise exceptions.CommandError(
"Unable to find and delete any of the "
"specified trigger.")

View File

@@ -0,0 +1,184 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Data protection V1 verification action implementations"""
import functools
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from osc_lib.command import command
from osc_lib import utils as osc_utils
from karborclient.common.apiclient import exceptions
from karborclient.i18n import _
from karborclient import utils
def format_verification(verification_info):
for key in ('parameters', 'resources_status',
'resources_reason'):
if key not in verification_info:
continue
verification_info[key] = jsonutils.dumps(verification_info[key],
indent=2, sort_keys=True)
verification_info.pop("links", None)
class ListVerifications(command.Lister):
_description = _("List verifications.")
log = logging.getLogger(__name__ + ".ListVerifications")
def get_parser(self, prog_name):
parser = super(ListVerifications, self).get_parser(prog_name)
parser.add_argument(
'--all-projects',
action='store_true',
default=False,
help=_('Include all projects (admin only)'),
)
parser.add_argument(
'--status',
metavar='<status>',
help=_('Filter results by status'),
)
parser.add_argument(
'--marker',
metavar='<verification>',
help=_('The last verification ID of the previous page'),
)
parser.add_argument(
'--limit',
type=int,
metavar='<num-verifications>',
help=_('Maximum number of verifications to display'),
)
parser.add_argument(
'--sort',
metavar="<key>[:<direction>]",
default=None,
help=_("Sort output by selected keys and directions(asc or desc), "
"multiple keys and directions can be "
"specified separated by comma"),
)
parser.add_argument(
'--project',
metavar='<project>',
help=_('Filter results by a project(admin only)')
)
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
data_protection_client = self.app.client_manager.data_protection
all_projects = bool(parsed_args.project) or parsed_args.all_projects
search_opts = {
'all_tenants': all_projects,
'project_id': parsed_args.project,
'status': parsed_args.status,
}
data = data_protection_client.verifications.list(
search_opts=search_opts, marker=parsed_args.marker,
limit=parsed_args.limit, sort=parsed_args.sort)
column_headers = ['Id', 'Project id', 'Provider id', 'Checkpoint id',
'Parameters', 'Status']
json_dumps = functools.partial(jsonutils.dumps,
indent=2,
sort_keys=True)
formatters = {
"Parameters": json_dumps,
}
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers, formatters=formatters,
) for s in data))
class ShowVerification(command.ShowOne):
_description = "Shows verification details"
def get_parser(self, prog_name):
parser = super(ShowVerification, self).get_parser(prog_name)
parser.add_argument(
'verification',
metavar="<verification>",
help=_('The UUID of the verification.')
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
verification = osc_utils.find_resource(client.verifications,
parsed_args.verification)
format_verification(verification._info)
return zip(*sorted(verification._info.items()))
class CreateVerification(command.ShowOne):
_description = "Creates a verification"
def get_parser(self, prog_name):
parser = super(CreateVerification, self).get_parser(prog_name)
parser.add_argument(
'provider_id',
metavar='<provider_id>',
help=_('The UUID of the provider.')
)
parser.add_argument(
'checkpoint_id',
metavar='<checkpoint_id>',
help=_('The UUID of the checkpoint.')
)
parser.add_argument(
'--parameters-json',
type=str,
dest='parameters_json',
metavar='<parameters>',
default=None,
help=_('Verification parameters in json format.')
)
parser.add_argument(
'--parameters',
action='append',
metavar='resource_type=<type>[,resource_id=<id>,key=val,...]',
default=[],
help=_("Verification parameters, may be specified multiple times. "
"resource_type: type of resource to apply parameters. "
"resource_id: limit the parameters to a specific resource. "
"Other keys and values: according to provider\'s "
"verification schema.")
)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.data_protection
if not uuidutils.is_uuid_like(parsed_args.provider_id):
raise exceptions.CommandError(
"Invalid provider id provided.")
if not uuidutils.is_uuid_like(parsed_args.checkpoint_id):
raise exceptions.CommandError(
"Invalid checkpoint id provided.")
verification_parameters = utils.extract_parameters(parsed_args)
verification = client.verifications.create(parsed_args.provider_id,
parsed_args.checkpoint_id,
verification_parameters)
format_verification(verification._info)
return zip(*sorted(verification._info.items()))

View File

@@ -165,19 +165,8 @@ class KarborShell(object):
self._find_actions(subparsers, submodule)
self._find_actions(subparsers, self)
self._add_bash_completion_subparser(subparsers)
return parser
def _add_bash_completion_subparser(self, subparsers):
subparser = subparsers.add_parser(
'bash_completion',
add_help=False,
formatter_class=HelpFormatter
)
self.subcommands['bash_completion'] = subparser
subparser.set_defaults(func=self.do_bash_completion)
def _find_actions(self, subparsers, actions_module):
for attr in (a for a in dir(actions_module) if a.startswith('do_')):
# I prefer to be hypen-separated instead of underscores.
@@ -411,9 +400,9 @@ class KarborShell(object):
if args.api_timeout:
kwargs['timeout'] = args.api_timeout
client = karbor_client.Client(api_version, endpoint, **kwargs)
self.cs = karbor_client.Client(api_version, endpoint, **kwargs)
args.func(client, args)
args.func(self.cs, args)
def do_bash_completion(self, args):
"""Prints all of the commands and options to stdout."""
@@ -425,7 +414,6 @@ class KarborShell(object):
options.add(option)
commands.remove('bash-completion')
commands.remove('bash_completion')
print(' '.join(commands | options))
@utils.arg('command', metavar='<subcommand>', nargs='?',

View File

@@ -44,11 +44,11 @@ class TestResponse(requests.Response):
self._text = None
super(TestResponse, self)
if isinstance(data, dict):
self.status_code = data.get('status_code', None)
self.headers = data.get('headers', None)
self.status_code = data.get('status_code')
self.headers = data.get('headers')
self.reason = data.get('reason', '')
# Fake the text attribute to streamline Response creation
self._text = data.get('text', None)
self._text = data.get('text')
else:
self.status_code = data

View File

View File

@@ -0,0 +1 @@
__author__ = 'c00179918'

View File

@@ -0,0 +1,29 @@
# Copyright (c) 2015 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from osc_lib.tests import utils
class TestDataProtection(utils.TestCommand):
def setUp(self):
super(TestDataProtection, self).setUp()
self.app.client_manager.data_protection = mock.Mock()
self.app.client_manager.network = mock.Mock()
self.app.client_manager.compute = mock.Mock()
self.app.client_manager.volume = mock.Mock()

View File

@@ -0,0 +1,254 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from oslo_serialization import jsonutils
from karborclient.osc.v1 import checkpoints as osc_checkpoints
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import checkpoints
CHECKPOINT_INFO = {
"id": "dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
"project_id": "e486a2f49695423ca9c47e589b948108",
"status": "available",
"protection_plan": {
"id": "3523a271-68aa-42f5-b9ba-56e5200a2ebb",
"name": "My application",
"provider_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
"resources": [{
"id": "99777fdd-8a5b-45ab-ba2c-52420008103f",
"type": "OS::Glance::Image",
"name": "cirros-0.3.4-x86_64-uec"}]
},
"resource_graph": jsonutils.dumps(
"[{'0x0': ['OS::Glance::Image', "
"'99777fdd-8a5b-45ab-ba2c-52420008103f', "
"'cirros-0.3.4-x86_64-uec']}, [[['0x0']]]]"
),
}
CHECKPOINT_INFO_2 = {
"id": "a6fd95fe-0892-43b2-ad3c-e56f3a1b86b8",
"project_id": "79b35e99a6a541b3bcede40f590d6878",
"status": "available",
"protection_plan": {
"id": "3b47fd5d-21f9-4e63-8409-0acb1bffc038",
"name": "My application",
"provider_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
"resources": [{
"id": "99777fdd-8a5b-45ab-ba2c-52420008103f",
"type": "OS::Glance::Image",
"name": "cirros-0.3.4-x86_64-uec"}]
},
"resource_graph": jsonutils.dumps(
"[{'0x0': ['OS::Glance::Image', "
"'99777fdd-8a5b-45ab-ba2c-52420008103f', "
"'cirros-0.3.4-x86_64-uec']}, [[['0x0']]]]"
),
}
class TestCheckpoints(fakes.TestDataProtection):
def setUp(self):
super(TestCheckpoints, self).setUp()
cm = self.app.client_manager
self.checkpoints_mock = cm.data_protection.checkpoints
self.checkpoints_mock.reset_mock()
class TestListCheckpoints(TestCheckpoints):
def setUp(self):
super(TestListCheckpoints, self).setUp()
# Command to test
self.cmd = osc_checkpoints.ListCheckpoints(self.app, None)
def test_checkpoints_list(self):
self.checkpoints_mock.list.return_value = [checkpoints.Checkpoint(
None, copy.deepcopy(CHECKPOINT_INFO))]
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9']
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
['Id', 'Project id', 'Status', 'Protection plan', 'Metadata',
'Created at'])
self.assertEqual(expected_columns, columns)
# Check that data is correct
expected_data = [(
"dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
"e486a2f49695423ca9c47e589b948108",
"available",
"Name: %(name)s\nId: %(id)s" % {
"id": "3523a271-68aa-42f5-b9ba-56e5200a2ebb",
"name": "My application",
},
'',
'')]
self.assertEqual(expected_data, list(data))
def test_checkpoints_list_with_all_projects(self):
self.checkpoints_mock.list.return_value = [checkpoints.Checkpoint(
None, copy.deepcopy(CHECKPOINT_INFO)), checkpoints.Checkpoint(
None, copy.deepcopy(CHECKPOINT_INFO_2))]
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9', '--all-projects']
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
('all_projects', True)]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
expected_columns = (
['Id', 'Project id', 'Status', 'Protection plan', 'Metadata',
'Created at'])
self.assertEqual(expected_columns, columns)
expected_data = [(
"dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
"e486a2f49695423ca9c47e589b948108",
"available",
"Name: %(name)s\nId: %(id)s" % {
"id": "3523a271-68aa-42f5-b9ba-56e5200a2ebb",
"name": "My application",
},
'',
''), (
"a6fd95fe-0892-43b2-ad3c-e56f3a1b86b8",
"79b35e99a6a541b3bcede40f590d6878",
"available",
"Name: %(name)s\nId: %(id)s" % {
"id": "3b47fd5d-21f9-4e63-8409-0acb1bffc038",
"name": "My application",
},
'',
'')
]
self.assertEqual(expected_data, list(data))
class TestCreateCheckpoint(TestCheckpoints):
def setUp(self):
super(TestCreateCheckpoint, self).setUp()
self.checkpoints_mock.create.return_value = checkpoints.Checkpoint(
None, copy.deepcopy(CHECKPOINT_INFO))
# Command to test
self.cmd = osc_checkpoints.CreateCheckpoint(self.app, None)
def test_checkpoint_create(self):
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'3523a271-68aa-42f5-b9ba-56e5200a2ebb']
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
('plan_id', '3523a271-68aa-42f5-b9ba-56e5200a2ebb')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.checkpoints_mock.create.assert_called_once_with(
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'3523a271-68aa-42f5-b9ba-56e5200a2ebb',
None)
class TestShowCheckpoint(TestCheckpoints):
def setUp(self):
super(TestShowCheckpoint, self).setUp()
self.checkpoints_mock.get.return_value = checkpoints.Checkpoint(
None, copy.deepcopy(CHECKPOINT_INFO))
# Command to test
self.cmd = osc_checkpoints.ShowCheckpoint(self.app, None)
def test_checkpoint_show(self):
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3']
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
('checkpoint_id',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.checkpoints_mock.get.assert_called_once_with(
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3')
class TestDeleteCheckpoint(TestCheckpoints):
def setUp(self):
super(TestDeleteCheckpoint, self).setUp()
self.checkpoints_mock.get.return_value = checkpoints.Checkpoint(
None, copy.deepcopy(CHECKPOINT_INFO))
# Command to test
self.cmd = osc_checkpoints.DeleteCheckpoint(self.app, None)
def test_checkpoint_delete(self):
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3']
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
('checkpoint',
['dcb20606-ad71-40a3-80e4-ef0fafdad0c3'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.checkpoints_mock.delete.assert_called_once_with(
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3')
class TestResetCheckpointState(TestCheckpoints):
def setUp(self):
super(TestResetCheckpointState, self).setUp()
self.cmd = osc_checkpoints.ResetCheckpointState(self.app, None)
def test_reset_checkpoint_with_default_state(self):
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3']
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
('checkpoint',
['dcb20606-ad71-40a3-80e4-ef0fafdad0c3']),
('state', 'error')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.checkpoints_mock.reset_state.assert_called_once_with(
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3',
'error')
def test_reset_checkpoint(self):
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3',
'--available']
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
('checkpoint',
['dcb20606-ad71-40a3-80e4-ef0fafdad0c3']),
('state', 'available')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.checkpoints_mock.reset_state.assert_called_once_with(
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3',
'available')

View File

@@ -0,0 +1,128 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from karborclient.osc.v1 import operation_logs as osc_operation_logs
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import operation_logs
OPERATIONLOG_INFO = {
"id": "22b82aa7-9179-4c71-bba2-caf5c0e68db7",
"project_id": "e486a2f49695423ca9c47e589b948108",
"operation_type": "protect",
"checkpoint_id": "dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
"plan_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
"provider_id": "23902b02-5666-4ee6-8dfe-962ac09c3994",
"restore_id": None,
"scheduled_operation_id": "23902b02-5666-4ee6-8dfe-962ac09c3991",
"started_at": "2015-08-27T09:50:58-05:00",
"ended_at": "2015-08-27T10:50:58-05:00",
"status": "protecting",
"error_info": "Could not access bank",
"extra_info": None
}
class TestOperationLogs(fakes.TestDataProtection):
def setUp(self):
super(TestOperationLogs, self).setUp()
self.operation_logs_mock = (
self.app.client_manager.data_protection.operation_logs)
self.operation_logs_mock.reset_mock()
class TestListOperationLogs(TestOperationLogs):
def setUp(self):
super(TestListOperationLogs, self).setUp()
self.operation_logs_mock.list.return_value = [
operation_logs.OperationLog(None,
copy.deepcopy(OPERATIONLOG_INFO))]
# Command to test
self.cmd = osc_operation_logs.ListOperationLogs(self.app, None)
def test_operation_logs_list(self):
arglist = ['--status', 'success']
verifylist = [('status', 'success')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
['Id', 'Operation Type', 'Checkpoint id', 'Plan Id',
'Provider id', 'Restore Id', 'Scheduled Operation Id',
'Status', 'Started At', 'Ended At', 'Error Info',
'Extra Info'])
self.assertEqual(expected_columns, columns)
# Check that data is correct
expected_data = [("22b82aa7-9179-4c71-bba2-caf5c0e68db7",
"protect",
"dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
"cf56bd3e-97a7-4078-b6d5-f36246333fd9",
"23902b02-5666-4ee6-8dfe-962ac09c3994",
None,
"23902b02-5666-4ee6-8dfe-962ac09c3991",
"protecting",
"2015-08-27T09:50:58-05:00",
"2015-08-27T10:50:58-05:00",
"Could not access bank",
None)]
self.assertEqual(expected_data, list(data))
class TestShowOperationLog(TestOperationLogs):
def setUp(self):
super(TestShowOperationLog, self).setUp()
self._oplog_info = copy.deepcopy(OPERATIONLOG_INFO)
self.operation_logs_mock.get.return_value = (
operation_logs.OperationLog(None, self._oplog_info))
# Command to test
self.cmd = osc_operation_logs.ShowOperationLog(self.app, None)
def test_operation_log_show(self):
arglist = ['22b82aa7-9179-4c71-bba2-caf5c0e68db7']
verifylist = [('operation_log',
'22b82aa7-9179-4c71-bba2-caf5c0e68db7')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
'checkpoint_id', 'ended_at', 'error_info', 'extra_info',
'id', 'operation_type', 'plan_id', 'project_id',
'provider_id', 'restore_id', 'scheduled_operation_id',
'started_at', 'status')
self.assertEqual(expected_columns, columns)
# Check that data is correct
self.assertEqual(self._oplog_info['checkpoint_id'], data[0])
self.assertEqual(self._oplog_info['ended_at'], data[1])
self.assertEqual(self._oplog_info['error_info'], data[2])
self.assertEqual(self._oplog_info['extra_info'], data[3])
self.assertEqual(self._oplog_info['id'], data[4])
self.assertEqual(self._oplog_info['operation_type'], data[5])
self.assertEqual(self._oplog_info['plan_id'], data[6])
self.assertEqual(self._oplog_info['project_id'], data[7])
self.assertEqual(self._oplog_info['provider_id'], data[8])
self.assertEqual(self._oplog_info['restore_id'], data[9])
self.assertEqual(self._oplog_info['scheduled_operation_id'], data[10])
self.assertEqual(self._oplog_info['started_at'], data[11])
self.assertEqual(self._oplog_info['status'], data[12])

View File

@@ -0,0 +1,183 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from karborclient.osc.v1 import plans as osc_plans
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import plans
PLAN_INFO = {
"status": "suspended",
"provider_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
"description": "",
"parameters": {},
"id": "204c825e-eb2f-4609-95ab-70b3caa43ac8",
"resources": [{
'type': 'OS::Cinder::Volume',
'id': '71bfe64a-e0b9-4a91-9e15-a7fc9ab31b14',
'name': 'testsinglevolume'}],
"name": "OS Volume protection plan."
}
class TestPlans(fakes.TestDataProtection):
def setUp(self):
super(TestPlans, self).setUp()
self.plans_mock = self.app.client_manager.data_protection.plans
self.plans_mock.reset_mock()
class TestListPlans(TestPlans):
def setUp(self):
super(TestListPlans, self).setUp()
self.plans_mock.list.return_value = [plans.Plan(
None, copy.deepcopy(PLAN_INFO))]
# Command to test
self.cmd = osc_plans.ListPlans(self.app, None)
def test_plans_list(self):
arglist = ['--status', 'suspended']
verifylist = [('status', 'suspended')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
['Id', 'Name', 'Description', 'Provider id', 'Status'])
self.assertEqual(expected_columns, columns)
# Check that data is correct
expected_data = [("204c825e-eb2f-4609-95ab-70b3caa43ac8",
"OS Volume protection plan.",
"",
"cf56bd3e-97a7-4078-b6d5-f36246333fd9",
"suspended")]
self.assertEqual(expected_data, list(data))
class TestCreatePlan(TestPlans):
def setUp(self):
super(TestCreatePlan, self).setUp()
self.plans_mock.create.return_value = plans.Plan(
None, copy.deepcopy(PLAN_INFO))
# Command to test
self.cmd = osc_plans.CreatePlan(self.app, None)
def test_plan_create(self):
arglist = ['OS Volume protection plan.',
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
"'71bfe64a-e0b9-4a91-9e15-a7fc9ab31b14'="
"'OS::Cinder::Volume'='testsinglevolume'"]
verifylist = [('name', 'OS Volume protection plan.'),
('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
('resources', "'71bfe64a-e0b9-4a91-9e15-a7fc9ab31b14'="
"'OS::Cinder::Volume'='testsinglevolume'")]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.plans_mock.create.assert_called_once_with(
'OS Volume protection plan.',
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
[{'id': "'71bfe64a-e0b9-4a91-9e15-a7fc9ab31b14'",
'type': "'OS::Cinder::Volume'",
'name': "'testsinglevolume'"}],
{}, description=None)
class TestUpdatePlan(TestPlans):
def setUp(self):
super(TestUpdatePlan, self).setUp()
self.plans_mock.get.return_value = plans.Plan(
None, copy.deepcopy(PLAN_INFO))
self.plans_mock.update.return_value = plans.Plan(
None, copy.deepcopy(PLAN_INFO))
# Command to test
self.cmd = osc_plans.UpdatePlan(self.app, None)
def test_plan_update(self):
arglist = ['204c825e-eb2f-4609-95ab-70b3caa43ac8',
'--status', 'started']
verifylist = [('plan_id', '204c825e-eb2f-4609-95ab-70b3caa43ac8'),
('status', 'started')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.plans_mock.update.assert_called_once_with(
'204c825e-eb2f-4609-95ab-70b3caa43ac8',
{'status': 'started'})
class TestDeletePlan(TestPlans):
def setUp(self):
super(TestDeletePlan, self).setUp()
self.plans_mock.get.return_value = plans.Plan(
None, copy.deepcopy(PLAN_INFO))
# Command to test
self.cmd = osc_plans.DeletePlan(self.app, None)
def test_plan_delete(self):
arglist = ['204c825e-eb2f-4609-95ab-70b3caa43ac8']
verifylist = [('plan', ['204c825e-eb2f-4609-95ab-70b3caa43ac8'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.plans_mock.delete.assert_called_once_with(
'204c825e-eb2f-4609-95ab-70b3caa43ac8')
class TestShowPlan(TestPlans):
def setUp(self):
super(TestShowPlan, self).setUp()
self._plan_info = copy.deepcopy(PLAN_INFO)
self.plans_mock.get.return_value = plans.Plan(
None, self._plan_info)
# Command to test
self.cmd = osc_plans.ShowPlan(self.app, None)
def test_plan_show(self):
arglist = ['204c825e-eb2f-4609-95ab-70b3caa43ac8']
verifylist = [('plan', '204c825e-eb2f-4609-95ab-70b3caa43ac8')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
'description', 'id', 'name', 'parameters', 'provider_id',
'resources', 'status')
self.assertEqual(expected_columns, columns)
# Check that data is correct
self.assertEqual(self._plan_info['description'], data[0])
self.assertEqual(self._plan_info['id'], data[1])
self.assertEqual(self._plan_info['name'], data[2])
self.assertEqual(self._plan_info['parameters'], data[3])
self.assertEqual(self._plan_info['provider_id'], data[4])
self.assertEqual(self._plan_info['resources'], data[5])
self.assertEqual(self._plan_info['status'], data[6])

View File

@@ -0,0 +1,163 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from karborclient.osc.v1 import protectables as osc_protectables
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import protectables
PROTECTABLE_LIST_INFO = {
"protectable_type": [
"OS::Keystone::Project",
"OS::Cinder::Volume",
"OS::Glance::Image",
"OS::Nova::Server"
]
}
PROTECTABLE_SHOW_INFO = {
"name": "OS::Nova::Server",
"dependent_types": [
"OS::Cinder::Volume",
"OS::Glance::Image"
]
}
PROTECTABLE_INSTANCE_LIST_INFO = {
"id": "25336116-f38e-4c22-81ad-e9b7bd71ba51",
"type": "OS::Cinder::Volume",
"name": "System volume",
"extra_info": {
"availability_zone": "az1"
}
}
PROTECTABLE_INSTANCE_SHOW_INFO = {
"id": "cb4ef2ff-10f5-46c9-bce4-cf7a49c65a01",
"type": "OS::Nova::Server",
"name": "My VM",
"dependent_resources": [{
"id": "99777fdd-8a5b-45ab-ba2c-52420008103f",
"type": "OS::Glance::Image",
"name": "cirros-0.3.4-x86_64-uec"}]
}
class TestProtectables(fakes.TestDataProtection):
def setUp(self):
super(TestProtectables, self).setUp()
cm = self.app.client_manager
self.protectables_mock = cm.data_protection.protectables
self.protectables_mock.reset_mock()
class TestListProtectables(TestProtectables):
def setUp(self):
super(TestListProtectables, self).setUp()
self.protectables_mock.list.return_value = [protectables.Protectable(
None, copy.deepcopy(PROTECTABLE_LIST_INFO))]
# Command to test
self.cmd = osc_protectables.ListProtectables(self.app, None)
def test_protectables_list(self):
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
['Protectable type'])
self.assertEqual(expected_columns, columns)
# Check that data is correct
expected_data = [(['OS::Keystone::Project',
'OS::Cinder::Volume',
'OS::Glance::Image',
'OS::Nova::Server'],)]
self.assertEqual(expected_data, list(data))
class TestShowProtectable(TestProtectables):
def setUp(self):
super(TestShowProtectable, self).setUp()
self.protectables_mock.get.return_value = protectables.Protectable(
None, copy.deepcopy(PROTECTABLE_SHOW_INFO))
# Command to test
self.cmd = osc_protectables.ShowProtectable(self.app, None)
def test_protectable_show(self):
arglist = ['OS::Nova::Server']
verifylist = [('protectable_type', 'OS::Nova::Server')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.protectables_mock.get.assert_called_once_with(
'OS::Nova::Server')
class TestListProtectableInstances(TestProtectables):
def setUp(self):
super(TestListProtectableInstances, self).setUp()
pm = self.protectables_mock
pm.list_instances.return_value = [protectables.Instances(
None, copy.deepcopy(PROTECTABLE_INSTANCE_LIST_INFO)), ]
# Command to test
self.cmd = osc_protectables.ListProtectableInstances(self.app, None)
def test_protectable_instances_list(self):
arglist = ['OS::Cinder::Volume']
verifylist = [('protectable_type', 'OS::Cinder::Volume')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.protectables_mock.list_instances.assert_called_once_with(
'OS::Cinder::Volume', limit=None, marker=None,
search_opts={'type': None, 'parameters': None},
sort=None)
class TestShowProtectableInstance(TestProtectables):
def setUp(self):
super(TestShowProtectableInstance, self).setUp()
pm = self.protectables_mock
pm.get_instance.return_value = protectables.Instances(
None, copy.deepcopy(PROTECTABLE_INSTANCE_SHOW_INFO))
# Command to test
self.cmd = osc_protectables.ShowProtectableInstance(self.app, None)
def test_protectable_instance_show(self):
arglist = ['OS::Nova::Server', 'cb4ef2ff-10f5-46c9-bce4-cf7a49c65a01']
verifylist = [('protectable_type', 'OS::Nova::Server'),
('protectable_id',
'cb4ef2ff-10f5-46c9-bce4-cf7a49c65a01')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.protectables_mock.get_instance.assert_called_once_with(
'OS::Nova::Server', 'cb4ef2ff-10f5-46c9-bce4-cf7a49c65a01',
search_opts={'parameters': None})

View File

@@ -0,0 +1,144 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from karborclient.osc.v1 import providers as osc_providers
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import providers
PROVIDER_INFO = {
"id": "2220f8b1-975d-4621-a872-fa9afb43cb6c",
"name": "OS Infra Provider",
"description": "provider description",
"extended_info_schema": {
"options_schema": {
"OS::Cinder::Volume": {
"required": [
"backup_mode"
],
"type": "object",
"properties": {
"backup_mode": {
"default": "auto",
"enum": [
"full",
"incremental",
"auto"
],
"type": "string",
"description": "The backup mode.",
"title": "Backup Mode"
}
},
"title": "Cinder Protection Options"
}
},
"saved_info_schema": {
"OS::Cinder::Volume": {
"required": [
"name"
],
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name for this backup.",
"title": "Name"
}
},
"title": "Cinder Protection Saved Info"
}
},
"restore_schema": {
"OS::Cinder::Volume": {
"type": "object",
"properties": {
"restore_name": {
"type": "string",
"description": "The name of the restored volume.",
"title": "Restore Name"
}
},
"title": "Cinder Protection Restore"
}
}
}
}
class TestProviders(fakes.TestDataProtection):
def setUp(self):
super(TestProviders, self).setUp()
self.providers_mock = self.app.client_manager.data_protection.providers
self.providers_mock.reset_mock()
class TestListProviders(TestProviders):
def setUp(self):
super(TestListProviders, self).setUp()
self.providers_mock.list.return_value = [providers.Provider(
None, copy.deepcopy(PROVIDER_INFO))]
# Command to test
self.cmd = osc_providers.ListProviders(self.app, None)
def test_providers_list(self):
arglist = ['--name', 'OS Infra Provider']
verifylist = [('name', 'OS Infra Provider')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
['Id', 'Name', 'Description'])
self.assertEqual(expected_columns, columns)
# Check that data is correct
expected_data = [("2220f8b1-975d-4621-a872-fa9afb43cb6c",
"OS Infra Provider",
"provider description")]
self.assertEqual(expected_data, list(data))
class TestShowProvider(TestProviders):
def setUp(self):
super(TestShowProvider, self).setUp()
self._provider_info = copy.deepcopy(PROVIDER_INFO)
self.providers_mock.get.return_value = providers.Provider(
None, self._provider_info)
# Command to test
self.cmd = osc_providers.ShowProvider(self.app, None)
def test_provider_show(self):
arglist = ['2220f8b1-975d-4621-a872-fa9afb43cb6c']
verifylist = [('provider', '2220f8b1-975d-4621-a872-fa9afb43cb6c')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
'description', 'extended_info_schema', 'id', 'name')
self.assertEqual(expected_columns, columns)
# Check that data is correct
self.assertEqual(self._provider_info['description'], data[0])
self.assertEqual(self._provider_info['extended_info_schema'], data[1])
self.assertEqual(self._provider_info['id'], data[2])
self.assertEqual(self._provider_info['name'], data[3])

View File

@@ -0,0 +1,77 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from karborclient.osc.v1 import quota_classes as osc_quota_classes
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import quota_classes
QUOTA_CLASSES_INFO = {
"id": "default",
"plans": "40"
}
class TestQuotaClasses(fakes.TestDataProtection):
def setUp(self):
super(TestQuotaClasses, self).setUp()
self.quotas_mock = (
self.app.client_manager.data_protection.quota_classes)
self.quotas_mock.reset_mock()
class TestUpdateQuotaClasses(TestQuotaClasses):
def setUp(self):
super(TestUpdateQuotaClasses, self).setUp()
self.quotas_mock.update.return_value = quota_classes.QuotaClass(
None, copy.deepcopy(QUOTA_CLASSES_INFO))
self.cmd = osc_quota_classes.UpdateQuotaClasses(self.app, None)
def test_quota_classes_update(self):
arglist = ['--plans',
'40', 'default']
verifylist = [('plans', 40),
('class_name',
'default')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.quotas_mock.update.assert_called_once_with(
'default',
{'plans': 40})
class TestShowQuotaClasses(TestQuotaClasses):
def setUp(self):
super(TestShowQuotaClasses, self).setUp()
self._quota_info = copy.deepcopy(QUOTA_CLASSES_INFO)
self.quotas_mock.get.return_value = quota_classes.QuotaClass(
None, copy.deepcopy(QUOTA_CLASSES_INFO))
self.cmd = osc_quota_classes.ShowQuotaClasses(self.app, None)
def test_quota_classes_show(self):
arglist = ['default']
verifylist = [('class_name', 'default')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(('id', 'plans'), columns)
self.assertEqual(('default', '40'),
data)

View File

@@ -0,0 +1,76 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from karborclient.osc.v1 import quotas as osc_quotas
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import quotas
QUOTA_INFO = {
"id": "73f74f90a1754bd7ad658afb3272323f",
"plans": "40"
}
class TestQuotas(fakes.TestDataProtection):
def setUp(self):
super(TestQuotas, self).setUp()
self.quotas_mock = self.app.client_manager.data_protection.quotas
self.quotas_mock.reset_mock()
class TestUpdateQuotas(TestQuotas):
def setUp(self):
super(TestUpdateQuotas, self).setUp()
self.quotas_mock.update.return_value = quotas.Quota(
None, copy.deepcopy(QUOTA_INFO))
self.cmd = osc_quotas.UpdateQuotas(self.app, None)
def test_quotas_update(self):
arglist = ['--plans',
'40', '73f74f90a1754bd7ad658afb3272323f']
verifylist = [('plans', 40),
('tenant',
'73f74f90a1754bd7ad658afb3272323f')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.quotas_mock.update.assert_called_once_with(
'73f74f90a1754bd7ad658afb3272323f',
{'plans': 40})
class TestShowQuotas(TestQuotas):
def setUp(self):
super(TestShowQuotas, self).setUp()
self._quota_info = copy.deepcopy(QUOTA_INFO)
self.quotas_mock.get.return_value = quotas.Quota(
None, copy.deepcopy(QUOTA_INFO))
self.cmd = osc_quotas.ShowQuotas(self.app, None)
def test_quota_show(self):
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(('id', 'plans'), columns)
self.assertEqual(('73f74f90a1754bd7ad658afb3272323f', '40'),
data)

View File

@@ -0,0 +1,139 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from oslo_serialization import jsonutils
from karborclient.osc.v1 import restores as osc_restores
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import restores
RESTORE_INFO = {
"id": "22b82aa7-9179-4c71-bba2-caf5c0e68db7",
"project_id": "e486a2f49695423ca9c47e589b948108",
"provider_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
"checkpoint_id": "dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
"restore_target": "",
"parameters": {},
"restore_auth": {},
"resources_status": {},
"resources_reason": {},
"status": "success"
}
class TestRestores(fakes.TestDataProtection):
def setUp(self):
super(TestRestores, self).setUp()
self.restores_mock = self.app.client_manager.data_protection.restores
self.restores_mock.reset_mock()
class TestListRestores(TestRestores):
def setUp(self):
super(TestListRestores, self).setUp()
self.restores_mock.list.return_value = [restores.Restore(
None, copy.deepcopy(RESTORE_INFO))]
# Command to test
self.cmd = osc_restores.ListRestores(self.app, None)
def test_restores_list(self):
arglist = ['--status', 'success']
verifylist = [('status', 'success')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
['Id', 'Project id', 'Provider id', 'Checkpoint id',
'Restore target', 'Parameters', 'Status'])
self.assertEqual(expected_columns, columns)
# Check that data is correct
expected_data = [("22b82aa7-9179-4c71-bba2-caf5c0e68db7",
"e486a2f49695423ca9c47e589b948108",
"cf56bd3e-97a7-4078-b6d5-f36246333fd9",
"dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
"",
jsonutils.dumps({}),
"success")]
self.assertEqual(expected_data, list(data))
class TestCreateRestore(TestRestores):
def setUp(self):
super(TestCreateRestore, self).setUp()
self.restores_mock.create.return_value = restores.Restore(
None, copy.deepcopy(RESTORE_INFO))
# Command to test
self.cmd = osc_restores.CreateRestore(self.app, None)
def test_restore_create(self):
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3']
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
('checkpoint_id',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.restores_mock.create.assert_called_once_with(
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3',
None, {}, None)
class TestShowRestore(TestRestores):
def setUp(self):
super(TestShowRestore, self).setUp()
self._restore_info = copy.deepcopy(RESTORE_INFO)
self.restores_mock.get.return_value = restores.Restore(
None, self._restore_info)
# Command to test
self.cmd = osc_restores.ShowRestore(self.app, None)
def test_restore_show(self):
arglist = ['22b82aa7-9179-4c71-bba2-caf5c0e68db7']
verifylist = [('restore', '22b82aa7-9179-4c71-bba2-caf5c0e68db7')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
'checkpoint_id', 'id', 'parameters', 'project_id',
'provider_id', 'resources_reason', 'resources_status',
'restore_auth', 'restore_target', 'status')
self.assertEqual(expected_columns, columns)
# Check that data is correct
self.assertEqual(self._restore_info['checkpoint_id'], data[0])
self.assertEqual(self._restore_info['id'], data[1])
self.assertEqual(self._restore_info['parameters'], data[2])
self.assertEqual(self._restore_info['project_id'], data[3])
self.assertEqual(self._restore_info['provider_id'], data[4])
self.assertEqual(self._restore_info['resources_reason'], data[5])
self.assertEqual(self._restore_info['resources_status'], data[6])
self.assertEqual(self._restore_info['restore_auth'], data[7])
self.assertEqual(self._restore_info['restore_target'], data[8])
self.assertEqual(self._restore_info['status'], data[9])

View File

@@ -0,0 +1,173 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import json
from karborclient.osc.v1 import scheduled_operations as osc_so
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import scheduled_operations
SCHEDULEDOPERATION_INFO = {
"id": "1a2c0c3d-f402-4cd8-b5db-82e85cb51fad",
"name": "My scheduled operation",
"description": "It will run everyday",
"operation_type": "protect",
"trigger_id": "23902b02-5666-4ee6-8dfe-962ac09c3995",
"operation_definition": {
"provider_id": "2a9ce1f3-cc1a-4516-9435-0ebb13caa399",
"plan_id": "2a9ce1f3-cc1a-4516-9435-0ebb13caa398"
},
"enabled": 1
}
class TestScheduledOperations(fakes.TestDataProtection):
def setUp(self):
super(TestScheduledOperations, self).setUp()
self.so_mock = self.app.client_manager.data_protection.\
scheduled_operations
self.so_mock.reset_mock()
class TestListScheduledOperations(TestScheduledOperations):
def setUp(self):
super(TestListScheduledOperations, self).setUp()
self.so_mock.list.return_value = [
scheduled_operations.ScheduledOperation(
None, copy.deepcopy(SCHEDULEDOPERATION_INFO))
]
# Command to test
self.cmd = osc_so.ListScheduledOperations(self.app, None)
def test_scheduled_operations_list(self):
arglist = ['--name', 'My scheduled operation']
verifylist = [('name', 'My scheduled operation')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
['Id', 'Name', 'Operation Type', 'Trigger Id',
'Operation Definition'])
self.assertEqual(expected_columns, columns)
operation_definition = {
"provider_id": "2a9ce1f3-cc1a-4516-9435-0ebb13caa399",
"plan_id": "2a9ce1f3-cc1a-4516-9435-0ebb13caa398"
}
# Check that data is correct
expected_data = [("1a2c0c3d-f402-4cd8-b5db-82e85cb51fad",
"My scheduled operation",
"protect",
"23902b02-5666-4ee6-8dfe-962ac09c3995",
json.dumps(operation_definition,
indent=2, sort_keys=True)
)]
self.assertEqual(expected_data, data)
class TestCreateScheduledOperation(TestScheduledOperations):
def setUp(self):
super(TestCreateScheduledOperation, self).setUp()
self.so_mock.create.return_value = scheduled_operations.\
ScheduledOperation(None, copy.deepcopy(SCHEDULEDOPERATION_INFO))
# Command to test
self.cmd = osc_so.CreateScheduledOperation(self.app, None)
def test_scheduled_operation_create(self):
arglist = ['My scheduled operation',
'protect',
"23902b02-5666-4ee6-8dfe-962ac09c3995",
"'provider_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa399,"
"plan_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa398'"]
verifylist = [('name', 'My scheduled operation'),
('operation_type', 'protect'),
('trigger_id', "23902b02-5666-4ee6-8dfe-962ac09c3995"),
('operation_definition',
"'provider_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa399,"
"plan_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa398'")]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.so_mock.create.assert_called_once_with(
'My scheduled operation',
'protect',
'23902b02-5666-4ee6-8dfe-962ac09c3995',
"'provider_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa399,"
"plan_id=2a9ce1f3-cc1a-4516-9435-0ebb13caa398'")
class TestDeleteScheduledOperation(TestScheduledOperations):
def setUp(self):
super(TestDeleteScheduledOperation, self).setUp()
self.so_mock.get.return_value = scheduled_operations.\
ScheduledOperation(None, copy.deepcopy(SCHEDULEDOPERATION_INFO))
# Command to test
self.cmd = osc_so.DeleteScheduledOperation(self.app, None)
def test_scheduled_operation_delete(self):
arglist = ['1a2c0c3d-f402-4cd8-b5db-82e85cb51fad']
verifylist = [('scheduledoperation',
['1a2c0c3d-f402-4cd8-b5db-82e85cb51fad'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.so_mock.delete.assert_called_once_with(
'1a2c0c3d-f402-4cd8-b5db-82e85cb51fad')
class TestShowScheduledOperation(TestScheduledOperations):
def setUp(self):
super(TestShowScheduledOperation, self).setUp()
self._schedop_info = copy.deepcopy(SCHEDULEDOPERATION_INFO)
self.so_mock.get.return_value = scheduled_operations.\
ScheduledOperation(None, self._schedop_info)
# Command to test
self.cmd = osc_so.ShowScheduledOperation(self.app, None)
def test_scheduled_operation_show(self):
arglist = ['1a2c0c3d-f402-4cd8-b5db-82e85cb51fad']
verifylist = [('scheduledoperation',
'1a2c0c3d-f402-4cd8-b5db-82e85cb51fad')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
'description', 'enabled', 'id', 'name', 'operation_definition',
'operation_type', 'trigger_id')
self.assertEqual(expected_columns, columns)
# Check that data is correct
self.assertEqual(self._schedop_info['description'], data[0])
self.assertEqual(self._schedop_info['enabled'], data[1])
self.assertEqual(self._schedop_info['id'], data[2])
self.assertEqual(self._schedop_info['name'], data[3])
self.assertEqual(self._schedop_info['operation_definition'], data[4])
self.assertEqual(self._schedop_info['operation_type'], data[5])
self.assertEqual(self._schedop_info['trigger_id'], data[6])

View File

@@ -0,0 +1,101 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from karborclient.osc.v1 import services as osc_services
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import services
SERVICE_INFO = {
"status": "enabled",
"binary": "karbor-operationengine",
"state": "up",
"updated_at": "2017-10-25T07:06:58.000000",
"host": "fake_host",
"disabled_reason": None,
"id": 1
}
class TestServices(fakes.TestDataProtection):
def setUp(self):
super(TestServices, self).setUp()
self.services_mock = self.app.client_manager.data_protection.services
self.services_mock.reset_mock()
class TestListServices(TestServices):
def setUp(self):
super(TestListServices, self).setUp()
self.services_mock.list.return_value = [
services.Service(None, copy.deepcopy(SERVICE_INFO))]
self.cmd = osc_services.ListServices(self.app, None)
def test_services_list(self):
arg_list = ['--host', 'fake_host']
verify_list = [('host', 'fake_host')]
parsed_args = self.check_parser(self.cmd, arg_list, verify_list)
columns, data = self.cmd.take_action(parsed_args)
expected_columns = (["Id", "Binary", "Host", "Status", "State",
"Updated_at", "Disabled Reason"])
self.assertEqual(expected_columns, columns)
expected_data = [(1,
"karbor-operationengine",
"fake_host",
"enabled",
"up",
"2017-10-25T07:06:58.000000",
None
)]
self.assertEqual(expected_data, list(data))
class TestEnableService(TestServices):
def setUp(self):
super(TestEnableService, self).setUp()
self.services_mock.enable.return_value = services.Service(
None, copy.deepcopy(SERVICE_INFO))
self.cmd = osc_services.EnableService(self.app, None)
def test_enable_service(self):
arg_list = ['1']
verify_list = [('service_id', '1')]
parsed_args = self.check_parser(self.cmd, arg_list, verify_list)
self.cmd.take_action(parsed_args)
self.services_mock.enable.assert_called_once_with('1')
class TestDisableService(TestServices):
def setUp(self):
super(TestDisableService, self).setUp()
self.services_mock.disable.return_value = services.Service(
None, copy.deepcopy(SERVICE_INFO))
self.services_mock.disable_log_reason.return_value = services.Service(
None, copy.deepcopy(SERVICE_INFO))
self.cmd = osc_services.DisableService(self.app, None)
def test_disable_service(self):
arg_list = ['1']
verify_list = [('service_id', '1')]
parsed_args = self.check_parser(self.cmd, arg_list, verify_list)
self.cmd.take_action(parsed_args)
self.services_mock.disable.assert_called_once_with('1')
def test_disable_service_with_reason(self):
arg_list = ['1', '--reason', 'fake_reason']
verify_list = [('service_id', '1'), ('reason', 'fake_reason')]
parsed_args = self.check_parser(self.cmd, arg_list, verify_list)
self.cmd.take_action(parsed_args)
self.services_mock.disable_log_reason.assert_called_once_with(
'1', 'fake_reason')

View File

@@ -0,0 +1,189 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from karborclient.osc.v1 import triggers as osc_triggers
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import triggers
TRIGGER_INFO = {
"id": "2a9ce1f3-cc1a-4516-9435-0ebb13caa398",
"name": "My backup trigger",
"type": "time",
"properties": {
"format": "calendar",
"pattern": "BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT",
"start_time": "2015-12-17T08:30:00",
"end_time": "2016-03-17T08:30:00",
"window": "3600"
}
}
class TestTriggers(fakes.TestDataProtection):
def setUp(self):
super(TestTriggers, self).setUp()
self.triggers_mock = self.app.client_manager.data_protection.triggers
self.triggers_mock.reset_mock()
class TestListTriggers(TestTriggers):
def setUp(self):
super(TestListTriggers, self).setUp()
self.triggers_mock.list.return_value = [triggers.Trigger(
None, copy.deepcopy(TRIGGER_INFO))]
# Command to test
self.cmd = osc_triggers.ListTriggers(self.app, None)
def test_triggers_list(self):
arglist = ['--name', 'My backup trigger']
verifylist = [('name', 'My backup trigger')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
['Id', 'Name', 'Type', 'Properties'])
self.assertEqual(expected_columns, columns)
# Check that data is correct
expected_data = [("2a9ce1f3-cc1a-4516-9435-0ebb13caa398",
"My backup trigger",
"time",
{"format": "calendar",
"pattern": "BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT", # noqa
"start_time": "2015-12-17T08:30:00",
"end_time": "2016-03-17T08:30:00",
"window": "3600"})]
self.assertEqual(expected_data, list(data))
class TestCreateTrigger(TestTriggers):
def setUp(self):
super(TestCreateTrigger, self).setUp()
self.triggers_mock.create.return_value = triggers.Trigger(
None, copy.deepcopy(TRIGGER_INFO))
# Command to test
self.cmd = osc_triggers.CreateTrigger(self.app, None)
def test_trigger_create(self):
arglist = ['My backup trigger',
'time',
"'format'='calendar',"
"'pattern'='BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT'," # noqa
"'start_time'='2015-12-17T08:30:00',"
"'end_time'='2016-03-17T08:30:00',"
"'window'='3600'"]
verifylist = [('name', 'My backup trigger'),
('type', 'time'),
('properties', "'format'='calendar',"
"'pattern'='BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT'," # noqa
"'start_time'='2015-12-17T08:30:00',"
"'end_time'='2016-03-17T08:30:00',"
"'window'='3600'")]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.triggers_mock.create.assert_called_once_with(
'My backup trigger',
'time',
"'format'='calendar',"
"'pattern'='BEGIN:VEVENT\\nRRULE:FREQ=HOURLY;INTERVAL=1;\\nEND:VEVENT'," # noqa
"'start_time'='2015-12-17T08:30:00',"
"'end_time'='2016-03-17T08:30:00',"
"'window'='3600'")
class TestUpdateTrigger(TestTriggers):
def setUp(self):
super(TestUpdateTrigger, self).setUp()
self.triggers_mock.get.return_value = triggers.Trigger(
None, copy.deepcopy(TRIGGER_INFO))
self.triggers_mock.update.return_value = triggers.Trigger(
None, copy.deepcopy(TRIGGER_INFO))
# Command to test
self.cmd = osc_triggers.UpdateTrigger(self.app, None)
def test_trigger_update(self):
arglist = ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398',
'--name', 'My backup trigger']
verifylist = [('trigger_id', '2a9ce1f3-cc1a-4516-9435-0ebb13caa398'),
('name', 'My backup trigger')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.triggers_mock.update.assert_called_once_with(
'2a9ce1f3-cc1a-4516-9435-0ebb13caa398',
{'name': 'My backup trigger'})
class TestDeleteTrigger(TestTriggers):
def setUp(self):
super(TestDeleteTrigger, self).setUp()
self.triggers_mock.get.return_value = triggers.Trigger(
None, copy.deepcopy(TRIGGER_INFO))
# Command to test
self.cmd = osc_triggers.DeleteTrigger(self.app, None)
def test_trigger_delete(self):
arglist = ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398']
verifylist = [('trigger', ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398'])]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
# Check that correct arguments were passed
self.triggers_mock.delete.assert_called_once_with(
'2a9ce1f3-cc1a-4516-9435-0ebb13caa398')
class TestShowTrigger(TestTriggers):
def setUp(self):
super(TestShowTrigger, self).setUp()
self._trigger_info = copy.deepcopy(TRIGGER_INFO)
self.triggers_mock.get.return_value = triggers.Trigger(
None, self._trigger_info)
# Command to test
self.cmd = osc_triggers.ShowTrigger(self.app, None)
def test_trigger_show(self):
arglist = ['2a9ce1f3-cc1a-4516-9435-0ebb13caa398']
verifylist = [('trigger', '2a9ce1f3-cc1a-4516-9435-0ebb13caa398')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
# Check that columns are correct
expected_columns = (
'id', 'name', 'properties', 'type')
self.assertEqual(expected_columns, columns)
# Check that data is correct
self.assertEqual(self._trigger_info['id'], data[0])
self.assertEqual(self._trigger_info['name'], data[1])
self.assertEqual(self._trigger_info['properties'], data[2])
self.assertEqual(self._trigger_info['type'], data[3])

View File

@@ -0,0 +1,130 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
from oslo_serialization import jsonutils
from karborclient.osc.v1 import verifications as osc_verifications
from karborclient.tests.unit.osc.v1 import fakes
from karborclient.v1 import verifications
VERIFICATION_INFO = {
"id": "22b82aa7-9179-4c71-bba2-caf5c0e68db7",
"project_id": "e486a2f49695423ca9c47e589b948108",
"provider_id": "cf56bd3e-97a7-4078-b6d5-f36246333fd9",
"checkpoint_id": "dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
"parameters": {},
"resources_status": {},
"resources_reason": {},
"status": "success"
}
class TestVerifications(fakes.TestDataProtection):
def setUp(self):
super(TestVerifications, self).setUp()
self.verifications_mock = (
self.app.client_manager.data_protection.verifications)
self.verifications_mock.reset_mock()
class TestListVerifications(TestVerifications):
def setUp(self):
super(TestListVerifications, self).setUp()
self.verifications_mock.list.return_value = (
[verifications.Verification(
None, copy.deepcopy(VERIFICATION_INFO))])
# Command to test
self.cmd = osc_verifications.ListVerifications(self.app, None)
def test_verifications_list(self):
arglist = ['--status', 'success']
verifylist = [('status', 'success')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
expected_columns = (
['Id', 'Project id', 'Provider id', 'Checkpoint id',
'Parameters', 'Status'])
self.assertEqual(expected_columns, columns)
# Check that data is correct
expected_data = [("22b82aa7-9179-4c71-bba2-caf5c0e68db7",
"e486a2f49695423ca9c47e589b948108",
"cf56bd3e-97a7-4078-b6d5-f36246333fd9",
"dcb20606-ad71-40a3-80e4-ef0fafdad0c3",
jsonutils.dumps({}),
"success")]
self.assertEqual(expected_data, list(data))
class TestCreateVerification(TestVerifications):
def setUp(self):
super(TestCreateVerification, self).setUp()
self.verifications_mock.create.return_value = (
verifications.Verification(
None, copy.deepcopy(VERIFICATION_INFO)))
self.cmd = osc_verifications.CreateVerification(self.app, None)
def test_verification_create(self):
arglist = ['cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3']
verifylist = [('provider_id', 'cf56bd3e-97a7-4078-b6d5-f36246333fd9'),
('checkpoint_id',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.verifications_mock.create.assert_called_once_with(
'cf56bd3e-97a7-4078-b6d5-f36246333fd9',
'dcb20606-ad71-40a3-80e4-ef0fafdad0c3', {})
class TestShowVerification(TestVerifications):
def setUp(self):
super(TestShowVerification, self).setUp()
self._verification_info = copy.deepcopy(VERIFICATION_INFO)
self.verifications_mock.get.return_value = (
verifications.Verification(None, self._verification_info))
self.cmd = osc_verifications.ShowVerification(self.app, None)
def test_verification_show(self):
arglist = ['22b82aa7-9179-4c71-bba2-caf5c0e68db7']
verifylist = [('verification', '22b82aa7-9179-4c71-bba2-caf5c0e68db7')]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
expected_columns = (
'checkpoint_id', 'id', 'parameters', 'project_id',
'provider_id', 'resources_reason', 'resources_status',
'status')
self.assertEqual(expected_columns, columns)
self.assertEqual(self._verification_info['checkpoint_id'], data[0])
self.assertEqual(self._verification_info['id'], data[1])
self.assertEqual(self._verification_info['parameters'], data[2])
self.assertEqual(self._verification_info['project_id'], data[3])
self.assertEqual(self._verification_info['provider_id'], data[4])
self.assertEqual(self._verification_info['resources_reason'], data[5])
self.assertEqual(self._verification_info['resources_status'], data[6])
self.assertEqual(self._verification_info['status'], data[7])

View File

@@ -37,12 +37,13 @@ class FakeClient(fakes.FakeClient, client.Client):
'project_id': PROJECT_ID,
}
client.Client.__init__(self, 'http://endpoint', **kwargs)
self.client = FakeHTTPClient(**kwargs)
self.client = self.http_client
class FakeHTTPClient(base_client.HTTPClient):
def __init__(self, **kwargs):
def __init__(self, endpoint, **kwargs):
super(FakeHTTPClient, self)
self.username = 'username'
self.password = 'password'
self.auth_url = 'auth_url'
@@ -50,6 +51,9 @@ class FakeHTTPClient(base_client.HTTPClient):
self.management_url = 'http://10.0.2.15:8776/v1/fake'
self.osapi_max_limit = 1000
self.marker = None
self.project_id = 'project_id'
self.auth_token = 'auth_token'
self.region_name = 'region_name'
def _cs_request(self, url, method, **kwargs):
# Check that certain things are called correctly
@@ -82,7 +86,7 @@ class FakeHTTPClient(base_client.HTTPClient):
(method, url, callback))
# Note the call
self.callstack.append((method, url, kwargs.get('body', None)))
self.callstack.append((method, url, kwargs.get('body')))
status, headers, body = getattr(self, callback)(**kwargs)
# add fake request-id header
headers['x-openstack-request-id'] = REQUEST_ID
@@ -92,3 +96,27 @@ class FakeHTTPClient(base_client.HTTPClient):
"headers": headers,
})
return r, body
def json_request(self, method, url, **kwargs):
return self._cs_request(url, method, **kwargs)
def get_providers_1234_checkpoints(self, **kwargs):
return 200, {}, {"checkpoints": []}
def get_plans(self, **kwargs):
return 200, {}, {"plans": []}
def get_operation_logs(self, **kwargs):
return 200, {}, {"operation_logs": []}
def get_restores(self, **kwargs):
return 200, {}, {"restores": []}
def get_scheduled_operations(self, **kwargs):
return 200, {}, {"operations": []}
def get_triggers(self, **kwargs):
return 200, {}, {"triggers": []}
def get_verifications(self, **kwargs):
return 200, {}, {"verifications": []}

View File

@@ -20,6 +20,7 @@ mock_request_return = ({}, {'checkpoint': {}})
FAKE_PROVIDER_ID = "2220f8b1-975d-4621-a872-fa9afb43cb6c"
FAKE_PLAN_ID = "3330f8b1-975d-4621-a872-fa9afb43cb6c"
FAKE_CHECKPOINT_ID = "e4381b1a-905e-4fec-8104-b4419ccaf963"
class CheckpointsTest(base.TestCaseShell):
@@ -33,6 +34,16 @@ class CheckpointsTest(base.TestCaseShell):
'/providers/{provider_id}/checkpoints'.format(
provider_id=FAKE_PROVIDER_ID), headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_checkpoints_with_all_tenants(self, mock_request):
mock_request.return_value = mock_request_return
cs.checkpoints.list(provider_id=FAKE_PROVIDER_ID,
search_opts={'all_tenants': 1})
mock_request.assert_called_with(
'GET',
'/providers/{provider_id}/checkpoints?all_tenants=1'.format(
provider_id=FAKE_PROVIDER_ID), headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_get_checkpoint(self, mock_request):
mock_request.return_value = mock_request_return
@@ -90,3 +101,17 @@ class CheckpointsTest(base.TestCaseShell):
data={
'checkpoint': {'plan_id': FAKE_PLAN_ID, 'extra-info': None}},
headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_reset_checkpoint_state(self, mock_request):
mock_request.return_value = ({}, {})
cs.checkpoints.reset_state(
FAKE_PROVIDER_ID, FAKE_CHECKPOINT_ID, 'error')
mock_request.assert_called_with(
'PUT',
'/providers/{provider_id}/checkpoints/{checkpoint_id}'.format(
provider_id=FAKE_PROVIDER_ID,
checkpoint_id=FAKE_CHECKPOINT_ID
),
data={'os-resetState': {'state': 'error'}},
headers={})

View File

@@ -0,0 +1,79 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from karborclient.tests.unit import base
from karborclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
mock_request_return = ({}, {'operation_log': {}})
class OperationLogsTest(base.TestCaseShell):
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_operation_logs(self, mock_request):
mock_request.return_value = mock_request_return
cs.operation_logs.list()
mock_request.assert_called_with(
'GET',
'/operation_logs', headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_operation_logs_with_all_tenants(self, mock_request):
mock_request.return_value = mock_request_return
cs.operation_logs.list(search_opts={'all_tenants': 1})
mock_request.assert_called_with(
'GET',
'/operation_logs?all_tenants=1', headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_operation_logs_with_marker_limit(self, mock_request):
mock_request.return_value = mock_request_return
cs.operation_logs.list(marker=1234, limit=2)
mock_request.assert_called_with(
'GET',
'/operation_logs?limit=2&marker=1234', headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_operation_logs_with_sort_key_dir(self, mock_request):
mock_request.return_value = mock_request_return
cs.operation_logs.list(sort_key='id', sort_dir='asc')
mock_request.assert_called_with(
'GET',
'/operation_logs?'
'sort_dir=asc&sort_key=id', headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_operation_logs_with_invalid_sort_key(self, mock_request):
self.assertRaises(ValueError,
cs.operation_logs.list,
sort_key='invalid', sort_dir='asc')
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_show_operation_log(self, mock_request):
mock_request.return_value = mock_request_return
cs.operation_logs.get('1')
mock_request.assert_called_with(
'GET',
'/operation_logs/1',
headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_show_operation_log_with_headers(self, mock_request):
mock_request.return_value = mock_request_return
cs.operation_logs.get('1', session_id='fake_session_id')
mock_request.assert_called_with(
'GET',
'/operation_logs/1',
headers={'X-Configuration-Session': 'fake_session_id'})

View File

@@ -0,0 +1,58 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from karborclient.tests.unit import base
from karborclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
mock_request_return = ({}, {'quota_class': {'plans': 50}})
class QuotaClassesTest(base.TestCaseShell):
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_quota_class_update(self, mock_request):
mock_request.return_value = mock_request_return
cs.quota_classes.update('default', {'plans': 50})
mock_request.assert_called_with(
'PUT',
'/quota_classes/default',
data={'quota_class': {'plans': 50}}, headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_quota_class_update_with_none(self, mock_request):
mock_request.return_value = mock_request_return
cs.quota_classes.update('default', {'plans': None})
mock_request.assert_called_with(
'PUT',
'/quota_classes/default',
data={'quota_class': {'plans': 50}}, headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_show_quota_class(self, mock_request):
mock_request.return_value = mock_request_return
cs.quota_classes.get('default')
mock_request.assert_called_with(
'GET',
'/quota_classes/default',
headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_show_quota_class_with_headers(self, mock_request):
mock_request.return_value = mock_request_return
cs.quota_classes.get('default', session_id='fake_session_id')
mock_request.assert_called_with(
'GET',
'/quota_classes/default',
headers={'X-Configuration-Session': 'fake_session_id'})

View File

@@ -0,0 +1,88 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from karborclient.tests.unit import base
from karborclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
mock_request_return = ({}, {'quota': {'plans': 50}})
class QuotasTest(base.TestCaseShell):
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_quota_update(self, mock_request):
mock_request.return_value = mock_request_return
cs.quotas.update(fakes.PROJECT_ID, {'plans': 50})
mock_request.assert_called_with(
'PUT',
'/quotas/{project_id}'.format(project_id=fakes.PROJECT_ID),
data={'quota': {'plans': 50}}, headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_quota_update_with_none(self, mock_request):
mock_request.return_value = mock_request_return
cs.quotas.update(fakes.PROJECT_ID, {'plans': None})
mock_request.assert_called_with(
'PUT',
'/quotas/{project_id}'.format(project_id=fakes.PROJECT_ID),
data={'quota': {'plans': 50}}, headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_show_quota(self, mock_request):
mock_request.return_value = mock_request_return
cs.quotas.get(fakes.PROJECT_ID, detail=False)
mock_request.assert_called_with(
'GET',
'/quotas/{project_id}'.format(project_id=fakes.PROJECT_ID),
headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_show_quota_with_headers(self, mock_request):
mock_request.return_value = mock_request_return
cs.quotas.get(fakes.PROJECT_ID, False, session_id='fake_session_id')
mock_request.assert_called_with(
'GET',
'/quotas/{project_id}'.format(project_id=fakes.PROJECT_ID),
headers={'X-Configuration-Session': 'fake_session_id'})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_show_quota_with_detail(self, mock_request):
mock_request.return_value = mock_request_return
cs.quotas.get(fakes.PROJECT_ID, detail=True)
mock_request.assert_called_with(
'GET',
'/quotas/{project_id}/detail'.format(
project_id=fakes.PROJECT_ID),
headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_show_quota_with_default(self, mock_request):
mock_request.return_value = mock_request_return
cs.quotas.defaults(fakes.PROJECT_ID)
mock_request.assert_called_with(
'GET',
'/quotas/{project_id}/defaults'.format(
project_id=fakes.PROJECT_ID),
headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_show_quota_default_with_headers(self, mock_request):
mock_request.return_value = mock_request_return
cs.quotas.defaults(fakes.PROJECT_ID, session_id='fake_session_id')
mock_request.assert_called_with(
'GET',
'/quotas/{project_id}/defaults'.format(
project_id=fakes.PROJECT_ID),
headers={'X-Configuration-Session': 'fake_session_id'})

View File

@@ -0,0 +1,104 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from karborclient.tests.unit import base
from karborclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
mock_request_return = ({}, {'service': {}})
class ServicesTest(base.TestCaseShell):
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_services(self, mock_request):
mock_request.return_value = mock_request_return
cs.services.list()
mock_request.assert_called_with(
'GET',
'/os-services',
headers={}
)
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_services_with_host(self, mock_request):
mock_request.return_value = mock_request_return
cs.services.list(host='fake_host')
mock_request.assert_called_with(
'GET',
'/os-services?host=fake_host',
headers={}
)
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_services_with_binary(self, mock_request):
mock_request.return_value = mock_request_return
cs.services.list(binary='fake_binary')
mock_request.assert_called_with(
'GET',
'/os-services?binary=fake_binary',
headers={}
)
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_services_with_host_and_binary(self, mock_request):
mock_request.return_value = mock_request_return
cs.services.list(host='fake_host', binary='fake_binary')
mock_request.assert_called_with(
'GET',
'/os-services?binary=fake_binary&host=fake_host',
headers={}
)
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_enable_service(self, mock_request):
mock_request.return_value = mock_request_return
body = {
'status': 'enabled'
}
cs.services.enable('1')
mock_request.assert_called_with(
'PUT',
'/os-services/1',
data=body,
headers={}
)
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_disable_service(self, mock_request):
mock_request.return_value = mock_request_return
body = {
'status': 'disabled'
}
cs.services.disable('1')
mock_request.assert_called_with(
'PUT',
'/os-services/1',
data=body,
headers={}
)
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_disable_service_with_reason(self, mock_request):
mock_request.return_value = mock_request_return
body = {
'status': 'disabled',
'disabled_reason': 'fake_reason'
}
cs.services.disable_log_reason('1', 'fake_reason')
mock_request.assert_called_with(
'PUT',
'/os-services/1',
data=body,
headers={}
)

View File

@@ -0,0 +1,137 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import mock
from karborclient import shell
from karborclient.tests.unit import base
from karborclient.tests.unit.v1 import fakes
FAKE_PROVIDER_ID = '1234'
FAKE_ENDPOINT = 'http://127.0.0.1/identity'
class ShellFixture(fixtures.Fixture):
def setUp(self):
super(ShellFixture, self).setUp()
self.shell = shell.KarborShell()
def tearDown(self):
# For some method like test_image_meta_bad_action we are
# testing a SystemExit to be thrown and object self.shell has
# no time to get instantiated which is OK in this case, so
# we make sure the method is there before launching it.
if hasattr(self.shell, 'cs'):
self.shell.cs.clear_callstack()
super(ShellFixture, self).tearDown()
class ShellTest(base.TestCaseShell):
FAKE_ENV = {
'OS_USERNAME': 'username',
'OS_PASSWORD': 'password',
'OS_TENANT_NAME': 'project_id',
'OS_AUTH_URL': 'http://no.where/v2.0',
'OS_AUTH_TOKEN': 'fake_token'
}
def setUp(self):
"""Run before each test."""
super(ShellTest, self).setUp()
for var in self.FAKE_ENV:
self.useFixture(fixtures.EnvironmentVariable(
var, self.FAKE_ENV[var]))
self.shell = self.useFixture(ShellFixture()).shell
get_endpoint = mock.MagicMock()
get_endpoint.return_value = FAKE_ENDPOINT
self.useFixture(fixtures.MonkeyPatch(
'keystoneauth1.identity.generic.token.Token.get_endpoint',
get_endpoint))
self.useFixture(fixtures.MonkeyPatch('karborclient.client.Client',
fakes.FakeClient))
self.useFixture(fixtures.MonkeyPatch(
'karborclient.common.http._construct_http_client',
fakes.FakeHTTPClient))
def run_command(self, cmd):
if not isinstance(cmd, list):
cmd = cmd.split()
self.shell.main(cmd)
def assert_called(self, method, url, body=None, **kwargs):
return self.shell.cs.assert_called(method, url, body, **kwargs)
def test_checkpoint_list_with_all_tenants(self):
self.run_command(
'checkpoint-list ' + FAKE_PROVIDER_ID + ' --all-tenants 1')
self.assert_called('GET',
'/providers/1234/'
'checkpoints?all_tenants=1')
def test_checkpoint_list_with_all(self):
self.run_command(
'checkpoint-list ' + FAKE_PROVIDER_ID + ' --all')
self.assert_called('GET',
'/providers/1234/'
'checkpoints?all_tenants=1')
def test_plan_list_with_all_tenants(self):
self.run_command('plan-list --all-tenants 1')
self.assert_called('GET', '/plans?all_tenants=1')
def test_plan_list_with_all(self):
self.run_command('plan-list --all')
self.assert_called('GET', '/plans?all_tenants=1')
def test_resotre_list_with_all_tenants(self):
self.run_command('restore-list --all-tenants 1')
self.assert_called('GET', '/restores?all_tenants=1')
def test_resotre_list_with_all(self):
self.run_command('restore-list --all')
self.assert_called('GET', '/restores?all_tenants=1')
def test_verification_list_with_all_tenants(self):
self.run_command('verification-list --all-tenants 1')
self.assert_called('GET', '/verifications?all_tenants=1')
def test_verification_list_with_all(self):
self.run_command('verification-list --all')
self.assert_called('GET', '/verifications?all_tenants=1')
def test_trigger_list_with_all_tenants(self):
self.run_command('trigger-list --all-tenants 1')
self.assert_called('GET', '/triggers?all_tenants=1')
def test_trigger_list_with_all(self):
self.run_command('trigger-list --all')
self.assert_called('GET', '/triggers?all_tenants=1')
def test_scheduledoperation_list_with_all_tenants(self):
self.run_command('scheduledoperation-list --all-tenants 1')
self.assert_called('GET', '/scheduled_operations?all_tenants=1')
def test_scheduledoperation_list_with_all(self):
self.run_command('scheduledoperation-list --all')
self.assert_called('GET', '/scheduled_operations?all_tenants=1')
def test_operationlog_list_with_all_tenants(self):
self.run_command('operationlog-list --all-tenants 1')
self.assert_called('GET', '/operation_logs?all_tenants=1')
def test_operationlog_list_with_all(self):
self.run_command('operationlog-list --all')
self.assert_called('GET', '/operation_logs?all_tenants=1')

View File

@@ -12,6 +12,7 @@
import mock
from karborclient.common.apiclient import exceptions
from karborclient.tests.unit import base
from karborclient.tests.unit.v1 import fakes
@@ -21,6 +22,22 @@ mock_request_return = ({}, {'trigger_info': {'name': 'fake_name'}})
class TriggersTest(base.TestCaseShell):
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_triggers(self, mock_request):
mock_request.return_value = mock_request_return
cs.triggers.list()
mock_request.assert_called_with(
'GET',
'/triggers', headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_triggers_with_all_tenants(self, mock_request):
mock_request.return_value = mock_request_return
cs.triggers.list(search_opts={'all_tenants': 1})
mock_request.assert_called_with(
'GET',
'/triggers?all_tenants=1', headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_triggers_with_marker_limit(self, mock_request):
mock_request.return_value = mock_request_return
@@ -46,16 +63,21 @@ class TriggersTest(base.TestCaseShell):
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_create_trigger(self, mock_request):
mock_request.return_value = mock_request_return
cs.triggers.create('name', 'time', 'properties')
cs.triggers.create('name', 'time', {})
mock_request.assert_called_with(
'POST',
'/triggers',
data={
'trigger_info': {'name': 'name',
'type': 'time',
'properties': 'properties'}},
'properties': {}}},
headers={})
def test_create_trigger_with_invalid_window(self):
self.assertRaises(exceptions.CommandError,
cs.triggers.create,
'name', 'time', {'window': 'fake'})
@mock.patch('karborclient.common.http.HTTPClient.raw_request')
def test_delete_trigger(self, mock_request):
mock_request.return_value = mock_request_return
@@ -97,3 +119,9 @@ class TriggersTest(base.TestCaseShell):
data=body,
headers={}
)
def test_update_trigger_with_invalid_window(self):
trigger_id = '123'
self.assertRaises(exceptions.CommandError,
cs.triggers.update,
trigger_id, {'properties': {'window': 'fake'}})

View File

@@ -0,0 +1,80 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from karborclient.tests.unit import base
from karborclient.tests.unit.v1 import fakes
cs = fakes.FakeClient()
mock_request_return = ({}, {'verification': {}})
class VerificationsTest(base.TestCaseShell):
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_verifications_with_marker_limit(self, mock_request):
mock_request.return_value = mock_request_return
cs.verifications.list(marker=1234, limit=2)
mock_request.assert_called_with(
'GET',
'/verifications?limit=2&marker=1234', headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_verifications_with_sort_key_dir(self, mock_request):
mock_request.return_value = mock_request_return
cs.verifications.list(sort_key='id', sort_dir='asc')
mock_request.assert_called_with(
'GET',
'/verifications?'
'sort_dir=asc&sort_key=id', headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_list_verifications_with_invalid_sort_key(self, mock_request):
self.assertRaises(ValueError,
cs.verifications.list,
sort_key='invalid', sort_dir='asc')
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_create_verification(self, mock_request):
mock_request.return_value = mock_request_return
cs.verifications.create('586cc6ce-e286-40bd-b2b5-dd32694d9944',
'2220f8b1-975d-4621-a872-fa9afb43cb6c',
'{}')
mock_request.assert_called_with(
'POST',
'/verifications',
data={
'verification':
{
'checkpoint_id': '2220f8b1-975d-4621-a872-fa9afb43cb6c',
'parameters': '{}',
'provider_id': '586cc6ce-e286-40bd-b2b5-dd32694d9944'
}}, headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_show_verification(self, mock_request):
mock_request.return_value = mock_request_return
cs.verifications.get('1')
mock_request.assert_called_with(
'GET',
'/verifications/1',
headers={})
@mock.patch('karborclient.common.http.HTTPClient.json_request')
def test_show_verification_with_headers(self, mock_request):
mock_request.return_value = mock_request_return
cs.verifications.get('1', session_id='fake_session_id')
mock_request.assert_called_with(
'GET',
'/verifications/1',
headers={'X-Configuration-Session': 'fake_session_id'})

146
karborclient/utils.py Normal file
View File

@@ -0,0 +1,146 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from karborclient.common.apiclient import exceptions
def extract_resources(args):
resources = []
for data in args.resources.split(','):
if '=' in data and len(data.split('=')) in [3, 4]:
resource = dict(zip(['id', 'type', 'name', 'extra_info'],
data.split('=')))
if resource.get('extra_info'):
resource['extra_info'] = jsonutils.loads(
resource.get('extra_info'))
else:
raise exceptions.CommandError(
"Unable to parse parameter resources. "
"The keys of resource are id , type, name and "
"extra_info. The extra_info field is optional.")
resources.append(resource)
return resources
def check_resources(cs, resources):
# check the resource whether it is available
for resource in resources:
try:
instance = cs.protectables.get_instance(
resource["type"], resource["id"])
except exceptions.NotFound:
raise exceptions.CommandError(
"The resource: %s can not be found." % resource["id"])
else:
if instance is None:
raise exceptions.CommandError(
"The resource: %s is invalid." % resource["id"])
def extract_parameters(args):
if all((args.parameters, args.parameters_json)):
raise exceptions.CommandError(
"Must provide parameters or parameters-json, not both")
if not any((args.parameters, args.parameters_json)):
return {}
if args.parameters_json:
return jsonutils.loads(args.parameters_json)
parameters = {}
for resource_params in args.parameters:
resource_type = None
resource_id = None
parameter = {}
for param_kv in resource_params.split(','):
try:
key, value = param_kv.split('=')
except Exception:
raise exceptions.CommandError(
'parameters must be in the form: key1=val1,key2=val2,...'
)
if key == "resource_type":
resource_type = value
elif key == "resource_id":
if not uuidutils.is_uuid_like(value):
raise exceptions.CommandError('resource_id must be a uuid')
resource_id = value
else:
parameter[key] = value
if resource_type is None:
raise exceptions.CommandError(
'Must specify resource_type for parameters'
)
if resource_id is None:
resource_key = resource_type
else:
resource_key = "%s#%s" % (resource_type, resource_id)
parameters[resource_key] = parameter
return parameters
def extract_instances_parameters(args):
parameters = {}
for parameter in args.parameters:
if '=' in parameter:
(key, value) = parameter.split('=', 1)
else:
key = parameter
value = None
parameters[key] = value
return parameters
def extract_extra_info(args):
checkpoint_extra_info = {}
for data in args.extra_info:
# unset doesn't require a val, so we have the if/else
if '=' in data:
(key, value) = data.split('=', 1)
else:
key = data
value = None
checkpoint_extra_info[key] = value
return checkpoint_extra_info
def extract_properties(args):
properties = {}
if args.properties is None:
return properties
for data in args.properties.split(','):
if '=' in data:
(resource_key, resource_value) = data.split('=', 1)
else:
raise exceptions.CommandError(
"Unable to parse parameter properties.")
properties[resource_key] = resource_value
return properties
def extract_operation_definition(args):
operation_definition = {}
for data in args.operation_definition.split(','):
if '=' in data:
(resource_key, resource_value) = data.split('=', 1)
else:
raise exceptions.CommandError(
"Unable to parse parameter operation_definition.")
operation_definition[resource_key] = resource_value
return operation_definition

View File

@@ -25,7 +25,7 @@ class Checkpoint(base.Resource):
return
plan = self.protection_plan
if plan is not None:
provider_id = plan.get("provider_id", None)
provider_id = plan.get("provider_id")
new = self.manager.get(provider_id, self.id)
if new:
self._add_details(new._info)
@@ -43,6 +43,15 @@ class CheckpointManager(base.ManagerWithFind):
"checkpoints" .format(provider_id=provider_id)
return self._create(url, body, 'checkpoint')
def reset_state(self, provider_id, checkpoint_id, state):
body = {'os-resetState': {'state': state}}
return self.update(provider_id, checkpoint_id, body)
def update(self, provider_id, checkpoint_id, values):
url = '/providers/{provider_id}/checkpoints/{checkpoint_id}'.format(
provider_id=provider_id, checkpoint_id=checkpoint_id)
return self._update(url, values)
def delete(self, provider_id, checkpoint_id):
path = '/providers/{provider_id}/checkpoints/' \
'{checkpoint_id}'.format(provider_id=provider_id,

View File

@@ -14,12 +14,17 @@
from karborclient.common import http
from karborclient.v1 import checkpoints
from karborclient.v1 import operation_logs
from karborclient.v1 import plans
from karborclient.v1 import protectables
from karborclient.v1 import providers
from karborclient.v1 import quota_classes
from karborclient.v1 import quotas
from karborclient.v1 import restores
from karborclient.v1 import scheduled_operations
from karborclient.v1 import services
from karborclient.v1 import triggers
from karborclient.v1 import verifications
class Client(object):
@@ -42,3 +47,10 @@ class Client(object):
self.triggers = triggers.TriggerManager(self.http_client)
self.scheduled_operations = \
scheduled_operations.ScheduledOperationManager(self.http_client)
self.operation_logs = \
operation_logs.OperationLogManager(self.http_client)
self.verifications = verifications.VerificationManager(
self.http_client)
self.services = services.ServiceManager(self.http_client)
self.quotas = quotas.QuotaManager(self.http_client)
self.quota_classes = quota_classes.QuotaClassManager(self.http_client)

View File

@@ -0,0 +1,44 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from karborclient.common import base
class OperationLog(base.Resource):
def __repr__(self):
return "<OperationLog %s>" % self._info
class OperationLogManager(base.ManagerWithFind):
resource_class = OperationLog
def get(self, operation_log_id, session_id=None):
if session_id:
headers = {'X-Configuration-Session': session_id}
else:
headers = {}
url = "/operation_logs/{operation_log_id}".format(
operation_log_id=operation_log_id)
return self._get(url, response_key="operation_log", headers=headers)
def list(self, detailed=False, search_opts=None, marker=None, limit=None,
sort_key=None, sort_dir=None, sort=None):
"""Lists all operation_logs.
"""
resource_type = "operation_logs"
url = self._build_list_url(
resource_type, detailed=detailed,
search_opts=search_opts, marker=marker,
limit=limit, sort_key=sort_key,
sort_dir=sort_dir, sort=sort)
return self._list(url, 'operation_logs')

View File

@@ -0,0 +1,45 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from karborclient.common import base
class QuotaClass(base.Resource):
def __repr__(self):
return "<QuotaClass %s>" % self._info
class QuotaClassManager(base.ManagerWithFind):
resource_class = QuotaClass
def list(self):
pass
def update(self, class_name, data):
if "plans" in data and data["plans"] is None:
data["plans"] = 50
body = {"quota_class": data}
return self._update('/quota_classes/{class_name}'
.format(class_name=class_name),
body, "quota_class")
def get(self, class_name, session_id=None):
if session_id:
headers = {'X-Configuration-Session': session_id}
else:
headers = {}
url = "/quota_classes/{class_name}".format(
class_name=class_name)
return self._get(url, response_key="quota_class", headers=headers)

58
karborclient/v1/quotas.py Normal file
View File

@@ -0,0 +1,58 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from karborclient.common import base
class Quota(base.Resource):
def __repr__(self):
return "<Quota %s>" % self._info
class QuotaManager(base.ManagerWithFind):
resource_class = Quota
def list(self):
pass
def update(self, project_id, data):
if "plans" in data and data["plans"] is None:
data["plans"] = 50
body = {"quota": data}
return self._update('/quotas/{project_id}'
.format(project_id=project_id),
body, "quota")
def get(self, project_id, detail, session_id=None):
if session_id:
headers = {'X-Configuration-Session': session_id}
else:
headers = {}
base_url = "/quotas/{project_id}".format(
project_id=project_id)
if detail:
url = base_url + '/detail'
else:
url = base_url
return self._get(url, response_key="quota", headers=headers)
def defaults(self, project_id, session_id=None):
if session_id:
headers = {'X-Configuration-Session': session_id}
else:
headers = {}
url = "/quotas/{project_id}/defaults".format(
project_id=project_id)
return self._get(url, response_key="quota", headers=headers)

View File

@@ -0,0 +1,64 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from karborclient.common import base
class Service(base.Resource):
def __repr__(self):
return "<Service %s>" % self._info
class ServiceManager(base.ManagerWithFind):
resource_class = Service
def enable(self, service_id):
"""Enable the service specified by the service ID
:param service_id: The ID of the service to enable.
"""
body = {
'status': 'enabled'
}
return self._update('/os-services/%s' % service_id, body, "service")
def disable(self, service_id):
"""Disable the service specified by the service ID.
:param service_id: The ID of the service to disable.
"""
body = {
'status': 'disabled'
}
return self._update('/os-services/%s' % service_id, body, "service")
def disable_log_reason(self, service_id, reason):
"""Disable the service with a reason.
:param service_id: The ID of the service to disable.
:param reason: The reason for disabling a service.
"""
body = {
'status': 'disabled',
'disabled_reason': reason
}
return self._update("/os-services/%s" % service_id, body, "service")
def list(self, host=None, binary=None):
"""Lists all services."""
search_opts = {
'host': host,
'binary': binary
}
resource_type = "os-services"
url = self._build_list_url(resource_type, search_opts=search_opts)
return self._list(url, 'services')

View File

@@ -11,7 +11,6 @@
# under the License.
import argparse
import json
import os
from datetime import datetime
@@ -21,6 +20,7 @@ from oslo_utils import uuidutils
from karborclient.common.apiclient import exceptions
from karborclient.common import base
from karborclient.common import utils
from karborclient import utils as arg_utils
@utils.arg('--all-tenants',
@@ -31,11 +31,6 @@ from karborclient.common import utils
const=1,
default=0,
help='Shows details for all tenants. Admin only.')
@utils.arg('--all_tenants',
nargs='?',
type=int,
const=1,
help=argparse.SUPPRESS)
@utils.arg('--name',
metavar='<name>',
default=None,
@@ -145,9 +140,9 @@ def do_plan_create(cs, args):
if not uuidutils.is_uuid_like(args.provider_id):
raise exceptions.CommandError(
"Invalid provider id provided.")
plan_resources = _extract_resources(args)
_check_resources(cs, plan_resources)
plan_parameters = _extract_parameters(args)
plan_resources = arg_utils.extract_resources(args)
arg_utils.check_resources(cs, plan_resources)
plan_parameters = arg_utils.extract_parameters(args)
plan = cs.plans.create(args.name, args.provider_id, plan_resources,
plan_parameters, description=args.description)
dict_format_list = {"resources", "parameters"}
@@ -188,6 +183,8 @@ def do_plan_delete(cs, args):
help="Id of plan to update.")
@utils.arg("--name", metavar="<name>",
help="A name to which the plan will be renamed.")
@utils.arg("--description", metavar="<description>",
help="Description to which the plan will be updated.")
@utils.arg("--resources", metavar="<id=type=name,id=type=name>",
help="Resources to which the plan will be updated.")
@utils.arg("--status", metavar="<suspended|started>",
@@ -197,8 +194,10 @@ def do_plan_update(cs, args):
data = {}
if args.name is not None:
data['name'] = args.name
if args.description is not None:
data['description'] = args.description
if args.resources is not None:
plan_resources = _extract_resources(args)
plan_resources = arg_utils.extract_resources(args)
data['resources'] = plan_resources
if args.status is not None:
data['status'] = args.status
@@ -211,39 +210,6 @@ def do_plan_update(cs, args):
utils.print_dict(plan.to_dict())
def _extract_resources(args):
resources = []
for data in args.resources.split(','):
if '=' in data and len(data.split('=')) in [3, 4]:
resource = dict(zip(['id', 'type', 'name', 'extra_info'],
data.split('=')))
if resource.get('extra_info'):
resource['extra_info'] = jsonutils.loads(
resource.get('extra_info'))
else:
raise exceptions.CommandError(
"Unable to parse parameter resources. "
"The keys of resource are id , type, name and "
"extra_info. The extra_info field is optional.")
resources.append(resource)
return resources
def _check_resources(cs, resources):
# check the resource whether it is available
for resource in resources:
try:
instance = cs.protectables.get_instance(
resource["type"], resource["id"])
except exceptions.NotFound:
raise exceptions.CommandError(
"The resource: %s can not be found." % resource["id"])
else:
if instance is None:
raise exceptions.CommandError(
"The resource: %s is invalid." % resource["id"])
@utils.arg('provider_id',
metavar='<provider_id>',
help='Provider id.')
@@ -286,7 +252,7 @@ def do_restore_create(cs, args):
raise exceptions.CommandError(
"Invalid checkpoint id provided.")
restore_parameters = _extract_parameters(args)
restore_parameters = arg_utils.extract_parameters(args)
restore_auth = None
if args.restore_target is not None:
if args.restore_username is None:
@@ -307,48 +273,6 @@ def do_restore_create(cs, args):
utils.print_dict(restore.to_dict(), dict_format_list=dict_format_list)
def _extract_parameters(args):
if all((args.parameters, args.parameters_json)):
raise exceptions.CommandError(
"Must provide parameters or parameters-json, not both")
if not any((args.parameters, args.parameters_json)):
return {}
if args.parameters_json:
return jsonutils.loads(args.parameters_json)
parameters = {}
for resource_params in args.parameters:
resource_type = None
resource_id = None
parameter = {}
for param_kv in resource_params.split(','):
try:
key, value = param_kv.split('=')
except Exception:
raise exceptions.CommandError(
'parameters must be in the form: key1=val1,key2=val2,...'
)
if key == "resource_type":
resource_type = value
elif key == "resource_id":
if not uuidutils.is_uuid_like(value):
raise exceptions.CommandError('resource_id must be a uuid')
resource_id = value
else:
parameter[key] = value
if resource_type is None:
raise exceptions.CommandError(
'Must specify resource_type for parameters'
)
if resource_id is None:
resource_key = resource_type
else:
resource_key = "%s#%s" % (resource_type, resource_id)
parameters[resource_key] = parameter
return parameters
@utils.arg('--all-tenants',
dest='all_tenants',
metavar='<0|1>',
@@ -357,11 +281,6 @@ def _extract_parameters(args):
const=1,
default=0,
help='Shows details for all tenants. Admin only.')
@utils.arg('--all_tenants',
nargs='?',
type=int,
const=1,
help=argparse.SUPPRESS)
@utils.arg('--status',
metavar='<status>',
default=None,
@@ -424,7 +343,7 @@ def do_restore_list(cs, args):
sortby_index = None
else:
sortby_index = 0
formatters = {"Parameters": lambda obj: json.dumps(
formatters = {"Parameters": lambda obj: jsonutils.dumps(
obj.parameters, indent=2, sort_keys=True)}
utils.print_list(restores, key_list, exclude_unavailable=True,
sortby_index=sortby_index, formatters=formatters)
@@ -440,6 +359,135 @@ def do_restore_show(cs, args):
utils.print_dict(restore.to_dict(), dict_format_list=dict_format_list)
@utils.arg('provider_id',
metavar='<provider_id>',
help='Provider id.')
@utils.arg('checkpoint_id',
metavar='<checkpoint_id>',
help='Checkpoint id.')
@utils.arg('--parameters-json',
type=str,
dest='parameters_json',
metavar='<parameters>',
default=None,
help='Verification parameters in json format.')
@utils.arg('--parameters',
action='append',
metavar='resource_type=<type>[,resource_id=<id>,key=val,...]',
default=[],
help='Verification parameters, may be specified multiple times. '
'resource_type: type of resource to apply parameters. '
'resource_id: limit the parameters to a specific resource. '
'Other keys and values: according to provider\'s schema.'
)
def do_verification_create(cs, args):
"""Creates a verification."""
if not uuidutils.is_uuid_like(args.provider_id):
raise exceptions.CommandError(
"Invalid provider id provided.")
if not uuidutils.is_uuid_like(args.checkpoint_id):
raise exceptions.CommandError(
"Invalid checkpoint id provided.")
verification_parameters = arg_utils.extract_parameters(args)
verification = cs.verifications.create(args.provider_id,
args.checkpoint_id,
verification_parameters)
dict_format_list = {"parameters"}
utils.print_dict(verification.to_dict(), dict_format_list=dict_format_list)
@utils.arg('--all-tenants',
dest='all_tenants',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=0,
help='Shows details for all tenants. Admin only.')
@utils.arg('--status',
metavar='<status>',
default=None,
help='Filters results by a status. Default=None.')
@utils.arg('--marker',
metavar='<marker>',
default=None,
help='Begin returning verifications that appear later in the'
'list than that represented by this verification id. '
'Default=None.')
@utils.arg('--limit',
metavar='<limit>',
default=None,
help='Maximum number of verifications to return. Default=None.')
@utils.arg('--sort_key',
metavar='<sort_key>',
default=None,
help=argparse.SUPPRESS)
@utils.arg('--sort_dir',
metavar='<sort_dir>',
default=None,
help=argparse.SUPPRESS)
@utils.arg('--sort',
metavar='<key>[:<direction>]',
default=None,
help=(('Comma-separated list of sort keys and directions in the '
'form of <key>[:<asc|desc>]. '
'Valid keys: %s. '
'Default=None.') % ', '.join(base.SORT_KEY_VALUES)))
@utils.arg('--tenant',
type=str,
dest='tenant',
nargs='?',
metavar='<tenant>',
help='Display information from single tenant (Admin only).')
def do_verification_list(cs, args):
"""Lists all verifications."""
all_tenants = 1 if args.tenant else \
int(os.environ.get("ALL_TENANTS", args.all_tenants))
search_opts = {
'all_tenants': all_tenants,
'project_id': args.tenant,
'status': args.status,
}
if args.sort and (args.sort_key or args.sort_dir):
raise exceptions.CommandError(
'The --sort_key and --sort_dir arguments are '
'not supported with --sort.')
verifications = cs.verifications.list(search_opts=search_opts,
marker=args.marker,
limit=args.limit,
sort_key=args.sort_key,
sort_dir=args.sort_dir,
sort=args.sort)
key_list = ['Id', 'Project id', 'Provider id', 'Checkpoint id',
'Parameters', 'Status']
if args.sort_key or args.sort_dir or args.sort:
sortby_index = None
else:
sortby_index = 0
formatters = {"Parameters": lambda obj: jsonutils.dumps(
obj.parameters, indent=2, sort_keys=True)}
utils.print_list(verifications, key_list, exclude_unavailable=True,
sortby_index=sortby_index, formatters=formatters)
@utils.arg('verification',
metavar='<verification>',
help='ID of verification.')
def do_verification_show(cs, args):
"""Shows verification details."""
verification = cs.verifications.get(args.verification)
dict_format_list = {"parameters"}
utils.print_dict(verification.to_dict(),
dict_format_list=dict_format_list)
def do_protectable_list(cs, args):
"""Lists all protectable types."""
@@ -475,7 +523,7 @@ def do_protectable_show(cs, args):
def do_protectable_show_instance(cs, args):
"""Shows instance details."""
search_opts = {
'parameters': (_extract_instances_parameters(args)
'parameters': (arg_utils.extract_instances_parameters(args)
if args.parameters else None),
}
instance = cs.protectables.get_instance(args.protectable_type,
@@ -529,7 +577,7 @@ def do_protectable_list_instances(cs, args):
search_opts = {
'type': args.type,
'parameters': (_extract_instances_parameters(args)
'parameters': (arg_utils.extract_instances_parameters(args)
if args.parameters else None),
}
@@ -551,25 +599,12 @@ def do_protectable_list_instances(cs, args):
else:
sortby_index = 0
formatters = {"Dependent resources": lambda obj: json.dumps(
formatters = {"Dependent resources": lambda obj: jsonutils.dumps(
obj.dependent_resources, indent=2, sort_keys=True)}
utils.print_list(instances, key_list, exclude_unavailable=True,
sortby_index=sortby_index, formatters=formatters)
def _extract_instances_parameters(args):
parameters = {}
for parameter in args.parameters:
if '=' in parameter:
(key, value) = parameter.split('=', 1)
else:
key = parameter
value = None
parameters[key] = value
return parameters
@utils.arg('provider_id',
metavar='<provider_id>',
help='Id of provider.')
@@ -657,7 +692,7 @@ def do_checkpoint_create(cs, args):
checkpoint_extra_info = None
if args.extra_info is not None:
checkpoint_extra_info = _extract_extra_info(args)
checkpoint_extra_info = arg_utils.extract_extra_info(args)
checkpoint = cs.checkpoints.create(args.provider_id, args.plan_id,
checkpoint_extra_info)
dict_format_list = {"protection_plan"}
@@ -666,20 +701,14 @@ def do_checkpoint_create(cs, args):
json_format_list=json_format_list)
def _extract_extra_info(args):
checkpoint_extra_info = {}
for data in args.extra_info:
# unset doesn't require a val, so we have the if/else
if '=' in data:
(key, value) = data.split('=', 1)
else:
key = data
value = None
checkpoint_extra_info[key] = value
return checkpoint_extra_info
@utils.arg('--all-tenants',
dest='all_tenants',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=0,
help='Shows details for all tenants. Admin only.')
@utils.arg('provider_id',
metavar='<provider_id>',
help='ID of provider.')
@@ -753,6 +782,7 @@ def do_checkpoint_list(cs, args):
'start_date': args.start_date,
'end_date': args.end_date,
'project_id': args.project_id,
'all_tenants': args.all_tenants
}
if args.sort and (args.sort_key or args.sort_dir):
@@ -772,8 +802,11 @@ def do_checkpoint_list(cs, args):
sortby_index = None
else:
sortby_index = 0
formatters = {"Protection plan": lambda obj: json.dumps(
obj.protection_plan, indent=2, sort_keys=True)}
def plan_formatter(obj):
return "Name: %s\nId: %s" % (obj.protection_plan['name'],
obj.protection_plan['id'])
formatters = {"Protection plan": plan_formatter}
utils.print_list(checkpoints, key_list, exclude_unavailable=True,
sortby_index=sortby_index, formatters=formatters)
@@ -817,6 +850,45 @@ def do_checkpoint_delete(cs, args):
"specified checkpoint.")
@utils.arg('provider_id',
metavar='<provider_id>',
help='Id of provider.')
@utils.arg('checkpoint',
metavar='<checkpoint>',
nargs="+",
help='ID of checkpoint.')
@utils.arg('--available',
action='store_const',
dest='state',
default='error',
const='available',
help='Request the checkpoint be reset to "available" state instead '
'of "error" state(the default).')
def do_checkpoint_reset_state(cs, args):
"""Reset state of a checkpoint."""
failure_count = 0
for checkpoint_id in args.checkpoint:
try:
cs.checkpoints.reset_state(args.provider_id, checkpoint_id,
args.state)
except exceptions.NotFound:
failure_count += 1
print("Failed to reset state of '{0}'; checkpoint not found".
format(checkpoint_id))
except exceptions.Forbidden:
failure_count += 1
print("Failed to reset state of '{0}'; not allowed".
format(checkpoint_id))
except exceptions.BadRequest:
failure_count += 1
print("Failed to reset state of '{0}'; invalid input or "
"current checkpoint state".format(checkpoint_id))
if failure_count == len(args.checkpoint):
raise exceptions.CommandError("Unable to find or reset any of the "
"specified checkpoint's state.")
@utils.arg('--all-tenants',
dest='all_tenants',
metavar='<0|1>',
@@ -825,11 +897,6 @@ def do_checkpoint_delete(cs, args):
const=1,
default=0,
help='Shows details for all tenants. Admin only.')
@utils.arg('--all_tenants',
nargs='?',
type=int,
const=1,
help=argparse.SUPPRESS)
@utils.arg('--name',
metavar='<name>',
default=None,
@@ -902,7 +969,7 @@ def do_trigger_list(cs, args):
else:
sortby_index = 0
formatters = {"Properties": lambda obj: json.dumps(
formatters = {"Properties": lambda obj: jsonutils.dumps(
obj.properties, indent=2, sort_keys=True)}
utils.print_list(triggers, key_list, exclude_unavailable=True,
sortby_index=sortby_index, formatters=formatters)
@@ -919,27 +986,12 @@ def do_trigger_list(cs, args):
help='Properties of trigger.')
def do_trigger_create(cs, args):
"""Creates a trigger."""
trigger_properties = _extract_properties(args)
trigger_properties = arg_utils.extract_properties(args)
trigger = cs.triggers.create(args.name, args.type, trigger_properties)
dict_format_list = {"properties"}
utils.print_dict(trigger.to_dict(), dict_format_list=dict_format_list)
def _extract_properties(args):
properties = {}
if args.properties is None:
return properties
for data in args.properties.split(','):
if '=' in data:
(resource_key, resource_value) = data.split('=', 1)
else:
raise exceptions.CommandError(
"Unable to parse parameter properties.")
properties[resource_key] = resource_value
return properties
@utils.arg("trigger_id", metavar="<TRIGGER ID>",
help="Id of trigger to update.")
@utils.arg("--name", metavar="<name>",
@@ -949,8 +1001,9 @@ def _extract_properties(args):
def do_trigger_update(cs, args):
"""Update a trigger."""
trigger_info = {}
trigger_properties = _extract_properties(args)
trigger_info['name'] = args.name
trigger_properties = arg_utils.extract_properties(args)
if args.name:
trigger_info['name'] = args.name
trigger_info['properties'] = trigger_properties
trigger = cs.triggers.update(args.trigger_id, trigger_info)
dict_format_list = {"properties"}
@@ -995,11 +1048,6 @@ def do_trigger_delete(cs, args):
const=1,
default=0,
help='Shows details for all tenants. Admin only.')
@utils.arg('--all_tenants',
nargs='?',
type=int,
const=1,
help=argparse.SUPPRESS)
@utils.arg('--name',
metavar='<name>',
default=None,
@@ -1086,7 +1134,9 @@ def do_scheduledoperation_list(cs, args):
help='Trigger name.')
@utils.arg('operation_type',
metavar='<operation_type>',
help='Operation Type of scheduled operation.')
choices=['protect', 'retention_protect'],
help='Operation Type of scheduled operation. Valid values are '
'"protect" or "retention_protect."')
@utils.arg('trigger_id',
metavar='<trigger_id>',
help='Trigger id of scheduled operation.')
@@ -1095,7 +1145,7 @@ def do_scheduledoperation_list(cs, args):
help='Operation definition of scheduled operation.')
def do_scheduledoperation_create(cs, args):
"""Creates a scheduled operation."""
operation_definition = _extract_operation_definition(args)
operation_definition = arg_utils.extract_operation_definition(args)
scheduledoperation = cs.scheduled_operations.create(args.name,
args.operation_type,
args.trigger_id,
@@ -1105,19 +1155,6 @@ def do_scheduledoperation_create(cs, args):
dict_format_list=dict_format_list)
def _extract_operation_definition(args):
operation_definition = {}
for data in args.operation_definition.split(','):
if '=' in data:
(resource_key, resource_value) = data.split('=', 1)
else:
raise exceptions.CommandError(
"Unable to parse parameter operation_definition.")
operation_definition[resource_key] = resource_value
return operation_definition
@utils.arg('scheduledoperation',
metavar='<scheduledoperation>',
help='ID of scheduled operation.')
@@ -1148,3 +1185,227 @@ def do_scheduledoperation_delete(cs, args):
if failure_count == len(args.scheduledoperation):
raise exceptions.CommandError("Unable to find and delete any of the "
"specified scheduled operation.")
@utils.arg('--all-tenants',
dest='all_tenants',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=0,
help='Shows details for all tenants. Admin only.')
@utils.arg('--status',
metavar='<status>',
default=None,
help='Filters results by a status. Default=None.')
@utils.arg('--marker',
metavar='<marker>',
default=None,
help='Begin returning restores that appear later in the '
'operation_log list than that represented by this '
'operation_logs id. Default=None.')
@utils.arg('--limit',
metavar='<limit>',
default=None,
help='Maximum number of operation_logs to return. Default=None.')
@utils.arg('--sort_key',
metavar='<sort_key>',
default=None,
help=argparse.SUPPRESS)
@utils.arg('--sort_dir',
metavar='<sort_dir>',
default=None,
help=argparse.SUPPRESS)
@utils.arg('--sort',
metavar='<key>[:<direction>]',
default=None,
help=(('Comma-separated list of sort keys and directions in the '
'form of <key>[:<asc|desc>]. '
'Valid keys: %s. '
'Default=None.') % ', '.join(base.SORT_KEY_VALUES)))
@utils.arg('--tenant',
type=str,
dest='tenant',
nargs='?',
metavar='<tenant>',
help='Display information from single tenant (Admin only).')
def do_operationlog_list(cs, args):
"""Lists all operation_logs."""
all_tenants = 1 if args.tenant else \
int(os.environ.get("ALL_TENANTS", args.all_tenants))
search_opts = {
'all_tenants': all_tenants,
'project_id': args.tenant,
'status': args.status,
}
if args.sort and (args.sort_key or args.sort_dir):
raise exceptions.CommandError(
'The --sort_key and --sort_dir arguments are deprecated and are '
'not supported with --sort.')
operation_logs = cs.operation_logs.list(
search_opts=search_opts, marker=args.marker,
limit=args.limit, sort_key=args.sort_key,
sort_dir=args.sort_dir, sort=args.sort)
key_list = ['Id', 'Operation Type', 'Checkpoint id', 'Plan Id',
'Provider id', 'Restore Id', 'Scheduled Operation Id',
'Status', 'Started At', 'Ended At', 'Error Info', 'Extra Info']
if args.sort_key or args.sort_dir or args.sort:
sortby_index = None
else:
sortby_index = 0
utils.print_list(operation_logs, key_list, exclude_unavailable=True,
sortby_index=sortby_index)
@utils.arg('operation_log',
metavar='<operation_log>',
help='ID of operation_log.')
def do_operationlog_show(cs, args):
"""Shows operation_log details."""
operation_log = cs.operation_logs.get(args.operation_log)
utils.print_dict(operation_log.to_dict())
@utils.arg('--host',
metavar='<hostname>',
default=None,
help='Name of host.')
@utils.arg('--binary',
metavar='<binary>',
default=None,
help='Service binary.')
def do_service_list(cs, args):
"""Show a list of all running services. Filter by host & binary."""
result = cs.services.list(host=args.host, binary=args.binary)
columns = ["Id", "Binary", "Host", "Status", "State",
"Updated_at", "Disabled Reason"]
utils.print_list(result, columns)
@utils.arg('service_id',
metavar='<service_id>',
help='ID of the service.')
def do_service_enable(cs, args):
"""Enable the service."""
result = cs.services.enable(args.service_id)
utils.print_list([result], ["Id", "Binary", "Host", "Status", "State",
"Updated_at", "Disabled Reason"])
@utils.arg('service_id',
metavar='<service_id>',
help='ID of the service.')
@utils.arg('--reason',
metavar='<reason>',
help='Reason for disabling the service.')
def do_service_disable(cs, args):
"""Disable the service"""
if args.reason:
result = cs.services.disable_log_reason(args.service_id, args.reason)
else:
result = cs.services.disable(args.service_id)
utils.print_list([result], ["Id", "Binary", "Host", "Status", "State",
"Updated_at", "Disabled Reason"])
@utils.arg(
'--tenant',
metavar='<tenant>',
default=None,
help='ID of tenant to list the quotas for.')
@utils.arg(
'--detail',
action='store_true',
help='Optional flag to indicate whether to show quota in detail. '
'Default false.')
def do_quota_show(cs, args):
"""List the quotas for a tenant."""
project_id = args.tenant or cs.http_client.get_project_id()
kwargs = {
"project_id": project_id,
"detail": args.detail,
}
result = cs.quotas.get(**kwargs)
_quota_set_pretty_show(result)
def _quota_set_pretty_show(quotas):
"""Convert quotas object to dict and display."""
new_quotas = {}
for quota_k, quota_v in sorted(quotas.to_dict().items()):
if isinstance(quota_v, dict):
quota_v = '\n'.join(
['%s = %s' % (k, v) for k, v in sorted(quota_v.items())])
new_quotas[quota_k] = quota_v
utils.print_dict(new_quotas)
@utils.arg(
'tenant',
metavar='<tenant>',
help='ID of tenant to set the quotas for.')
@utils.arg(
'--plans',
metavar='<plans>',
type=int,
default=None,
help='New value for the "plans" quota. The default value is 50.')
def do_quota_update(cs, args):
"""Update the quotas for a project (Admin only)."""
project_id = args.tenant
data = {
"plans": args.plans,
}
result = cs.quotas.update(project_id, data)
_quota_set_pretty_show(result)
@utils.arg(
'--tenant',
metavar='<tenant>',
default=None,
help='ID of tenant to list the quotas for.')
def do_quota_defaults(cs, args):
"""List the default quotas for a tenant."""
project_id = args.tenant or cs.http_client.get_project_id()
result = cs.quotas.defaults(project_id)
_quota_set_pretty_show(result)
@utils.arg(
'class_name',
metavar='<class_name>',
help='Name of quota class to list the quotas for.')
def do_quota_class_show(cs, args):
"""List the quotas for a quota class."""
result = cs.quota_classes.get(args.class_name)
_quota_set_pretty_show(result)
@utils.arg(
'class_name',
metavar='<class_name>',
help='Name of quota class to set the quotas for.')
@utils.arg(
'--plans',
metavar='<plans>',
type=int,
default=None,
help='New value for the "plans" quota. The default value is 50.')
def do_quota_class_update(cs, args):
"""Update the quotas for a quota class (Admin only)."""
class_name = args.class_name
data = {
"plans": args.plans,
}
result = cs.quota_classes.update(class_name, data)
_quota_set_pretty_show(result)

View File

@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from karborclient.common.apiclient import exceptions
from karborclient.common import base
@@ -22,6 +23,12 @@ class TriggerManager(base.ManagerWithFind):
resource_class = Trigger
def create(self, name, type, properties):
if properties.get('window', None):
try:
properties['window'] = int(properties['window'])
except Exception:
msg = 'The trigger window is not integer'
raise exceptions.CommandError(msg)
body = {'trigger_info': {'name': name,
'type': type,
'properties': properties,
@@ -45,8 +52,14 @@ class TriggerManager(base.ManagerWithFind):
def update(self, trigger_id, data):
if data['properties'].get('window', None):
try:
data['properties']['window'] = int(
data['properties']['window'])
except Exception:
msg = 'The trigger window is not integer'
raise exceptions.CommandError(msg)
body = {"trigger_info": data}
return self._update('/triggers/{trigger_id}'
.format(trigger_id=trigger_id),
body, "trigger_info")

View File

@@ -0,0 +1,65 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from karborclient.common import base
class Verification(base.Resource):
def __repr__(self):
return "<Verification %s>" % self._info
class VerificationManager(base.ManagerWithFind):
resource_class = Verification
def create(self, provider_id, checkpoint_id, parameters):
body = {
'verification': {
'provider_id': provider_id,
'checkpoint_id': checkpoint_id,
'parameters': parameters,
}
}
url = "/verifications"
return self._create(url, body, 'verification')
def get(self, verification_id, session_id=None):
if session_id:
headers = {'X-Configuration-Session': session_id}
else:
headers = {}
url = "/verifications/{verification_id}".format(
verification_id=verification_id)
return self._get(url, response_key="verification", headers=headers)
def list(self, detailed=False, search_opts=None, marker=None, limit=None,
sort_key=None, sort_dir=None, sort=None):
"""Lists all verifications.
:param detailed: Whether to return detailed verification info.
:param search_opts: Search options to filter out verifications.
:param marker: Begin returning verifications that appear later in the
list than that represented by this verification id.
:param limit: Maximum number of verifications to return.
:param sort_key: Key to be sorted;
:param sort_dir: Sort direction, should be 'desc' or 'asc';
:param sort: Sort information
:rtype: list of :class:`Verification`
"""
resource_type = "verifications"
url = self._build_list_url(
resource_type, detailed=detailed,
search_opts=search_opts, marker=marker,
limit=limit, sort_key=sort_key,
sort_dir=sort_dir, sort=sort)
return self._list(url, 'verifications')

88
lower-constraints.txt Normal file
View File

@@ -0,0 +1,88 @@
alabaster==0.7.10
appdirs==1.3.0
asn1crypto==0.23.0
Babel==2.3.4
cffi==1.7.0
cliff==2.8.0
cmd2==0.8.0
coverage==4.0
cryptography==2.1
debtcollector==1.2.0
decorator==3.4.0
deprecation==1.0
docutils==0.11
dogpile.cache==0.6.2
dulwich==0.15.0
extras==1.0.0
fixtures==3.0.0
flake8==2.5.5
hacking==0.12.0
idna==2.6
imagesize==0.7.1
iso8601==0.1.11
Jinja2==2.10
jmespath==0.9.0
jsonpatch==1.16
jsonpointer==1.13
jsonschema==2.6.0
keystoneauth1==3.4.0
linecache2==1.0.0
MarkupSafe==1.0
mccabe==0.2.1
mock==2.0.0
monotonic==0.6
mox3==0.20.0
msgpack-python==0.4.0
munch==2.1.0
netaddr==0.7.18
netifaces==0.10.4
openstackdocstheme==1.18.1
openstacksdk==0.11.2
os-client-config==1.28.0
os-service-types==1.2.0
osc-lib==1.8.0
oslo.config==5.2.0
oslo.context==2.19.2
oslo.i18n==3.15.3
oslo.log==3.36.0
oslo.serialization==2.18.0
oslo.utils==3.33.0
oslotest==3.2.0
pbr==2.0.0
pep8==1.5.7
positional==1.2.1
prettytable==0.7.1
pycparser==2.18
pyflakes==0.8.1
Pygments==2.2.0
pyinotify==0.9.6
pyOpenSSL==17.1.0
pyparsing==2.1.0
pyperclip==1.5.27
python-cinderclient==3.3.0
python-dateutil==2.5.3
python-glanceclient==2.8.0
python-keystoneclient==3.8.0
python-mimeparse==1.6.0
python-novaclient==9.1.0
python-openstackclient==3.12.0
python-subunit==1.0.0
pytz==2013.6
PyYAML==3.12
requests-mock==1.2.0
requests==2.14.2
requestsexceptions==1.2.0
rfc3986==0.3.1
simplejson==3.5.1
six==1.10.0
snowballstemmer==1.2.1
Sphinx==1.6.2
sphinxcontrib-websupport==1.0.1
stevedore==1.20.0
testrepository==0.0.18
testscenarios==0.4
testtools==2.2.0
traceback2==1.4.0
unittest2==1.1.0
warlock==1.2.0
wrapt==1.7.0

View File

@@ -3,11 +3,12 @@
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
PrettyTable<0.8,>=0.7.1 # BSD
keystoneauth1>=2.20.0 # Apache-2.0
requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0
simplejson>=2.2.0 # MIT
keystoneauth1>=3.4.0 # Apache-2.0
requests>=2.14.2 # Apache-2.0
simplejson>=3.5.1 # MIT
Babel!=2.4.0,>=2.3.4 # BSD
six>=1.9.0 # MIT
oslo.utils>=3.20.0 # Apache-2.0
oslo.log>=3.22.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
six>=1.10.0 # MIT
osc-lib>=1.8.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
oslo.log>=3.36.0 # Apache-2.0
oslo.i18n>=3.15.3 # Apache-2.0

View File

@@ -4,8 +4,8 @@ summary = Python client library for Karbor API
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = https://docs.openstack.org/developer/karbor/
author-email = openstack-discuss@lists.openstack.org
home-page = https://docs.openstack.org/python-karborclient/latest/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
@@ -16,7 +16,8 @@ classifier =
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
[global]
setup-hooks =
@@ -30,13 +31,65 @@ packages =
console_scripts =
karbor = karborclient.shell:main
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
openstack.cli.extension =
data_protection = karborclient.osc.plugin
[upload_sphinx]
upload-dir = doc/build/html
openstack.data_protection.v1 =
data_protection_plan_list = karborclient.osc.v1.plans:ListPlans
data_protection_plan_show = karborclient.osc.v1.plans:ShowPlan
data_protection_plan_create = karborclient.osc.v1.plans:CreatePlan
data_protection_plan_update = karborclient.osc.v1.plans:UpdatePlan
data_protection_plan_delete = karborclient.osc.v1.plans:DeletePlan
data_protection_restore_list = karborclient.osc.v1.restores:ListRestores
data_protection_restore_show = karborclient.osc.v1.restores:ShowRestore
data_protection_restore_create = karborclient.osc.v1.restores:CreateRestore
data_protection_provider_list = karborclient.osc.v1.providers:ListProviders
data_protection_provider_show = karborclient.osc.v1.providers:ShowProvider
data_protection_protectable_list = karborclient.osc.v1.protectables:ListProtectables
data_protection_protectable_show = karborclient.osc.v1.protectables:ShowProtectable
data_protection_protectable_instance_list = karborclient.osc.v1.protectables:ListProtectableInstances
data_protection_protectable_instance_show = karborclient.osc.v1.protectables:ShowProtectableInstance
data_protection_trigger_list = karborclient.osc.v1.triggers:ListTriggers
data_protection_trigger_show = karborclient.osc.v1.triggers:ShowTrigger
data_protection_trigger_create = karborclient.osc.v1.triggers:CreateTrigger
data_protection_trigger_update = karborclient.osc.v1.triggers:UpdateTrigger
data_protection_trigger_delete = karborclient.osc.v1.triggers:DeleteTrigger
data_protection_checkpoint_list = karborclient.osc.v1.checkpoints:ListCheckpoints
data_protection_checkpoint_show = karborclient.osc.v1.checkpoints:ShowCheckpoint
data_protection_checkpoint_create = karborclient.osc.v1.checkpoints:CreateCheckpoint
data_protection_checkpoint_delete = karborclient.osc.v1.checkpoints:DeleteCheckpoint
data_protection_checkpoint_reset_state = karborclient.osc.v1.checkpoints:ResetCheckpointState
data_protection_scheduledoperation_list = karborclient.osc.v1.scheduled_operations:ListScheduledOperations
data_protection_scheduledoperation_show = karborclient.osc.v1.scheduled_operations:ShowScheduledOperation
data_protection_scheduledoperation_create = karborclient.osc.v1.scheduled_operations:CreateScheduledOperation
data_protection_scheduledoperation_delete = karborclient.osc.v1.scheduled_operations:DeleteScheduledOperation
data_protection_operationlog_list = karborclient.osc.v1.operation_logs:ListOperationLogs
data_protection_operationlog_show = karborclient.osc.v1.operation_logs:ShowOperationLog
data_protection_verification_list = karborclient.osc.v1.verifications:ListVerifications
data_protection_verification_show = karborclient.osc.v1.verifications:ShowVerification
data_protection_verification_create = karborclient.osc.v1.verifications:CreateVerification
data_protection_service_list = karborclient.osc.v1.services:ListServices
data_protection_service_enable = karborclient.osc.v1.services:EnableService
data_protection_service_disable = karborclient.osc.v1.services:DisableService
data_protection_quotas_show = karborclient.osc.v1.quotas:ShowQuotas
data_protection_quotas_default = karborclient.osc.v1.quotas:ShowDefaultQuotas
data_protection_quotas_update = karborclient.osc.v1.quotas:UpdateQuotas
data_protection_quotaclass_show = karborclient.osc.v1.quota_classes:ShowQuotaClasses
data_protection_quotaclass_update = karborclient.osc.v1.quota_classes:UpdateQuotaClasses
[compile_catalog]
directory = karborclient/locale
domain = karborclient
[update_catalog]
domain = karborclient
output_dir = karborclient/locale
input_file = karborclient/locale/karborclient.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = karborclient/locale/karborclient.pot
[wheel]
universal = 1

View File

@@ -5,11 +5,11 @@
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
coverage!=4.4,>=4.0 # Apache-2.0
python-subunit>=0.0.18 # Apache-2.0/BSD
python-subunit>=1.0.0 # Apache-2.0/BSD
docutils>=0.11 # OSI-Approved Open Source, Public Domain
sphinx!=1.6.1,>=1.5.1 # BSD
oslosphinx>=4.7.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
oslotest>=3.2.0 # Apache-2.0
python-openstackclient>=3.12.0 # Apache-2.0
requests-mock>=1.2.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.0 # MIT
testtools>=2.2.0 # MIT

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env bash
# Client constraint file contains this client version pin that is in conflict
# with installing the client from source. We should remove the version pin in
# the constraints file before applying it for from-source installation.
CONSTRAINTS_FILE="$1"
shift 1
set -e
# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
# published to logs.openstack.org for easy debugging.
localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
if [[ "$CONSTRAINTS_FILE" != http* ]]; then
CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE"
fi
# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile"
pip install -c"$localfile" openstack-requirements
# This is the main purpose of the script: Allow local installation of
# the current repo. It is listed in constraints file and thus any
# install will be constrained and we need to unconstrain it.
edit-constraints "$localfile" -- "$CLIENT_NAME"
pip install -c"$localfile" -U "$@"
exit $?

44
tox.ini
View File

@@ -1,44 +1,58 @@
[tox]
minversion = 2.0
envlist = py35,py27,pypy,pep8
envlist = py27,py37,pypy,pep8
skipsdist = True
[testenv]
usedevelop = True
install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
install_command = pip install {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
BRANCH_NAME=master
CLIENT_NAME=python-karborclient
PYTHONWARNINGS=default::DeprecationWarning
deps = -r{toxinidir}/requirements.txt
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python setup.py test --slowest --testr-args='{posargs}'
whitelist_externals = rm
commands =
rm -f .testrepository/times.dbm
python setup.py test --slowest --testr-args='{posargs}'
[testenv:pep8]
basepython = python3
commands = flake8
[testenv:venv]
basepython = python3
commands = {posargs}
[testenv:functional]
setenv =
{[testenv]setenv}
OS_TEST_PATH = ./karborclient/tests/functional
passenv = OS_*
[testenv:cover]
commands = python setup.py test --coverage --testr-args='{posargs}'
basepython = python3
commands =
python setup.py test --coverage --testr-args='{posargs}'
coverage report
[testenv:docs]
commands = python setup.py build_sphinx
basepython = python3
deps =
-c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
-r{toxinidir}/doc/requirements.txt
commands = sphinx-build -W -b html doc/source doc/build/html
[testenv:debug]
basepython = python3
commands = oslo_debug_helper -t karborclient/tests {posargs}
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
ignore = E123,E125
ignore =
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,tools
[testenv:lower-constraints]
basepython = python3
deps =
-c{toxinidir}/lower-constraints.txt
-r{toxinidir}/test-requirements.txt
-r{toxinidir}/requirements.txt