ILO: Add unit tests for ris.RISOperations._rest_op

This commit adds the unit test cases for the main
method ris.RISOperations._rest_op which handles the
REST interactions with the iLO.

Change-Id: I1dea76aaed8a7f92c2ccc6b91ea7cb8fc03a9969
This commit is contained in:
Ramakrishnan G 2015-03-19 07:45:38 +00:00
parent 34218d260a
commit ac01ccb3f5
4 changed files with 465 additions and 12 deletions

View File

@ -45,6 +45,8 @@ class RISOperations(operations.IloOperations):
"""Generic REST Operation handler."""
url = urlparse.urlparse('https://' + self.host + suburi)
# Used for logging on redirection error.
start_url = url.geturl()
if request_headers is None:
request_headers = dict()
@ -54,7 +56,7 @@ class RISOperations(operations.IloOperations):
hr = "BASIC " + base64.b64encode(self.login + ":" + self.password)
request_headers['Authorization'] = hr
redir_count = 4
redir_count = 5
while redir_count:
conn = None
if url.scheme == 'https':
@ -86,6 +88,11 @@ class RISOperations(operations.IloOperations):
redir_count -= 1
else:
break
else:
# Redirected for 5th time. Throw error
msg = ("URL Redirected 5 times continuously. "
"URL incorrect: %s" % start_url)
raise exception.IloConnectionError(msg)
response = dict()
try:

View File

