Compare commits

...

55 Commits

Author SHA1 Message Date
OpenDev Sysadmins
5d5b18ac48 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
Nguyen Hai
d3c2c4711e 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

* This patch also fix docs failure.

Change-Id: I0637ad0d18603c4d476472492339e3233bb8870b
Story: #2002586
Task: #24303
2018-08-22 23:14:25 +09:00
OpenStack Proposal Bot
f906cbcf48 Updated from global requirements
Change-Id: Ia91f8a03b49d8f7df918bc42c97922c14e4a229b
2017-09-21 01:06:13 +00:00
OpenStack Proposal Bot
bf5e08d6f6 Updated from global requirements
Change-Id: Ib1b703c5ccc437e668827a28e3f0d071d93309b6
2017-08-11 19:05:14 +00:00
90a525627c Update UPPER_CONSTRAINTS_FILE for stable/pike
Change-Id: I502c207caf338cc81fe77aa9922042be45d89fb6
2017-07-28 21:07:09 +00:00
0e16067e8d Update .gitreview for stable/pike
Change-Id: I4c189e46dff47d59417b81c8bada268dcf6e7161
2017-07-28 21:07:08 +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
Jenkins
e55840dd82 Merge "Add a verification about the provider_id of plan" 2017-05-18 03:40:34 +00:00
chenying
74f828c876 Add a verification about the provider_id of plan
Change-Id: Ideb96b4d96fec673ffc0815d1f9840911c8afe70
Closes-Bug: #1686765
2017-05-16 22:20:08 +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
53 changed files with 3494 additions and 213 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,5 @@
[gerrit]
host=review.openstack.org
host=review.opendev.org
port=29418
project=openstack/python-karborclient.git
defaultbranch=stable/pike

12
.zuul.yaml Normal file
View File

@@ -0,0 +1,12 @@
- project:
templates:
- openstack-python-jobs
- openstack-python35-jobs
- check-requirements
- publish-openstack-sphinx-docs
- openstackclient-plugin-jobs
check:
jobs:
- openstack-tox-cover:
voting: false

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

@@ -7,6 +7,7 @@ Team and repository tags
.. Change things from this point on
======
Karbor
======
@@ -39,8 +40,8 @@ Karbor Mission Statement
.. _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
.. _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 +63,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:

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

@@ -4,12 +4,12 @@ Welcome to karborclient's documentation!
Contents:
.. toctree::
:maxdepth: 2
:maxdepth: 1
readme
installation
usage
contributing
install/index
user/index
contributor/index
Indices and tables
==================

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

@@ -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', '')

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,205 @@
# 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 karborclient.common.apiclient import exceptions
from karborclient.i18n import _
from karborclient import utils
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(
'--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
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,
}
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']
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers
) 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)
checkpoint._info.pop("links", None)
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)
checkpoint._info.pop("links", None)
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.")

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,265 @@
# 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_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
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)
plan._info.pop("links", None)
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)
plan._info.pop("links", None)
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(
"--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.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:
plan._info.pop("links", None)
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,177 @@
# 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"""
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 _
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)
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']
return (column_headers, 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)
instance._info.pop("links", None)
return zip(*sorted(instance._info.items()))

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 provider 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 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)
provider._info.pop("links", None)
return zip(*sorted(provider._info.items()))

View File

@@ -0,0 +1,195 @@
# 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"""
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
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']
return (column_headers,
(osc_utils.get_item_properties(
s, column_headers
) 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)
restore._info.pop("links", None)
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)
restore._info.pop("links", None)
return zip(*sorted(restore._info.items()))

View File

@@ -0,0 +1,208 @@
# 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 six
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 _
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', 'OperationType', 'TriggerId',
'OperationDefinition']
scheduled_operations = []
for s in data:
scheduled_operation = (s.id, s.name, s.operation_type,
s.trigger_id, s.operation_definition)
scheduled_operations.append(scheduled_operation)
return (column_headers, scheduled_operations)
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)
so._info.pop("links", None)
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)
so._info.pop("links", None)
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,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

@@ -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,160 @@
# 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.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": "[{'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()
self.checkpoints_mock.list.return_value = [checkpoints.Checkpoint(
None, CHECKPOINT_INFO)]
# Command to test
self.cmd = osc_checkpoints.ListCheckpoints(self.app, None)
def test_checkpoints_list(self):
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",
{
"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"}]
},
'',
'')]
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, 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, 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, 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')

View File

