client retrieval of freezer api endpoint

The api client queries keystone to obtain the freezer api endpoint,
provided that the freezer api service and endpoint have been registered in
keystone.

An optional parameter to specify the api endpoint is supported

Change-Id: I6626a60d1fd5d18a59376165e94c789832865ae0
Implements: blueprint freezer-apiclient-endpoint
This commit is contained in:
Fabrizio Vanni 2015-04-16 12:00:36 +01:00
parent 72a6c7396e
commit 616e742792
7 changed files with 319 additions and 47 deletions

View File

@ -29,10 +29,13 @@ class BackupsManager(object):
def __init__(self, client):
self.client = client
self.endpoint = self.client.endpoint + 'backups/'
self.headers = {'X-Auth-Token': client.token}
self.endpoint = self.client.api_endpoint + '/v1/backups/'
def create(self, backup_metadata, username=None, tenant_name=None):
@property
def headers(self):
return {'X-Auth-Token': self.client.auth_token}
def create(self, backup_metadata):
r = requests.post(self.endpoint,
data=json.dumps(backup_metadata),
headers=self.headers)
@ -42,14 +45,14 @@ class BackupsManager(object):
backup_id = r.json()['backup_id']
return backup_id
def delete(self, backup_id, username=None, tenant_name=None):
def delete(self, backup_id):
endpoint = self.endpoint + backup_id
r = requests.delete(endpoint, headers=self.headers)
if r.status_code != 204:
raise exceptions.MetadataDeleteFailure(
"[*] Error {0}".format(r.status_code))
def list(self, username=None, tenant_name=None):
def list(self):
r = requests.get(self.endpoint, headers=self.headers)
if r.status_code != 200:
raise exceptions.MetadataGetFailure(
@ -57,7 +60,7 @@ class BackupsManager(object):
return r.json()['backups']
def get(self, backup_id, username=None, tenant_name=None):
def get(self, backup_id):
endpoint = self.endpoint + backup_id
r = requests.get(endpoint, headers=self.headers)
if r.status_code == 200:

View File

@ -22,14 +22,15 @@ Hudson (tjh@cryptsoft.com).
import os
import sys
from openstackclient.identity import client as os_client
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir, os.pardir, os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'freezer', '__init__.py')):
sys.path.insert(0, possible_topdir)
import keystoneclient
from freezer.apiclient.backups import BackupsManager
import exceptions
class Client(object):
@ -40,26 +41,66 @@ class Client(object):
password=None,
tenant_name=None,
auth_url=None,
endpoint=None,
session=None):
if endpoint is None:
raise Exception('Missing endpoint information')
self.endpoint = endpoint
if token is not None:
# validate the token ?
self.token = token
elif session is not None:
pass
# TODO: handle session auth
# assert isinstance(session, keystoneclient.session.Session)
else:
self.username = username
self.tenant_name = tenant_name
kc = keystoneclient.v2_0.client.Client(
username=username,
password=password,
tenant_name=tenant_name,
auth_url=auth_url)
self.token = kc.auth_token
session=None,
api_endpoint=None):
self.version = version
self.token = token
self.username = username
self.tenant_name = tenant_name
self.password = password
self.auth_url = auth_url
self._api_endpoint = api_endpoint
self.session = session
self._auth = None
self.backups = BackupsManager(self)
def _update_api_endpoint(self):
services = self.auth.services.list()
try:
freezer_service = next(x for x in services if x.name == 'freezer')
except:
raise exceptions.AuthFailure(
'freezer service not found in services list')
endpoints = self.auth.endpoints.list()
try:
freezer_endpoint =\
next(x for x in endpoints
if x.service_id == freezer_service.id)
except:
raise exceptions.AuthFailure(
'freezer endpoint not found in endpoint list')
self._api_endpoint = freezer_endpoint.publicurl
@property
def auth(self):
if self._auth is None:
if self.username and self.password:
self._auth = os_client.IdentityClientv2(
auth_url=self.auth_url,
username=self.username,
password=self.password,
tenant_name=self.tenant_name)
elif self.token:
self._auth = os_client.IdentityClientv2(
endpoint=self.auth_url,
token=self.token)
else:
raise exceptions.AuthFailure("Missing auth credentials")
return self._auth
@property
def auth_token(self):
return self.auth.auth_token
@property
def api_endpoint(self):
if self._api_endpoint is None:
self._update_api_endpoint()
return self._api_endpoint
def api_exists(self):
try:
if self.api_endpoint is not None:
return True
except:
return False

View File