@ -19,3 +19,264 @@ MODULE = "RIS"
HTTP_BOOT_URL = {
"UefiShellStartupUrl": "http://10.10.1.30:8081/startup.nsh"
}
RESPONSE_BODY_FOR_REST_OP = """
{
"AssetTag": "",
"AvailableActions": [
{
"Action": "Reset",
"Capabilities": [
{
"AllowableValues": [
"On",
"ForceOff",
"ForceRestart",
"Nmi",
"PushPowerButton"
],
"PropertyName": "ResetType"
}
]
}
],
"Bios": {
"Current": {
"VersionString": "I36 v1.40 (01/28/2015)"
}
},
"Boot": {
"BootSourceOverrideEnabled": "Disabled",
"BootSourceOverrideSupported": [
"None",
"Cd",
"Hdd",
"Usb",
"Utilities",
"Diags",
"BiosSetup",
"Pxe",
"UefiShell",
"UefiTarget"
],
"BootSourceOverrideTarget": "None",
"UefiTargetBootSourceOverride": "None",
"UefiTargetBootSourceOverrideSupported": [
"HD.Emb.1.2",
"Generic.USB.1.1",
"NIC.FlexLOM.1.1.IPv4",
"NIC.FlexLOM.1.1.IPv6",
"CD.Virtual.2.1"
]
},
"Description": "Computer System View",
"HostCorrelation": {
"HostMACAddress": [
"6c:c2:17:39:fe:80",
"6c:c2:17:39:fe:88"
],
"HostName": "",
"IPAddress": [
"",
""
]
},
"IndicatorLED": "Off",
"Manufacturer": "HP",
"Memory": {
"TotalSystemMemoryGB": 16
},
"Model": "ProLiant BL460c Gen9",
"Name": "Computer System",
"Oem": {
"Hp": {
"AvailableActions": [
{
"Action": "PowerButton",
"Capabilities": [
{
"AllowableValues": [
"Press",
"PressAndHold"
],
"PropertyName": "PushType"
},
{
"AllowableValues": [
"/Oem/Hp"
],
"PropertyName": "Target"
}
]
},
{
"Action": "SystemReset",
"Capabilities": [
{
"AllowableValues": [
"ColdBoot"
],
"PropertyName": "ResetType"
},
{
"AllowableValues": [
"/Oem/Hp"
],
"PropertyName": "Target"
}
]
}
],
"Battery": [],
"Bios": {
"Backup": {
"Date": "v1.40 (01/28/2015)",
"Family": "I36",
"VersionString": "I36 v1.40 (01/28/2015)"
},
"Current": {
"Date": "01/28/2015",
"Family": "I36",
"VersionString": "I36 v1.40 (01/28/2015)"
},
"UefiClass": 2
},
"DeviceDiscoveryComplete": {
"AMSDeviceDiscovery": "NoAMS",
"SmartArrayDiscovery": "Initial",
"vAuxDeviceDiscovery": "DataIncomplete",
"vMainDeviceDiscovery": "ServerOff"
},
"PostState": "PowerOff",
"PowerAllocationLimit": 500,
"PowerAutoOn": "PowerOn",
"PowerOnDelay": "Minimum",
"PowerRegulatorMode": "Dynamic",
"PowerRegulatorModesSupported": [
"OSControl",
"Dynamic",
"Max",
"Min"
],
"ServerSignature": 0,
"Type": "HpComputerSystemExt.0.10.1",
"VirtualProfile": "Inactive",
"VirtualUUID": null,
"links": {
"BIOS": {
"href": "/rest/v1/systems/1/bios"
},
"MEMORY": {
"href": "/rest/v1/Systems/1/Memory"
},
"PCIDevices": {
"href": "/rest/v1/Systems/1/PCIDevices"
},
"PCISlots": {
"href": "/rest/v1/Systems/1/PCISlots"
},
"SecureBoot": {
"href": "/rest/v1/Systems/1/SecureBoot"
}
}
}
},
"Power": "Off",
"Processors": {
"Count": 1,
"ProcessorFamily": "Intel(R) Xeon(R) CPU E5-2609 v3 @ 1.90GHz",
"Status": {
"HealthRollUp": "OK"
}
},
"SKU": "727021-B21",
"SerialNumber": "SGH449WNL3",
"Status": {
"Health": "OK",
"State": "Disabled"
},
"SystemType": "Physical",
"Type": "ComputerSystem.0.9.6",
"UUID": "30373237-3132-4753-4834-3439574E4C33",
"links": {
"Chassis": [
{
"href": "/rest/v1/Chassis/1"
}
],
"Logs": {
"href": "/rest/v1/Systems/1/Logs"
},
"ManagedBy": [
{
"href": "/rest/v1/Managers/1"
}
],
"self": {
"href": "/rest/v1/Systems/1"
}
}
}
"""
BASE64_GZIPPED_RESPONSE = """
H4sIAHN9ClUC/+1YW2/iOBR+51dEeZqR2pAQCoWnhUALGm5qSndXoz6YYMAaEyPHoWVH/e9r50JJ7ARajVbzsH0p8rnZ5xx/53MqPysa/9M7QQDZI1jrbU3Xr5K1PUAYLDDseAwRP+Cy75FE/P08/op1IxVh/QC5p8TFUeyAHVggjBiCWTdqd+9uMSYvYgtPAIcFpkflqZ8Lm5HeEerB6Wp1VocfgAHKyvQmW1QmnoXBZkZeIO2GjPGsKDWf1Q70GSU7SNlhArbwmM/Hww7Kbt4yK8+V7HoSQO8iIhL3nmHdCSmFPsssRoInSANeRpdR5EetMLQb2t4y6qb2xbSqtdtqzbRuvuq5SG9pJEKyTqMVl4Qi83tIKVrCvi/KuRTOeyiIf1+VGbjhbkcoi0yyxdcnxIdSpy3zK4OltDQPFtISS9szJ+ghsJYWRU5dyMJdXjB7lXY0hyvkbiDGKsEjoGt+XSqKrlDkItFuS0c/8SVbfVS/JOODntHfLgzLqOUPcw99SJFnzN0uF1t58WToGHcYvo6mYyE2hrN9/QKdhlTenvGEKAsBNmo8SiXb/Gkj9mDgUbRLIckh213IINXcQ8DgVntC8CUFuQEJmEP4fcAgUT9pXyEcd5zOcklhIKOP3vDaXq1tNdt2q72C7Vszv928wq260iJOet9PqzScFYbORypKxdBfIg8wQkf9nnD/joD6GPjhCngspJAK0WB2lMAtoYdsLh4JAzhOYCy+73IFq5GJNiZLiIUvjmIjBHymdUf1hulpvD1aqff0pLmypOIp/5mtwk5GqtLZdG6oHGfVKUgXwPHZyVUe7FOT7GQWiNpfXajY8ZcDgpd6qfpzuTdp/IhZpp4+xxlw9Z/mpMr7o8pb4peeMg/D5ZNWnrgluTjbhHH3q2jT79GEDu+paLL/0oyX0Jr/G+v5HNXL0xHAOI4KwP7+rGAqEnwmRt6PcKeUxUMUsOgICv5X0KZ3YIvwIeGNRUof5pgl7VDIZKVDvHv+fTYvOJiDQTTda5USbc5n9siDnC97hHO0gxicGEYHU9S1M3Zz+jEB5OuKY+nulj92OpSCQ0Z/6HO0AVhlse+Er4oIPNVg6Hvp3lSGY4B8haULKf8pmElpFmacJbksKWg0uuXnXLwu7r8X8bkR2iLRHjemqVQMGZm+UwHpBZku9zg9jLY6Rj7ahlul2gNch1hQLcGCoowcfN5U3nnloJhyx5TIdYjPKFGWQx0lYXivymWUe5PmQSMuiIvWPhDskG8qn70IuQVn3KUsLh5j/VdmmIZlyi+AhLZzgFwhDOMW4+QT7WGB5nw+FIzVDzHOKWDk/ygCteHULUaDDYUrEbnK6RKr7q1qEG06qFrVhcDJi67tuD+ePvz9gSDuMUjCqy8KM3OG8VUJPhXqxPzScC4m7NPBYuOLQrnQ400lfSy4NNiJ+Zkx+VbwnSK6gLnHEO9LnquA0Py3EhJG88U6eZYddU9mhs8g/vLwVfsLEl/8d2ZzrX9zXWuYLW1va39oltEy7wf/nD7vBJiFcsb1AQSYbR4IxvNdtM1vRV9c3G9zodCsNc2add2tpbdO3GCO3pNwu4hP6t4P6vXWn5ORfdSQgyeBk5C5jcLMJ5vsLqLapJAw2xwC/uRMseoIFVmg4CjRMtI5qyd3XbdNu2nX7Oa1bdm163rzxr6u39r1a7tut26a9X7dsY8HkFFAdzZ8miKZ7ymAQmqwxLZq6QU9dPpgH5G1om4lTRsZVBS3QrzCwRouu4dP7Tq2phduO4B49ZFtS21Xeav8C14gvWoyFgAA
"""
HEADERS_FOR_REST_OP = [('content-length', '2729'),
('server', 'HP-iLO-Server/1.30'),
('etag', 'W/"B61EB245"'),
('allow', 'GET, HEAD, POST, PATCH'),
('cache-control', 'no-cache'),
('date', 'Thu, 19 Mar 2015 06:55:59 GMT'),
('x_hp-chrp-service-version', '1.0.3'),
('content-type', 'application/json')]
COLLECTIONS_SAMPLE = """
{
"Description": "iLO User Accounts",
"links": {
"Member": [
{
"href": "/rest/v1/AccountService/Accounts/1"
}
],
"self": {
"href": "/rest/v1/AccountService/Accounts"
}
},
"Items": [
{
"UserName": "Administrator",
"Description": "iLO User Account",
"links": {
"self": {
"href": "/rest/v1/AccountService/Accounts/1"
}
},
"Oem": {
"Hp": {
"Privileges": {
"RemoteConsolePriv": "true",
"iLOConfigPriv": "true",
"VirtualMediaPriv": "true",
"UserConfigPriv": "true",
"VirtualPowerAndResetPriv": "true",
"LoginPriv": "true"
},
"LoginName": "Administrator",
"Type": "HpiLOAccount.0.9.7"
}
},
"Password": null,
"Type": "ManagerAccount.0.9.7",
"Name": "User Account"
}
],
"MemberType": "ManagerAccount.0",
"Total": 1,
"Type": "Collection.0.9.5",
"Name": "Accounts"
}
"""

