Adding functionality required by ilo-inspection

Implements: blueprint ironic-node-properties-discovery
Implements: blueprint ilo-properties-capabilities-discovery

Change-Id: I2d6b9fed80ebdfeb97529a0b8fd18eca7b21ac49
This commit is contained in:
Nisha Agarwal 2015-03-17 23:45:45 -07:00
parent 8146a5c40c
commit cffe85f287
7 changed files with 5586 additions and 3 deletions

View File

@ -31,7 +31,8 @@ SUPPORTED_RIS_METHODS = [
'reset_secure_boot_keys',
'set_http_boot_url',
'set_pending_boot_mode',
'set_secure_boot_mode'
'set_secure_boot_mode',
'get_server_capabilities'
]
@ -288,3 +289,31 @@ class IloClient(operations.IloOperations):
:raises: IloError, on an error from iLO.
"""
return self._call_method('get_host_power_readings')
def get_essential_properties(self):
"""Get the essential scheduling properties
:returns: a dictionary containing memory size, disk size,
number of cpus, cpu arch, port numbers and
mac addresses.
:raises: IloError, on an error from iLO.
:raises: IloCommandNotSupportedError, if the command is not supported
on the server.
"""
return self._call_method('get_essential_properties')
def get_server_capabilities(self):
"""Get hardware properties which can be used for scheduling
:return: a dictionary of server capabilities.
:raises: IloError, on an error from iLO.
:raises: IloCommandNotSupportedError, if the command is not supported
on the server.
"""
if 'Gen9' in self.model:
capabilities = self.ris.get_server_capabilities()
gpu = self.ribcl._get_number_of_gpu_devices_connected()
capabilities.update(gpu)
return capabilities
else:
return self.ribcl.get_server_capabilities()

View File

@ -259,3 +259,25 @@ class IloOperations(object):
:raises: IloError, on an error from iLO.
"""
raise exception.IloCommandNotSupportedError(ERRMSG)
def get_essential_properties(self):
"""Get the essential scheduling properties
:returns: a dictionary containing memory size, disk size,
number of cpus, cpu arch, port numbers and
mac addresses.
:raises: IloError, on an error from iLO.
:raises: IloCommandNotSupportedError, if the command is not supported
on the server.
"""
raise exception.IloCommandNotSupportedError(ERRMSG)
def get_server_capabilities(self):
"""Get hardware properties which can be used for scheduling
:return: a dictionary of server capabilities.
:raises: IloError, on an error from iLO.
:raises: IloCommandNotSupportedError, if the command is not supported
on the server.
"""
raise exception.IloCommandNotSupportedError(ERRMSG)

View File