@ -20,27 +20,17 @@ Hudson (tjh@cryptsoft.com).
"""
class FreezerClientException(Exception):
"""
Base Freezer API Exception
"""
message = ("Unknown exception occurred")
def __init__(self, message=None, *args, **kwargs):
if not message:
message = self.message
message = message % kwargs
Exception.__init__(self, message)
class MetadataCreationFailure(FreezerClientException):
class MetadataCreationFailure(Exception):
message = "Metadata creation failed: %reason"
class MetadataGetFailure(FreezerClientException):
class MetadataGetFailure(Exception):
message = "Metadata read failed: %reason"
class MetadataDeleteFailure(FreezerClientException):
class MetadataDeleteFailure(Exception):
message = "Metadata deletion failed: %reason"
class AuthFailure(Exception):
message = "Authentication Error: %reason"

View File

@ -61,6 +61,22 @@ utilizes the timestamp of the first (level 0) backup in the session
It is identified by (container, hostname, backupname, timestamp-of-level-0)
API registration
================
keystone user-create --name freezer --pass FREEZER_PWD
keystone user-role-add --user freezer --tenant service --role admin
keystone service-create --name freezer --type backup \
--description "Freezer Backup Service"
keystone endpoint-create \
--service-id $(keystone service-list | awk '/ backup / {print $2}') \
--publicurl http://freezer_api_publicurl:port \
--internalurl http://freezer_api_internalurl:port \
--adminurl http://freezer_api_adminurl:port \
--region regionOne
API routes
==========

View File

@ -0,0 +1,112 @@
"""Freezer swift.py related tests
Copyright 2014 Hewlett-Packard
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This product includes cryptographic software written by Eric Young
(eay@cryptsoft.com). This product includes software written by Tim
Hudson (tjh@cryptsoft.com).
========================================================================
"""
import unittest
from mock import Mock, patch
from freezer.apiclient import exceptions
from freezer.apiclient import backups
class TestBackupManager(unittest.TestCase):
def setUp(self):
self.mock_client = Mock()
self.mock_client.api_endpoint = 'http://testendpoint:9999'
self.mock_client.auth_token = 'testtoken'
self.b = backups.BackupsManager(self.mock_client)
@patch('freezer.apiclient.backups.requests')
def test_create(self, mock_requests):
self.assertEqual(self.b.endpoint, 'http://testendpoint:9999/v1/backups/')
self.assertEqual(self.b.headers, {'X-Auth-Token': 'testtoken'})
@patch('freezer.apiclient.backups.requests')
def test_create_ok(self, mock_requests):
mock_response = Mock()
mock_response.status_code = 201
mock_response.json.return_value = {'backup_id': 'qwerqwer'}
mock_requests.post.return_value = mock_response
retval = self.b.create(backup_metadata={'backup': 'metadata'})
self.assertEqual(retval, 'qwerqwer')
@patch('freezer.apiclient.backups.requests')
def test_create_fail(self, mock_requests):
mock_response = Mock()
mock_response.status_code = 500
#mock_response.json.return_value = {'backup_id': 'qwerqwer'}
mock_requests.post.return_value = mock_response
self.assertRaises(exceptions.MetadataCreationFailure, self.b.create, {'backup': 'metadata'})
@patch('freezer.apiclient.backups.requests')
def test_delete_ok(self, mock_requests):
mock_response = Mock()
mock_response.status_code = 204
mock_requests.delete.return_value = mock_response
retval = self.b.delete('test_backup_id')
self.assertIsNone(retval)
@patch('freezer.apiclient.backups.requests')
def test_delete_fail(self, mock_requests):
mock_response = Mock()
mock_response.status_code = 500
mock_requests.delete.return_value = mock_response
self.assertRaises(exceptions.MetadataDeleteFailure, self.b.delete, 'test_backup_id')
@patch('freezer.apiclient.backups.requests')
def test_get_ok(self, mock_requests):
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {'backup_id': 'qwerqwer'}
mock_requests.get.return_value = mock_response
retval = self.b.get('test_backup_id')
self.assertEqual(retval, {'backup_id': 'qwerqwer'})
@patch('freezer.apiclient.backups.requests')
def test_get_none(self, mock_requests):
mock_response = Mock()
mock_response.status_code = 404
mock_requests.get.return_value = mock_response
retval = self.b.get('test_backup_id')
self.assertIsNone(retval)
# get_error
@patch('freezer.apiclient.backups.requests')
def test_list_ok(self, mock_requests):
mock_response = Mock()
mock_response.status_code = 200
backup_list = [{'backup_id_0': 'qwerqwer'}, {'backup_id_1': 'asdfasdf'}]
mock_response.json.return_value = {'backups': backup_list}
mock_requests.get.return_value = mock_response
retval = self.b.list()
self.assertEqual(retval, backup_list)
@patch('freezer.apiclient.backups.requests')
def test_list_error(self, mock_requests):
mock_response = Mock()
mock_response.status_code = 404
backup_list = [{'backup_id_0': 'qwerqwer'}, {'backup_id_1': 'asdfasdf'}]
mock_response.json.return_value = {'backups': backup_list}
mock_requests.get.return_value = mock_response
self.assertRaises(exceptions.MetadataGetFailure, self.b.list)

View File

@ -0,0 +1,108 @@
"""Freezer swift.py related tests
Copyright 2014 Hewlett-Packard
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This product includes cryptographic software written by Eric Young
(eay@cryptsoft.com). This product includes software written by Tim
Hudson (tjh@cryptsoft.com).
========================================================================
"""
import unittest
from mock import Mock, patch
from freezer.apiclient import client
from freezer.apiclient import exceptions
class TestClientMock(unittest.TestCase):
def create_mock_endpoint(self, service_id):
m = Mock()
m.service_id = service_id
m.publicurl = 'http://frezerapiurl:9090'
return m
def create_mock_service(self, name, id):
m = Mock()
m.name = name
m.id = id
return m
def setUp(self):
mock_enpointlist_ok = [self.create_mock_endpoint('idqwerty'),
self.create_mock_endpoint('idfreak'),
self.create_mock_endpoint('blabla')]
mock_servicelist_ok = [self.create_mock_service(name='glance', id='idqwerty'),
self.create_mock_service(name='freezer', id='idfreak')]
self.mock_IdentityClientv2 = Mock()
self.mock_IdentityClientv2.endpoints.list.return_value = mock_enpointlist_ok
self.mock_IdentityClientv2.services.list.return_value = mock_servicelist_ok
@patch('freezer.apiclient.client.os_client')
def test_client_create_username(self, mock_os_client):
mock_os_client.IdentityClientv2.return_value = self.mock_IdentityClientv2
c = client.Client(username='myname',
password='mypasswd',
tenant_name='mytenant',
auth_url='http://whatever:35357/v2.0/')
self.assertIsInstance(c, client.Client)
self.assertEqual(c.api_endpoint, 'http://frezerapiurl:9090')
@patch('freezer.apiclient.client.os_client')
def test_client_create_token(self, mock_os_client):
mock_os_client.IdentityClientv2.return_value = self.mock_IdentityClientv2
c = client.Client(token='mytoken',
auth_url='http://whatever:35357/v2.0/')
self.assertIsInstance(c, client.Client)
self.assertEqual(c.api_endpoint, 'http://frezerapiurl:9090')
@patch('freezer.apiclient.client.os_client')
def test_client_error_no_credentials(self, mock_os_client):
mock_os_client.IdentityClientv2.return_value = self.mock_IdentityClientv2
self.assertRaises(exceptions.AuthFailure, client.Client, auth_url='http://whatever:35357/v2.0/')
@patch('freezer.apiclient.client.os_client')
def test_client_service_not_found(self, mock_os_client):
mock_servicelist_bad = [self.create_mock_service(name='glance', id='idqwerty'),
self.create_mock_service(name='spanishinquisition', id='idfreak')]
self.mock_IdentityClientv2.services.list.return_value = mock_servicelist_bad
mock_os_client.IdentityClientv2.return_value = self.mock_IdentityClientv2
self.assertRaises(exceptions.AuthFailure, client.Client, token='mytoken', auth_url='http://whatever:35357/v2.0/')
@patch('freezer.apiclient.client.os_client')
def test_client_endpoint_not_found(self, mock_os_client):
mock_enpointlist_bad = [self.create_mock_endpoint('idqwerty'),
self.create_mock_endpoint('idfiasco'),
self.create_mock_endpoint('blabla')]
self.mock_IdentityClientv2.endpoints.list.return_value = mock_enpointlist_bad
mock_os_client.IdentityClientv2.return_value = self.mock_IdentityClientv2
self.assertRaises(exceptions.AuthFailure, client.Client, token='mytoken', auth_url='http://whatever:35357/v2.0/')
@patch('freezer.apiclient.client.os_client')
def test_client_api_exists(self, mock_os_client):
mock_os_client.IdentityClientv2.return_value = self.mock_IdentityClientv2
c = client.Client(token='mytoken',
auth_url='http://whatever:35357/v2.0/')
self.assertTrue(c.api_exists())
@patch('freezer.apiclient.client.os_client')
def test_client_auth_token(self, mock_os_client):
self.mock_IdentityClientv2.auth_token = 'stotoken'
mock_os_client.IdentityClientv2.return_value = self.mock_IdentityClientv2
c = client.Client(token='mytoken',
auth_url='http://whatever:35357/v2.0/')
self.assertEqual(c.auth_token, 'stotoken')

View File

@ -11,6 +11,8 @@ deps =
pytest-cov
pytest-xdist
pymysql
python-openstackclient
mock
install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}