Restore backward compatibility for init client

After merged the patch I08c8b753972c27b4e6bbe07a8aa51e0e72fbc56d,
blazarclient can't be used with blazar_url and auth_token since this
patch only allows sessions for the initiation of client.
For backward compatibility, blazarclient should be initiated with
blazar_url and auth_token as before.

This patch enables using blazarclient with a set of blazar_url and
auth_token or session by reviving BaseClientManager class with adding
a logic to chose an auth method based on given params.

Change-Id: I25a665145b0503cc04e49bc85c39e2f6dca36925
Closes-Bug: #1724757
This commit is contained in:
Hiroki Ito 2017-11-04 23:42:30 +09:00 committed by Masahito Muroi
parent 8d897c5220
commit 2afa290302
6 changed files with 341 additions and 31 deletions

135
blazarclient/base.py Normal file
View File

@ -0,0 +1,135 @@
# Copyright (c) 2013 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from keystoneauth1 import adapter
import requests
from blazarclient import exception
from blazarclient.i18n import _
class RequestManager(object):
"""Manager to create request from given Blazar URL and auth token."""
def __init__(self, blazar_url, auth_token, user_agent):
self.blazar_url = blazar_url
self.auth_token = auth_token
self.user_agent = user_agent
def get(self, url):
"""Sends get request to Blazar.
:param url: URL to the wanted Blazar resource.
:type url: str
"""
return self.request(url, 'GET')
def post(self, url, body):
"""Sends post request to Blazar.
:param url: URL to the wanted Blazar resource.
:type url: str
:param body: Values resource to be created from.
:type body: dict
"""
return self.request(url, 'POST', body=body)
def delete(self, url):
"""Sends delete request to Blazar.
:param url: URL to the wanted Blazar resource.
:type url: str
"""
return self.request(url, 'DELETE')
def put(self, url, body):
"""Sends update request to Blazar.
:param url: URL to the wanted Blazar resource.
:type url: str
:param body: Values resource to be updated from.
:type body: dict
"""
return self.request(url, 'PUT', body=body)
def request(self, url, method, **kwargs):
"""Base request method.
Adds specific headers and URL prefix to the request.
:param url: Resource URL.
:type url: str
:param method: Method to be called (GET, POST, PUT, DELETE).
:type method: str
:returns: Response and body.
:rtype: tuple
"""
kwargs.setdefault('headers', kwargs.get('headers', {}))
kwargs['headers']['User-Agent'] = self.user_agent
kwargs['headers']['Accept'] = 'application/json'
kwargs['headers']['x-auth-token'] = self.auth_token
if 'body' in kwargs:
kwargs['headers']['Content-Type'] = 'application/json'
kwargs['data'] = json.dumps(kwargs['body'])
del kwargs['body']
resp = requests.request(method, self.blazar_url + url, **kwargs)
try:
body = json.loads(resp.text)
except ValueError:
body = None
if resp.status_code >= 400:
if body is not None:
error_message = body.get('error_message', body)
else:
error_message = resp.text
body = _("ERROR: {0}").format(error_message)
raise exception.BlazarClientException(body, code=resp.status_code)
return resp, body
class BaseClientManager(object):
"""Base class for managing resources of Blazar."""
user_agent = 'python-blazarclient'
def __init__(self, blazar_url, auth_token, session, **kwargs):
self.blazar_url = blazar_url
self.auth_token = auth_token
self.session = session
if self.session:
self.request_manager = adapter.LegacyJsonAdapter(
session=self.session,
user_agent=self.user_agent,
**kwargs
)
elif self.blazar_url and self.auth_token:
self.request_manager = RequestManager(blazar_url=self.blazar_url,
auth_token=self.auth_token,
user_agent=self.user_agent)
else:
raise exception.InsufficientAuthInfomation

View File

@ -82,3 +82,11 @@ class DuplicatedLeaseParameters(BlazarClientException):
"""Occurs if lease parameters are duplicated.""" """Occurs if lease parameters are duplicated."""
message = _("The lease parameters are duplicated.") message = _("The lease parameters are duplicated.")
code = 400 code = 400
class InsufficientAuthInfomation(BlazarClientException):
"""Occurs if the auth info passed to blazar client is insufficient."""
message = _("The passed arguments are insufficient "
"for the authentication. The instance of "
"keystoneauth1.session.Session class is required.")
code = 400

View File