@@ -0,0 +1,124 @@
# 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.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, 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.operation_logs_mock.get.return_value = (
operation_logs.OperationLog(None, OPERATIONLOG_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(OPERATIONLOG_INFO['checkpoint_id'], data[0])
self.assertEqual(OPERATIONLOG_INFO['ended_at'], data[1])
self.assertEqual(OPERATIONLOG_INFO['error_info'], data[2])
self.assertEqual(OPERATIONLOG_INFO['extra_info'], data[3])
self.assertEqual(OPERATIONLOG_INFO['id'], data[4])
self.assertEqual(OPERATIONLOG_INFO['operation_type'], data[5])
self.assertEqual(OPERATIONLOG_INFO['plan_id'], data[6])
self.assertEqual(OPERATIONLOG_INFO['project_id'], data[7])
self.assertEqual(OPERATIONLOG_INFO['provider_id'], data[8])
self.assertEqual(OPERATIONLOG_INFO['restore_id'], data[9])
self.assertEqual(OPERATIONLOG_INFO['scheduled_operation_id'], data[10])
self.assertEqual(OPERATIONLOG_INFO['started_at'], data[11])
self.assertEqual(OPERATIONLOG_INFO['status'], data[12])

View File

@@ -0,0 +1,180 @@
# 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.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, 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, 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, PLAN_INFO)
self.plans_mock.update.return_value = plans.Plan(
None, 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, 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.plans_mock.get.return_value = plans.Plan(
None, 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(PLAN_INFO['description'], data[0])
self.assertEqual(PLAN_INFO['id'], data[1])
self.assertEqual(PLAN_INFO['name'], data[2])
self.assertEqual(PLAN_INFO['parameters'], data[3])
self.assertEqual(PLAN_INFO['provider_id'], data[4])
self.assertEqual(PLAN_INFO['resources'], data[5])
self.assertEqual(PLAN_INFO['status'], data[6])

View File

@@ -0,0 +1,161 @@
# 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.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, 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, 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, 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, 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,141 @@
# 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.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, 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.providers_mock.get.return_value = providers.Provider(
None, 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(PROVIDER_INFO['description'], data[0])
self.assertEqual(PROVIDER_INFO['extended_info_schema'], data[1])
self.assertEqual(PROVIDER_INFO['id'], data[2])
self.assertEqual(PROVIDER_INFO['name'], data[3])

View File

@@ -0,0 +1,134 @@
# 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.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, 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",
"",
{},
"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, 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.restores_mock.get.return_value = restores.Restore(
None, 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(RESTORE_INFO['checkpoint_id'], data[0])
self.assertEqual(RESTORE_INFO['id'], data[1])
self.assertEqual(RESTORE_INFO['parameters'], data[2])
self.assertEqual(RESTORE_INFO['project_id'], data[3])
self.assertEqual(RESTORE_INFO['provider_id'], data[4])
self.assertEqual(RESTORE_INFO['resources_reason'], data[5])
self.assertEqual(RESTORE_INFO['resources_status'], data[6])
self.assertEqual(RESTORE_INFO['restore_auth'], data[7])
self.assertEqual(RESTORE_INFO['restore_target'], data[8])
self.assertEqual(RESTORE_INFO['status'], data[9])

View File

@@ -0,0 +1,167 @@
# 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.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,
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', 'OperationType', 'TriggerId',
'OperationDefinition'])
self.assertEqual(expected_columns, columns)
# Check that data is correct
expected_data = [("1a2c0c3d-f402-4cd8-b5db-82e85cb51fad",
"My scheduled operation",
"protect",
"23902b02-5666-4ee6-8dfe-962ac09c3995",
{
"provider_id":
"2a9ce1f3-cc1a-4516-9435-0ebb13caa399", # noqa
"plan_id":
"2a9ce1f3-cc1a-4516-9435-0ebb13caa398"
})]
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, 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, 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.so_mock.get.return_value = scheduled_operations.\
ScheduledOperation(None, SCHEDULEDOPERATION_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(SCHEDULEDOPERATION_INFO['description'], data[0])
self.assertEqual(SCHEDULEDOPERATION_INFO['enabled'], data[1])
self.assertEqual(SCHEDULEDOPERATION_INFO['id'], data[2])
self.assertEqual(SCHEDULEDOPERATION_INFO['name'], data[3])
self.assertEqual(SCHEDULEDOPERATION_INFO['operation_definition'],
data[4])
self.assertEqual(SCHEDULEDOPERATION_INFO['operation_type'], data[5])
self.assertEqual(SCHEDULEDOPERATION_INFO['trigger_id'], data[6])

View File

@@ -0,0 +1,186 @@
# 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.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, 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, 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, TRIGGER_INFO)
self.triggers_mock.update.return_value = triggers.Trigger(
None, 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, 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.triggers_mock.get.return_value = triggers.Trigger(
None, 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(TRIGGER_INFO['id'], data[0])
self.assertEqual(TRIGGER_INFO['name'], data[1])
self.assertEqual(TRIGGER_INFO['properties'], data[2])
self.assertEqual(TRIGGER_INFO['type'], data[3])

View File

@@ -82,7 +82,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

View File

@@ -0,0 +1,63 @@
# 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_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'})

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)

View File

@@ -14,6 +14,7 @@
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
@@ -42,3 +43,5 @@ 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)

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

@@ -15,12 +15,12 @@ import json
import os
from datetime import datetime
from oslo_serialization import jsonutils
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',
@@ -142,9 +142,12 @@ def do_plan_list(cs, args):
help='The description of a plan.')
def do_plan_create(cs, args):
"""Creates a plan."""
plan_resources = _extract_resources(args)
_check_resources(cs, plan_resources)
plan_parameters = _extract_parameters(args)
if not uuidutils.is_uuid_like(args.provider_id):
raise exceptions.CommandError(
"Invalid provider id provided.")
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"}
@@ -195,7 +198,7 @@ def do_plan_update(cs, args):
if args.name is not None:
data['name'] = args.name
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
@@ -208,39 +211,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.')
@@ -283,7 +253,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:
@@ -304,48 +274,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>',
@@ -472,7 +400,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,
@@ -526,7 +454,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),
}
@@ -554,19 +482,6 @@ def do_protectable_list_instances(cs, args):
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.')
@@ -654,7 +569,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"}
@@ -663,20 +578,6 @@ 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('provider_id',
metavar='<provider_id>',
help='ID of provider.')
@@ -916,27 +817,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>",
@@ -946,7 +832,7 @@ def _extract_properties(args):
def do_trigger_update(cs, args):
"""Update a trigger."""
trigger_info = {}
trigger_properties = _extract_properties(args)
trigger_properties = arg_utils.extract_properties(args)
trigger_info['name'] = args.name
trigger_info['properties'] = trigger_properties
trigger = cs.triggers.update(args.trigger_id, trigger_info)
@@ -1092,7 +978,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,
@@ -1102,19 +988,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.')
@@ -1145,3 +1018,93 @@ 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('--all_tenants',
nargs='?',
type=int,
const=1,
help=argparse.SUPPRESS)
@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())

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
keystoneauth1>=3.1.0 # Apache-2.0
requests>=2.14.2 # Apache-2.0
simplejson>=2.2.0 # MIT
Babel!=2.4.0,>=2.3.4 # BSD
six>=1.9.0 # MIT
osc-lib>=1.7.0 # Apache-2.0
oslo.utils>=3.20.0 # Apache-2.0
oslo.log>=3.22.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0

