Manage blade expansions as independent devices
This patch adds the ability to detect and manage GPU and Disk expansion bays, and either add their internal components into the device corresponding to the blade server, or into a dedicated device. It takes advantage of the work made by @cyrinux on GPU bays management, and applies the same principle to the external disk bays, but harmonize the inventory management: - If no argument is specified on the command line, the GPU cards, RAID controllers and their attached disks are added in the blade device, and the device corresponding to an expansion device is deleted. - If the `--expansion-as-device` option is specified on the command line, a dedicated device corresponding to the expansion bay is created, and the GPUs, RAID card and attached disks are removed from the blade device and added to the expansion device.
This commit is contained in:
parent
8a46af19b8
commit
2f09cf8d42
6 changed files with 195 additions and 175 deletions
|
@ -1,106 +1,65 @@
|
|||
import re
|
||||
import subprocess
|
||||
|
||||
from netbox_agent.config import config
|
||||
from netbox_agent.misc import get_vendor
|
||||
from netbox_agent.raid.base import Raid, RaidController
|
||||
|
||||
REGEXP_CONTROLLER_HP = re.compile(r'Smart Array ([a-zA-Z0-9- ]+) in Slot ([0-9]+)')
|
||||
|
||||
|
||||
def _get_indentation(string):
|
||||
"""Return the number of spaces before the current line."""
|
||||
return len(string) - len(string.lstrip(' '))
|
||||
def _parse_ctrl_output(lines):
|
||||
controllers = {}
|
||||
current_ctrl = None
|
||||
|
||||
|
||||
def _get_key_value(string):
|
||||
"""Return the (key, value) as a tuple from a string."""
|
||||
# Normally all properties look like this:
|
||||
# Unique Identifier: 600508B1001CE4ACF473EE9C826230FF
|
||||
# Disk Name: /dev/sda
|
||||
# Mount Points: None
|
||||
key = ''
|
||||
value = ''
|
||||
try:
|
||||
key, value = string.split(':')
|
||||
except ValueError:
|
||||
# This handles the case when the property of a logical drive
|
||||
# returned is as follows. Here we cannot split by ':' because
|
||||
# the disk id has colon in it. So if this is about disk,
|
||||
# then strip it accordingly.
|
||||
# Mirror Group 0: physicaldrive 6I:1:5
|
||||
string = string.lstrip(' ')
|
||||
if string.startswith('physicaldrive'):
|
||||
fields = string.split(' ')
|
||||
key = fields[0]
|
||||
value = fields[1]
|
||||
else:
|
||||
# TODO(rameshg87): Check if this ever occurs.
|
||||
return None, None
|
||||
|
||||
return key.lstrip(' ').rstrip(' '), value.lstrip(' ').rstrip(' ')
|
||||
|
||||
|
||||
def _get_dict(lines, start_index, indentation):
|
||||
"""Recursive function for parsing hpssacli/ssacli output."""
|
||||
|
||||
info = {}
|
||||
current_item = None
|
||||
|
||||
i = start_index
|
||||
while i < len(lines):
|
||||
current_line = lines[i]
|
||||
if current_line.startswith('Note:'):
|
||||
i = i + 1
|
||||
for line in lines:
|
||||
if not line or line.startswith('Note:'):
|
||||
continue
|
||||
|
||||
current_line_indentation = _get_indentation(current_line)
|
||||
# This check ignore some useless information that make
|
||||
# crash the parsing
|
||||
product_name = REGEXP_CONTROLLER_HP.search(current_line)
|
||||
if current_line_indentation == 0 and not product_name:
|
||||
i = i + 1
|
||||
ctrl = REGEXP_CONTROLLER_HP.search(line)
|
||||
if ctrl is not None:
|
||||
current_ctrl = ctrl.group(1)
|
||||
controllers[current_ctrl] = {"Slot": ctrl.group(2)}
|
||||
if "Embedded" not in line:
|
||||
controllers[current_ctrl]["External"] = True
|
||||
continue
|
||||
attr, val = line.split(": ", 1)
|
||||
attr = attr.strip()
|
||||
val = val.strip()
|
||||
controllers[current_ctrl][attr] = val
|
||||
return controllers
|
||||
|
||||
if current_line_indentation == indentation:
|
||||
current_item = current_line.lstrip(' ')
|
||||
|
||||
info[current_item] = {}
|
||||
i = i + 1
|
||||
def _parse_pd_output(lines):
|
||||
drives = {}
|
||||
current_array = None
|
||||
current_drv = None
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('Note:'):
|
||||
continue
|
||||
|
||||
if i >= len(lines) - 1:
|
||||
key, value = _get_key_value(current_line)
|
||||
# If this is some unparsable information, then
|
||||
# just skip it.
|
||||
if key:
|
||||
info[current_item][key] = value
|
||||
return info, i
|
||||
|
||||
next_line = lines[i + 1]
|
||||
next_line_indentation = _get_indentation(next_line)
|
||||
|
||||
if current_line_indentation == next_line_indentation:
|
||||
key, value = _get_key_value(current_line)
|
||||
if key:
|
||||
info[current_item][key] = value
|
||||
i = i + 1
|
||||
elif next_line_indentation > current_line_indentation:
|
||||
ret_dict, j = _get_dict(lines, i, current_line_indentation)
|
||||
info[current_item].update(ret_dict)
|
||||
i = j + 1
|
||||
elif next_line_indentation < current_line_indentation:
|
||||
key, value = _get_key_value(current_line)
|
||||
if key:
|
||||
info[current_item][key] = value
|
||||
return info, i
|
||||
|
||||
return info, i
|
||||
# Parses the Array the drives are in
|
||||
if line.startswith("Array"):
|
||||
current_array = line.split(None, 1)[1]
|
||||
# Detects new physical drive
|
||||
if line.startswith("physicaldrive"):
|
||||
current_drv = line.split(None, 1)[1]
|
||||
drives[current_drv] = {}
|
||||
if current_array is not None:
|
||||
drives[current_drv]["Array"] = current_array
|
||||
continue
|
||||
if ": " not in line:
|
||||
continue
|
||||
attr, val = line.split(": ", 1)
|
||||
drives.setdefault(current_drv, {})[attr] = val
|
||||
return drives
|
||||
|
||||
|
||||
class HPRaidController(RaidController):
|
||||
def __init__(self, controller_name, data):
|
||||
self.controller_name = controller_name
|
||||
self.data = data
|
||||
self.drives = self._get_physical_disks()
|
||||
|
||||
def get_product_name(self):
|
||||
return self.controller_name
|
||||
|
@ -114,40 +73,42 @@ class HPRaidController(RaidController):
|
|||
def get_firmware_version(self):
|
||||
return self.data['Firmware Version']
|
||||
|
||||
def get_physical_disks(self):
|
||||
ret = []
|
||||
def is_external(self):
|
||||
return self.data.get('External', False)
|
||||
|
||||
def _get_physical_disks(self):
|
||||
output = subprocess.getoutput(
|
||||
'ssacli ctrl slot={slot} pd all show detail'.format(slot=self.data['Slot'])
|
||||
)
|
||||
lines = output.split('\n')
|
||||
lines = list(filter(None, lines))
|
||||
j = -1
|
||||
while j < len(lines):
|
||||
info_dict, j = _get_dict(lines, j + 1, 0)
|
||||
drives = _parse_pd_output(lines)
|
||||
ret = []
|
||||
|
||||
key = next(iter(info_dict))
|
||||
for array, physical_disk in info_dict[key].items():
|
||||
for _, pd_attr in physical_disk.items():
|
||||
model = pd_attr.get('Model', '').strip()
|
||||
vendor = None
|
||||
if model.startswith('HP'):
|
||||
vendor = 'HP'
|
||||
elif len(model.split()) > 1:
|
||||
vendor = get_vendor(model.split()[1])
|
||||
else:
|
||||
vendor = get_vendor(model)
|
||||
for name, attrs in drives.items():
|
||||
model = attrs.get('Model', '').strip()
|
||||
vendor = None
|
||||
if model.startswith('HP'):
|
||||
vendor = 'HP'
|
||||
elif len(model.split()) > 1:
|
||||
vendor = get_vendor(model.split()[1])
|
||||
else:
|
||||
vendor = get_vendor(model)
|
||||
|
||||
ret.append({
|
||||
'Model': model,
|
||||
'Vendor': vendor,
|
||||
'SN': pd_attr.get('Serial Number', '').strip(),
|
||||
'Size': pd_attr.get('Size', '').strip(),
|
||||
'Type': 'SSD' if pd_attr.get('Interface Type') == 'Solid State SATA'
|
||||
else 'HDD',
|
||||
'_src': self.__class__.__name__,
|
||||
})
|
||||
ret.append({
|
||||
'Model': model,
|
||||
'Vendor': vendor,
|
||||
'SN': attrs.get('Serial Number', '').strip(),
|
||||
'Size': attrs.get('Size', '').strip(),
|
||||
'Type': 'SSD' if attrs.get('Interface Type') == 'Solid State SATA'
|
||||
else 'HDD',
|
||||
'_src': self.__class__.__name__,
|
||||
})
|
||||
return ret
|
||||
|
||||
def get_physical_disks(self):
|
||||
return self.drives
|
||||
|
||||
|
||||
class HPRaid(Raid):
|
||||
def __init__(self):
|
||||
|
@ -158,16 +119,11 @@ class HPRaid(Raid):
|
|||
def convert_to_dict(self):
|
||||
lines = self.output.split('\n')
|
||||
lines = list(filter(None, lines))
|
||||
j = -1
|
||||
while j < len(lines):
|
||||
info_dict, j = _get_dict(lines, j + 1, 0)
|
||||
if len(info_dict.keys()):
|
||||
_product_name = list(info_dict.keys())[0]
|
||||
product_name = REGEXP_CONTROLLER_HP.search(_product_name)
|
||||
if product_name:
|
||||
self.controllers.append(
|
||||
HPRaidController(product_name.group(1), info_dict[_product_name])
|
||||
)
|
||||
controllers = _parse_ctrl_output(lines)
|
||||
for controller, attrs in controllers.items():
|
||||
self.controllers.append(
|
||||
HPRaidController(controller, attrs)
|
||||
)
|
||||
|
||||
def get_controllers(self):
|
||||
return self.controllers
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue