Skip to content

Commit

Permalink
Release 0.2.24
Browse files Browse the repository at this point in the history
Enhancements:
* Support to verify Unity array certificate.
  • Loading branch information
Murray-LIANG committed Nov 11, 2016
2 parents 89cf6ad + f9a54bc commit 9f77d6a
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 15 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ storops-*/

# IDE related
.idea/
.swp

# tests
junit-result.xml
Expand All @@ -23,4 +24,4 @@ htmlcov/
.coverage
.coverage.*
logs/
*_names.json
*_names.json
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ StorOps: The Python Library for VNX & Unity
.. image:: https://img.shields.io/pypi/v/storops.svg
:target: https://pypi.python.org/pypi/storops

VERSION: 0.2.23
VERSION: 0.2.24

A minimalist Python library to manage VNX/Unity systems.
This document lies in the source code and go with the release.
Expand Down
19 changes: 12 additions & 7 deletions storops/connection/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from requests.exceptions import RequestException
from retryz import retry

from storops.connection.exceptions import from_response
from storops.connection import exceptions

log = logging.getLogger(__name__)

Expand All @@ -41,23 +41,27 @@ def _wait_callback(tried):

class HTTPClient(object):
def __init__(self, base_url, headers, insecure=False, auth=None,
timeout=None, retries=None):
timeout=None, retries=None, ca_cert_path=None):
self.base_url = base_url
if retries is None:
retries = 2
self.retries = retries
self.request_options = self._set_request_options(
insecure, auth, timeout)
insecure, auth, timeout, ca_cert_path)
self.headers = headers
self.session = requests.session()

def __del__(self):
self.session.close()

@staticmethod
def _set_request_options(insecure=None, auth=None, timeout=None):
def _set_request_options(insecure=None, auth=None, timeout=None,
ca_cert_path=None):
options = {'verify': True}

if ca_cert_path is not None:
options['verify'] = ca_cert_path

if insecure:
options['verify'] = False

Expand All @@ -74,8 +78,9 @@ def request(self, full_url, method, **kwargs):

options = copy.deepcopy(self.request_options)

content_type = headers.get('Content-Type', None)
if 'body' in kwargs:
if headers['Content-Type'] == 'application/json':
if content_type == 'application/json':
options['data'] = json.dumps(kwargs['body'])
else:
options['data'] = kwargs['body']
Expand All @@ -90,7 +95,7 @@ def request(self, full_url, method, **kwargs):
body = None
if resp.text:
try:
if headers['Content-Type'] == 'application/json':
if content_type == 'application/json':
body = json.loads(resp.text)
else:
body = resp.text
Expand All @@ -99,7 +104,7 @@ def request(self, full_url, method, **kwargs):
pass

if resp.status_code == 401:
raise from_response(resp, method, full_url)
raise exceptions.from_response(resp, method, full_url)

return resp, body

Expand Down
12 changes: 10 additions & 2 deletions storops/connection/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,21 @@ class UnityRESTConnector(object):
'User-agent': 'EMC-OpenStack',
}

def __init__(self, host, port=443, user='admin', password=''):
def __init__(self, host, port=443, user='admin', password='',
verify=True):
base_url = 'https://{host}:{port}'.format(host=host, port=port)

insecure = False
ca_cert_path = None
if isinstance(verify, bool):
insecure = not verify
else:
ca_cert_path = verify
self.http_client = client.HTTPClient(base_url=base_url,
headers=self.HEADERS,
auth=(user, password),
insecure=True)
insecure=insecure,
ca_cert_path=ca_cert_path)

def get(self, url, **kwargs):
return self.http_client.get(url, **kwargs)
Expand Down
5 changes: 3 additions & 2 deletions storops/unity/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@


class UnityClient(object):
def __init__(self, ip, username, password, port=443):
def __init__(self, ip, username, password, port=443, verify=True):
self._rest = UnityRESTConnector(ip, port=port, user=username,
password=password)
password=password,
verify=verify)

