Add hpssa module to proliantutils

This commit introduces the hpssa module. The module has
the ability to parse the current raid configuration and
hardware details of the server. It also has a manager
which has ability to create/delete/get raid configuration.

For now, create is limited to machine-dependent configuration.
Each logical disk to be created will require the controller
and physical disks to be used.

Change-Id: Ib58620d8d4f47eb55c55c6abb5d3b294623205bf
Implements: blueprint hpssa-support
This commit is contained in:
root 2014-08-19 05:57:34 +00:00 committed by Ramakrishnan G
parent 1d3517e476
commit fe5bcccfe2
18 changed files with 1570 additions and 7 deletions

2
.gitignore vendored
View File

@ -33,6 +33,7 @@ htmlcov/
.cache
nosetests.xml
coverage.xml
.testrepository/
# Translations
*.mo
@ -52,3 +53,4 @@ coverage.xml
# Sphinx documentation
docs/_build/

62
doc/hpssa/index.rst Normal file
View File

@ -0,0 +1,62 @@
hpssa module
============
Example::
# cat raid_configuration.json
{
"logical_disks": [
{
"size_gb": 100,
"raid_level": "1",
"controller": "Smart Array P822 in Slot 2",
"physical_disks": [
"5I:1:1",
"5I:1:2"
]
},
{
"size_gb": 100,
"raid_level": "5",
"controller": "Smart Array P822 in Slot 2",
"physical_disks": [
"5I:1:3",
"5I:1:4",
"6I:1:5"
]
}
]
}
# python
Python 2.7.5 (default, Nov 3 2014, 14:26:24)
[GCC 4.8.3 20140911 (Red Hat 4.8.3-7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import json
>>> raid_config = json.loads(open('raid_configuration.json', 'r').read())
>>> from proliantutils.hpssa import manager
>>> manager.get_configuration()
{'logical_disks': []}
>>> manager.create_configuration(raid_config)
>>> manager.get_configuration()
{'logical_disks': [{'size_gb': 100, 'physical_disks': ['5I:1:3', '6I:1:5', '5I:1:4'], 'raid_level': '5', 'root_device_hint': {'wwn': '600508B1001C9F62EB256593E19BBA30'}, 'controller': 'Smart Array P822 in Slot 2', 'volume_name': '061D6735PDVTF0BRH5T0MO4682'}, {'size_gb': 100, 'physical_disks': ['5I:1:1', '5I:1:2'], 'raid_level': '1', 'root_device_hint': {'wwn': '600508B1001C59DB9584108610B04BB0'}, 'controller': 'Smart Array P822 in Slot 2', 'volume_name': '021D672FPDVTF0BRH5T0MO287A'}]}
>>> exit
Use exit() or Ctrl-D (i.e. EOF) to exit
>>> exit()
# ls /dev/sd*
/dev/sda /dev/sdb
# python
Python 2.7.5 (default, Nov 3 2014, 14:26:24)
[GCC 4.8.3 20140911 (Red Hat 4.8.3-7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from proliantutils.hpssa import manager
>>> manager.delete_configuration()
>>> manager.get_configuration()
{'logical_disks': []}
>>>
# ls /dev/sd*
ls: cannot access /dev/sd*: No such file or directory
#

View File

@ -12,10 +12,20 @@
# License for the specific language governing permissions and limitations
# under the License.
"""Exception Class for iLO"""
"""Exception Class for proliantutils module."""
class IloError(Exception):
class ProliantUtilsException(Exception):
"""Parent class for all Proliantutils exceptions."""
pass
class InvalidInputError(Exception):
message = "Invalid Input: %(reason)s"
class IloError(ProliantUtilsException):
"""Base Exception.
This exception is used when a problem is encountered in
@ -69,6 +79,11 @@ class IloConnectionError(IloError):
def __init__(self, message):
super(IloConnectionError, self).__init__(message)
# This is not merged with generic InvalidInputError because
# of backward-compatibility reasons. If we changed this,
# use-cases of excepting 'IloError' to catch 'IloInvalidInputError'
# will be broken.
class IloInvalidInputError(IloError):
"""Invalid Input passed.
@ -78,3 +93,27 @@ class IloInvalidInputError(IloError):
"""
def __init__(self, message):
super(IloInvalidInputError, self).__init__(message)
class HPSSAException(ProliantUtilsException):
message = "An exception occured in hpssa module"
def __init__(self, message=None, **kwargs):
if not message:
message = self.message
message = message % kwargs
super(HPSSAException, self).__init__(message)
class PhysicalDisksNotFoundError(HPSSAException):
message = ("Not enough physical disks were found to create logical disk "
"of size %(size_gb)s GB and raid level %(raid_level)s")
class HPSSAOperationError(HPSSAException):
message = ("An error was encountered while doing hpssa configuration: "
"%(reason)s.")

View File

View File

@ -0,0 +1,158 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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.
import json
import os
import jsonschema
from jsonschema import exceptions as json_schema_exc
from proliantutils import exception
from proliantutils.hpssa import objects
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
RAID_CONFIG_SCHEMA = os.path.join(CURRENT_DIR, "raid_config_schema.json")
def _compare_logical_disks(ld1, ld2):
"""Compares the two logical disks provided based on size."""
return ld1['size_gb'] - ld2['size_gb']
def _find_physical_disks(logical_disk, server):
# To be implemented
pass
def validate(raid_config):
"""Validates the RAID configuration provided.
This method validates the RAID configuration provided against
a JSON schema.
:param raid_config: The RAID configuration to be validated.
:raises: InvalidInputError, if validation of the input fails.
"""
raid_schema_fobj = open(RAID_CONFIG_SCHEMA, 'r')
raid_config_schema = json.load(raid_schema_fobj)
try:
jsonschema.validate(raid_config, raid_config_schema)
except json_schema_exc.ValidationError as e:
raise exception.InvalidInputError(e.message)
def create_configuration(raid_config):
"""Create a RAID configuration on this server.
This method creates the given RAID configuration on the
server based on the input passed.
:param raid_config: The dictionary containing the requested
RAID configuration. This data structure should be as follows:
raid_config = {'logical_disks': [{'raid_level': 1, 'size_gb': 100},
<info-for-logical-disk-2>
]}
:raises exception.InvalidInputError, if input is invalid.
"""
validate(raid_config)
server = objects.Server()
logical_disks_sorted = sorted(raid_config['logical_disks'],
cmp=_compare_logical_disks)
for logical_disk in logical_disks_sorted:
if 'physical_disks' not in logical_disk:
# TODO(rameshg87): hpssa module should be capable of finding
# the suitable controller and physical disks if it not provided
# by using hints. This is a supported use-case, but not implemented
# as of now. Will be implemented soon.
# _find_physical_disks(logical_disk, server)
msg = ("Mentioning logical_disks without 'controller' and "
"'physical_disks' is not supported as of now.")
raise exception.InvalidInputError(reason=msg)
controller_id = logical_disk['controller']
controller = server.get_controller_by_id(controller_id)
if not controller:
msg = ("Unable to find controller named '%s'" % controller_id)
raise exception.InvalidInputError(reason=msg)
for physical_disk in logical_disk['physical_disks']:
disk_obj = controller.get_physical_drive_by_id(physical_disk)
if not disk_obj:
msg = ("Unable to find physical disk '%(physical_disk)s' "
"on '%(controller)s'" %
{'physical_disk': physical_disk,
'controller': controller_id})
raise exception.InvalidInputError(reason=msg)
physical_drive_ids = logical_disk['physical_disks']
controller.create_logical_drive(logical_disk, physical_drive_ids)
server.refresh()
def delete_configuration():
"""Delete a RAID configuration on this server."""
server = objects.Server()
for controller in server.controllers:
controller.delete_all_logical_drives()
def get_configuration():
"""Get the current RAID configuration.
Get the RAID configuration from the server and return it
as a dictionary.
:returns: A dictionary of the below format.
raid_config = {
'logical_disks': [{
'size_gb': 100,
'raid_level': 1,
'physical_disks': [
'5I:0:1',
'5I:0:2'],
'controller': 'Smart array controller'
},
]
}
"""
server = objects.Server()
logical_drives = server.get_logical_drives()
raid_config = dict()
raid_config['logical_disks'] = []
for logical_drive in logical_drives:
logical_drive_info = {}
logical_drive_info['size_gb'] = logical_drive.size_gb
logical_drive_info['raid_level'] = logical_drive.raid_level
array = logical_drive.parent
controller = array.parent
logical_drive_info['controller'] = controller.id
physical_drive_ids = map(lambda x: x.id, array.physical_drives)
logical_drive_info['physical_disks'] = physical_drive_ids
vol_name = logical_drive.get_property('Logical Drive Label')
logical_drive_info['volume_name'] = vol_name
wwn = logical_drive.get_property('Unique Identifier')
logical_drive_info['root_device_hint'] = {'wwn': wwn}
raid_config['logical_disks'].append(logical_drive_info)
return raid_config

View File

@ -0,0 +1,395 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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.
import time
from oslo.concurrency import processutils
from proliantutils import exception
from proliantutils.hpssa import types
def _get_indentation(string):
"""Return the number of spaces before the current line."""
return len(string) - len(string.lstrip(' '))
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 output."""
info = dict()
current_item = None
i = start_index
while i < len(lines):
current_line = lines[i]
current_line_indentation = _get_indentation(current_line)
if current_line_indentation == indentation:
current_item = current_line.lstrip(' ')
info[current_item] = dict()
i = i + 1
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
def _convert_to_dict(stdout):
"""Wrapper function for parsing hpssacli command.
This function gets the output from hpssacli command
and calls the recursive function _get_dict to return
the complete dictionary containing the RAID information.
"""
lines = stdout.split("\n")
lines = filter(None, lines)
info_dict, j = _get_dict(lines, 0, 0)
return info_dict
def _hpssacli(*args):
"""Wrapper function for executing hpssacli command."""
try:
stdout, stderr = processutils.execute("hpssacli",
*args)
except (OSError, processutils.ProcessExecutionError) as e:
raise exception.HPSSAOperationError(reason=e)
return stdout, stderr
class Server(object):
"""Class for Server object
This can consists of many RAID controllers - both internal
and external.
"""
def __init__(self):
"""Constructor for Server object."""
self.last_updated = None
self.controllers = []
self.refresh()
def _get_all_details(self):
"""Gets the current RAID configuration on the server.
This methods gets the current RAID configuration on the server using
hpssacli command and returns the output.
:returns: stdout after running the hpssacli command. The output looks
as follows:
Smart Array P822 in Slot 2
Bus Interface: PCI
Slot: 2
Serial Number: PDVTF0BRH5T0MO
.
.
.
Array: A
Interface Type: SAS
Unused Space: 0 MB
Status: OK
Logical Drive: 1
Size: 2.7 TB
Fault Tolerance: 6
Heads: 255
Unique Identifier: 600508B1001C45441D106BDFAAEBA41E
Disk Name: /dev/sda
.
.
physicaldrive 5I:1:1
Port: 5I
Box: 1
Bay: 1
Status: OK
Interface Type: SAS
Drive Type: Data Drive
.
.
.
physicaldrive 5I:1:2
Port: 5I
Box: 1
Bay: 2
Status: OK
Interface Type: SAS
Drive Type: Data Drive
:raises: HPSSAOperationError, if hpssacli operation failed.
"""
stdout, stderr = _hpssacli("controller", "all", "show",
"config", "detail")
return stdout
def refresh(self):
"""Refresh the server and it's child objects.
This method removes all the cache information in the server
and it's child objects, and fetches the information again from
the server using hpssacli command.
:raises: HPSSAOperationError, if hpssacli operation failed.
"""
config = self._get_all_details()
raid_info = _convert_to_dict(config)
self.controllers = []
for key, value in raid_info.iteritems():
self.controllers.append(Controller(key, value, self))
self.last_updated = time.time()
def get_controller_by_id(self, id):
"""Get the controller object given the id.
This method returns the controller object for given id.
:param id: id of the controller, for example
'Smart Array P822 in Slot 2'
:returns: Controller object which has the id or None if the
controller is not found.
"""
for controller in self.controllers:
if controller.id == id:
return controller
return None
def get_logical_drives(self):
"""Get all the RAID logical drives in the Server.
This method returns all the RAID logical drives on the server
by examining all the controllers.
:returns: a list of LogicalDrive objects.
"""
logical_drives = []
for controller in self.controllers:
for array in controller.raid_arrays:
for logical_drive in array.logical_drives:
logical_drives.append(logical_drive)
return logical_drives
class Controller(object):
"""This is the class for RAID controller."""
def __init__(self, id, properties, parent):
"""Constructor for Controller object."""
self.parent = parent
self.properties = properties
self.id = id
self.unassigned_physical_drives = []
self.raid_arrays = []
unassigned_drives = properties.get('unassigned', dict())
for key, value in unassigned_drives.iteritems():
self.unassigned_physical_drives.append(PhysicalDrive(key,
value,
self))
raid_arrays = filter(lambda x: x.startswith('Array'),
properties.keys())
for array in raid_arrays:
self.raid_arrays.append(RaidArray(array, properties[array], self))
def get_physical_drive_by_id(self, id):
"""Get a PhysicalDrive object for given id.
This method examines both assigned and unassigned physical
drives of the controller and returns the physical drive.
:param id: id of physical drive, for example '5I:1:1'.
:returns: PhysicalDrive object having the id, or None if
physical drive is not found.
"""
for phy_drive in self.unassigned_physical_drives:
if phy_drive.id == id:
return phy_drive
for array in self.raid_arrays:
for phy_drive in array.physical_drives:
if phy_drive.id == id:
return phy_drive
return None
def execute_cmd(self, *args):
"""Execute a given hpssacli command on the controller.
This method executes a given command on the controller.
:params args: a tuple consisting of sub-commands to be appended
after specifying the controller in hpssacli command.
:raises: HPSSAOperationError, if hpssacli operation failed.
"""
slot = self.properties['Slot']
base_cmd = ("controller", "slot=%s" % slot)
cmd = base_cmd + args
return _hpssacli(*cmd)
def create_logical_drive(self, logical_drive_info, physical_drive_ids):
"""Create a logical drive on the controller.
This method creates a logical drive on the controller when the
logical drive details and physical drive ids are passed to it.
:param logical_drive_info: a dictionary containing the details
of the logical drive as specified in raid config.
:param physical_drive_ids: a list of physical drive ids to be used.
:raises: HPSSAOperationError, if hpssacli operation failed.
"""
phy_drive_ids = ','.join(physical_drive_ids)
size_mb = logical_drive_info['size_gb'] * 1024
raid_level = logical_drive_info['raid_level']
self.execute_cmd("create", "type=logicaldrive",
"drives=%s" % phy_drive_ids,
"raid=%s" % raid_level,
"size=%s" % size_mb)
def delete_all_logical_drives(self):
"""Deletes all logical drives on trh controller.
This method deletes all logical drives on trh controller.
:raises: HPSSAOperationError, if hpssacli operation failed.
"""
self.execute_cmd("logicaldrive", "all", "delete", "forced")
class RaidArray(object):
"""Class for a RAID Array.
RAID array consists of many logical drives and many physical
drives.
"""
def __init__(self, id, properties, parent):
"""Constructor for a RAID Array object."""
self.parent = parent
self.properties = properties
self.id = id[7:]
self.logical_drives = []
self.physical_drives = []
logical_drives = filter(lambda x: x.startswith('Logical Drive'),
properties.keys())
for logical_drive in logical_drives:
self.logical_drives.append(LogicalDrive(logical_drive,
properties[logical_drive],
self))
physical_drives = filter(lambda x: x.startswith('physicaldrive'),
properties.keys())
for physical_drive in physical_drives:
self.physical_drives.append(PhysicalDrive(physical_drive,
properties[physical_drive],
self))
class LogicalDrive(object):
"""Class for LogicalDrive object."""
def __init__(self, id, properties, parent):
"""Constructor for a LogicalDrive object."""
# Strip off 'Logical Drive' before storing it in id
self.id = id[15:]
self.parent = parent
self.properties = properties
# TODO(rameshg87): Check if size is always reported in GB
self.size_gb = int(float(self.properties['Size'].rstrip(' GB')))
self.raid_level = self.properties['Fault Tolerance']
def get_property(self, prop):
if not self.properties:
return None
return self.properties.get(prop)
class PhysicalDrive:
"""Class for PhysicalDrive object."""
def __init__(self, id, properties, parent):
"""Constructor for a PhysicalDrive object."""
self.parent = parent
self.properties = properties
# Strip off physicaldrive before storing it in id
self.id = id[14:]
# TODO(rameshg87): Check if size is always reported in GB
self.size_gb = int(float(self.properties['Size'].rstrip(' GB')))
ssa_interface = self.properties['Interface Type']
self.interface_type = types.get_interface_type(ssa_interface)
self.disk_type = types.get_disk_type(ssa_interface)

View File

@ -0,0 +1,67 @@
{
"title": "raid configuration json schema",
"type": "object",
"properties": {
"logical_disks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"raid_level": {
"type": "string",
"enum": [ "0", "1", "2", "5", "6", "1+0" ],
"description": "RAID level for the logical disk. Required."
},
"size_gb": {
"type": "integer",
"minimum": 0,
"exclusiveMinimum": true,
"description": "Size (Integer) for the logical disk. Required."
},
"volume_name": {
"type": "string",
"description": "Name of the volume to be created. Optional."
},
"is_root_volume": {
"type": "boolean",
"description": "Specifies whether this disk is a root volume. Optional."
},
"share_physical_disks": {
"type": "boolean",
"description": "Specifies whether other logical disks can share physical disks with this logical disk. Optional."
},
"disk_type": {
"type": "string",
"enum": [ "hdd", "ssd" ],
"description": "Specifies the type of disk preferred. Valid values are 'hdd' and 'ssd'. Optional."
},
"interface_type": {
"type": "string",
"enum": [ "sata", "scsi", "sas" ],
"description": "Specifies the interface type of disk. Valid values are 'sata', 'scsi' and 'sas'. Optional."
},
"number_of_physical_disks": {
"type": "integer",
"minimum": 0,
"exclusiveMinimum": true,
"description": "Number of physical disks to use for this logical disk. Optional."
},
"controller": {
"type": "string",
"description": "Controller to use for this logical disk. Optional."
},
"physical_disks": {
"type": "array",
"items": { "type": "string" },
"description": "The physical disks to use for this logical disk. Optional"
}
},
"required": ["raid_level", "size_gb"],
"additionalProperties": false
},
"minItems": 1
}
},
"required": ["logical_disks"],
"additionalProperties": false
}