@ -0,0 +1,162 @@
# Copyright (c) 2014 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from keystoneauth1 import adapter
from blazarclient import base
from blazarclient import exception
from blazarclient import tests
class RequestManagerTestCase(tests.TestCase):
def setUp(self):
super(RequestManagerTestCase, self).setUp()
self.blazar_url = "www.fake.com/reservation"
self.auth_token = "aaa-bbb-ccc"
self.user_agent = "python-blazarclient"
self.manager = base.RequestManager(blazar_url=self.blazar_url,
auth_token=self.auth_token,
user_agent=self.user_agent)
@mock.patch('blazarclient.base.RequestManager.request',
return_value=(200, {"fake": "FAKE"}))
def test_get(self, m):
url = '/leases'
resp, body = self.manager.get(url)
self.assertEqual(resp, 200)
self.assertDictEqual(body, {"fake": "FAKE"})
m.assert_called_once_with(url, "GET")
@mock.patch('blazarclient.base.RequestManager.request',
return_value=(200, {"fake": "FAKE"}))
def test_post(self, m):
url = '/leases'
req_body = {
'start': '2020-07-24 20:00',
'end': '2020-08-09 22:30',
'before_end': '2020-08-09 21:30',
'events': [],
'name': 'lease-test',
'reservations': [
{
'min': '1',
'max': '2',
'hypervisor_properties':
'[">=", "$vcpus", "2"]',
'resource_properties':
'["==", "$extra_key", "extra_value"]',
'resource_type': 'physical:host',
'before_end': 'default'
}
]
}
resp, body = self.manager.post(url, req_body)
self.assertEqual(resp, 200)
self.assertDictEqual(body, {"fake": "FAKE"})
m.assert_called_once_with(url, "POST", body=req_body)
@mock.patch('blazarclient.base.RequestManager.request',
return_value=(200, {"fake": "FAKE"}))
def test_delete(self, m):
url = '/leases/aaa-bbb-ccc'
resp, body = self.manager.delete(url)
self.assertEqual(resp, 200)
self.assertDictEqual(body, {"fake": "FAKE"})
m.assert_called_once_with(url, "DELETE")
@mock.patch('blazarclient.base.RequestManager.request',
return_value=(200, {"fake": "FAKE"}))
def test_put(self, m):
url = '/leases/aaa-bbb-ccc'
req_body = {
'name': 'lease-test',
}
resp, body = self.manager.put(url, req_body)
self.assertEqual(resp, 200)
self.assertDictEqual(body, {"fake": "FAKE"})
m.assert_called_once_with(url, "PUT", body=req_body)
@mock.patch('requests.request')
def test_request_ok_with_body(self, m):
m.return_value.status_code = 200
m.return_value.text = '{"resp_key": "resp_value"}'
url = '/leases'
kwargs = {"body": {"req_key": "req_value"}}
self.assertEqual(self.manager.request(url, "POST", **kwargs),
(m(), {"resp_key": "resp_value"}))
@mock.patch('requests.request')
def test_request_ok_without_body(self, m):
m.return_value.status_code = 200
m.return_value.text = "resp"
url = '/leases'
kwargs = {"body": {"req_key": "req_value"}}
self.assertEqual(self.manager.request(url, "POST", **kwargs),
(m(), None))
@mock.patch('requests.request')
def test_request_fail_with_body(self, m):
m.return_value.status_code = 400
m.return_value.text = '{"resp_key": "resp_value"}'
url = '/leases'
kwargs = {"body": {"req_key": "req_value"}}
self.assertRaises(exception.BlazarClientException,
self.manager.request, url, "POST", **kwargs)
@mock.patch('requests.request')
def test_request_fail_without_body(self, m):
m.return_value.status_code = 400
m.return_value.text = "resp"
url = '/leases'
kwargs = {"body": {"req_key": "req_value"}}
self.assertRaises(exception.BlazarClientException,
self.manager.request, url, "POST", **kwargs)
class BaseClientManagerTestCase(tests.TestCase):
def setUp(self):
super(BaseClientManagerTestCase, self).setUp()
self.blazar_url = "www.fake.com/reservation"
self.auth_token = "aaa-bbb-ccc"
self.session = mock.MagicMock()
self.user_agent = "python-blazarclient"
def test_init_with_session(self):
manager = base.BaseClientManager(blazar_url=None,
auth_token=None,
session=self.session)
self.assertIsInstance(manager.request_manager,
adapter.LegacyJsonAdapter)
def test_init_with_url_and_token(self):
manager = base.BaseClientManager(blazar_url=self.blazar_url,
auth_token=self.auth_token,
session=None)
self.assertIsInstance(manager.request_manager,
base.RequestManager)
def test_init_with_insufficient_info(self):
self.assertRaises(exception.InsufficientAuthInfomation,
base.BaseClientManager,
blazar_url=None,
auth_token=self.auth_token,
session=None)

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import logging
from blazarclient.v1 import hosts from blazarclient.v1 import hosts
from blazarclient.v1 import leases from blazarclient.v1 import leases
@ -31,15 +32,26 @@ class Client(object):
... ...
""" """
def __init__(self, session, *args, **kwargs): version = '1'
self.session = session
self.version = '1'
self.lease = leases.LeaseClientManager(session=self.session, def __init__(self, blazar_url=None, auth_token=None, session=None,
**kwargs):
self.blazar_url = blazar_url
self.auth_token = auth_token
self.session = session
if not self.session:
logging.warning('Use a keystoneauth session object for the '
'authentication. The authentication with '
'blazar_url and auth_token is deprecated.')
self.lease = leases.LeaseClientManager(blazar_url=self.blazar_url,
auth_token=self.auth_token,
session=self.session,
version=self.version, version=self.version,
*args,
**kwargs) **kwargs)
self.host = hosts.ComputeHostClientManager(session=self.session, self.host = hosts.ComputeHostClientManager(blazar_url=self.blazar_url,
auth_token=self.auth_token,
session=self.session,
version=self.version, version=self.version,
*args,
**kwargs) **kwargs)

