New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for HP iLO + bugfix for GetFromRalph method. #15
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
#!/usr/bin/env python | ||
|
||
import json | ||
import os | ||
import sys | ||
from copy import deepcopy | ||
|
||
import hpilo | ||
|
||
|
||
MAC_PREFIX_BLACKLIST = [ | ||
'505054', '33506F', '009876', '000000', '00000C', '204153', '149120', | ||
'020054', 'FEFFFF', '1AF920', '020820', 'DEAD2C', 'FEAD4D', | ||
] | ||
|
||
DEVICE_INFO_TEMPLATE = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if it's template for ralph-cli, and since most of our script will be written in python, maybe we could make a simple, tiny python package with base class for the plugin and some utilities like this? it could be installed by default in venv for every python plugin. @xor-xor what do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, yes, I think it's a good idea. But IMO it's too soon for such optimizations - there are only two simple scripts here, and not enough common stuff between them to justify the existence of a separate package. |
||
"model_name": "", | ||
"processors": [], | ||
"mac_addresses": [], | ||
"disks": [], # unused (hpilo doesn't provide such info) | ||
"serial_number": "", | ||
"memory": [], | ||
} | ||
|
||
PROCESSOR_TEMPLATE = { | ||
"model_name": "", # unused (hpilo doesn't provide such info) | ||
"family": "", # hpilo returns int here, but only for iLO2 (for iLO3 this field is empty) # noqa | ||
"label": "", | ||
"index": None, # unused, but similar info is available as "label" | ||
"speed": None, | ||
"cores": None, | ||
} | ||
|
||
MEMORY_TEMPLATE = { | ||
"label": "", | ||
"size": None, | ||
"speed": None, | ||
"index": None, # unused, but similar info is available as "label" | ||
} | ||
|
||
|
||
class IloError(Exception): | ||
pass | ||
|
||
|
||
def normalize_mac_address(mac_address): | ||
mac_address = mac_address.upper().replace('-', ':') | ||
return mac_address | ||
|
||
|
||
def get_ilo_instance(host, user, password): | ||
ilo = hpilo.Ilo(hostname=host, login=user, password=password) | ||
return ilo | ||
|
||
|
||
def _get_macs(raw_macs, ilo_version): | ||
# The data structure for MAC addresses returned from hpilo is pretty nasty, | ||
# especially for iLO3 (no clear distinction between embedded NICs and | ||
# iSCSI ports). | ||
if ilo_version == 3: | ||
start_idx = 0 | ||
else: | ||
start_idx = 1 | ||
mac_addresses = [] | ||
for m in raw_macs: | ||
fields = m.get('fields', []) | ||
for i in range(start_idx, len(fields), 2): | ||
if ( | ||
fields[i]['name'] == 'Port' and | ||
fields[i]['value'] != 'iLO' # belongs to mgmt address | ||
): | ||
mac = normalize_mac_address(fields[i + 1]['value']) | ||
if mac[:6] not in MAC_PREFIX_BLACKLIST: | ||
mac_addresses.append(mac) | ||
return mac_addresses | ||
|
||
|
||
def _get_speed(s): | ||
# sample return value from hpilo: "2533 MHz" | ||
if s is not None: | ||
s = int(s.split(" ")[0]) | ||
return s | ||
|
||
|
||
def _get_processors(raw_procs): | ||
|
||
def get_cores(c): | ||
# sample return value from hpilo: "4 of 4 cores; 8 threads" | ||
if c is not None: | ||
c = int(c.split(" of ")[0]) | ||
return c | ||
|
||
processors = [] | ||
for p in raw_procs: | ||
proc = deepcopy(PROCESSOR_TEMPLATE) | ||
proc['family'] = str(p.get('Family', "")) | ||
proc['label'] = p.get('Label', "") | ||
proc['speed'] = _get_speed(p.get('Speed')) | ||
proc['cores'] = get_cores(p.get('Execution Technology')) | ||
processors.append(proc) | ||
return processors | ||
|
||
|
||
def _get_memory(raw_memory): | ||
|
||
def get_size(s): | ||
# sample return value from hpilo: "4096 MB" | ||
if s is not None: | ||
s = int(s.split(" ")[0]) | ||
return s | ||
|
||
memory = [] | ||
for m in raw_memory: | ||
mem = deepcopy(MEMORY_TEMPLATE) | ||
mem['label'] = m.get('Label', "") | ||
mem['size'] = get_size(m.get('Size')) | ||
mem['speed'] = _get_speed(m.get('Speed')) | ||
memory.append(mem) | ||
return memory | ||
|
||
|
||
# The data structure returned from python-hpilo is quite inconvenient for our | ||
# use-case, therefore we need to reshape it a little bit. | ||
def _prepare_host_data(raw_host_data, ilo_version): | ||
host_data = { | ||
"sys_info": [], | ||
"processors": [], | ||
"memory": [], | ||
"mac_addresses": [], | ||
} | ||
if ilo_version == 2: | ||
for part in raw_host_data: | ||
if part.get('Subject') == 'System Information': | ||
host_data['sys_info'].append(part) | ||
continue | ||
if part.get('Subject') == 'Processor Information': | ||
host_data['processors'].append(part) | ||
continue | ||
if ( | ||
part.get('Subject') == 'Memory Device' and | ||
part.get('Size') != 'not installed' | ||
): | ||
host_data['memory'].append(part) | ||
continue | ||
if part.get('Subject') is None and part.get('fields') is not None: | ||
fields = part.get('fields') | ||
for field in fields: | ||
if ( | ||
isinstance(field, dict) and | ||
field['value'] == 'Embedded NIC MAC Assignment' | ||
): | ||
host_data['mac_addresses'].append(part) | ||
break | ||
continue | ||
elif ilo_version == 3: | ||
for part in raw_host_data: | ||
if part.get('Product Name') is not None: | ||
host_data['sys_info'].append(part) | ||
continue | ||
if part.get('Execution Technology') is not None: | ||
host_data['processors'].append(part) | ||
continue | ||
if ( | ||
part.get('Label') is not None and | ||
part.get('Size') is not None and | ||
part.get('Speed') is not None | ||
): | ||
host_data['memory'].append(part) | ||
continue | ||
if part.get('fields') is not None: | ||
for field in part.get('fields'): | ||
# The condition here is not very reliable, but that's the | ||
# only way to distinguish between 'fields' list containing | ||
# embedded NICs vs. iSCSIs ports. | ||
if ( | ||
isinstance(field, dict) and | ||
field['name'] == 'Port' and | ||
field['value'] == 'iLO' | ||
): | ||
host_data['mac_addresses'].append(part) | ||
break | ||
continue | ||
else: | ||
raise IloError("Unknown version of iLO: %d".format(ilo_version)) | ||
if len(host_data['sys_info']) > 1: | ||
raise IloError( | ||
"There should be only one 'System Information' dict " | ||
"in the data returned by python-hpilo." | ||
) | ||
return host_data | ||
|
||
|
||
def get_ilo_version(ilo_manager): | ||
fw_version = ilo_manager.get_fw_version() | ||
if fw_version.get('management_processor') == "iLO3": | ||
ilo_version = 3 | ||
elif fw_version.get('management_processor') == "iLO2": | ||
ilo_version = 2 | ||
else: | ||
ilo_version = None | ||
return ilo_version | ||
|
||
|
||
def ilo_device_info(ilo_manager, ilo_version): | ||
raw_host_data = ilo_manager.get_host_data() | ||
host_data = _prepare_host_data(raw_host_data, ilo_version) | ||
device_info = DEVICE_INFO_TEMPLATE | ||
device_info['processors'] = _get_processors(host_data['processors']) | ||
device_info['mac_addresses'] = ( | ||
_get_macs(host_data['mac_addresses'], ilo_version) | ||
) | ||
device_info['serial_number'] = ( | ||
host_data['sys_info'][0].get('Serial Number', "").strip() | ||
) | ||
device_info['model_name'] = ( | ||
host_data['sys_info'][0].get('Product Name', "") | ||
) | ||
device_info['memory'] = _get_memory(host_data['memory']) | ||
return device_info | ||
|
||
|
||
def scan(host, user, password): | ||
if host == "": | ||
raise IloError("No IP address to scan has been provided.") | ||
if user == "": | ||
raise IloError("No management username has been provided.") | ||
if host == "": | ||
raise IloError("No management password has been provided.") | ||
ilo_manager = get_ilo_instance(host, user, password) | ||
ilo_version = get_ilo_version(ilo_manager) | ||
device_info = ilo_device_info(ilo_manager, ilo_version) | ||
print(json.dumps(device_info)) | ||
|
||
|
||
if __name__ == '__main__': | ||
host = os.environ.get('IP_TO_SCAN', "") | ||
user = os.environ.get('MANAGEMENT_USER_NAME', "") | ||
password = os.environ.get('MANAGEMENT_USER_PASSWORD', "") | ||
try: | ||
scan(host, user, password) | ||
except (IloError, hpilo.IloCommunicationError) as e: | ||
print(e.args[0]) | ||
sys.exit(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we plan to test these scripts (on some sample output) from management? We have some tests for it in Ralph2: https://github.com/allegro/ralph/tree/develop/src/ralph/scan/tests/plugins (other thing is how to run these tests here).
Btw we could do some end-to-end testing for calling
ralph-cli
, mocks response from the management (or just mock script output), and see how data is changed in ralph web (it should be easy to setup on travis using docker). Of course not in this PR, I'm just thinking about it :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been thinking about testing these scripts too, but as you've noticed, running Python's tests along with Golang's may be tricky/complicated (if not impossible) on e.g. Travis.
And speaking of e2e tests - yes, that may be a sensible way to approach above problem - I'll create a ticket for that.