View File

@ -0,0 +1,51 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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.
INTERFACE_TYPE_SAS = 'SAS'
INTERFACE_TYPE_SCSI = 'SCSI'
INTERFACE_TYPE_SATA = 'SATA'
DISK_TYPE_HDD = 'HDD'
DISK_TYPE_SSD = 'SSD'
RAID_0 = '0'
RAID_1 = '1'
RAID_1_ADM = '1ADM'
RAID_10 = '10'
RAID_10_ADM = '10ADM'
RAID_5 = '5'
RAID_6 = '6'
RAID_50 = '50'
RAID_60 = '60'
INTERFACE_TYPE_MAP = {'SCSI': INTERFACE_TYPE_SCSI,
'SAS': INTERFACE_TYPE_SAS,
'SATA': INTERFACE_TYPE_SATA,
'SATASSD': INTERFACE_TYPE_SATA,
'SASSSD': INTERFACE_TYPE_SAS}
DISK_TYPE_MAP = {'SCSI': DISK_TYPE_HDD,
'SAS': DISK_TYPE_HDD,
'SATA': DISK_TYPE_HDD,
'SATASSD': DISK_TYPE_SSD,
'SASSSD': DISK_TYPE_SSD}
def get_interface_type(ssa_interface):
return INTERFACE_TYPE_MAP[ssa_interface]
def get_disk_type(ssa_interface):
return DISK_TYPE_MAP[ssa_interface]