@ -21,6 +21,7 @@ import re
import urllib2
import xml.etree.ElementTree as etree
from oslo_utils import strutils
import six
from proliantutils import exception
@ -664,6 +665,261 @@ class RIBCLOperations(operations.IloOperations):
return virtual_list
def get_essential_properties(self):
"""Gets essential scheduling properties as required by ironic
:returns: a dictionary of server properties like memory size,
disk size, number of cpus, cpu arch, port numbers
and mac addresses.
:raises:IloError if iLO returns an error in command execution.
"""
data = self.get_host_health_data()
properties = {}
properties['memory_mb'] = self._parse_memory_embedded_health(data)
cpus, cpu_arch = self._parse_processor_embedded_health(data)
properties['cpus'] = cpus
properties['cpu_arch'] = cpu_arch
properties['local_gb'] = self._parse_storage_embedded_health(data)
macs = self._parse_nics_embedded_health(data)
return_value = {'properties': properties, 'macs': macs}
return return_value
def _get_server_boot_modes(self):
"""Gets boot modes supported by the server
:returns: a dictionary of supported boot modes or None.
:raises:IloError, if iLO returns an error in command execution.
"""
bootmode = self.get_supported_boot_mode()
if bootmode == 'LEGACY_ONLY':
BootMode = ['LEGACY']
elif bootmode == 'LEGACY_UEFI':
BootMode = ['LEGACY', 'UEFI']
elif bootmode == 'UEFI_ONLY':
BootMode = ['UEFI']
else:
BootMode = None
return {'BootMode': BootMode}
def get_server_capabilities(self):
"""Gets server properties which can be used for scheduling
:returns: a dictionary of hardware properties like firmware
versions, server model.
:raises: IloError, if iLO returns an error in command execution.
"""
# Commenting out the BootMode as we dont plan to add it for Kilo.
# BootMode = self._get_server_boot_modes()
capabilities = {}
data = self.get_host_health_data()
capabilities.update(self._get_ilo_firmware_version(data))
capabilities.update(self._get_rom_firmware_version(data))
capabilities.update({'server_model': self.get_product_name()})
capabilities.update(self._get_number_of_gpu_devices_connected(data))
return capabilities
def _parse_memory_embedded_health(self, data):
"""Parse the get_host_health_data() for essential properties
:param data: the output returned by get_host_health_data()
:returns: memory size in MB.
:raises IloError, if unable to get the memory details.
"""
memory_mb = 0
try:
memory = (data['GET_EMBEDDED_HEALTH_DATA']['MEMORY']
['MEMORY_DETAILS_SUMMARY'])
except KeyError as e:
msg = "Unable to get memory data. Error: Data missing %s"
raise exception.IloError((msg) % e)
# here the value can be either a dictionary or a list.
# Convert it tolist so that its uniform across servers.
if not isinstance(memory, list):
memory = [memory]
total_memory_size = 0
for item in memory:
for val in item.values():
memsize = val['TOTAL_MEMORY_SIZE']['VALUE']
if memsize != 'N/A':
memory_bytes = (
strutils.string_to_bytes(
memsize.replace(' ', ''), return_int=True))
memory_mb = memory_bytes / (1024 * 1024)
total_memory_size = total_memory_size + memory_mb
return total_memory_size
def _parse_processor_embedded_health(self, data):
"""Parse the get_host_health_data() for essential properties
:param data: the output returned by get_host_health_data()
:returns: processor details like cpu arch and number of cpus.
"""
try:
processor = (data['GET_EMBEDDED_HEALTH_DATA']
['PROCESSORS']['PROCESSOR'])
except KeyError as e:
msg = "Unable to get cpu data. Error: Data missing %s"
raise exception.IloError((msg) % e)
# here the value can be either a dictionary or a list.
# Convert it tolist so that its uniform across servers.
if not isinstance(processor, list):
processor = [processor]
cpus = len(processor)
cpu_arch = 'x86_64'
return cpus, cpu_arch
def _parse_storage_embedded_health(self, data):
"""Gets the storage data from get_embedded_health
Parse the get_host_health_data() for essential properties
:param data: the output returned by get_host_health_data()
:returns: disk size in GB.
"""
try:
s = data['GET_EMBEDDED_HEALTH_DATA']['STORAGE']
storage = s['CONTROLLER']['LOGICAL_DRIVE']
except KeyError:
# We dont raise exception because this dictionary
# is available only when RAID is configured.
# If we raise error here then we will always fail
# inspection where this module is consumed. Hence
# as a workaround just return 0.
local_gb = 0
return local_gb
local_gb = 0
minimum = local_gb
# here the value can be either a dictionary or a list.
# Convert it to a list so that its uniform across servers.
if not isinstance(storage, list):
storage = [storage]
for item in storage:
for key, val in item.items():
if key == 'CAPACITY':
capacity = val['VALUE']
local_bytes = (strutils.string_to_bytes(
capacity.replace(' ', ''), return_int=True))
local_gb = local_bytes / (1024 * 1024 * 1024)
if minimum >= local_gb or minimum == 0:
minimum = local_gb
return minimum
def _parse_nics_embedded_health(self, data):
"""Gets the NIC details from get_embedded_health data
Parse the get_host_health_data() for essential properties
:param data: the output returned by get_host_health_data()
:returns: a dictionary of port numbers and their corresponding
mac addresses.
:raises IloError, if unable to get NIC data.
"""
try:
nic_data = (data['GET_EMBEDDED_HEALTH_DATA']
['NIC_INFORMATION']['NIC'])
except KeyError as e:
msg = "Unable to get NIC details. Data missing %s"
raise exception.IloError((msg) % e)
# here the value can be either a dictionary or a list.
# Convert it tolist so that its uniform across servers.
if not isinstance(nic_data, list):
nic_data = [nic_data]
nic_dict = {}
for item in nic_data:
try:
port = item['NETWORK_PORT']['VALUE']
mac = item['MAC_ADDRESS']['VALUE']
location = item['LOCATION']['VALUE']
if location == 'Embedded':
nic_dict[port] = mac
except KeyError as e:
msg = "Unable to get NIC details. Data missing %s"
raise exception.IloError((msg) % e)
return nic_dict
def _get_firmware_embedded_health(self, data):
"""Parse the get_host_health_data() for server capabilities
:param data: the output returned by get_host_health_data()
:returns: a dictionary of firmware name and firmware version.
"""
try:
firmware = data['GET_EMBEDDED_HEALTH_DATA']['FIRMWARE_INFORMATION']
except KeyError:
pass
if not isinstance(firmware, list):
firmware = [firmware]
return dict((y['FIRMWARE_NAME']['VALUE'],
y['FIRMWARE_VERSION']['VALUE'])
for x in firmware for y in x.values())
def _get_rom_firmware_version(self, data):
"""Gets the rom firmware version for server capabilities
Parse the get_host_health_data() to retreive the firmware
details.
:param data: the output returned by get_host_health_data()
:returns: a dictionary of rom firmware version.
"""
firmware_details = self._get_firmware_embedded_health(data)
rom_firmware_version = firmware_details['HP ProLiant System ROM']
return {'rom_firmware_version': rom_firmware_version}
def _get_ilo_firmware_version(self, data):
"""Gets the ilo firmware version for server capabilities
Parse the get_host_health_data() to retreive the firmware
details.
:param data: the output returned by get_host_health_data()
:returns: a dictionary of iLO firmware version.
"""
firmware_details = self._get_firmware_embedded_health(data)
return {'ilo_firmware_version': firmware_details['iLO']}
def _get_number_of_gpu_devices_connected(self, data):
"""Gets the number of GPU devices connected to the server
Parse the get_host_health_data() and get the count of
number of GPU devices connected to the server.
:param data: the output returned by get_host_health_data()
:returns: a dictionary of rom firmware version.
"""
try:
temp = data['GET_EMBEDDED_HEALTH_DATA']['TEMPERATURE']['TEMP']
except KeyError:
pass
if not isinstance(temp, list):
temp = [temp]
count = 0
for key in temp:
for name, value in key.items():
if name == 'LABEL' and 'GPU' in value['VALUE']:
count = count + 1
return {'pci_gpu_devices': count}
# The below block of code is there only for backward-compatibility
# reasons (before commit 47608b6 for ris-support).
IloClient = RIBCLOperations