def get_all(self, type_name, base_fields=None, the_filter=None,
nested_fields=None):
Expand Down
5 changes: 3 additions & 2 deletions storops/unity/resource/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@

class UnitySystem(UnitySingletonResource):
def __init__(self, host=None, username=None, password=None,
port=443, cli=None):
port=443, cli=None, verify=True):
super(UnitySystem, self).__init__(cli=cli)
if cli is None:
self._cli = UnityClient(host, username, password, port)
self._cli = UnityClient(host, username, password, port,
verify=verify)
else:
self._cli = cli

Expand Down
Empty file added test/connection/__init__.py
Empty file.
186 changes: 186 additions & 0 deletions test/connection/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# coding=utf-8
# Copyright (c) 2015 EMC Corporation.
# 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.
from __future__ import unicode_literals

import unittest

from hamcrest import assert_that, calling, equal_to, raises
import mock
from requests import exceptions

from storops.connection import client
from storops.connection import exceptions as storops_ex


class MockResponse(object):
def __init__(self, text, status_code):
self.text = text
self.status_code = status_code


def _request_side_effect(method, url, **kwargs):
response_map = {'right_url': MockResponse('OK', 200),
'right_url_json': MockResponse('{"id": "id_123"}', 200),
'bad_url': MockResponse('Failed', 404),
'bad_url_raise': MockResponse('Failed', 401)}
return response_map[url]


class ClientModuleTest(unittest.TestCase):
def test_on_error_callback_true(self):
result = client._on_error_callback(exceptions.RequestException())
assert_that(True, equal_to(result))

def test_on_error_callback_false(self):
result = client._on_error_callback(ValueError())
assert_that(False, equal_to(result))

def test_wait_callback(self):
assert_that(8, equal_to(client._wait_callback(4)))


class HTTPClientTest(unittest.TestCase):

def setUp(self):
self.client = client.HTTPClient('https://10.10.10.10', {})

def test_set_request_options_insecure_true(self):
options = client.HTTPClient._set_request_options(insecure=False,
auth=None,
timeout=None,
ca_cert_path=None)
assert_that(options, equal_to({'verify': True,
'auth': None}))

def test_set_request_options_insecure_true_path(self):
options = client.HTTPClient._set_request_options(
insecure=False, auth=None, timeout=None, ca_cert_path='/tmp.crt')
assert_that(options, equal_to({'verify': '/tmp.crt',
'auth': None}))

def test_set_request_options_insecure_false(self):
options = client.HTTPClient._set_request_options(
insecure=True, auth=None, timeout=None, ca_cert_path='/tmp.crt')
assert_that(options, equal_to({'verify': False,
'auth': None}))

def test_request_content_json(self):
self.client.session.request = mock.MagicMock(
side_effect=_request_side_effect)
self.client.headers['Content-Type'] = 'application/json'

resp, body = self.client.request('right_url_json', 'GET',
body={"k_abc": "v_abc"})

self.client.session.request.assert_called_with(
'GET', 'right_url_json', auth=None, verify=True,
headers={'Content-Type': 'application/json'},
data='{"k_abc": "v_abc"}')
assert_that(resp.status_code, equal_to(200))
assert_that(body, equal_to({'id': 'id_123'}))

def test_request_content_plain(self):
self.client.session.request = mock.MagicMock(
side_effect=_request_side_effect)

resp, body = self.client.request('right_url', 'GET',
body='{"k_abc": "v_abc"}')

self.client.session.request.assert_called_with(
'GET', 'right_url', auth=None, verify=True, headers={},
data='{"k_abc": "v_abc"}')
assert_that(resp.status_code, equal_to(200))
assert_that(body, equal_to('OK'))

def test_request_content_404(self):
self.client.session.request = mock.MagicMock(
side_effect=_request_side_effect)

resp, body = self.client.request('bad_url', 'GET',
body='{"k_abc": "v_abc"}')