View File

@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from proliantutils.ilo import exception
from proliantutils import exception
ERRMSG = "The specified operation is not supported on current platform."

View File

@ -23,7 +23,7 @@ import xml.etree.ElementTree as etree
import six
from proliantutils.ilo import exception
from proliantutils import exception
from proliantutils.ilo import operations

View File

@ -22,7 +22,7 @@ import json
import StringIO
import urlparse
from proliantutils.ilo import exception
from proliantutils import exception
from proliantutils.ilo import operations
""" Currently this class supports only secure boot and firmware settings
@ -589,4 +589,4 @@ class RISOperations(operations.IloOperations):
new_bios_settings)
if status >= 300:
msg = self._get_extended_error(response)
raise exception.IloError(msg)
raise exception.IloError(msg)

View File

View File

@ -0,0 +1,191 @@
Smart Array P822 in Slot 2
Bus Interface: PCI
Slot: 2
Serial Number: PDVTF0BRH5T0MO
Cache Serial Number: PBKUD0BRH5T3I6
RAID 6 (ADG) Status: Enabled
Controller Status: OK
Hardware Revision: B
Firmware Version: 4.68
Wait for Cache Room: Disabled
Surface Analysis Inconsistency Notification: Disabled
Post Prompt Timeout: 15 secs
Cache Board Present: True
Cache Status: OK
Drive Write Cache: Disabled
Total Cache Size: 2.0 GB
Total Cache Memory Available: 1.8 GB
No-Battery Write Cache: Disabled
Cache Backup Power Source: Capacitors
Battery/Capacitor Count: 1
Battery/Capacitor Status: OK
SATA NCQ Supported: True
Spare Activation Mode: Activate on physical drive failure (default)
Controller Temperature (C): 88
Cache Module Temperature (C): 37
Capacitor Temperature (C): 21
Number of Ports: 6 (2 Internal / 4 External )
Driver Name: hpsa
Driver Version: 3.4.4
Driver Supports HP SSD Smart Path: True
unassigned
physicaldrive 5I:1:1
Port: 5I
Box: 1
Bay: 1
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7G55D0000N4173JLT
Model: HP EF0600FARNA
Current Temperature (C): 35
Maximum Temperature (C): 43
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
physicaldrive 5I:1:2
Port: 5I
Box: 1
Bay: 2
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7H2DM0000B41800Y0
Model: HP EF0600FARNA
Current Temperature (C): 35
Maximum Temperature (C): 44
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
physicaldrive 5I:1:3
Port: 5I
Box: 1
Bay: 3
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7G4ZN0000B41707PD
Model: HP EF0600FARNA
Current Temperature (C): 33
Maximum Temperature (C): 42
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
physicaldrive 5I:1:4
Port: 5I
Box: 1
Bay: 4
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7H27F0000B41800S0
Model: HP EF0600FARNA
Current Temperature (C): 36
Maximum Temperature (C): 45
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
physicaldrive 6I:1:5
Port: 6I
Box: 1
Bay: 5
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7H2BR0000B41800V8
Model: HP EF0600FARNA
Current Temperature (C): 32
Maximum Temperature (C): 41
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
physicaldrive 6I:1:6
Port: 6I
Box: 1
Bay: 6
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7G4WD0000N4180GEJ
Model: HP EF0600FARNA
Current Temperature (C): 35
Maximum Temperature (C): 44
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
physicaldrive 6I:1:7
Port: 6I
Box: 1
Bay: 7
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7G54Q0000N4180W34
Model: HP EF0600FARNA
Current Temperature (C): 31
Maximum Temperature (C): 39
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
SEP (Vendor ID PMCSIERA, Model SRCv24x6G) 380
Device Number: 380
Firmware Version: RevB
WWID: 5001438028842E1F
Vendor ID: PMCSIERA
Model: SRCv24x6G

View File

@ -0,0 +1,232 @@
Smart Array P822 in Slot 2
Bus Interface: PCI
Slot: 2
Serial Number: PDVTF0BRH5T0MO
Cache Serial Number: PBKUD0BRH5T3I6
RAID 6 (ADG) Status: Enabled
Controller Status: OK
Hardware Revision: B
Firmware Version: 4.68
Rebuild Priority: Medium
Expand Priority: Medium
Surface Scan Delay: 3 secs
Surface Scan Mode: Idle
Queue Depth: Automatic
Monitor and Performance Delay: 60 min
Elevator Sort: Enabled
Degraded Performance Optimization: Disabled
Inconsistency Repair Policy: Disabled
Wait for Cache Room: Disabled
Surface Analysis Inconsistency Notification: Disabled
Post Prompt Timeout: 15 secs
Cache Board Present: True
Cache Status: OK
Cache Ratio: 10% Read / 90% Write
Drive Write Cache: Disabled
Total Cache Size: 2.0 GB
Total Cache Memory Available: 1.8 GB
No-Battery Write Cache: Disabled
Cache Backup Power Source: Capacitors
Battery/Capacitor Count: 1
Battery/Capacitor Status: OK
SATA NCQ Supported: True
Spare Activation Mode: Activate on physical drive failure (default)
Controller Temperature (C): 88
Cache Module Temperature (C): 37
Capacitor Temperature (C): 22
Number of Ports: 6 (2 Internal / 4 External )
Driver Name: hpsa
Driver Version: 3.4.4
Driver Supports HP SSD Smart Path: True
Array: A
Interface Type: SAS
Unused Space: 0 MB
Status: OK
MultiDomain Status: OK
Array Type: Data
HP SSD Smart Path: disable
Logical Drive: 1
Size: 558.9 GB
Fault Tolerance: 1
Heads: 255
Sectors Per Track: 32
Cylinders: 65535
Strip Size: 256 KB
Full Stripe Size: 256 KB
Status: OK
MultiDomain Status: OK
Caching: Enabled
Unique Identifier: 600508B1001C321CCA06EB7CD847939D
Disk Name: /dev/sda
Mount Points: None
Logical Drive Label: 01F42227PDVTF0BRH5T0MOAB64
Mirror Group 0:
physicaldrive 5I:1:1 (port 5I:box 1:bay 1, SAS, 600 GB, OK)
Mirror Group 1:
physicaldrive 5I:1:2 (port 5I:box 1:bay 2, SAS, 600 GB, OK)
Drive Type: Data
LD Acceleration Method: Controller Cache
physicaldrive 5I:1:1
Port: 5I
Box: 1
Bay: 1
Status: OK
Drive Type: Data Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7G55D0000N4173JLT
Model: HP EF0600FARNA
Current Temperature (C): 35
Maximum Temperature (C): 43
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
physicaldrive 5I:1:2
Port: 5I
Box: 1
Bay: 2
Status: OK
Drive Type: Data Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7H2DM0000B41800Y0
Model: HP EF0600FARNA
Current Temperature (C): 35
Maximum Temperature (C): 44
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
unassigned
physicaldrive 5I:1:3
Port: 5I
Box: 1
Bay: 3
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7G4ZN0000B41707PD
Model: HP EF0600FARNA
Current Temperature (C): 33
Maximum Temperature (C): 42
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
physicaldrive 5I:1:4
Port: 5I
Box: 1
Bay: 4
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7H27F0000B41800S0
Model: HP EF0600FARNA
Current Temperature (C): 36
Maximum Temperature (C): 45
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
physicaldrive 6I:1:5
Port: 6I
Box: 1
Bay: 5
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7H2BR0000B41800V8
Model: HP EF0600FARNA
Current Temperature (C): 32
Maximum Temperature (C): 41
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
physicaldrive 6I:1:6
Port: 6I
Box: 1
Bay: 6
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7G4WD0000N4180GEJ
Model: HP EF0600FARNA
Current Temperature (C): 35
Maximum Temperature (C): 44
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
physicaldrive 6I:1:7
Port: 6I
Box: 1
Bay: 7
Status: OK
Drive Type: Unassigned Drive
Interface Type: SAS
Size: 600 GB
Native Block Size: 512
Rotational Speed: 15000
Firmware Revision: HPD6
Serial Number: 6SL7G54Q0000N4180W34
Model: HP EF0600FARNA
Current Temperature (C): 31
Maximum Temperature (C): 39
PHY Count: 2
PHY Transfer Rate: 6.0Gbps, Unknown
Drive Authentication Status: OK
Carrier Application Version: 11
Carrier Bootloader Version: 6
SEP (Vendor ID PMCSIERA, Model SRCv24x6G) 380
Device Number: 380
Firmware Version: RevB
WWID: 5001438028842E1F
Vendor ID: PMCSIERA
Model: SRCv24x6G

View File

@ -0,0 +1,122 @@
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# 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.
import mock
import testtools
from proliantutils import exception
from proliantutils.hpssa import manager
from proliantutils.hpssa import objects
@mock.patch.object(objects.Server, '_get_all_details')
class ManagerTestCases(testtools.TestCase):
@mock.patch.object(objects.Controller, 'execute_cmd')
def test_create_configuration(self, controller_exec_cmd_mock,
get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/no_drives.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
ld1 = {'size_gb': 50,
'raid_level': '1',
'controller': 'Smart Array P822 in Slot 2',
'physical_disks': ['5I:1:1',
'5I:1:2']}
ld2 = {'size_gb': 100,
'raid_level': '5',
'controller': 'Smart Array P822 in Slot 2',
'physical_disks': ['5I:1:3',
'5I:1:4',
'6I:1:5']}
raid_info = {'logical_disks': [ld1, ld2]}
manager.create_configuration(raid_info)
ld1_drives = '5I:1:1,5I:1:2'
ld2_drives = '5I:1:3,5I:1:4,6I:1:5'
controller_exec_cmd_mock.assert_any_call("create",
"type=logicaldrive",
"drives=%s" % ld1_drives,
"raid=1",
"size=%d" % (50*1024))
controller_exec_cmd_mock.assert_any_call("create",
"type=logicaldrive",
"drives=%s" % ld2_drives,
"raid=5",
"size=%d" % (100*1024))
def test_create_configuration_invalid_logical_disks(self,
get_all_details_mock):
raid_info = {}
self.assertRaises(exception.InvalidInputError,
manager.create_configuration,
raid_info)
raid_info = {'logical_disks': 'foo'}
self.assertRaises(exception.InvalidInputError,
manager.create_configuration,
raid_info)
@mock.patch.object(objects.Controller, 'execute_cmd')
def test_delete_configuration(self, controller_exec_cmd_mock,
get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/one_drive.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
manager.delete_configuration()
controller_exec_cmd_mock.assert_called_with("logicaldrive",
"all",
"delete",
"forced")
def test_get_configuration(self, get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/one_drive.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
raid_info_returned = manager.get_configuration()
ld1_expected = {'size_gb': 558,
'raid_level': '1',
'controller': 'Smart Array P822 in Slot 2',
'physical_disks': ['5I:1:1',
'5I:1:2'],
'volume_name': '01F42227PDVTF0BRH5T0MOAB64',
'root_device_hint': {
'wwn': '600508B1001C321CCA06EB7CD847939D'}}
# NOTE(rameshg87: Cannot directly compare because
# of 'physical_disks' key.
ld1_returned = raid_info_returned['logical_disks'][0]
self.assertEqual(ld1_expected['size_gb'],
ld1_returned['size_gb'])
self.assertEqual(ld1_expected['raid_level'],
ld1_returned['raid_level'])
self.assertEqual(ld1_expected['controller'],
ld1_returned['controller'])
self.assertEqual(ld1_expected['volume_name'],
ld1_returned['volume_name'])
self.assertEqual(ld1_expected['root_device_hint'],
ld1_returned['root_device_hint'])
self.assertEqual(sorted(ld1_expected['physical_disks']),
sorted(ld1_returned['physical_disks']))

View File

@ -0,0 +1,242 @@
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# 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.
import mock
from oslo.concurrency import processutils
import testtools
from proliantutils import exception
from proliantutils.hpssa import objects
from proliantutils.hpssa import types
@mock.patch.object(objects.Server, '_get_all_details')
class ServerTest(testtools.TestCase):
def test_server_object_no_logical_drives(self, get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/no_drives.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
server = objects.Server()
# Assertions on server
self.assertEqual(1, len(server.controllers))
# Assertions on RAID controller properties
controller = server.controllers[0]
self.assertEqual(server, controller.parent)
self.assertIsInstance(controller.properties, dict)
self.assertEqual('Smart Array P822 in Slot 2', controller.id)
self.assertEqual(7, len(controller.unassigned_physical_drives))
self.assertFalse(controller.raid_arrays)
# Assertion on physical drives on controller
physical_drives_expected = ['5I:1:1', '5I:1:2', '5I:1:3', '5I:1:4',
'6I:1:5', '6I:1:6', '6I:1:7']
physical_drives_found = map(lambda x: x.id,
controller.unassigned_physical_drives)
self.assertEqual(sorted(physical_drives_expected),
sorted(physical_drives_found))
physical_drive = filter(lambda x: x.id == '5I:1:1',
controller.unassigned_physical_drives)[0]
self.assertEqual(controller, physical_drive.parent)
self.assertEqual(600, physical_drive.size_gb)
self.assertEqual(types.INTERFACE_TYPE_SAS,
physical_drive.interface_type)
self.assertEqual(types.DISK_TYPE_HDD,
physical_drive.disk_type)
def test_server_object_one_logical_drive(self, get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/one_drive.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
server = objects.Server()
controller = server.controllers[0]
self.assertEqual(5, len(controller.unassigned_physical_drives))
self.assertEqual(1, len(controller.raid_arrays))
# Assertion on raid_arrays
array = controller.raid_arrays[0]
self.assertEqual(array.parent, controller)
self.assertIsInstance(array.properties, dict)
self.assertEqual('A', array.id)
self.assertEqual(1, len(array.logical_drives))
self.assertEqual(2, len(array.physical_drives))
# Assertion on logical drives of array
logical_drive = array.logical_drives[0]
self.assertEqual('1', logical_drive.id)
self.assertEqual(logical_drive.parent, array)
self.assertEqual(558, logical_drive.size_gb)
self.assertEqual(types.RAID_1, logical_drive.raid_level)
self.assertIsInstance(logical_drive.properties, dict)
# Assertion on physical drives of array
physical_drive = filter(lambda x: x.id == '5I:1:1',
array.physical_drives)[0]
self.assertEqual(array, physical_drive.parent)
self.assertEqual(600, physical_drive.size_gb)
# Assertion on physical drives of controller
physical_drive = filter(lambda x: x.id == '5I:1:3',
controller.unassigned_physical_drives)[0]
self.assertEqual(controller, physical_drive.parent)
self.assertEqual(600, physical_drive.size_gb)
def test_get_controller_by_id(self, get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/one_drive.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
server = objects.Server()
id = 'Smart Array P822 in Slot 2'
self.assertEqual(server.controllers[0],
server.get_controller_by_id(id))
self.assertIsNone(server.get_controller_by_id('foo'))
def test_get_logical_drives(self, get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/one_drive.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
server = objects.Server()
exp_ld = server.controllers[0].raid_arrays[0].logical_drives[0]
self.assertEqual(exp_ld, server.get_logical_drives()[0])
def test_get_logical_drives_no_drives(self, get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/no_drives.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
server = objects.Server()
self.assertFalse(server.get_logical_drives())
@mock.patch.object(objects.Server, '_get_all_details')
class ControllerTest(testtools.TestCase):
@mock.patch.object(processutils, 'execute')
def test_execute_cmd(self, processutils_mock, get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/no_drives.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
server = objects.Server()
controller = server.controllers[0]
processutils_mock.return_value = ('stdout', 'stderr')
stdout, stderr = controller.execute_cmd('foo', 'bar')
processutils_mock.assert_called_once_with("hpssacli",
"controller",
"slot=2",
"foo",
"bar")
self.assertEqual(stdout, 'stdout')
self.assertEqual(stderr, 'stderr')
@mock.patch.object(processutils, 'execute')
def test_execute_cmd_fails(self, processutils_mock, get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/no_drives.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
server = objects.Server()
controller = server.controllers[0]
processutils_mock.side_effect = OSError
self.assertRaises(exception.HPSSAOperationError,
controller.execute_cmd,
'foo', 'bar')
@mock.patch.object(objects.Controller, 'execute_cmd')
def test_create_logical_drive(self, execute_mock,
get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/no_drives.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
server = objects.Server()
controller = server.controllers[0]
logical_drive_info = {'size_gb': 50,
'raid_level': '1',
'volume_name': 'boot_volume',
'is_boot_volume': 'true',
'controller': 'Smart Array P822 in Slot 2',
'physical_disks': ['5I:1:1',
'5I:1:2',
'5I:1:3']}
controller.create_logical_drive(logical_drive_info,
['5I:1:1',
'5I:1:2',
'5I:1:3'])
execute_mock.assert_called_once_with("create",
"type=logicaldrive",
"drives=5I:1:1,5I:1:2,5I:1:3",
"raid=1",
"size=51200")
@mock.patch.object(objects.Controller, 'execute_cmd')
def test_delete_all_logical_drives(self, execute_mock,
get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/no_drives.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
server = objects.Server()
controller = server.controllers[0]
controller.delete_all_logical_drives()
execute_mock.assert_called_once_with("logicaldrive", "all",
"delete", "forced")
def test_get_physical_drive_by_id(self, get_all_details_mock):
fobj = open('proliantutils/tests/hpssa/outputs/one_drive.out', 'r')
stdout = '\n'.join(fobj.readlines())
get_all_details_mock.return_value = stdout
server = objects.Server()
controller = server.controllers[0]
array = controller.raid_arrays[0]
physical_drive = filter(lambda x: x.id == '5I:1:1',
array.physical_drives)[0]
self.assertEqual(physical_drive,
controller.get_physical_drive_by_id('5I:1:1'))
physical_drive = filter(lambda x: x.id == '5I:1:3',
controller.unassigned_physical_drives)[0]
self.assertEqual(physical_drive,
controller.get_physical_drive_by_id('5I:1:3'))
self.assertIsNone(controller.get_physical_drive_by_id('foo'))

View File

@ -20,7 +20,7 @@ import unittest
import constants
import mock
from proliantutils.ilo import exception
from proliantutils import exception
from proliantutils.ilo import ribcl

View File

@ -1 +1,3 @@
six>=1.9.0
oslo.concurrency>=1.4.1 # Apache-2.0
jsonschema>=2.4.0