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:
Memo Garcia 2017-05-23 12:11:57 +01:00
parent 9578f23509
commit bb19aa5b4d
17 changed files with 1751 additions and 12 deletions

4
.gitignore vendored
View File

@ -27,4 +27,6 @@ ChangeLog
# Coverage data
.coverage.*
releasenotes/build
releasenotes/build
.vscode/

View File

@ -1,3 +0,0 @@
// Place your settings in this file to overwrite default and user settings.
{
}

View File

@ -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:]):

View File

173
freezerclient/v2/actions.py Normal file
View 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
View 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
View 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
View 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
View 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))

View File

View 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']

View 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)

View 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)

View 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()

View 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()

View 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))

View 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