View File

@ -600,8 +600,8 @@ class RISOperations(operations.IloOperations):
msg = "iLO Account with specified username is not found."
raise exception.IloError(msg)
def reset_ilo(self):
"""Resets the iLO.
def _get_ilo_details(self):
"""Gets iLO details
:raises: IloError, on an error from iLO.
:raises: IloConnectionError, if iLO is not up after reset.
@ -621,6 +621,17 @@ class RISOperations(operations.IloOperations):
msg = "%s is not a valid Manager type " % mtype
raise exception.IloError(msg)
return manager, reset_uri
def reset_ilo(self):
"""Resets the iLO.
:raises: IloError, on an error from iLO.
:raises: IloConnectionError, if iLO is not up after reset.
:raises: IloCommandNotSupportedError, if the command is not supported
on the server.
"""
manager, reset_uri = self._get_ilo_details()
action = {'Action': 'Reset'}
# perform the POST
@ -676,3 +687,38 @@ class RISOperations(operations.IloOperations):
if status >= 300:
msg = self._get_extended_error(response)
raise exception.IloError(msg)
def _get_ilo_firmware_version(self):
"""Gets the ilo firmware version for server capabilities
:returns: a dictionary of iLO firmware version.
"""
manager, reset_uri = self._get_ilo_details()
ilo_firmware_version = manager['Firmware']['Current']['VersionString']
return {'ilo_firmware_version': ilo_firmware_version}
def get_server_capabilities(self):
"""Gets server properties which can be used for scheduling
:returns: a dictionary of hardware properties like firmware
versions, server model.
:raises: IloError, if iLO returns an error in command execution.
"""
capabilities = {}
system = self._get_host_details()
capabilities['server_model'] = system['Model']
rom_firmware_version = (
system['Oem']['Hp']['Bios']['Current']['VersionString'])
capabilities['rom_firmware_version'] = rom_firmware_version
capabilities.update(self._get_ilo_firmware_version())
try:
self.get_secure_boot_mode()
capabilities['secure_boot'] = 'true'
except exception.IloCommandNotSupportedError:
# If an error is raised dont populate the capability
# secure_boot
pass
return capabilities

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@
"""Test class for RIBCL Module."""
import json
import unittest
import mock
@ -345,6 +346,136 @@ class IloRibclTestCase(unittest.TestCase):
self.assertIn('ProLiant ML110 G7', name)
self.assertIn('37363536-3636-4D32-3232-303130324A41', uuid)
def test__parse_processor_embedded_health(self):
data = constants.GET_EMBEDDED_HEALTH_OUTPUT
json_data = json.loads(data)
cpus, cpu_arch = self.ilo._parse_processor_embedded_health(json_data)
self.assertEqual('2', str(cpus))
self.assertEqual('x86_64', cpu_arch)
self.assertTrue(type(cpus), int)
def test__parse_memory_embedded_health(self):
data = constants.GET_EMBEDDED_HEALTH_OUTPUT
json_data = json.loads(data)
memory_mb = self.ilo._parse_memory_embedded_health(json_data)
self.assertEqual('32768', str(memory_mb))
self.assertTrue(type(memory_mb), int)
def test__parse_nics_embedded_health(self):
data = constants.GET_EMBEDDED_HEALTH_OUTPUT
json_data = json.loads(data)
expected_output = {u'Port 4': u'40:a8:f0:1e:86:77',
u'Port 3': u'40:a8:f0:1e:86:76',
u'Port 2': u'40:a8:f0:1e:86:75',
u'Port 1': u'40:a8:f0:1e:86:74'}
nic_data = self.ilo._parse_nics_embedded_health(json_data)
self.assertIsInstance(nic_data, dict)
for key, val in nic_data.items():
self.assertIn("Port", key)
self.assertEqual(expected_output, nic_data)
def test__parse_storage_embedded_health(self):
data = constants.GET_EMBEDDED_HEALTH_OUTPUT
json_data = json.loads(data)
local_gb = self.ilo._parse_storage_embedded_health(json_data)
self.assertTrue(type(local_gb), int)
self.assertEqual("99", str(local_gb))
def test__get_firmware_embedded_health(self):
data = constants.GET_EMBEDDED_HEALTH_OUTPUT
json_data = json.loads(data)
firmware_dict = self.ilo._get_firmware_embedded_health(json_data)
self.assertIsInstance(firmware_dict, dict)
def test__get_rom_firmware_version(self):
data = constants.GET_EMBEDDED_HEALTH_OUTPUT
json_data = json.loads(data)
expected_rom = {'rom_firmware_version': "11/26/2014"}
rom_firmware = self.ilo._get_rom_firmware_version(json_data)
self.assertIsInstance(rom_firmware, dict)
self.assertEqual(expected_rom, rom_firmware)
def test__get_ilo_firmware_version(self):
data = constants.GET_EMBEDDED_HEALTH_OUTPUT
json_data = json.loads(data)
expected_ilo = {'ilo_firmware_version': "2.02 Sep 05 2014"}
ilo_firmware = self.ilo._get_ilo_firmware_version(json_data)
self.assertIsInstance(ilo_firmware, dict)
self.assertEqual(expected_ilo, ilo_firmware)
def test__get_number_of_gpu_devices_connected(self):
data = constants.GET_EMBEDDED_HEALTH_OUTPUT
json_data = json.loads(data)
gpu_cnt = self.ilo._get_number_of_gpu_devices_connected(json_data)
self.assertIsInstance(gpu_cnt, dict)
self.assertIn('pci_gpu_devices', gpu_cnt)
@mock.patch.object(ribcl.RIBCLOperations, 'get_host_health_data')
def test_get_essential_properties(self, health_data_mock):
data = constants.GET_EMBEDDED_HEALTH_OUTPUT
json_data = json.loads(data)
health_data_mock.return_value = json_data
expected_properties = {'macs': {
u'Port 4': u'40:a8:f0:1e:86:77',
u'Port 3': u'40:a8:f0:1e:86:76',
u'Port 2': u'40:a8:f0:1e:86:75',
u'Port 1': u'40:a8:f0:1e:86:74'
},
'properties': {
'memory_mb': 32768,
'cpu_arch': 'x86_64',
'local_gb': 99,
'cpus': 2}
}
properties = self.ilo.get_essential_properties()
self.assertIsInstance(properties, dict)
self.assertIn('macs', properties)
self.assertIn('properties', properties)
self.assertEqual(expected_properties, properties)
@mock.patch.object(ribcl.RIBCLOperations, 'get_product_name')
@mock.patch.object(ribcl.RIBCLOperations, 'get_host_health_data')
def test_get_server_capabilities_gen8(self, health_data_mock, server_mock):
data = constants.GET_EMBEDDED_HEALTH_OUTPUT
json_data = json.loads(data)
health_data_mock.return_value = json_data
server_mock.return_value = 'ProLiant DL580 Gen8'
capabilities = self.ilo.get_server_capabilities()
self.assertIsInstance(capabilities, dict)
self.assertIn('ilo_firmware_version', capabilities)
self.assertIn('rom_firmware_version', capabilities)
self.assertIn('server_model', capabilities)
self.assertIn('pci_gpu_devices', capabilities)
self.assertNotIn('secure_boot', capabilities)
@mock.patch.object(ribcl.RIBCLOperations, 'get_supported_boot_mode')
def test__get_server_boot_modes_bios(self, boot_mock):
boot_mock.return_value = 'LEGACY_ONLY'
expected_boot_mode = {'BootMode': ['LEGACY']}
boot_mode = self.ilo._get_server_boot_modes()
self.assertEqual(expected_boot_mode, boot_mode)
@mock.patch.object(ribcl.RIBCLOperations, 'get_supported_boot_mode')
def test__get_server_boot_modes_bios_uefi(self, boot_mock):
boot_mock.return_value = 'LEGACY_UEFI'
expected_boot_mode = {'BootMode': ['LEGACY', 'UEFI']}
boot_mode = self.ilo._get_server_boot_modes()
self.assertEqual(expected_boot_mode, boot_mode)
@mock.patch.object(ribcl.RIBCLOperations, 'get_supported_boot_mode')
def test__get_server_boot_modes_uefi(self, boot_mock):
boot_mock.return_value = 'UEFI_ONLY'
expected_boot_mode = {'BootMode': ['UEFI']}
boot_mode = self.ilo._get_server_boot_modes()
self.assertEqual(expected_boot_mode, boot_mode)
@mock.patch.object(ribcl.RIBCLOperations, 'get_supported_boot_mode')
def test__get_server_boot_modes_None(self, boot_mock):
boot_mock.return_value = 'unknown'
expected_boot_mode = {'BootMode': None}
boot_mode = self.ilo._get_server_boot_modes()
self.assertEqual(expected_boot_mode, boot_mode)
class IloRibclTestCaseBeforeRisSupport(unittest.TestCase):