self.client.session.request.assert_called_with(
'GET', 'bad_url', auth=None, verify=True, headers={},
data='{"k_abc": "v_abc"}')
assert_that(resp.status_code, equal_to(404))
assert_that(body, equal_to('Failed'))

@mock.patch('storops.connection.exceptions.from_response')
def test_request_content_raise(self, mocked_from_response):
self.client.session.request = mock.MagicMock(
side_effect=_request_side_effect)
mocked_from_response.return_value = storops_ex.HttpError()

def _tmp_func():
self.client.request('bad_url_raise', 'GET',
body='{"k_abc": "v_abc"}')
assert_that(calling(_tmp_func),
raises(storops_ex.HttpError))

self.client.session.request.assert_called_with(
'GET', 'bad_url_raise', auth=None, verify=True,
headers={},
data='{"k_abc": "v_abc"}')

@mock.patch(
'storops.connection.client.HTTPClient._cs_request_with_retries')
def test_cs_request(self, mocked_cs_request_with_retries):
self.client.base_url = 'https://10.10.10.10'
self.client._cs_request('/api/types/instance', 'GET')

mocked_cs_request_with_retries.assert_called_with(
'https://10.10.10.10/api/types/instance',
'GET')

def test_get_limit(self):
self.client.retries = 99
assert_that(99, equal_to(self.client._get_limit()))

@mock.patch(
'storops.connection.client.HTTPClient._cs_request')
def test_get(self, mocked_cs_request):
self.client.get('/api/types/instance')

mocked_cs_request.assert_called_with(
'/api/types/instance',
'GET')

@mock.patch(
'storops.connection.client.HTTPClient._cs_request')
def test_post(self, mocked_cs_request):
self.client.post('/api/types/instance')

mocked_cs_request.assert_called_with(
'/api/types/instance',
'POST')

@mock.patch(
'storops.connection.client.HTTPClient._cs_request')
def test_put(self, mocked_cs_request):
self.client.put('/api/types/instance')

mocked_cs_request.assert_called_with(
'/api/types/instance',
'PUT')

@mock.patch(
'storops.connection.client.HTTPClient._cs_request')
def test_delete(self, mocked_cs_request):
self.client.delete('/api/types/instance')

mocked_cs_request.assert_called_with(
'/api/types/instance',
'DELETE')
65 changes: 65 additions & 0 deletions test/connection/test_connector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# coding=utf-8
# Copyright (c) 2015 EMC Corporation.
# 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.

from __future__ import unicode_literals

import unittest

import mock

from storops.connection import connector


class UnityRESTConnectorTest(unittest.TestCase):

@mock.patch('storops.connection.client.HTTPClient')
def test_new_connector_verify_false(self, mocked_httpclient):

connector.UnityRESTConnector('10.10.10.10',
verify=False)

mocked_httpclient.assert_called_with(
base_url='https://10.10.10.10:443',
headers=connector.UnityRESTConnector.HEADERS,
auth=('admin', ''),
insecure=True,
ca_cert_path=None)

@mock.patch('storops.connection.client.HTTPClient')
def test_new_connector_verify_true(self, mocked_httpclient):

connector.UnityRESTConnector('10.10.10.10',
verify=True)

mocked_httpclient.assert_called_with(
base_url='https://10.10.10.10:443',
headers=connector.UnityRESTConnector.HEADERS,
auth=('admin', ''),
insecure=False,
ca_cert_path=None)

@mock.patch('storops.connection.client.HTTPClient')
def test_new_connector_verify_path(self, mocked_httpclient):

connector.UnityRESTConnector('10.10.10.10',
verify='/tmp/ca_cert.crt')

mocked_httpclient.assert_called_with(
base_url='https://10.10.10.10:443',
headers=connector.UnityRESTConnector.HEADERS,
auth=('admin', ''),
insecure=False,
ca_cert_path='/tmp/ca_cert.crt')

0 comments on commit 9f77d6a

Please sign in to comment.