View File

@ -13,45 +13,41 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from keystoneauth1 import adapter from blazarclient import base
from blazarclient.i18n import _ from blazarclient.i18n import _
class ComputeHostClientManager(adapter.LegacyJsonAdapter): class ComputeHostClientManager(base.BaseClientManager):
"""Manager for the ComputeHost connected requests.""" """Manager for the ComputeHost connected requests."""
client_name = 'python-blazarclient'
def create(self, name, **kwargs): def create(self, name, **kwargs):
"""Creates host from values passed.""" """Creates host from values passed."""
values = {'name': name} values = {'name': name}
values.update(**kwargs) values.update(**kwargs)
resp, body = self.post('/os-hosts', body=values) resp, body = self.request_manager.post('/os-hosts', body=values)
return body['host'] return body['host']
def get(self, host_id): def get(self, host_id):
"""Describes host specifications such as name and details.""" """Describes host specifications such as name and details."""
resp, body = super(ComputeHostClientManager, resp, body = self.request_manager.get('/os-hosts/%s' % host_id)
self).get('/os-hosts/%s' % host_id)
return body['host'] return body['host']
def update(self, host_id, values): def update(self, host_id, values):
"""Update attributes of the host.""" """Update attributes of the host."""
if not values: if not values:
return _('No values to update passed.') return _('No values to update passed.')
resp, body = self.put('/os-hosts/%s' % host_id, body=values) resp, body = self.request_manager.put(
'/os-hosts/%s' % host_id, body=values
)
return body['host'] return body['host']
def delete(self, host_id): def delete(self, host_id):
"""Deletes host with specified ID.""" """Deletes host with specified ID."""
resp, body = super(ComputeHostClientManager, resp, body = self.request_manager.delete('/os-hosts/%s' % host_id)
self).delete('/os-hosts/%s' % host_id)
def list(self, sort_by=None): def list(self, sort_by=None):
"""List all hosts.""" """List all hosts."""
resp, body = super(ComputeHostClientManager, resp, body = self.request_manager.get('/os-hosts')
self).get('/os-hosts')
hosts = body['hosts'] hosts = body['hosts']
if sort_by: if sort_by:
hosts = sorted(hosts, key=lambda l: l[sort_by]) hosts = sorted(hosts, key=lambda l: l[sort_by])

View File

@ -13,33 +13,30 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from keystoneauth1 import adapter
from oslo_utils import timeutils from oslo_utils import timeutils
from blazarclient import base
from blazarclient.i18n import _ from blazarclient.i18n import _
from blazarclient import utils from blazarclient import utils
class LeaseClientManager(adapter.LegacyJsonAdapter): class LeaseClientManager(base.BaseClientManager):
"""Manager for the lease connected requests.""" """Manager for the lease connected requests."""
client_name = 'python-blazarclient'
def create(self, name, start, end, reservations, events, before_end=None): def create(self, name, start, end, reservations, events, before_end=None):
"""Creates lease from values passed.""" """Creates lease from values passed."""
values = {'name': name, 'start_date': start, 'end_date': end, values = {'name': name, 'start_date': start, 'end_date': end,
'reservations': reservations, 'events': events, 'reservations': reservations, 'events': events,
'before_end_date': before_end} 'before_end_date': before_end}
resp, body = self.post('/leases', body=values) resp, body = self.request_manager.post('/leases', body=values)
return body['lease'] return body['lease']
def get(self, lease_id): def get(self, lease_id):
"""Describes lease specifications such as name, status and locked """Describes lease specifications such as name, status and locked
condition. condition.
""" """
resp, body = super(LeaseClientManager, resp, body = self.request_manager.get('/leases/%s' % lease_id)
self).get('/leases/%s' % lease_id)
return body['lease'] return body['lease']
def update(self, lease_id, name=None, prolong_for=None, reduce_by=None, def update(self, lease_id, name=None, prolong_for=None, reduce_by=None,
@ -81,17 +78,17 @@ class LeaseClientManager(adapter.LegacyJsonAdapter):
if not values: if not values:
return _('No values to update passed.') return _('No values to update passed.')
resp, body = self.put('/leases/%s' % lease_id, body=values) resp, body = self.request_manager.put('/leases/%s' % lease_id,
body=values)
return body['lease'] return body['lease']
def delete(self, lease_id): def delete(self, lease_id):
"""Deletes lease with specified ID.""" """Deletes lease with specified ID."""
resp, body = super(LeaseClientManager, resp, body = self.request_manager.delete('/leases/%s' % lease_id)
self).delete('/leases/%s' % lease_id)
def list(self, sort_by=None): def list(self, sort_by=None):
"""List all leases.""" """List all leases."""
resp, body = super(LeaseClientManager, self).get('/leases') resp, body = self.request_manager.get('/leases')
leases = body['leases'] leases = body['leases']
if sort_by: if sort_by:
leases = sorted(leases, key=lambda l: l[sort_by]) leases = sorted(leases, key=lambda l: l[sort_by])