Freezerclient v2
Freezer client now has the ability to talk to v2 api endpoint if you are using BaaS please use: OS_BACKUP_API_VERSION=2 otherwise OS_BACKUP_API_VERSION=1 Change-Id: I93715e18f96c35b7952f3aeac3cb05b56313f9a9
This commit is contained in:
parent
9578f23509
commit
bb19aa5b4d
2
.gitignore
vendored
2
.gitignore
vendored
@ -28,3 +28,5 @@ ChangeLog
|
||||
# Coverage data
|
||||
.coverage.*
|
||||
releasenotes/build
|
||||
|
||||
.vscode/
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,3 +0,0 @@
|
||||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
}
|
@ -20,12 +20,8 @@ from cliff import app
|
||||
from cliff import commandmanager
|
||||
|
||||
import freezerclient
|
||||
from freezerclient.v1 import actions
|
||||
from freezerclient.v1 import backups
|
||||
from freezerclient.v1 import client
|
||||
from freezerclient.v1 import clients
|
||||
from freezerclient.v1 import jobs
|
||||
from freezerclient.v1 import sessions
|
||||
from freezerclient.v1 import client as v1_client
|
||||
from freezerclient.v2 import client as v2_client
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -33,8 +29,36 @@ log = logging.getLogger(__name__)
|
||||
logging.getLogger('requests').setLevel(logging.WARN)
|
||||
|
||||
|
||||
def check_api_version():
|
||||
"""Check freezer version API to use
|
||||
1: not multi-tenant, useful for infrastructure
|
||||
2: multi-tenant, useful for backup as a service
|
||||
:return: str
|
||||
"""
|
||||
freezer_api_version = os.environ.get('OS_BACKUP_API_VERSION', '2')
|
||||
if freezer_api_version == '1':
|
||||
return '1'
|
||||
elif freezer_api_version == '2':
|
||||
return '2'
|
||||
else:
|
||||
raise Exception('Freezer API version not supported')
|
||||
|
||||
|
||||
class FreezerCommandManager(commandmanager.CommandManager):
|
||||
"""All commands available for the shell are registered here"""
|
||||
if check_api_version() == '1':
|
||||
from freezerclient.v1 import actions
|
||||
from freezerclient.v1 import backups
|
||||
from freezerclient.v1 import clients
|
||||
from freezerclient.v1 import jobs
|
||||
from freezerclient.v1 import sessions
|
||||
else:
|
||||
from freezerclient.v2 import actions
|
||||
from freezerclient.v2 import backups
|
||||
from freezerclient.v2 import clients
|
||||
from freezerclient.v2 import jobs
|
||||
from freezerclient.v2 import sessions
|
||||
|
||||
SHELL_COMMANDS = {
|
||||
'job-show': jobs.JobShow,
|
||||
'job-list': jobs.JobList,
|
||||
@ -174,6 +198,13 @@ class FreezerShell(app.App):
|
||||
help='Tenant to request authorization on'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-project-id',
|
||||
dest='os_project_id',
|
||||
default=os.environ.get('OS_PROJECT_ID'),
|
||||
help='Project to request authorization on'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--os-tenant-name',
|
||||
dest='os_tenant_name',
|
||||
@ -225,7 +256,10 @@ class FreezerShell(app.App):
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
"""Build a client object to communicate with the API
|
||||
"""Factory function to create a new freezer service client.
|
||||
|
||||
The returned client will be either a V1 or V2 client.
|
||||
|
||||
:return: freezerclient object
|
||||
"""
|
||||
opts = {
|
||||
@ -238,6 +272,7 @@ class FreezerShell(app.App):
|
||||
'endpoint': self.options.os_backup_url,
|
||||
'endpoint_type': self.options.os_endpoint_type,
|
||||
'project_name': self.options.os_project_name,
|
||||
'project_id': self.options.os_project_id,
|
||||
'user_domain_name': self.options.os_user_domain_name,
|
||||
'user_domain_id': self.options.os_user_domain_id,
|
||||
'project_domain_name': self.options.os_project_domain_name,
|
||||
@ -246,7 +281,10 @@ class FreezerShell(app.App):
|
||||
'cacert': self.options.os_cacert,
|
||||
'insecure': self.options.insecure
|
||||
}
|
||||
return client.Client(**opts)
|
||||
if check_api_version() == '1':
|
||||
return v1_client.Client(**opts)
|
||||
else:
|
||||
return v2_client.Client(**opts)
|
||||
|
||||
|
||||
def main(argv=sys.argv[1:]):
|
||||
|
0
freezerclient/v2/__init__.py
Normal file
0
freezerclient/v2/__init__.py
Normal file
173
freezerclient/v2/actions.py
Normal file
173
freezerclient/v2/actions.py
Normal file
@ -0,0 +1,173 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from cliff import command
|
||||
from cliff import lister
|
||||
from cliff import show
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient import utils
|
||||
|
||||
|
||||
logging = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ActionShow(show.ShowOne):
|
||||
"""Show a single action """
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ActionShow, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='action_id',
|
||||
help='ID of the action')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
action = self.app.client.actions.get(parsed_args.action_id)
|
||||
|
||||
if not action:
|
||||
raise exceptions.ApiClientException('Action not found')
|
||||
|
||||
column = (
|
||||
'Action ID',
|
||||
'Name',
|
||||
'Action',
|
||||
'Mode',
|
||||
'Path to Backup or Restore',
|
||||
'Storage',
|
||||
'Snapshot'
|
||||
)
|
||||
|
||||
data = (
|
||||
action.get('action_id'),
|
||||
action.get('freezer_action', {}).get('backup_name', ''),
|
||||
action.get('freezer_action', {}).get('action', 'backup'),
|
||||
action.get('freezer_action', {}).get('mode', 'fs'),
|
||||
action.get('freezer_action', {}).get('path_to_backup', ''),
|
||||
action.get('freezer_action', {}).get('storage', 'swift'),
|
||||
action.get('freezer_action', {}).get('snapshot', 'False'),
|
||||
)
|
||||
|
||||
return column, data
|
||||
|
||||
|
||||
class ActionList(lister.Lister):
|
||||
"""List all actions for your user"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ActionList, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
dest='limit',
|
||||
default=100,
|
||||
help='Specify a limit for search query',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--offset',
|
||||
dest='offset',
|
||||
default=0,
|
||||
help='',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--search',
|
||||
dest='search',
|
||||
default='',
|
||||
help='Define a filter for the query',
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
search = utils.prepare_search(parsed_args.search)
|
||||
|
||||
actions = self.app.client.actions.list(
|
||||
limit=parsed_args.limit,
|
||||
offset=parsed_args.offset,
|
||||
search=search
|
||||
)
|
||||
|
||||
columns = ('Action ID', 'Name', 'Action',
|
||||
'Path to Backup or Restore', 'Mode', 'Storage', 'snapshot')
|
||||
|
||||
# Print empty table if no actions found
|
||||
if not actions:
|
||||
actions = [{}]
|
||||
data = ((action.get('action-id', ''),
|
||||
action.get('freezer_action', {}).get('backup_name', ''),
|
||||
action.get('freezer_action', {}).get('action', ''),
|
||||
action.get('freezer_action', {}).get(
|
||||
'path_to_backup', ''),
|
||||
action.get('freezer_action', {}).get('mode', ''),
|
||||
action.get('freezer_action', {}).get('storage', ''),
|
||||
action.get('freezer_action', {}).get('snapshot', '')
|
||||
) for action in actions)
|
||||
else:
|
||||
data = ((action.get('action_id'),
|
||||
action.get('freezer_action', {}).get('backup_name', ''),
|
||||
action.get('freezer_action', {}).get('action', 'backup'),
|
||||
action.get('freezer_action', {}).get(
|
||||
'path_to_backup', ''),
|
||||
action.get('freezer_action', {}).get('mode', 'fs'),
|
||||
action.get('freezer_action', {}).get('storage', 'swift'),
|
||||
action.get('freezer_action', {}).get('snapshot', 'False')
|
||||
) for action in actions)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
class ActionDelete(command.Command):
|
||||
"""Delete an action from the api"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ActionDelete, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='action_id',
|
||||
help='ID of the action')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.actions.delete(parsed_args.action_id)
|
||||
logging.info('Action {0} deleted'.format(parsed_args.action_id))
|
||||
|
||||
|
||||
class ActionCreate(command.Command):
|
||||
"""Create an action from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ActionCreate, self).get_parser(prog_name)
|
||||
parser.add_argument('--file',
|
||||
dest='file',
|
||||
required=True,
|
||||
help='Path to json file with the action')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
action = utils.doc_from_json_file(parsed_args.file)
|
||||
action_id = self.app.client.actions.create(action)
|
||||
logging.info('Action {0} created'.format(action_id))
|
||||
|
||||
|
||||
class ActionUpdate(command.Command):
|
||||
"""Update an action from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ActionUpdate, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='action_id',
|
||||
help='ID of the session')
|
||||
|
||||
parser.add_argument(dest='file',
|
||||
help='Path to json file with the action')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
action = utils.doc_from_json_file(parsed_args.file)
|
||||
self.app.client.actions.update(parsed_args.action_id, action)
|
||||
logging.info('Action {0} updated'.format(parsed_args.action_id))
|
119
freezerclient/v2/backups.py
Normal file
119
freezerclient/v2/backups.py
Normal file
@ -0,0 +1,119 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import pprint
|
||||
|
||||
from cliff import command
|
||||
from cliff import lister
|
||||
from cliff import show
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient import utils
|
||||
|
||||
|
||||
logging = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BackupShow(show.ShowOne):
|
||||
"""Show the metadata of a single backup"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(BackupShow, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='backup_uuid',
|
||||
help='ID of the backup')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
backup = self.app.client.backups.get(parsed_args.backup_uuid)
|
||||
if not backup:
|
||||
raise exceptions.ApiClientException('Backup not found')
|
||||
|
||||
column = (
|
||||
'Backup ID',
|
||||
'Metadata'
|
||||
)
|
||||
data = (
|
||||
backup.get('backup_uuid'),
|
||||
pprint.pformat(backup.get('backup_metadata'))
|
||||
)
|
||||
return column, data
|
||||
|
||||
|
||||
class BackupList(lister.Lister):
|
||||
"""List all backups for your user"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(BackupList, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
dest='limit',
|
||||
default=100,
|
||||
help='Specify a limit for search query',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--offset',
|
||||
dest='offset',
|
||||
default=0,
|
||||
help='',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--search',
|
||||
dest='search',
|
||||
default='',
|
||||
help='Define a filter for the query',
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
search = utils.prepare_search(parsed_args.search)
|
||||
|
||||
backups = self.app.client.backups.list(limit=parsed_args.limit,
|
||||
offset=parsed_args.offset,
|
||||
search=search)
|
||||
|
||||
columns = ('Backup ID', 'Backup UUID', 'Hostname', 'Path',
|
||||
'Created at', 'Level')
|
||||
|
||||
# Print empty table if no backups found
|
||||
if not backups:
|
||||
backups = [{}]
|
||||
|
||||
data = ((b.get('backup_id', ''),
|
||||
b.get('backup_uuid', ''),
|
||||
b.get('backup_metadata', {}).get('hostname', ''),
|
||||
b.get('backup_metadata', {}).get('path_to_backup', ''),
|
||||
datetime.datetime.fromtimestamp(
|
||||
int(b.get('backup_metadata', {}).get(
|
||||
'time_stamp', ''))) if b.get(
|
||||
'backup_metadata') else '',
|
||||
b.get('backup_metadata', {}).get('curr_backup_level', '')
|
||||
) for b in backups)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
class BackupDelete(command.Command):
|
||||
"""Delete a backup from the api"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(BackupDelete, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='backup_uuid',
|
||||
help='UUID of the backup')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.backups.delete(parsed_args.backup_uuid)
|
||||
logging.info('Backup {0} deleted'.format(parsed_args.backup_uuid))
|
197
freezerclient/v2/client.py
Normal file
197
freezerclient/v2/client.py
Normal file
@ -0,0 +1,197 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import socket
|
||||
|
||||
from keystoneauth1.identity import v2
|
||||
from keystoneauth1.identity import v3
|
||||
from keystoneauth1 import session as ksa_session
|
||||
|
||||
from freezerclient import utils
|
||||
from freezerclient.v2.managers import actions
|
||||
from freezerclient.v2.managers import backups
|
||||
from freezerclient.v2.managers import clients
|
||||
from freezerclient.v2.managers import jobs
|
||||
from freezerclient.v2.managers import sessions
|
||||
|
||||
FREEZER_SERVICE_TYPE = 'backup'
|
||||
|
||||
|
||||
def guess_auth_version(opts):
|
||||
"""Guess keystone version to connect to"""
|
||||
if opts.os_identity_api_version == '3':
|
||||
return '3'
|
||||
elif opts.os_identity_api_version == '2.0':
|
||||
return '2.0'
|
||||
elif opts.os_auth_url.endswith('v3'):
|
||||
return '3'
|
||||
elif opts.os_auth_url.endswith('v2.0'):
|
||||
return '2.0'
|
||||
raise Exception('Please provide valid keystone auth url with valid'
|
||||
' keystone api version to use')
|
||||
|
||||
|
||||
def get_auth_plugin(opts):
|
||||
"""Create the right keystone connection depending on the version
|
||||
for the api, if username/password and token are provided, username and
|
||||
password takes precedence.
|
||||
"""
|
||||
auth_version = guess_auth_version(opts)
|
||||
if opts.os_username:
|
||||
if auth_version == '3':
|
||||
return v3.Password(auth_url=opts.os_auth_url,
|
||||
username=opts.os_username,
|
||||
password=opts.os_password,
|
||||
project_name=opts.os_project_name,
|
||||
user_domain_name=opts.os_user_domain_name,
|
||||
user_domain_id=opts.os_user_domain_id,
|
||||
project_domain_name=opts.os_project_domain_name,
|
||||
project_domain_id=opts.os_project_domain_id,
|
||||
project_id=opts.os_project_id)
|
||||
elif auth_version == '2.0':
|
||||
return v2.Password(auth_url=opts.os_auth_url,
|
||||
username=opts.os_username,
|
||||
password=opts.os_password,
|
||||
tenant_name=opts.os_tenant_name)
|
||||
elif opts.os_token:
|
||||
if auth_version == '3':
|
||||
return v3.Token(auth_url=opts.os_auth_url,
|
||||
token=opts.os_token,
|
||||
project_name=opts.os_project_name,
|
||||
project_domain_name=opts.os_project_domain_name,
|
||||
project_domain_id=opts.os_project_domain_id,
|
||||
project_id=opts.os_project_id)
|
||||
elif auth_version == '2.0':
|
||||
return v2.Token(auth_url=opts.os_auth_url,
|
||||
token=opts.os_token,
|
||||
tenant_name=opts.os_tenant_name)
|
||||
raise Exception('Unable to determine correct auth method, please provide'
|
||||
' either username or token')
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""Client for the OpenStack Disaster Recovery v1 API.
|
||||
"""
|
||||
|
||||
def __init__(self, version='3', token=None, username=None, password=None,
|
||||
tenant_name=None, auth_url=None, session=None, endpoint=None,
|
||||
endpoint_type=None, opts=None, project_name=None,
|
||||
user_domain_name=None, user_domain_id=None,
|
||||
project_domain_name=None, project_domain_id=None,
|
||||
cert=None, cacert=None, insecure=False, project_id=None):
|
||||
"""
|
||||
Initialize a new client for the Disaster Recovery v1 API.
|
||||
:param version: keystone version to use
|
||||
:param token: keystone token
|
||||
:param username: openstack username
|
||||
:param password: openstack password
|
||||
:param tenant_name: tenant
|
||||
:param auth_url: keystone-api endpoint
|
||||
:param session: keystone.Session
|
||||
:param endpoint: freezer-api endpoint
|
||||
:param endpoint_type: type of endpoint
|
||||
:param opts: a namespace to store all keystone data
|
||||
:param project_name: only for version 3
|
||||
:param tenant_id: only for version 2
|
||||
:param user_domain_name: only for version 3
|
||||
:param user_domain_id: only for version 3
|
||||
:param project_domain_name: only for version 3
|
||||
:param project_domain_id: only for version 3
|
||||
:param insecure: The verification arguments to pass to requests.
|
||||
These are of the same form as requests expects,
|
||||
so True or False to verify (or not) against system
|
||||
certificates or a path to a bundle or CA certs to
|
||||
check against or None for requests to
|
||||
attempt to locate and use certificates. (optional,
|
||||
defaults to True)
|
||||
:param cert: Path to cert
|
||||
:return: freezerclient.Client
|
||||
"""
|
||||
|
||||
self.project_id = project_id
|
||||
|
||||
if opts is None:
|
||||
self.opts = utils.Namespace({})
|
||||
self.opts.os_token = token or None
|
||||
self.opts.os_username = username or None
|
||||
self.opts.os_password = password or None
|
||||
self.opts.os_tenant_name = tenant_name or None
|
||||
self.opts.os_auth_url = auth_url or None
|
||||
self.opts.os_backup_url = endpoint or None
|
||||
self.opts.os_endpoint_type = endpoint_type or None
|
||||
self.opts.os_project_name = project_name or None
|
||||
self.opts.os_project_id = project_id or None
|
||||
self.opts.os_user_domain_name = user_domain_name or None
|
||||
self.opts.os_user_domain_id = user_domain_id or None
|
||||
self.opts.os_project_domain_name = project_domain_name or None
|
||||
self.opts.os_project_domain_id = project_domain_id or None
|
||||
self.opts.auth_version = version
|
||||
self.opts.os_cacert = cacert or None
|
||||
self.opts.insecure = insecure
|
||||
self.opts.cert = cert
|
||||
else:
|
||||
self.opts = opts
|
||||
|
||||
self.cert = cert
|
||||
self.cacert = cacert or self.opts.os_cacert
|
||||
self._session = session
|
||||
verify = self.opts.os_cacert
|
||||
if self.opts.insecure:
|
||||
verify = False
|
||||
|
||||
self.validate()
|
||||
|
||||
self.jobs = jobs.JobManager(self, verify=verify)
|
||||
self.clients = clients.ClientManager(self, verify=verify)
|
||||
self.backups = backups.BackupsManager(self, verify=verify)
|
||||
self.sessions = sessions.SessionManager(self, verify=verify)
|
||||
self.actions = actions.ActionManager(self, verify=verify)
|
||||
|
||||
@utils.CachedProperty
|
||||
def session(self):
|
||||
if self._session:
|
||||
return self._session
|
||||
auth_plugin = get_auth_plugin(self.opts)
|
||||
return ksa_session.Session(auth=auth_plugin,
|
||||
verify=(self.cacert or
|
||||
not self.opts.insecure),
|
||||
cert=self.cert)
|
||||
|
||||
@utils.CachedProperty
|
||||
def endpoint(self):
|
||||
if self.opts.os_backup_url:
|
||||
return self.opts.os_backup_url
|
||||
else:
|
||||
auth_ref = self.session.auth.get_auth_ref(self.session)
|
||||
endpoint = auth_ref.service_catalog.url_for(
|
||||
service_type=FREEZER_SERVICE_TYPE,
|
||||
interface=self.opts.os_endpoint_type,
|
||||
)
|
||||
return endpoint
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
return self.session.get_token()
|
||||
|
||||
@utils.CachedProperty
|
||||
def client_id(self):
|
||||
return '{0}_{1}'.format(self.session.get_project_id(),
|
||||
socket.gethostname())
|
||||
|
||||
def validate(self):
|
||||
"""Validate that the client objects gets created correctly.
|
||||
:return: bool
|
||||
"""
|
||||
if self.opts.os_auth_url is None:
|
||||
raise Exception('OS_AUTH_URL should be provided.')
|
137
freezerclient/v2/clients.py
Normal file
137
freezerclient/v2/clients.py
Normal file
@ -0,0 +1,137 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from cliff import command
|
||||
from cliff import lister
|
||||
from cliff import show
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient import utils
|
||||
|
||||
|
||||
logging = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClientShow(show.ShowOne):
|
||||
"""Show a single client"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ClientShow, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='client_id',
|
||||
help='ID of the client')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = self.app.client.clients.get(parsed_args.client_id)
|
||||
|
||||
if not client:
|
||||
raise exceptions.ApiClientException('Client not found')
|
||||
|
||||
column = (
|
||||
'Client ID',
|
||||
'Client UUID',
|
||||
'hostname',
|
||||
'description'
|
||||
)
|
||||
data = (
|
||||
client.get('client', {}).get('client_id'),
|
||||
client.get('client', {}).get('uuid'),
|
||||
client.get('client', {}).get('hostname'),
|
||||
client.get('client', {}).get('description', '')
|
||||
)
|
||||
|
||||
return column, data
|
||||
|
||||
|
||||
class ClientList(lister.Lister):
|
||||
"""List of clients registered in the api"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ClientList, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
dest='limit',
|
||||
default=100,
|
||||
help='Specify a limit for search query',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--offset',
|
||||
dest='offset',
|
||||
default=0,
|
||||
help='',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--search',
|
||||
dest='search',
|
||||
default='',
|
||||
help='Define a filter for the query',
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
search = utils.prepare_search(parsed_args.search)
|
||||
|
||||
clients = self.app.client.clients.list(limit=parsed_args.limit,
|
||||
offset=parsed_args.offset,
|
||||
search=search)
|
||||
|
||||
# Print empty table if no clients found
|
||||
if not clients:
|
||||
clients = [{}]
|
||||
|
||||
columns = ('Client ID', 'uuid', 'hostname', 'description')
|
||||
data = ((
|
||||
client.get('client', {}).get('client_id', ''),
|
||||
client.get('client', {}).get('uuid', ''),
|
||||
client.get('client', {}).get('hostname', ''),
|
||||
client.get('client', {}).get('description', '')
|
||||
) for client in clients)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
class ClientDelete(command.Command):
|
||||
"""Delete a client from the api"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ClientDelete, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='client_id',
|
||||
help='ID of the client')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.clients.delete(parsed_args.client_id)
|
||||
logging.info('Client {0} deleted'.format(parsed_args.client_id))
|
||||
|
||||
|
||||
class ClientRegister(command.Command):
|
||||
"""Register a new client"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ClientRegister, self).get_parser(prog_name)
|
||||
parser.add_argument('--file',
|
||||
dest='file',
|
||||
required=True,
|
||||
help='Path to json file with the client')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
client = utils.doc_from_json_file(parsed_args.file)
|
||||
try:
|
||||
client_id = self.app.client.clients.create(client)
|
||||
except Exception as err:
|
||||
raise exceptions.ApiClientException(err.message)
|
||||
else:
|
||||
logging.info("Client {0} registered".format(client_id))
|
268
freezerclient/v2/jobs.py
Normal file
268
freezerclient/v2/jobs.py
Normal file
@ -0,0 +1,268 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
import pprint
|
||||
|
||||
from cliff import command
|
||||
from cliff import lister
|
||||
from cliff import show
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient import utils
|
||||
|
||||
|
||||
logging = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JobShow(show.ShowOne):
|
||||
"""Show a single job"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobShow, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
job = self.app.client.jobs.get(parsed_args.job_id)
|
||||
|
||||
if not job:
|
||||
raise exceptions.ApiClientException('Job not found')
|
||||
|
||||
column = (
|
||||
'Job ID',
|
||||
'Client ID',
|
||||
'User ID',
|
||||
'Session ID',
|
||||
'Description',
|
||||
'Actions',
|
||||
'Start Date',
|
||||
'End Date',
|
||||
'Interval',
|
||||
)
|
||||
data = (
|
||||
job.get('job_id'),
|
||||
job.get('client_id'),
|
||||
job.get('user_id'),
|
||||
job.get('session_id', ''),
|
||||
job.get('description'),
|
||||
pprint.pformat(job.get('job_actions')),
|
||||
job.get('job_schedule', {}).get('schedule_start_date', ''),
|
||||
job.get('job_schedule', {}).get('schedule_end_date', ''),
|
||||
job.get('job_schedule', {}).get('schedule_interval', ''),
|
||||
)
|
||||
return column, data
|
||||
|
||||
|
||||
class JobList(lister.Lister):
|
||||
"""List all the jobs for your user"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobList, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
dest='limit',
|
||||
default=100,
|
||||
help='Specify a limit for search query',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--offset',
|
||||
dest='offset',
|
||||
default=0,
|
||||
help='',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--search',
|
||||
dest='search',
|
||||
default={},
|
||||
help='Define a filter for the query',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--client', '-C',
|
||||
dest='client_id',
|
||||
default='',
|
||||
help='Get jobs for a specific client',
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
|
||||
search = utils.prepare_search(parsed_args.search)
|
||||
|
||||
if parsed_args.client_id:
|
||||
jobs = self.app.client.jobs.list(
|
||||
limit=parsed_args.limit,
|
||||
offset=parsed_args.offset,
|
||||
search=search,
|
||||
client_id=parsed_args.client_id
|
||||
)
|
||||
else:
|
||||
jobs = self.app.client.jobs.list_all(
|
||||
limit=parsed_args.limit,
|
||||
offset=parsed_args.offset,
|
||||
search=search
|
||||
)
|
||||
|
||||
columns = ('Job ID', 'Description', '# Actions', 'Result', 'Status',
|
||||
'Event', 'Session ID')
|
||||
|
||||
# Print empty table if no jobs found
|
||||
if not jobs:
|
||||
jobs = [{}]
|
||||
data = ((job.get('job_id', ''),
|
||||
job.get('description', ''),
|
||||
job.get('job_actions', ''),
|
||||
job.get('job_schedule', {}).get('result', ''),
|
||||
job.get('job_schedule', {}).get('status', ''),
|
||||
job.get('job_schedule', {}).get('event', ''),
|
||||
job.get('session_id', '')
|
||||
) for job in jobs)
|
||||
else:
|
||||
data = ((job.get('job_id'),
|
||||
job.get('description'),
|
||||
len(job.get('job_actions', [])),
|
||||
job.get('job_schedule', {}).get('result', ''),
|
||||
job.get('job_schedule', {}).get('status', ''),
|
||||
job.get('job_schedule', {}).get('event', ''),
|
||||
job.get('session_id', '')
|
||||
) for job in jobs)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
class JobGet(command.Command):
|
||||
"""Download a job as a json file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobGet, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
|
||||
parser.add_argument('--no-format',
|
||||
dest='no_format',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Return a job in json without pretty print')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
job = self.app.client.jobs.get(parsed_args.job_id)
|
||||
|
||||
if not job:
|
||||
raise exceptions.ApiClientException('Job not found')
|
||||
|
||||
if parsed_args.no_format:
|
||||
print(job)
|
||||
else:
|
||||
pprint.pprint(job)
|
||||
|
||||
|
||||
class JobDelete(command.Command):
|
||||
"""Delete a job from the api"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobDelete, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.jobs.delete(parsed_args.job_id)
|
||||
logging.info('Job {0} deleted'.format(parsed_args.job_id))
|
||||
|
||||
|
||||
class JobCreate(command.Command):
|
||||
"""Create a new job from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobCreate, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'--file',
|
||||
dest='file',
|
||||
required=True,
|
||||
help='Path to json file with the job')
|
||||
|
||||
parser.add_argument(
|
||||
'--client', '-C',
|
||||
dest='client_id',
|
||||
required=True,
|
||||
help='Select a client for this job',
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
job = utils.doc_from_json_file(parsed_args.file)
|
||||
job['client_id'] = parsed_args.client_id
|
||||
job_id = self.app.client.jobs.create(job)
|
||||
logging.info('Job {0} created'.format(job_id))
|
||||
|
||||
|
||||
class JobStart(command.Command):
|
||||
"""Send a start signal for a job"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobStart, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.jobs.start_job(parsed_args.job_id)
|
||||
logging.info("Start request sent "
|
||||
"for job {0}".format(parsed_args.job_id))
|
||||
|
||||
|
||||
class JobStop(command.Command):
|
||||
"""Send a stop signal for a job"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobStop, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.jobs.stop_job(parsed_args.job_id)
|
||||
logging.info("Stop request sent "
|
||||
"for job {0}".format(parsed_args.job_id))
|
||||
|
||||
|
||||
class JobAbort(command.Command):
|
||||
"""Abort a running job"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobAbort, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.jobs.abort_job(parsed_args.job_id)
|
||||
logging.info("Abort request sent "
|
||||
"for job {0}".format(parsed_args.job_id))
|
||||
|
||||
|
||||
class JobUpdate(command.Command):
|
||||
"""Update a job from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(JobUpdate, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='job_id',
|
||||
help='ID of the job')
|
||||
|
||||
parser.add_argument(dest='file',
|
||||
help='Path to json file with the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
job = utils.doc_from_json_file(parsed_args.file)
|
||||
self.app.client.jobs.update(parsed_args.job_id, job)
|
||||
logging.info('Job {0} updated'.format(parsed_args.job_id))
|
0
freezerclient/v2/managers/__init__.py
Normal file
0
freezerclient/v2/managers/__init__.py
Normal file
77
freezerclient/v2/managers/actions.py
Normal file
77
freezerclient/v2/managers/actions.py
Normal file
@ -0,0 +1,77 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient import utils
|
||||
|
||||
|
||||
class ActionManager(object):
|
||||
|
||||
def __init__(self, client, verify=True):
|
||||
self.client = client
|
||||
self.endpoint = '{0}/v2/{1}/actions/'.format(
|
||||
self.client.endpoint, self.client.project_id)
|
||||
self.verify = verify
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return utils.create_headers_for_request(self.client.auth_token)
|
||||
|
||||
def create(self, doc, action_id=''):
|
||||
action_id = action_id or doc.get('action_id', '')
|
||||
endpoint = self.endpoint + action_id
|
||||
r = requests.post(endpoint,
|
||||
data=json.dumps(doc),
|
||||
headers=self.headers,
|
||||
verify=self.verify)
|
||||
if r.status_code != 201:
|
||||
raise exceptions.ApiClientException(r)
|
||||
action_id = r.json()['action_id']
|
||||
return action_id
|
||||
|
||||
def delete(self, action_id):
|
||||
endpoint = self.endpoint + action_id
|
||||
r = requests.delete(endpoint, headers=self.headers, verify=self.verify)
|
||||
if r.status_code != 204:
|
||||
raise exceptions.ApiClientException(r)
|
||||
|
||||
def list(self, limit=10, offset=0, search=None):
|
||||
data = json.dumps(search) if search else None
|
||||
query = {'limit': int(limit), 'offset': int(offset)}
|
||||
r = requests.get(self.endpoint, headers=self.headers,
|
||||
params=query, data=data, verify=self.verify)
|
||||
if r.status_code != 200:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()['actions']
|
||||
|
||||
def get(self, action_id):
|
||||
endpoint = self.endpoint + action_id
|
||||
r = requests.get(endpoint, headers=self.headers, verify=self.verify)
|
||||
if r.status_code == 200:
|
||||
return r.json()
|
||||
if r.status_code == 404:
|
||||
return None
|
||||
raise exceptions.ApiClientException(r)
|
||||
|
||||
def update(self, action_id, update_doc):
|
||||
endpoint = self.endpoint + action_id
|
||||
r = requests.patch(endpoint,
|
||||
headers=self.headers,
|
||||
data=json.dumps(update_doc), verify=self.verify)
|
||||
if r.status_code != 200:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()['version']
|
79
freezerclient/v2/managers/backups.py
Normal file
79
freezerclient/v2/managers/backups.py
Normal file
@ -0,0 +1,79 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient import utils
|
||||
|
||||
|
||||
class BackupsManager(object):
|
||||
|
||||
def __init__(self, client, verify=True):
|
||||
self.client = client
|
||||
self.endpoint = self.endpoint = '{0}/v2/{1}/backups/'.format(
|
||||
self.client.endpoint, self.client.project_id)
|
||||
self.verify = verify
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return utils.create_headers_for_request(self.client.auth_token)
|
||||
|
||||
def create(self, backup_metadata):
|
||||
r = requests.post(self.endpoint,
|
||||
data=json.dumps(backup_metadata),
|
||||
headers=self.headers,
|
||||
verify=self.verify)
|
||||
if r.status_code != 201:
|
||||
raise exceptions.ApiClientException(r)
|
||||
backup_id = r.json()['backup_id']
|
||||
return backup_id
|
||||
|
||||
def delete(self, backup_id):
|
||||
endpoint = self.endpoint + backup_id
|
||||
r = requests.delete(endpoint, headers=self.headers, verify=self.verify)
|
||||
if r.status_code != 204:
|
||||
raise exceptions.ApiClientException(r)
|
||||
|
||||
def list(self, limit=10, offset=0, search=None):
|
||||
"""
|
||||
Retrieves a list of backup infos
|
||||
|
||||
:param limit: number of result to return (optional, default 10)
|
||||
:param offset: order of first document (optional, default 0)
|
||||
:param search: structured query (optional)
|
||||
can contain:
|
||||
* "time_before": timestamp
|
||||
* "time_after": timestamp
|
||||
Example:
|
||||
{ "time_before": 1428529956 }
|
||||
"""
|
||||
data = json.dumps(search) if search else None
|
||||
query = {'limit': int(limit), 'offset': int(offset)}
|
||||
r = requests.get(self.endpoint, headers=self.headers,
|
||||
params=query, data=data, verify=self.verify)
|
||||
if r.status_code != 200:
|
||||
raise exceptions.ApiClientException(r)
|
||||
|
||||
return r.json()['backups']
|
||||
|
||||
def get(self, backup_id):
|
||||
endpoint = self.endpoint + backup_id
|
||||
r = requests.get(endpoint, headers=self.headers, verify=self.verify)
|
||||
if r.status_code == 200:
|
||||
return r.json()
|
||||
if r.status_code == 404:
|
||||
return None
|
||||
raise exceptions.ApiClientException(r)
|
83
freezerclient/v2/managers/clients.py
Normal file
83
freezerclient/v2/managers/clients.py
Normal file
@ -0,0 +1,83 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient import utils
|
||||
|
||||
|
||||
class ClientManager(object):
|
||||
|
||||
def __init__(self, client, verify=True):
|
||||
self.client = client
|
||||
self.endpoint = self.endpoint = '{0}/v2/{1}/clients/'.format(
|
||||
self.client.endpoint, self.client.project_id)
|
||||
self.verify = verify
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return utils.create_headers_for_request(self.client.auth_token)
|
||||
|
||||
def create(self, client_info):
|
||||
r = requests.post(self.endpoint,
|
||||
data=json.dumps(client_info),
|
||||
headers=self.headers,
|
||||
verify=self.verify)
|
||||
if r.status_code != 201:
|
||||
raise exceptions.ApiClientException(r)
|
||||
client_id = r.json()['client_id']
|
||||
return client_id
|
||||
|
||||
def delete(self, client_id):
|
||||
endpoint = self.endpoint + client_id
|
||||
r = requests.delete(endpoint, headers=self.headers,
|
||||
verify=self.verify)
|
||||
if r.status_code != 204:
|
||||
raise exceptions.ApiClientException(r)
|
||||
|
||||
def list(self, limit=10, offset=0, search=None):
|
||||
"""
|
||||
Retrieves a list of client info structures
|
||||
|
||||
:param limit: number of result to return (optional, default 10)
|
||||
:param offset: order of first document (optional, default 0)
|
||||
:param search: structured query (optional)
|
||||
can contain:
|
||||
* "match": list of {field, value}
|
||||
Example:
|
||||
{ "match": [
|
||||
{"description": "some search text here"},
|
||||
{"client_id": "giano"},
|
||||
...
|
||||
],
|
||||
}
|
||||
"""
|
||||
data = json.dumps(search) if search else None
|
||||
query = {'limit': int(limit), 'offset': int(offset)}
|
||||
r = requests.get(self.endpoint, headers=self.headers,
|
||||
params=query, data=data, verify=self.verify)
|
||||
if r.status_code != 200:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()['clients']
|
||||
|
||||
def get(self, client_id):
|
||||
endpoint = self.endpoint + client_id
|
||||
r = requests.get(endpoint, headers=self.headers, verify=self.verify)
|
||||
if r.status_code == 200:
|
||||
return r.json()
|
||||
if r.status_code == 404:
|
||||
return None
|
||||
raise exceptions.ApiClientException(r)
|
149
freezerclient/v2/managers/jobs.py
Normal file
149
freezerclient/v2/managers/jobs.py
Normal file
@ -0,0 +1,149 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient import utils
|
||||
|
||||
|
||||
class JobManager(object):
|
||||
|
||||
def __init__(self, client, verify=True):
|
||||
self.client = client
|
||||
self.endpoint = '{0}/v2/{1}/jobs/'.format(
|
||||
self.client.endpoint, self.client.project_id)
|
||||
self.verify = verify
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return utils.create_headers_for_request(self.client.auth_token)
|
||||
|
||||
def create(self, doc, job_id=''):
|
||||
job_id = job_id or doc.get('job_id', '')
|
||||
endpoint = self.endpoint + job_id
|
||||
doc['client_id'] = doc.get('client_id', '') or self.client.client_id
|
||||
r = requests.post(endpoint,
|
||||
data=json.dumps(doc),
|
||||
headers=self.headers,
|
||||
verify=self.verify)
|
||||
if r.status_code != 201:
|
||||
raise exceptions.ApiClientException(r)
|
||||
job_id = r.json()['job_id']
|
||||
return job_id
|
||||
|
||||
def delete(self, job_id):
|
||||
endpoint = self.endpoint + job_id
|
||||
r = requests.delete(endpoint, headers=self.headers, verify=self.verify)
|
||||
if r.status_code != 204:
|
||||
raise exceptions.ApiClientException(r)
|
||||
|
||||
def list_all(self, limit=10, offset=0, search=None):
|
||||
data = json.dumps(search) if search else None
|
||||
query = {'limit': int(limit), 'offset': int(offset)}
|
||||
r = requests.get(self.endpoint, headers=self.headers,
|
||||
params=query, data=data, verify=self.verify)
|
||||
if r.status_code != 200:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()['jobs']
|
||||
|
||||
def list(self, limit=10, offset=0, search={}, client_id=None):
|
||||
client_id = client_id or self.client.client_id
|
||||
new_search = search.copy()
|
||||
new_search['match'] = search.get('match', [])
|
||||
new_search['match'].append({'client_id': client_id})
|
||||
return self.list_all(limit, offset, new_search)
|
||||
|
||||
def get(self, job_id):
|
||||
endpoint = self.endpoint + job_id
|
||||
r = requests.get(endpoint, headers=self.headers, verify=self.verify)
|
||||
if r.status_code == 200:
|
||||
return r.json()
|
||||
if r.status_code == 404:
|
||||
return None
|
||||
raise exceptions.ApiClientException(r)
|
||||
|
||||
def update(self, job_id, update_doc):
|
||||
endpoint = self.endpoint + job_id
|
||||
r = requests.patch(endpoint,
|
||||
headers=self.headers,
|
||||
data=json.dumps(update_doc),
|
||||
verify=self.verify)
|
||||
if r.status_code != 200:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()['version']
|
||||
|
||||
def start_job(self, job_id):
|
||||
"""
|
||||
Request to start a job
|
||||
|
||||
:param job_id: the id of the job to start
|
||||
:return: the response obj:
|
||||
{
|
||||
result: string 'success' or 'already started'
|
||||
}
|
||||
"""
|
||||
# endpoint /v1/jobs/{job_id}/event
|
||||
endpoint = '{0}{1}/event'.format(self.endpoint, job_id)
|
||||
doc = {"start": None}
|
||||
r = requests.post(endpoint,
|
||||
headers=self.headers,
|
||||
data=json.dumps(doc),
|
||||
verify=self.verify)
|
||||
if r.status_code != 202:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()
|
||||
|
||||
def stop_job(self, job_id):
|
||||
"""
|
||||
Request to stop a job
|
||||
|
||||
:param job_id: the id of the job to start
|
||||
:return: the response obj:
|
||||
{
|
||||
result: string 'success' or 'already stopped'
|
||||
}
|
||||
"""
|
||||
# endpoint /v1/jobs/{job_id}/event
|
||||
endpoint = '{0}{1}/event'.format(self.endpoint, job_id)
|
||||
doc = {"stop": None}
|
||||
r = requests.post(endpoint,
|
||||
headers=self.headers,
|
||||
data=json.dumps(doc),
|
||||
verify=self.verify)
|
||||
if r.status_code != 202:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()
|
||||
|
||||
def abort_job(self, job_id):
|
||||
"""
|
||||
Request to abort a job
|
||||
|
||||
:param job_id: the id of the job to start
|
||||
:return: the response obj:
|
||||
{
|
||||
result: string 'success' or 'already stopped'
|
||||
}
|
||||
"""
|
||||
# endpoint /v1/jobs/{job_id}/event
|
||||
endpoint = '{0}{1}/event'.format(self.endpoint, job_id)
|
||||
doc = {"abort": None}
|
||||
r = requests.post(endpoint,
|
||||
headers=self.headers,
|
||||
data=json.dumps(doc),
|
||||
verify=self.verify)
|
||||
if r.status_code != 202:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()
|
162
freezerclient/v2/managers/sessions.py
Normal file
162
freezerclient/v2/managers/sessions.py
Normal file
@ -0,0 +1,162 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient import utils
|
||||
|
||||
|
||||
class SessionManager(object):
|
||||
|
||||
def __init__(self, client, verify=True):
|
||||
self.client = client
|
||||
self.endpoint = '{0}/v2/{1}/sessions/'.format(
|
||||
self.client.endpoint, self.client.project_id)
|
||||
self.verify = verify
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return utils.create_headers_for_request(self.client.auth_token)
|
||||
|
||||
def create(self, doc, session_id=''):
|
||||
session_id = session_id or doc.get('session_id', '')
|
||||
endpoint = self.endpoint + session_id
|
||||
r = requests.post(endpoint,
|
||||
data=json.dumps(doc),
|
||||
headers=self.headers,
|
||||
verify=self.verify)
|
||||
if r.status_code != 201:
|
||||
raise exceptions.ApiClientException(r)
|
||||
session_id = r.json()['session_id']
|
||||
return session_id
|
||||
|
||||
def delete(self, session_id):
|
||||
endpoint = self.endpoint + session_id
|
||||
r = requests.delete(endpoint, headers=self.headers, verify=self.verify)
|
||||
if r.status_code != 204:
|
||||
raise exceptions.ApiClientException(r)
|
||||
|
||||
def list_all(self, limit=10, offset=0, search=None):
|
||||
data = json.dumps(search) if search else None
|
||||
query = {'limit': int(limit), 'offset': int(offset)}
|
||||
r = requests.get(self.endpoint, headers=self.headers,
|
||||
params=query, data=data, verify=self.verify)
|
||||
if r.status_code != 200:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()['sessions']
|
||||
|
||||
def list(self, limit=10, offset=0, search={}):
|
||||
new_search = search.copy()
|
||||
new_search['match'] = search.get('match', [])
|
||||
return self.list_all(limit, offset, new_search)
|
||||
|
||||
def get(self, session_id):
|
||||
endpoint = self.endpoint + session_id
|
||||
r = requests.get(endpoint, headers=self.headers, verify=self.verify)
|
||||
if r.status_code == 200:
|
||||
return r.json()
|
||||
if r.status_code == 404:
|
||||
return None
|
||||
raise exceptions.ApiClientException(r)
|
||||
|
||||
def update(self, session_id, update_doc):
|
||||
endpoint = self.endpoint + session_id
|
||||
r = requests.patch(endpoint,
|
||||
headers=self.headers,
|
||||
data=json.dumps(update_doc),
|
||||
verify=self.verify)
|
||||
if r.status_code != 200:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()['version']
|
||||
|
||||
def add_job(self, session_id, job_id):
|
||||
# endpoint /v1/sessions/{sessions_id}/jobs/{job_id}
|
||||
endpoint = '{0}{1}/jobs/{2}'.format(self.endpoint, session_id, job_id)
|
||||
r = requests.put(endpoint,
|
||||
headers=self.headers, verify=self.verify)
|
||||
if r.status_code != 204:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return
|
||||
|
||||
def remove_job(self, session_id, job_id):
|
||||
# endpoint /v1/sessions/{sessions_id}/jobs/{job_id}
|
||||
endpoint = '{0}{1}/jobs/{2}'.format(self.endpoint, session_id, job_id)
|
||||
retry = 5
|
||||
r = ''
|
||||
while retry:
|
||||
r = requests.delete(endpoint,
|
||||
headers=self.headers, verify=self.verify)
|
||||
if r.status_code == 204:
|
||||
return
|
||||
retry -= 1
|
||||
raise exceptions.ApiClientException(r)
|
||||
|
||||
def start_session(self, session_id, job_id, session_tag):
|
||||
"""
|
||||
Informs the api that the client is starting the session
|
||||
identified by the session_id and request the session_tag
|
||||
to be incremented up to the requested value.
|
||||
The returned session_id could be:
|
||||
* current_tag + 1 if the session has started
|
||||
* > current_tag + 1 if the action had already been started
|
||||
by some other node and this node was out of sync
|
||||
|
||||
:param session_id:
|
||||
:param job_id:
|
||||
:param session_tag: the new session_id
|
||||
:return: the response obj:
|
||||
{ result: string 'running' or 'error',
|
||||
'session_tag': the new session_tag )
|
||||
"""
|
||||
# endpoint /v1/sessions/{sessions_id}/action
|
||||
endpoint = '{0}{1}/action'.format(self.endpoint, session_id)
|
||||
doc = {"start": {
|
||||
"job_id": job_id,
|
||||
"current_tag": session_tag
|
||||
}}
|
||||
r = requests.post(endpoint,
|
||||
headers=self.headers,
|
||||
data=json.dumps(doc),
|
||||
verify=self.verify)
|
||||
if r.status_code != 202:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()
|
||||
|
||||
def end_session(self, session_id, job_id, session_tag, result):
|
||||
"""
|
||||
Informs the freezer service that the job has ended.
|
||||
Provides information about the job's result and the session tag
|
||||
|
||||
:param session_id:
|
||||
:param job_id:
|
||||
:param session_tag:
|
||||
:param result:
|
||||
:return:
|
||||
"""
|
||||
# endpoint /v1/sessions/{sessions_id}/action
|
||||
endpoint = '{0}{1}/action'.format(self.endpoint, session_id)
|
||||
doc = {"end": {
|
||||
"job_id": job_id,
|
||||
"current_tag": session_tag,
|
||||
"result": result
|
||||
}}
|
||||
r = requests.post(endpoint,
|
||||
headers=self.headers,
|
||||
data=json.dumps(doc),
|
||||
verify=self.verify)
|
||||
if r.status_code != 202:
|
||||
raise exceptions.ApiClientException(r)
|
||||
return r.json()
|
245
freezerclient/v2/sessions.py
Normal file
245
freezerclient/v2/sessions.py
Normal file
@ -0,0 +1,245 @@
|
||||
# (c) Copyright 2014-2016 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
import pprint
|
||||
|
||||
from cliff import command
|
||||
from cliff import lister
|
||||
from cliff import show
|
||||
|
||||
from freezerclient import exceptions
|
||||
from freezerclient import utils
|
||||
|
||||
|
||||
logging = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SessionShow(show.ShowOne):
|
||||
"""Show a single session"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionShow, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='session_id',
|
||||
help='ID of the session')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
session = self.app.client.sessions.get(parsed_args.session_id)
|
||||
|
||||
if not session:
|
||||
raise exceptions.ApiClientException('Session not found')
|
||||
|
||||
column = (
|
||||
'Session ID',
|
||||
'Description',
|
||||
'Status',
|
||||
'Jobs'
|
||||
)
|
||||
|
||||
data = (
|
||||
session.get('session_id'),
|
||||
session.get('description'),
|
||||
session.get('status'),
|
||||
pprint.pformat(session.get('jobs'))
|
||||
)
|
||||
return column, data
|
||||
|
||||
|
||||
class SessionList(lister.Lister):
|
||||
"""List all the sessions for your user"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionList, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument(
|
||||
'--limit',
|
||||
dest='limit',
|
||||
default=100,
|
||||
help='Specify a limit for search query',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--offset',
|
||||
dest='offset',
|
||||
default=0,
|
||||
help='',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--search',
|
||||
dest='search',
|
||||
default='',
|
||||
help='Define a filter for the query',
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
sessions = self.app.client.sessions.list_all(
|
||||
limit=parsed_args.limit,
|
||||
offset=parsed_args.offset,
|
||||
search=parsed_args.search
|
||||
)
|
||||
|
||||
# Print empty table if no sessions found
|
||||
if not sessions:
|
||||
sessions = [{}]
|
||||
|
||||
columns = ('Session ID', 'Description', 'Status', '# Jobs')
|
||||
data = ((
|
||||
session.get('session_id', ''),
|
||||
session.get('description', ''),
|
||||
session.get('status', ''),
|
||||
len(session.get('jobs', [])) if session.get(
|
||||
'session_id') else '',
|
||||
) for session in sessions)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
class SessionCreate(command.Command):
|
||||
"""Create a session from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionCreate, self).get_parser(prog_name)
|
||||
parser.add_argument('--file',
|
||||
dest='file',
|
||||
required=True,
|
||||
help='Path to json file with the job')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
session = utils.doc_from_json_file(parsed_args.file)
|
||||
session_id = self.app.client.sessions.create(session)
|
||||
logging.info('Session {0} created'.format(session_id))
|
||||
|
||||
|
||||
class SessionDelete(command.Command):
|
||||
"""Delete a session"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionDelete, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='session_id',
|
||||
help='ID of the session')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
session = self.app.client.sessions.get(parsed_args.session_id)
|
||||
if not session:
|
||||
logging.info('Unable to delete specified session.')
|
||||
raise exceptions.ApiClientException('Session not found')
|
||||
|
||||
self.app.client.sessions.delete(parsed_args.session_id)
|
||||
logging.info('Session {0} deleted'.format(parsed_args.session_id))
|
||||
|
||||
|
||||
class SessionAddJob(command.Command):
|
||||
"""Add a job to a session"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionAddJob, self).get_parser(prog_name)
|
||||
parser.add_argument('--session-id',
|
||||
dest='session_id',
|
||||
required=True,
|
||||
help='ID of the session')
|
||||
parser.add_argument('--job-id',
|
||||
dest='job_id',
|
||||
required=True,
|
||||
help='ID of the job to add')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.app.client.sessions.add_job(parsed_args.session_id,
|
||||
parsed_args.job_id)
|
||||
logging.info('Job {0} added correctly to session {1}'.format(
|
||||
parsed_args.job_id, parsed_args.session_id))
|
||||
|
||||
|
||||
class SessionRemoveJob(command.Command):
|
||||
"""Remove a job from a session"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionRemoveJob, self).get_parser(prog_name)
|
||||
parser.add_argument('--session-id',
|
||||
dest='session_id',
|
||||
required=True,
|
||||
help='ID of the session')
|
||||
parser.add_argument('--job-id',
|
||||
dest='job_id',
|
||||
required=True,
|
||||
help='ID of the job to add')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
try:
|
||||
self.app.client.sessions.remove_job(parsed_args.session_id,
|
||||
parsed_args.job_id)
|
||||
except Exception as error:
|
||||
# there is an error coming from the api when a job is removed
|
||||
# with the following text:
|
||||
# Additional properties are not allowed
|
||||
# ('job_event' was unexpected)
|
||||
# but in reality the job gets removed correctly.
|
||||
if 'Additional properties are not allowed' in error.message:
|
||||
pass
|
||||
else:
|
||||
raise exceptions.ApiClientException(error.message)
|
||||
else:
|
||||
logging.info('Job {0} removed correctly from session {1}'.format(
|
||||
parsed_args.job_id, parsed_args.session_id))
|
||||
|
||||
|
||||
class SessionUpdate(command.Command):
|
||||
"""Update a session from a file"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionUpdate, self).get_parser(prog_name)
|
||||
parser.add_argument(dest='session_id',
|
||||
help='ID of the session')
|
||||
|
||||
parser.add_argument(dest='file',
|
||||
help='Path to json file with the session')
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
session = utils.doc_from_json_file(parsed_args.file)
|
||||
self.app.client.sessions.update(parsed_args.session_id, session)
|
||||
logging.info('Session {0} updated'.format(parsed_args.session_id))
|
||||
|
||||
|
||||
class SessionStart(command.Command):
|
||||
"""Start a session"""
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(SessionStart, self).get_parser(prog_name)
|
||||
parser.add_argument('--session-id',
|
||||
dest='session_id',
|
||||
required=True,
|
||||
help='ID of the session')
|
||||
parser.add_argument('--job-id',
|
||||
dest='job_id',
|
||||
required=True,
|
||||
help='ID of the job')
|
||||
parser.add_argument('--job-tag',
|
||||
dest='job_tag',
|
||||
required=True,
|
||||
help='Job tag value')
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
session = self.app.client.sessions.get(parsed_args.session_id)
|
||||
if not session:
|
||||
logging.info('Unable to start specified session.')
|
||||
raise exceptions.ApiClientException('Session not found')
|
||||
|
||||
self.app.client.sessions.start_session(
|
||||
parsed_args.session_id,
|
||||
parsed_args.job_id,
|
||||
parsed_args.job_tag
|
||||
)
|
||||
logging.info('Session {0} start requested'.format(
|
||||
parsed_args.session_id))
|
13
releasenotes/notes/freezerclient-v2-d0729e1ee77d341b.yaml
Normal file
13
releasenotes/notes/freezerclient-v2-d0729e1ee77d341b.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
prelude: >
|
||||
Python freezer client now has the ability to use the version 2 of the API.
|
||||
features:
|
||||
- |
|
||||
Freezer client v2 support
|
||||
upgrade:
|
||||
- |
|
||||
for Backup as a service use "export OS_BACKUP_API=2"
|
||||
for control plane backup use "export OS_BACKUP_API=1"
|
||||
critical:
|
||||
- |
|
||||
if no OS_BACKUP_API is use, freezerclient will default to version 2
|
Loading…
Reference in New Issue
Block a user