View File

@@ -5,7 +5,7 @@ description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = https://docs.openstack.org/developer/karbor/
home-page = https://docs.openstack.org/karbor/latest
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
@@ -30,10 +30,59 @@ packages =
console_scripts =
karbor = karborclient.shell:main
openstack.cli.extension =
data_protection = karborclient.osc.plugin
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_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
[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
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
warning-is-error = 1
[upload_sphinx]
upload-dir = doc/build/html

View File

@@ -7,9 +7,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
docutils>=0.11 # OSI-Approved Open Source, Public Domain
sphinx!=1.6.1,>=1.5.1 # BSD
oslosphinx>=4.7.0 # Apache-2.0
sphinx>=1.6.2 # BSD
openstackdocstheme>=1.16.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
python-openstackclient>=3.11.0 # Apache-2.0
requests-mock>=1.1 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
testtools>=1.4.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 $?

12
tox.ini
View File

@@ -5,13 +5,14 @@ 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://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/pike}
-r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python setup.py test --slowest --testr-args='{posargs}'
@@ -27,7 +28,9 @@ setenv =
OS_TEST_PATH = ./karborclient/tests/functional
passenv = OS_*
[testenv:cover]
commands = python setup.py test --coverage --testr-args='{posargs}'
commands =
python setup.py test --coverage --testr-args='{posargs}'
coverage report
[testenv:docs]
commands = python setup.py build_sphinx
@@ -36,9 +39,8 @@ commands = python setup.py build_sphinx
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