Merge "Add RAIDInterface for RAID configuration"
This commit is contained in:
commit
c279382eb8
|
@ -0,0 +1,129 @@
|
|||
# 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 datetime
|
||||
|
||||
import jsonschema
|
||||
from jsonschema import exceptions as json_schema_exc
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import utils
|
||||
|
||||
|
||||
def _check_and_return_root_volumes(raid_config):
|
||||
"""Returns root logical disks after validating RAID config.
|
||||
|
||||
This method checks if multiple logical disks had 'is_root_volume'
|
||||
set to True and raises an exception if it is True. Otherwise,
|
||||
returns the root logical disk mentioned in the RAID config.
|
||||
|
||||
:param raid_config: target RAID configuration or current RAID
|
||||
configuration.
|
||||
:returns: the dictionary for the root logical disk if it is
|
||||
present, otherwise None.
|
||||
:raises: InvalidParameterValue, if there were more than one
|
||||
root volume specified in the RAID configuration.
|
||||
"""
|
||||
logical_disks = raid_config['logical_disks']
|
||||
root_logical_disks = [x for x in logical_disks if x.get('is_root_volume')]
|
||||
if len(root_logical_disks) > 1:
|
||||
msg = _("Raid config cannot have more than one root volume. "
|
||||
"%d root volumes were specified") % len(root_logical_disks)
|
||||
raise exception.InvalidParameterValue(msg)
|
||||
|
||||
if root_logical_disks:
|
||||
return root_logical_disks[0]
|
||||
|
||||
|
||||
def validate_configuration(raid_config, raid_config_schema):
|
||||
"""Validates the RAID configuration passed using JSON schema.
|
||||
|
||||
This method validates a RAID configuration against a RAID configuration
|
||||
schema.
|
||||
|
||||
:param raid_config: A dictionary containing RAID configuration information
|
||||
:param raid_config_schema: A dictionary which is the schema to be used for
|
||||
validation.
|
||||
:raises: InvalidParameterValue, if validation of the RAID configuration
|
||||
fails.
|
||||
"""
|
||||
try:
|
||||
jsonschema.validate(raid_config, raid_config_schema)
|
||||
except json_schema_exc.ValidationError as e:
|
||||
# NOTE: Even though e.message is deprecated in general, it is said
|
||||
# in jsonschema documentation to use this still.
|
||||
msg = _("RAID config validation error: %s") % e.message
|
||||
raise exception.InvalidParameterValue(msg)
|
||||
|
||||
# Check if there are multiple root volumes specified.
|
||||
_check_and_return_root_volumes(raid_config)
|
||||
|
||||
|
||||
def get_logical_disk_properties(raid_config_schema):
|
||||
"""Get logical disk properties from RAID configuration schema.
|
||||
|
||||
This method reads the logical properties and their textual description
|
||||
from the schema that is passed.
|
||||
|
||||
:param raid_config_schema: A dictionary which is the schema to be used for
|
||||
getting properties that may be specified for the logical disk.
|
||||
:returns: A dictionary containing the logical disk properties as keys
|
||||
and a textual description for them as values.
|
||||
"""
|
||||
logical_disk_schema = raid_config_schema['properties']['logical_disks']
|
||||
properties = logical_disk_schema['items']['properties']
|
||||
return {prop: prop_dict['description']
|
||||
for prop, prop_dict in properties.items()}
|
||||
|
||||
|
||||
def update_raid_info(node, raid_config):
|
||||
"""Update the node's information based on the RAID config.
|
||||
|
||||
This method updates the node's information to make use of the configured
|
||||
RAID for scheduling purposes (through properties['capabilities'] and
|
||||
properties['local_gb']) and deploying purposes (using
|
||||
properties['root_device']).
|
||||
|
||||
:param node: a node object
|
||||
:param raid_config: The dictionary containing the current RAID
|
||||
configuration.
|
||||
:raises: InvalidParameterValue, if 'raid_config' has more than
|
||||
one root volume or if node.properties['capabilities'] is malformed.
|
||||
"""
|
||||
current = raid_config.copy()
|
||||
current['last_updated'] = str(datetime.datetime.utcnow())
|
||||
node.raid_config = current
|
||||
node.target_raid_config = None
|
||||
|
||||
# Current RAID configuration can have 0 or 1 root volumes. If there
|
||||
# are > 1 root volumes, then it's invalid. We check for this condition
|
||||
# while accepting target RAID configuration, but this check is just in
|
||||
# place, if some drivers pass > 1 root volumes to this method.
|
||||
root_logical_disk = _check_and_return_root_volumes(raid_config)
|
||||
if root_logical_disk:
|
||||
# Update local_gb and root_device_hint
|
||||
properties = node.properties
|
||||
properties['local_gb'] = root_logical_disk['size_gb']
|
||||
try:
|
||||
properties['root_device'] = (
|
||||
root_logical_disk['root_device_hint'])
|
||||
except KeyError:
|
||||
pass
|
||||
properties['capabilities'] = utils.get_updated_capabilities(
|
||||
properties.get('capabilities', ''),
|
||||
{'raid_level': root_logical_disk['raid_level']})
|
||||
node.properties = properties
|
||||
|
||||
node.save()
|
|
@ -22,6 +22,8 @@ import collections
|
|||
import copy
|
||||
import functools
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
|
||||
import eventlet
|
||||
from oslo_log import log as logging
|
||||
|
@ -31,9 +33,13 @@ import six
|
|||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _LE
|
||||
from ironic.common import raid
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
RAID_CONFIG_SCHEMA = os.path.join(os.path.dirname(__file__),
|
||||
'raid_config_schema.json')
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseDriver(object):
|
||||
|
@ -111,6 +117,14 @@ class BaseDriver(object):
|
|||
"""
|
||||
standard_interfaces.append('inspect')
|
||||
|
||||
raid = None
|
||||
"""`Standard` attribute for RAID related features.
|
||||
|
||||
A reference to an instance of :class:RaidInterface.
|
||||
May be None, if unsupported by a driver.
|
||||
"""
|
||||
standard_interfaces.append('raid')
|
||||
|
||||
@abc.abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
@ -866,6 +880,89 @@ class InspectInterface(object):
|
|||
"""
|
||||
|
||||
|
||||
class RAIDInterface(BaseInterface):
|
||||
interface_type = 'raid'
|
||||
|
||||
def __init__(self):
|
||||
"""Constructor for RAIDInterface class."""
|
||||
with open(RAID_CONFIG_SCHEMA, 'r') as raid_schema_fobj:
|
||||
self.raid_schema = json.load(raid_schema_fobj)
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_properties(self):
|
||||
"""Return the properties of the interface.
|
||||
|
||||
:returns: dictionary of <property name>:<property description> entries.
|
||||
"""
|
||||
|
||||
def validate(self, task):
|
||||
"""Validate a given RAID configuration.
|
||||
|
||||
This method validates the properties defined by Ironic for RAID
|
||||
configuration. Driver implementations of this interface can override
|
||||
this method for doing more validations.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:raises: InvalidParameterValue, if the RAID configuration is invalid.
|
||||
"""
|
||||
target_raid_config = task.node.target_raid_config
|
||||
if not target_raid_config:
|
||||
return
|
||||
|
||||
raid.validate_configuration(target_raid_config, self.raid_schema)
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_configuration(self, task,
|
||||
create_root_volume=True,
|
||||
create_nonroot_volumes=True):
|
||||
"""Creates RAID configuration on the given node.
|
||||
|
||||
This method creates a RAID configuration on the given node.
|
||||
It assumes that the target RAID configuration is already
|
||||
available in node.target_raid_config.
|
||||
Implementations of this interface are supposed to read the
|
||||
RAID configuration from node.target_raid_config. After the
|
||||
RAID configuration is done (either in this method OR in a call-back
|
||||
method), ironic.common.raid.update_raid_info()
|
||||
may be called to sync the node's RAID-related information with the
|
||||
RAID configuration applied on the node.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param create_root_volume: Setting this to False indicates
|
||||
not to create root volume that is specified in the node's
|
||||
target_raid_config. Default value is True.
|
||||
:param create_nonroot_volumes: Setting this to False indicates
|
||||
not to create non-root volumes (all except the root volume) in the
|
||||
node's target_raid_config. Default value is True.
|
||||
:returns: states.CLEANWAIT if RAID configuration is in progress
|
||||
asynchronously or None if it is complete.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_configuration(self, task):
|
||||
"""Deletes RAID configuration on the given node.
|
||||
|
||||
This method deletes the RAID configuration on the give node.
|
||||
After RAID configuration is deleted, node.raid_config should be
|
||||
cleared by the implementation.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:returns: states.CLEANWAIT if deletion is in progress
|
||||
asynchronously or None if it is complete.
|
||||
"""
|
||||
|
||||
def get_logical_disk_properties(self):
|
||||
"""Get the properties that can be specified for logical disks.
|
||||
|
||||
This method returns a dictionary containing the properties that can
|
||||
be specified for logical disks and a textual description for them.
|
||||
|
||||
:returns: A dictionary containing properties that can be mentioned for
|
||||
logical disks and a textual description for them.
|
||||
"""
|
||||
return raid.get_logical_disk_properties(self.raid_schema)
|
||||
|
||||
|
||||
def clean_step(priority):
|
||||
"""Decorator for cleaning and zapping steps.
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ class FakeDriver(base.BaseDriver):
|
|||
self.console = fake.FakeConsole()
|
||||
self.management = fake.FakeManagement()
|
||||
self.inspect = fake.FakeInspect()
|
||||
self.raid = fake.FakeRAID()
|
||||
|
||||
|
||||
class FakeIPMIToolDriver(base.BaseDriver):
|
||||
|
|
|
@ -205,3 +205,17 @@ class FakeInspect(base.InspectInterface):
|
|||
|
||||
def inspect_hardware(self, task):
|
||||
return states.MANAGEABLE
|
||||
|
||||
|
||||
class FakeRAID(base.RAIDInterface):
|
||||
"""Example implementation of simple RAIDInterface."""
|
||||
|
||||
def get_properties(self):
|
||||
return {}
|
||||
|
||||
def create_configuration(self, task, create_root_volume=True,
|
||||
create_nonroot_volumes=True):
|
||||
pass
|
||||
|
||||
def delete_configuration(self, task):
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"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", "5+0", "6+0" ],
|
||||
"description": "RAID level for the logical disk. Required."
|
||||
},
|
||||
"size_gb": {
|
||||
"anyOf": [{
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"exclusiveMinimum": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [ "MAX" ]
|
||||
}],
|
||||
"description": "Size in GiB (Integer) for the logical disk. Use 'MAX' as size_gb if this logical disk is supposed to use rest of the space available. Required."
|
||||
},
|
||||
"volume_name": {
|
||||
"type": "string",
|
||||
"description": "Name of the volume to be created. If this is not specified, it will be auto-generated. Optional."
|
||||
},
|
||||
"is_root_volume": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies whether this disk is a root volume. By default, this is False. Optional."
|
||||
},
|
||||
"share_physical_disks": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies whether other logical disks can share physical disks with this logical disk. By default, this is False. Optional."
|
||||
},
|
||||
"disk_type": {
|
||||
"type": "string",
|
||||
"enum": [ "hdd", "ssd" ],
|
||||
"description": "The type of disk preferred. Valid values are 'hdd' and 'ssd'. If this is not specified, disk type will not be a selection criteria for choosing backing physical disks. Optional."
|
||||
},
|
||||
"interface_type": {
|
||||
"type": "string",
|
||||
"enum": [ "sata", "scsi", "sas" ],
|
||||
"description": "The interface type of disk. Valid values are 'sata', 'scsi' and 'sas'. If this is not specified, interface type will not be a selection criteria for choosing backing physical disks. Optional."
|
||||
},
|
||||
"number_of_physical_disks": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"exclusiveMinimum": true,
|
||||
"description": "Number of physical disks to use for this logical disk. Will be defaulted by the driver to minimum number of disks required for that RAID level. Optional."
|
||||
},
|
||||
"controller": {
|
||||
"type": "string",
|
||||
"description": "Controller to use for this logical disk. If not specified, driver will choose a suitable RAID controller on the bare metal node. Optional."
|
||||
},
|
||||
"physical_disks": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "The physical disks to use for this logical disk. If not specified, driver will choose a suitable physical disks to use. Optional"
|
||||
}
|
||||
},
|
||||
"required": ["raid_level", "size_gb"],
|
||||
"additionalProperties": false,
|
||||
"dependencies": {
|
||||
"physical_disks": ["controller"]
|
||||
}
|
||||
},
|
||||
"minItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["logical_disks"],
|
||||
"additionalProperties": false
|
||||
}
|
|
@ -2154,7 +2154,10 @@ class MiscTestCase(_ServiceSetUpMixin, _CommonMixIn, tests_db_base.DbTestCase):
|
|||
@mock.patch.object(images, 'is_whole_disk_image')
|
||||
def test_validate_driver_interfaces(self, mock_iwdi):
|
||||
mock_iwdi.return_value = False
|
||||
node = obj_utils.create_test_node(self.context, driver='fake')
|
||||
target_raid_config = {'logical_disks': [{'size_gb': 1,
|
||||
'raid_level': '1'}]}
|
||||
node = obj_utils.create_test_node(
|
||||
self.context, driver='fake', target_raid_config=target_raid_config)
|
||||
ret = self.service.validate_driver_interfaces(self.context,
|
||||
node.uuid)
|
||||
expected = {'console': {'result': True},
|
||||
|
@ -2162,6 +2165,7 @@ class MiscTestCase(_ServiceSetUpMixin, _CommonMixIn, tests_db_base.DbTestCase):
|
|||
'inspect': {'result': True},
|
||||
'management': {'result': True},
|
||||
'boot': {'result': True},
|
||||
'raid': {'result': True},
|
||||
'deploy': {'result': True}}
|
||||
self.assertEqual(expected, ret)
|
||||
mock_iwdi.assert_called_once_with(self.context, node.instance_info)
|
||||
|
|
|
@ -13,10 +13,13 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
|
||||
import eventlet
|
||||
import mock
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import raid
|
||||
from ironic.drivers import base as driver_base
|
||||
from ironic.tests import base
|
||||
|
||||
|
@ -183,3 +186,45 @@ class CleanStepTestCase(base.TestCase):
|
|||
# Ensure we can execute the function.
|
||||
obj.execute_clean_step(task_mock, obj.get_clean_steps(task_mock)[0])
|
||||
method_mock.assert_called_once_with(task_mock)
|
||||
|
||||
|
||||
class MyRAIDInterface(driver_base.RAIDInterface):
|
||||
|
||||
def create_configuration(self, task):
|
||||
pass
|
||||
|
||||
def delete_configuration(self, task):
|
||||
pass
|
||||
|
||||
|
||||
class RAIDInterfaceTestCase(base.TestCase):
|
||||
|
||||
@mock.patch.object(raid, 'validate_configuration')
|
||||
def test_validate(self, validate_mock):
|
||||
with open(driver_base.RAID_CONFIG_SCHEMA, 'r') as raid_schema_fobj:
|
||||
raid_schema = json.load(raid_schema_fobj)
|
||||
raid_interface = MyRAIDInterface()
|
||||
node_mock = mock.MagicMock(target_raid_config='some_raid_config')
|
||||
task_mock = mock.MagicMock(node=node_mock)
|
||||
|
||||
raid_interface.validate(task_mock)
|
||||
|
||||
validate_mock.assert_called_once_with('some_raid_config', raid_schema)
|
||||
|
||||
@mock.patch.object(raid, 'validate_configuration')
|
||||
def test_validate_no_target_raid_config(self, validate_mock):
|
||||
raid_interface = MyRAIDInterface()
|
||||
node_mock = mock.MagicMock(target_raid_config={})
|
||||
task_mock = mock.MagicMock(node=node_mock)
|
||||
|
||||
raid_interface.validate(task_mock)
|
||||
|
||||
self.assertFalse(validate_mock.called)
|
||||
|
||||
@mock.patch.object(raid, 'get_logical_disk_properties')
|
||||
def test_get_logical_disk_properties(self, get_properties_mock):
|
||||
with open(driver_base.RAID_CONFIG_SCHEMA, 'r') as raid_schema_fobj:
|
||||
raid_schema = json.load(raid_schema_fobj)
|
||||
raid_interface = MyRAIDInterface()
|
||||
raid_interface.get_logical_disk_properties()
|
||||
get_properties_mock.assert_called_once_with(raid_schema)
|
||||
|
|
|
@ -0,0 +1,298 @@
|
|||
# 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.
|
||||
|
||||
# Different RAID configurations for unit tests in test_raid.py
|
||||
|
||||
RAID_CONFIG_OKAY = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"volume_name": "my-volume",
|
||||
"is_root_volume": true,
|
||||
"share_physical_disks": false,
|
||||
"disk_type": "ssd",
|
||||
"interface_type": "sas",
|
||||
"number_of_physical_disks": 2,
|
||||
"controller": "Smart Array P822 in Slot 2",
|
||||
"physical_disks": [
|
||||
"5I:1:1",
|
||||
"5I:1:2"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_NO_LOGICAL_DISKS = '''
|
||||
{
|
||||
"logical_disks": []
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_NO_RAID_LEVEL = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_INVALID_RAID_LEVEL = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"size_gb": 100,
|
||||
"raid_level": "foo"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_NO_SIZE_GB = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_INVALID_SIZE_GB = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": "abcd"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_MAX_SIZE_GB = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": "MAX"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_INVALID_IS_ROOT_VOL = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"is_root_volume": "True"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_MULTIPLE_IS_ROOT_VOL = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"is_root_volume": true
|
||||
},
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"is_root_volume": true
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_INVALID_SHARE_PHY_DISKS = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"share_physical_disks": "True"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_INVALID_DISK_TYPE = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"disk_type": "foo"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_INVALID_INT_TYPE = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"interface_type": "foo"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_INVALID_NUM_PHY_DISKS = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"number_of_physical_disks": "a"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_INVALID_PHY_DISKS = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"controller": "Smart Array P822 in Slot 2",
|
||||
"physical_disks": "5I:1:1"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_ADDITIONAL_PROP = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_levelllllll": "1",
|
||||
"size_gb": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
CUSTOM_SCHEMA_RAID_CONFIG = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"foo": "bar"
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
CUSTOM_RAID_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."
|
||||
},
|
||||
"size_gb": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"exclusiveMinimum": true,
|
||||
"description": "Size (Integer) for the logical disk."
|
||||
},
|
||||
"foo": {
|
||||
"type": "string",
|
||||
"description": "property foo"
|
||||
}
|
||||
},
|
||||
"required": ["raid_level", "size_gb"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"minItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["logical_disks"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
'''
|
||||
|
||||
CURRENT_RAID_CONFIG = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"controller": "Smart Array P822 in Slot 2",
|
||||
"is_root_volume": true,
|
||||
"physical_disks": [
|
||||
"5I:1:1",
|
||||
"5I:1:2"
|
||||
],
|
||||
"root_device_hint": {
|
||||
"wwn": "600508B100"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
||||
|
||||
RAID_CONFIG_MULTIPLE_ROOT = '''
|
||||
{
|
||||
"logical_disks": [
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"controller": "Smart Array P822 in Slot 2",
|
||||
"is_root_volume": true,
|
||||
"physical_disks": [
|
||||
"5I:1:1",
|
||||
"5I:1:2"
|
||||
],
|
||||
"root_device_hint": {
|
||||
"wwn": "600508B100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"raid_level": "1",
|
||||
"size_gb": 100,
|
||||
"controller": "Smart Array P822 in Slot 2",
|
||||
"is_root_volume": true,
|
||||
"physical_disks": [
|
||||
"5I:1:1",
|
||||
"5I:1:2"
|
||||
],
|
||||
"root_device_hint": {
|
||||
"wwn": "600508B100"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
'''
|
|
@ -0,0 +1,228 @@
|
|||
# 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 json
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import raid
|
||||
from ironic.drivers import base as drivers_base
|
||||
from ironic.tests import base
|
||||
from ironic.tests.db import base as db_base
|
||||
from ironic.tests.objects import utils as obj_utils
|
||||
from ironic.tests import raid_constants
|
||||
|
||||
|
||||
class ValidateRaidConfigurationTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
with open(drivers_base.RAID_CONFIG_SCHEMA, 'r') as raid_schema_fobj:
|
||||
self.schema = json.load(raid_schema_fobj)
|
||||
super(ValidateRaidConfigurationTestCase, self).setUp()
|
||||
|
||||
def test_validate_configuration_okay(self):
|
||||
raid_config = json.loads(raid_constants.RAID_CONFIG_OKAY)
|
||||
raid.validate_configuration(
|
||||
raid_config, raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_no_logical_disk(self):
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
{},
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_zero_logical_disks(self):
|
||||
raid_config = json.loads(raid_constants.RAID_CONFIG_NO_LOGICAL_DISKS)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_no_raid_level(self):
|
||||
raid_config = json.loads(raid_constants.RAID_CONFIG_NO_RAID_LEVEL)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_invalid_raid_level(self):
|
||||
raid_config = json.loads(raid_constants.RAID_CONFIG_INVALID_RAID_LEVEL)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_no_size_gb(self):
|
||||
raid_config = json.loads(raid_constants.RAID_CONFIG_NO_SIZE_GB)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_max_size_gb(self):
|
||||
raid_config = json.loads(raid_constants.RAID_CONFIG_MAX_SIZE_GB)
|
||||
raid.validate_configuration(raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_invalid_size_gb(self):
|
||||
raid_config = json.loads(raid_constants.RAID_CONFIG_INVALID_SIZE_GB)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_invalid_is_root_volume(self):
|
||||
raid_config_str = raid_constants.RAID_CONFIG_INVALID_IS_ROOT_VOL
|
||||
raid_config = json.loads(raid_config_str)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_invalid_multiple_is_root_volume(self):
|
||||
raid_config_str = raid_constants.RAID_CONFIG_MULTIPLE_IS_ROOT_VOL
|
||||
raid_config = json.loads(raid_config_str)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_invalid_share_physical_disks(self):
|
||||
raid_config_str = raid_constants.RAID_CONFIG_INVALID_SHARE_PHY_DISKS
|
||||
raid_config = json.loads(raid_config_str)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_invalid_disk_type(self):
|
||||
raid_config = json.loads(raid_constants.RAID_CONFIG_INVALID_DISK_TYPE)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_invalid_int_type(self):
|
||||
raid_config = json.loads(raid_constants.RAID_CONFIG_INVALID_INT_TYPE)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_invalid_number_of_phy_disks(self):
|
||||
raid_config_str = raid_constants.RAID_CONFIG_INVALID_NUM_PHY_DISKS
|
||||
raid_config = json.loads(raid_config_str)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_invalid_physical_disks(self):
|
||||
raid_config = json.loads(raid_constants.RAID_CONFIG_INVALID_PHY_DISKS)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_additional_property(self):
|
||||
raid_config = json.loads(raid_constants.RAID_CONFIG_ADDITIONAL_PROP)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
raid.validate_configuration,
|
||||
raid_config,
|
||||
raid_config_schema=self.schema)
|
||||
|
||||
def test_validate_configuration_custom_schema(self):
|
||||
raid_config = json.loads(raid_constants.CUSTOM_SCHEMA_RAID_CONFIG)
|
||||
schema = json.loads(raid_constants.CUSTOM_RAID_SCHEMA)
|
||||
raid.validate_configuration(raid_config,
|
||||
raid_config_schema=schema)
|
||||
|
||||
|
||||
class RaidPublicMethodsTestCase(db_base.DbTestCase):
|
||||
|
||||
def test_get_logical_disk_properties(self):
|
||||
with open(drivers_base.RAID_CONFIG_SCHEMA, 'r') as raid_schema_fobj:
|
||||
schema = json.load(raid_schema_fobj)
|
||||
logical_disk_properties = raid.get_logical_disk_properties(schema)
|
||||
self.assertIn('raid_level', logical_disk_properties)
|
||||
self.assertIn('size_gb', logical_disk_properties)
|
||||
self.assertIn('volume_name', logical_disk_properties)
|
||||
self.assertIn('is_root_volume', logical_disk_properties)
|
||||
self.assertIn('share_physical_disks', logical_disk_properties)
|
||||
self.assertIn('disk_type', logical_disk_properties)
|
||||
self.assertIn('interface_type', logical_disk_properties)
|
||||
self.assertIn('number_of_physical_disks', logical_disk_properties)
|
||||
self.assertIn('controller', logical_disk_properties)
|
||||
self.assertIn('physical_disks', logical_disk_properties)
|
||||
|
||||
def test_get_logical_disk_properties_custom_schema(self):
|
||||
raid_schema = json.loads(raid_constants.CUSTOM_RAID_SCHEMA)
|
||||
logical_disk_properties = raid.get_logical_disk_properties(
|
||||
raid_config_schema=raid_schema)
|
||||
self.assertIn('raid_level', logical_disk_properties)
|
||||
self.assertIn('size_gb', logical_disk_properties)
|
||||
self.assertIn('foo', logical_disk_properties)
|
||||
|
||||
def _test_update_raid_info(self, current_config,
|
||||
capabilities=None):
|
||||
node = obj_utils.create_test_node(self.context,
|
||||
driver='fake')
|
||||
if capabilities:
|
||||
properties = node.properties
|
||||
properties['capabilities'] = capabilities
|
||||
del properties['local_gb']
|
||||
node.properties = properties
|
||||
node.save()
|
||||
raid.update_raid_info(node, current_config)
|
||||
properties = node.properties
|
||||
current = node.raid_config
|
||||
target = node.target_raid_config
|
||||
self.assertIsNotNone(current['last_updated'])
|
||||
self.assertIsInstance(current['logical_disks'][0], dict)
|
||||
if current_config['logical_disks'][0].get('is_root_volume'):
|
||||
self.assertEqual({'wwn': '600508B100'},
|
||||
properties['root_device'])
|
||||
self.assertEqual(100, properties['local_gb'])
|
||||
self.assertIn('raid_level:1', properties['capabilities'])
|
||||
if capabilities:
|
||||
self.assertIn(capabilities, properties['capabilities'])
|
||||
else:
|
||||
self.assertNotIn('local_gb', properties)
|
||||
self.assertNotIn('root_device', properties)
|
||||
if capabilities:
|
||||
self.assertNotIn('raid_level:1', properties['capabilities'])
|
||||
|
||||
self.assertEqual({}, target)
|
||||
|
||||
def test_update_raid_info_okay(self):
|
||||
current_config = json.loads(raid_constants.CURRENT_RAID_CONFIG)
|
||||
self._test_update_raid_info(current_config,
|
||||
capabilities='boot_mode:bios')
|
||||
|
||||
def test_update_raid_info_okay_no_root_volumes(self):
|
||||
current_config = json.loads(raid_constants.CURRENT_RAID_CONFIG)
|
||||
del current_config['logical_disks'][0]['is_root_volume']
|
||||
del current_config['logical_disks'][0]['root_device_hint']
|
||||
self._test_update_raid_info(current_config,
|
||||
capabilities='boot_mode:bios')
|
||||
|
||||
def test_update_raid_info_okay_current_capabilities_empty(self):
|
||||
current_config = json.loads(raid_constants.CURRENT_RAID_CONFIG)
|
||||
self._test_update_raid_info(current_config,
|
||||
capabilities=None)
|
||||
|
||||
def test_update_raid_info_multiple_root_volumes(self):
|
||||
current_config = json.loads(raid_constants.RAID_CONFIG_MULTIPLE_ROOT)
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
self._test_update_raid_info,
|
||||
current_config)
|
|
@ -41,3 +41,4 @@ oslo.messaging!=1.17.0,!=1.17.1,>=1.16.0 # Apache-2.0
|
|||
retrying!=1.3.0,>=1.2.3 # Apache-2.0
|
||||
posix-ipc
|
||||
oslo.versionedobjects>=0.6.0
|
||||
jsonschema>=2.0.0,<3.0.0,!=2.5.0
|
||||
|
|
Loading…
Reference in New Issue