manila/manila/tests/share/drivers/quobyte/test_jsonrpc.py

344 lines
13 KiB
Python

# Copyright (c) 2015 Quobyte, Inc.
# All Rights Reserved.
#
# 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
import ssl
import tempfile
import time
import mock
from oslo_serialization import jsonutils
import six
from six.moves import http_client
from manila import exception
from manila.share.drivers.quobyte import jsonrpc
from manila import test
class FakeResponse(object):
def __init__(self, status, body):
self.status = status
self.reason = "HTTP reason"
self._body = body
def read(self):
return self._body
class QuobyteBasicAuthCredentialsTestCase(test.TestCase):
def test_get_authorization_header(self):
creds = jsonrpc.BasicAuthCredentials('fakeuser', 'fakepwd')
self.assertEqual('BASIC ZmFrZXVzZXI6ZmFrZXB3ZA==',
creds.get_authorization_header())
class QuobyteHttpsConnectionWithCaVerificationTestCase(test.TestCase):
@mock.patch.object(socket, "create_connection",
return_value="fake_socket")
@mock.patch.object(ssl, "wrap_socket")
def test_https_with_ca_connect(self, mock_ssl, mock_cc):
key_file = tempfile.TemporaryFile()
cert_file = tempfile.gettempdir()
ca_file = tempfile.gettempdir()
mycon = (jsonrpc.
HTTPSConnectionWithCaVerification(host="localhost",
key_file=key_file,
cert_file=cert_file,
ca_file=ca_file,
port=1234,
timeout=999))
mycon.connect()
mock_cc.assert_called_once_with(("localhost", 1234), 999)
mock_ssl.assert_called_once_with("fake_socket",
keyfile=key_file,
certfile=cert_file,
ca_certs=ca_file,
cert_reqs=mock.ANY)
@mock.patch.object(http_client.HTTPConnection, "_tunnel")
@mock.patch.object(socket, "create_connection",
return_value="fake_socket")
@mock.patch.object(ssl, "wrap_socket")
def test_https_with_ca_connect_tunnel(self,
mock_ssl,
mock_cc,
mock_tunnel):
key_file = tempfile.TemporaryFile()
cert_file = tempfile.gettempdir()
ca_file = tempfile.gettempdir()
mycon = (jsonrpc.
HTTPSConnectionWithCaVerification(host="localhost",
key_file=key_file,
cert_file=cert_file,
ca_file=ca_file,
port=1234,
timeout=999))
mycon._tunnel_host = "fake_tunnel_host"
mycon.connect()
mock_tunnel.assert_called_once_with()
mock_cc.assert_called_once_with(("localhost", 1234), 999)
mock_ssl.assert_called_once_with("fake_socket",
keyfile=key_file,
certfile=cert_file,
ca_certs=ca_file,
cert_reqs=mock.ANY)
class QuobyteJsonRpcTestCase(test.TestCase):
def setUp(self):
super(QuobyteJsonRpcTestCase, self).setUp()
self.rpc = jsonrpc.JsonRpc(url="http://test",
user_credentials=("me", "team"))
self.mock_object(self.rpc, '_connection')
self.mock_object(time, 'sleep')
def test_request_generation_and_basic_auth(self):
self.mock_object(
self.rpc._connection,
'getresponse',
mock.Mock(return_value=FakeResponse(200, '{"result":"yes"}')))
self.rpc.call('method', {'param': 'value'})
self.rpc._connection.request.assert_called_once_with(
'POST', 'http://test/',
jsonutils.dumps({'jsonrpc': '2.0',
'method': 'method',
'params': {'retry': 'INFINITELY',
'param': 'value'},
'id': '1'}),
dict(Authorization=jsonrpc.BasicAuthCredentials("me", "team")
.get_authorization_header()))
@mock.patch.object(jsonrpc.HTTPSConnectionWithCaVerification,
'__init__',
return_value=None)
def test_jsonrpc_init_with_ca(self, mock_init):
foofile = tempfile.TemporaryFile()
self.rpc = jsonrpc.JsonRpc("https://foo.bar/",
('fakeuser', 'fakepwd'),
foofile)
mock_init.assert_called_once_with("foo.bar",
ca_file=foofile.name)
@mock.patch.object(jsonrpc.LOG, "warning")
def test_jsonrpc_init_without_ca(self, mock_warning):
self.rpc = jsonrpc.JsonRpc("https://foo.bar/",
('fakeuser', 'fakepwd'),
None)
mock_warning.assert_called_once_with(
"Will not verify the server certificate of the API service"
" because the CA certificate is not available.")
@mock.patch.object(http_client.HTTPConnection,
'__init__',
return_value=None)
def test_jsonrpc_init_no_ssl(self, mock_init):
self.rpc = jsonrpc.JsonRpc("http://foo.bar/",
('fakeuser', 'fakepwd'))
mock_init.assert_called_once_with("foo.bar")
def test_successful_call(self):
self.mock_object(
self.rpc._connection, 'getresponse',
mock.Mock(return_value=FakeResponse(
200, '{"result":"Sweet gorilla of Manila"}')))
result = self.rpc.call('method', {'param': 'value'})
self.rpc._connection.connect.assert_called_once_with()
self.assertEqual("Sweet gorilla of Manila", result)
@mock.patch('six.moves.http_client.HTTPSConnection')
def test_jsonrpc_call_ssl_disable(self, mock_connection):
mock_connection.return_value = self.rpc._connection
self.mock_object(
self.rpc._connection,
'request',
mock.Mock(side_effect=ssl.SSLError))
self.mock_object(
self.rpc._connection,
'getresponse',
mock.Mock(return_value=FakeResponse(
403, '{"error":{"code":28,"message":"text"}}')))
self.mock_object(jsonrpc.LOG, 'warning')
self.assertRaises(exception.QBException,
self.rpc.call,
'method', {'param': 'value'})
self.assertTrue(self.rpc._disabled_cert_verification)
jsonrpc.LOG.warning.assert_called_once_with(
"Could not verify server certificate of "
"API service against CA.")
def test_jsonrpc_call_ssl_error(self):
"""This test succeeds if a specific exception is thrown.
Throwing a different exception or none at all
is a failure in this specific test case.
"""
self.mock_object(
self.rpc._connection,
'request',
mock.Mock(side_effect=ssl.SSLError))
self.rpc._disabled_cert_verification = True
try:
self.rpc.call('method', {'param': 'value'})
except exception.QBException as me:
self.rpc._connection.connect.assert_called_once_with()
(self.assertTrue(six.text_type(me).startswith
('Client SSL subsystem returned error:')))
except Exception as e:
self.fail('Unexpected exception thrown: %s' % e)
else:
self.fail('Expected exception not thrown')
def test_jsonrpc_call_bad_status_line(self):
self.mock_object(
self.rpc._connection,
'getresponse',
mock.Mock(side_effect=http_client.BadStatusLine("fake_line")))
self.assertRaises(exception.QBException,
self.rpc.call,
'method', {'param': 'value'})
def test_jsonrpc_call_http_exception(self):
self.mock_object(
self.rpc._connection,
'getresponse',
mock.Mock(side_effect=http_client.HTTPException))
self.mock_object(jsonrpc.LOG, 'warning')
self.assertRaises(exception.QBException,
self.rpc.call,
'method', {'param': 'value'})
self.rpc._connection.connect.assert_called_once_with()
jsonrpc.LOG.warning.assert_has_calls([])
def test_jsonrpc_call_http_exception_retry(self):
self.mock_object(
self.rpc._connection,
'getresponse',
mock.Mock(side_effect=http_client.HTTPException))
self.mock_object(jsonrpc.LOG, 'warning')
self.rpc._fail_fast = False
self.assertRaises(exception.QBException,
self.rpc.call,
'method', {'param': 'value'})
self.rpc._connection.connect.assert_called_once_with()
jsonrpc.LOG.warning.assert_called_with(
"Encountered error, retrying: %s", "")
def test_jsonrpc_call_no_connect(self):
orig_retries = jsonrpc.CONNECTION_RETRIES
jsonrpc.CONNECTION_RETRIES = 0
try:
self.rpc.call('method', {'param': 'value'})
except exception.QBException as me:
self.rpc._connection.connect.assert_called_once_with()
self.assertEqual("Unable to connect to backend after 0 retries",
six.text_type(me))
else:
self.fail('Expected exception not thrown')
finally:
jsonrpc.CONNECTION_RETRIES = orig_retries
def test_http_error_401(self):
self.mock_object(
self.rpc._connection,
'getresponse',
mock.Mock(return_value=FakeResponse(401, '')))
self.assertRaises(exception.QBException,
self.rpc.call, 'method', {'param': 'value'})
self.rpc._connection.connect.assert_called_once_with()
def test_http_error_other(self):
self.mock_object(
self.rpc._connection,
'getresponse',
mock.Mock(return_value=FakeResponse(300, '')))
self.assertRaises(exception.QBException,
self.rpc.call, 'method', {'param': 'value'})
self.rpc._connection.connect.assert_called_once_with()
self.assertTrue(self.rpc._connection.getresponse.called)
def test_application_error(self):
self.mock_object(
self.rpc._connection,
'getresponse',
mock.Mock(return_value=FakeResponse(
200, '{"error":{"code":28,"message":"text"}}')))
self.assertRaises(exception.QBRpcException,
self.rpc.call, 'method', {'param': 'value'})
self.rpc._connection.connect.assert_called_once_with()
self.assertTrue(self.rpc._connection.getresponse.called)
def test_broken_application_error(self):
self.mock_object(
self.rpc._connection,
'getresponse',
mock.Mock(return_value=FakeResponse(
200, '{"error":{"code":28,"message":"text"}}')))
self.assertRaises(exception.QBRpcException,
self.rpc.call, 'method', {'param': 'value'})
self.rpc._connection.connect.assert_called_once_with()
self.assertTrue(self.rpc._connection.getresponse.called)
def test_checked_for_application_error(self):
resultdict = {"result": "Sweet gorilla of Manila"}
self.assertEqual("Sweet gorilla of Manila",
(self.rpc.
_checked_for_application_error(result=resultdict))
)
def test_checked_for_application_error_no_entry(self):
resultdict = {"result": "Sweet gorilla of Manila",
"error": {"message": "No Gorilla",
"code": jsonrpc.ERROR_ENOENT}}
self.assertIsNone(
self.rpc._checked_for_application_error(result=resultdict))
def test_checked_for_application_error_exception(self):
self.assertRaises(exception.QBRpcException,
self.rpc._checked_for_application_error,
{"result": "Sweet gorilla of Manila",
"error": {"message": "No Gorilla",
"code": 666
}
}
)