View File

@ -281,6 +281,32 @@ class IloRisTestCase(testtools.TestCase):
validate_mock.assert_called_once_with(ris_outputs.GET_HEADERS,
settings_uri)
@mock.patch.object(ris.RISOperations, 'get_secure_boot_mode')
@mock.patch.object(ris.RISOperations, '_get_ilo_firmware_version')
@mock.patch.object(ris.RISOperations, '_get_host_details')
def test_get_server_capabilities(self, get_details_mock, ilo_firm_mock,
secure_mock):
host_details = json.loads(ris_outputs.RESPONSE_BODY_FOR_REST_OP)
get_details_mock.return_value = host_details
ilo_firm_mock.return_value = {'ilo_firmware_version': 'iLO 4 v2.20'}
secure_mock.return_value = False
expected_caps = {'secure_boot': 'true',
'ilo_firmware_version': 'iLO 4 v2.20',
'rom_firmware_version': u'I36 v1.40 (01/28/2015)',
'server_model': u'ProLiant BL460c Gen9'}
capabilities = self.client.get_server_capabilities()
self.assertEqual(expected_caps, capabilities)
@mock.patch.object(ris.RISOperations, '_get_ilo_details')
def test__get_ilo_firmware_version(self, get_ilo_details_mock):
ilo_details = json.loads(ris_outputs.GET_MANAGER_DETAILS)
uri = '/rest/v1/Managers/1'
get_ilo_details_mock.return_value = (ilo_details, uri)
ilo_firm = self.client._get_ilo_firmware_version()
expected_ilo_firm = {'ilo_firmware_version': 'iLO 4 v2.20'}
self.assertIn('ilo_firmware_version', ilo_firm)
self.assertEqual(expected_ilo_firm, ilo_firm)
class TestRISOperationsPrivateMethods(testtools.TestCase):