View File

@ -15,28 +15,31 @@
"""Test class for RIS Module."""
import unittest
import base64
import httplib
import json
import mock
import ris_sample_outputs as ris_constants
import testtools
from proliantutils import exception
from proliantutils.ilo import ris
from proliantutils.tests.ilo import ris_sample_outputs as ris_outputs
class IloRisTestCase(unittest.TestCase):
class IloRisTestCase(testtools.TestCase):
def setUp(self):
super(IloRisTestCase, self).setUp()
self.ilo = ris.RISOperations("x.x.x.x", "Administrator", "admin", None)
self.client = ris.RISOperations("1.2.3.4", "admin", "Admin")
@mock.patch.object(ris.RISOperations, '_get_bios_setting')
@mock.patch.object(ris.RISOperations, '_validate_uefi_boot_mode')
def test_get_http_boot_url_uefi(self, _validate_uefi_boot_mode_mock,
get_bios_settings_mock):
get_bios_settings_mock.return_value = ris_constants.HTTP_BOOT_URL
get_bios_settings_mock.return_value = ris_outputs.HTTP_BOOT_URL
_validate_uefi_boot_mode_mock.return_value = True
result = self.ilo.get_http_boot_url()
result = self.client.get_http_boot_url()
_validate_uefi_boot_mode_mock.assert_called_once_with()
self.assertEqual(
'http://10.10.1.30:8081/startup.nsh', result['UefiShellStartupUrl']
@ -47,7 +50,7 @@ class IloRisTestCase(unittest.TestCase):
def test_set_http_boot_url_uefi(self, _validate_uefi_boot_mode_mock,
change_bios_setting_mock):
_validate_uefi_boot_mode_mock.return_value = True
self.ilo.set_http_boot_url('http://10.10.1.30:8081/startup.nsh')
self.client.set_http_boot_url('http://10.10.1.30:8081/startup.nsh')
_validate_uefi_boot_mode_mock.assert_called_once_with()
change_bios_setting_mock.assert_called_once_with({
"UefiShellStartupUrl": "http://10.10.1.30:8081/startup.nsh"
@ -57,23 +60,204 @@ class IloRisTestCase(unittest.TestCase):
def test_get_http_boot_url_bios(self, _validate_uefi_boot_mode_mock):
_validate_uefi_boot_mode_mock.return_value = False
self.assertRaises(exception.IloCommandNotSupportedInBiosError,
self.ilo.get_http_boot_url)
self.client.get_http_boot_url)
@mock.patch.object(ris.RISOperations, '_validate_uefi_boot_mode')
def test_set_http_boot_url_bios(self, _validate_uefi_boot_mode_mock):
_validate_uefi_boot_mode_mock.return_value = False
self.assertRaises(exception.IloCommandNotSupportedInBiosError,
self.ilo.set_http_boot_url,
self.client.set_http_boot_url,
'http://10.10.1.30:8081/startup.nsh')
class TestRISOperationsPrivateMethods(testtools.TestCase):
def setUp(self):
super(TestRISOperationsPrivateMethods, self).setUp()
self.client = ris.RISOperations("1.2.3.4", "admin", "Admin")
@mock.patch.object(ris.RISOperations, 'get_current_boot_mode')
def test__validate_uefi_boot_mode_uefi(self, get_current_boot_mode_mock):
get_current_boot_mode_mock.return_value = 'UEFI'
result = self.ilo._validate_uefi_boot_mode()
result = self.client._validate_uefi_boot_mode()
self.assertTrue(result)
@mock.patch.object(ris.RISOperations, 'get_current_boot_mode')
def test__validate_uefi_boot_mode_bios(self, get_current_boot_mode_mock):
get_current_boot_mode_mock.return_value = 'LEGACY'
result = self.ilo._validate_uefi_boot_mode()
result = self.client._validate_uefi_boot_mode()
self.assertFalse(result)
@mock.patch.object(httplib, 'HTTPSConnection')
def test__rest_op_okay(self, https_con_mock):
connection_mock_obj = mock.MagicMock()
response_mock_obj = mock.MagicMock(status=200)
https_con_mock.return_value = connection_mock_obj
connection_mock_obj.getresponse.return_value = response_mock_obj
sample_response_body = ris_outputs.RESPONSE_BODY_FOR_REST_OP
response_mock_obj.read.return_value = sample_response_body
sample_headers = ris_outputs.HEADERS_FOR_REST_OP
response_mock_obj.getheaders.return_value = sample_headers
exp_headers = dict((x.lower(), y) for x, y in sample_headers)
status, headers, response = self.client._rest_op(
'GET', '/v1/foo', None, None)
self.assertEqual(200, status)
self.assertEqual(exp_headers, headers)
self.assertEqual(json.loads(sample_response_body), response)
https_con_mock.assert_called_once_with(host='1.2.3.4', strict=True)
connection_mock_obj.request.assert_called_once_with(
'GET', '/v1/foo',
# base64 encoded username + password for admin/Admin
headers={'Authorization': 'BASIC YWRtaW46QWRtaW4='},
body="null")
@mock.patch.object(httplib, 'HTTPSConnection')
def test__rest_op_request_error(self, https_con_mock):
connection_mock_obj = mock.MagicMock()
https_con_mock.return_value = connection_mock_obj
connection_mock_obj.request.side_effect = RuntimeError("boom")
exc = self.assertRaises(exception.IloConnectionError,
self.client._rest_op,
'GET', '/v1/foo', {}, None)
https_con_mock.assert_called_once_with(host='1.2.3.4', strict=True)
self.assertIn("boom", str(exc))
@mock.patch.object(httplib, 'HTTPSConnection')
def test__rest_op_get_response_error(self, https_con_mock):
connection_mock_obj = mock.MagicMock()
https_con_mock.return_value = connection_mock_obj
connection_mock_obj.getresponse.side_effect = RuntimeError("boom")
exc = self.assertRaises(exception.IloConnectionError,
self.client._rest_op,
'GET', '/v1/foo', None, None)
https_con_mock.assert_called_once_with(host='1.2.3.4', strict=True)
connection_mock_obj.request.assert_called_once_with(
'GET', '/v1/foo',
headers={'Authorization': 'BASIC YWRtaW46QWRtaW4='},
body="null")
self.assertIn("boom", str(exc))
@mock.patch.object(httplib, 'HTTPSConnection')
def test__rest_op_response_read_error(self, https_con_mock):
connection_mock_obj = mock.MagicMock()
response_mock_obj = mock.MagicMock(status=200)
https_con_mock.return_value = connection_mock_obj
connection_mock_obj.getresponse.return_value = response_mock_obj
response_mock_obj.read.side_effect = RuntimeError("boom")
exc = self.assertRaises(exception.IloConnectionError,
self.client._rest_op,
'GET', '/v1/foo', None, None)
self.assertIn("boom", str(exc))
@mock.patch.object(httplib, 'HTTPSConnection')
def test__rest_op_continous_redirection(self, https_con_mock):
connection_mock_obj = mock.MagicMock()
response_mock_obj = mock.MagicMock(status=301)
https_con_mock.side_effect = [connection_mock_obj,
connection_mock_obj,
connection_mock_obj,
connection_mock_obj,
connection_mock_obj]
connection_mock_obj.getresponse.return_value = response_mock_obj
sample_response_body = ris_outputs.RESPONSE_BODY_FOR_REST_OP
response_mock_obj.read.return_value = sample_response_body
sample_headers = ris_outputs.HEADERS_FOR_REST_OP
sample_headers.append(('location', 'https://foo'))
response_mock_obj.getheaders.return_value = sample_headers
exc = self.assertRaises(exception.IloConnectionError,
self.client._rest_op,
'GET', '/v1/foo', {}, None)
self.assertEqual(5, https_con_mock.call_count)
self.assertEqual(5, connection_mock_obj.request.call_count)
self.assertIn('https://1.2.3.4/v1/foo', str(exc))
@mock.patch.object(httplib, 'HTTPConnection')
@mock.patch.object(httplib, 'HTTPSConnection')
def test__rest_op_one_redirection(self, https_con_mock,
http_con_mock):
connection_mock_obj = mock.MagicMock()
response_mock_obj1 = mock.MagicMock(status=301)
response_mock_obj2 = mock.MagicMock(status=200)
https_con_mock.return_value = connection_mock_obj
http_con_mock.return_value = connection_mock_obj
connection_mock_obj.getresponse.side_effect = [response_mock_obj1,
response_mock_obj2]
sample_response_body = ris_outputs.RESPONSE_BODY_FOR_REST_OP
response_mock_obj1.read.return_value = sample_response_body
response_mock_obj2.read.return_value = sample_response_body
sample_headers1 = ris_outputs.HEADERS_FOR_REST_OP
sample_headers2 = ris_outputs.HEADERS_FOR_REST_OP
sample_headers1.append(('location', 'http://5.6.7.8/v1/foo'))
response_mock_obj1.getheaders.return_value = sample_headers1
response_mock_obj2.getheaders.return_value = sample_headers2
status, headers, response = self.client._rest_op(
'GET', '/v1/foo', {}, None)
exp_headers = dict((x.lower(), y) for x, y in sample_headers1)
self.assertEqual(200, status)
self.assertEqual(exp_headers, headers)
self.assertEqual(json.loads(sample_response_body), response)
https_con_mock.assert_any_call(host='1.2.3.4', strict=True)
http_con_mock.assert_any_call(host='5.6.7.8', strict=True)
self.assertEqual(2, connection_mock_obj.request.call_count)
self.assertTrue(response_mock_obj1.read.called)
self.assertTrue(response_mock_obj2.read.called)
@mock.patch.object(httplib, 'HTTPSConnection')
def test__rest_op_response_decode_error(self, https_con_mock):
connection_mock_obj = mock.MagicMock()
response_mock_obj = mock.MagicMock(status=200)
https_con_mock.return_value = connection_mock_obj
connection_mock_obj.getresponse.return_value = response_mock_obj
sample_response_body = "{[wrong json"
response_mock_obj.read.return_value = sample_response_body
sample_headers = ris_outputs.HEADERS_FOR_REST_OP
response_mock_obj.getheaders.return_value = sample_headers
self.assertRaises(exception.IloError,
self.client._rest_op,
'GET', '/v1/foo', {}, None)
https_con_mock.assert_called_once_with(host='1.2.3.4', strict=True)
connection_mock_obj.request.assert_called_once_with(
'GET', '/v1/foo',
# base64 encoded username + password for admin/Admin
headers={'Authorization': 'BASIC YWRtaW46QWRtaW4='},
body="null")
@mock.patch.object(httplib, 'HTTPSConnection')
def test__rest_op_response_gzipped_response(self, https_con_mock):
connection_mock_obj = mock.MagicMock()
response_mock_obj = mock.MagicMock(status=200)
https_con_mock.return_value = connection_mock_obj
connection_mock_obj.getresponse.return_value = response_mock_obj
sample_response_body = ris_outputs.RESPONSE_BODY_FOR_REST_OP
gzipped_response_body = base64.b64decode(
ris_outputs.BASE64_GZIPPED_RESPONSE)
response_mock_obj.read.return_value = gzipped_response_body
sample_headers = ris_outputs.HEADERS_FOR_REST_OP
response_mock_obj.getheaders.return_value = sample_headers
exp_headers = dict((x.lower(), y) for x, y in sample_headers)
status, headers, response = self.client._rest_op(
'GET', '/v1/foo', {}, None)
self.assertEqual(200, status)
self.assertEqual(exp_headers, headers)
self.assertEqual(json.loads(sample_response_body), response)

View File

@ -1,3 +1,4 @@
mock
hacking>=0.9.2,<0.10
testrepository>=0.0.18
testtools>=0.9.36,!=1.2.0