Merge "Add RAIDInterface for RAID configuration"

This commit is contained in:
Jenkins 2015-08-14 11:26:04 +00:00 committed by Gerrit Code Review
commit c279382eb8
10 changed files with 894 additions and 1 deletions

129
ironic/common/raid.py Normal file
View File

@ -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()

View File

@ -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.

View File

@ -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):

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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"
}
}
]
}
'''

228
ironic/tests/test_raid.py Normal file
View File

@ -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)

View File

@ -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