Merge "Add better error messages for invalid conf molds" into stable/wallaby
This commit is contained in:
commit
d0c70aaf28
|
@ -81,7 +81,17 @@ def get_configuration(task, url):
|
|||
auth_header = _get_auth_header(task)
|
||||
response = _request(url, auth_header)
|
||||
if response.status_code == requests.codes.ok:
|
||||
return response.json()
|
||||
if not response.content:
|
||||
raise exception.IronicException(_(
|
||||
"Configuration mold for node %(node_uuid)s at %(url)s is "
|
||||
"empty") % {'node_uuid': task.node.uuid, 'url': url})
|
||||
try:
|
||||
return response.json()
|
||||
except json.decoder.JSONDecodeError as jde:
|
||||
raise exception.IronicException(_(
|
||||
"Configuration mold for node %(node_uuid)s at %(url)s has "
|
||||
"invalid JSON: %(error)s)")
|
||||
% {'node_uuid': task.node.uuid, 'url': url, 'error': jde})
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ import time
|
|||
|
||||
from futurist import periodics
|
||||
from ironic_lib import metrics_utils
|
||||
import jsonschema
|
||||
from jsonschema import exceptions as json_schema_exc
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
|
||||
|
@ -93,6 +95,23 @@ _CLEAR_JOB_IDS = 'JID_CLEARALL'
|
|||
# Clean steps constant
|
||||
_CLEAR_JOBS_CLEAN_STEPS = ['clear_job_queue', 'known_good_state']
|
||||
|
||||
_CONF_MOLD_SCHEMA = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'oem': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'interface': {'const': 'idrac-redfish'},
|
||||
'data': {'type': 'object', 'minProperties': 1}
|
||||
},
|
||||
'required': ['interface', 'data']
|
||||
}
|
||||
|
||||
},
|
||||
'required': ['oem'],
|
||||
'additionalProperties': False
|
||||
}
|
||||
|
||||
|
||||
def _get_boot_device(node, drac_boot_devices=None):
|
||||
client = drac_common.get_drac_client(node)
|
||||
|
@ -186,6 +205,19 @@ def _flexibly_program_boot_order(device, drac_boot_mode):
|
|||
return bios_settings
|
||||
|
||||
|
||||
def _validate_conf_mold(data):
|
||||
"""Validates iDRAC configuration mold JSON schema
|
||||
|
||||
:param data: dictionary of configuration mold data
|
||||
:raises InvalidParameterValue: If configuration mold validation fails
|
||||
"""
|
||||
try:
|
||||
jsonschema.validate(data, _CONF_MOLD_SCHEMA)
|
||||
except json_schema_exc.ValidationError as e:
|
||||
raise exception.InvalidParameterValue(
|
||||
_("Invalid configuration mold: %(error)s") % {'error': e})
|
||||
|
||||
|
||||
def set_boot_device(node, device, persistent=False):
|
||||
"""Set the boot device for a node.
|
||||
|
||||
|
@ -407,15 +439,7 @@ class DracRedfishManagement(redfish_management.RedfishManagement):
|
|||
{'node': task.node.uuid,
|
||||
'configuration_name': import_configuration_location}))
|
||||
|
||||
interface = configuration["oem"]["interface"]
|
||||
if interface != "idrac-redfish":
|
||||
raise exception.DracOperationError(
|
||||
error=(_("Invalid configuration for node %(node)s "
|
||||
"in %(configuration_name)s. Supports only "
|
||||
"idrac-redfish, but found %(interface)s") %
|
||||
{'node': task.node.uuid,
|
||||
'configuration_name': import_configuration_location,
|
||||
'interface': interface}))
|
||||
_validate_conf_mold(configuration)
|
||||
|
||||
task_monitor = drac_utils.execute_oem_manager_method(
|
||||
task, 'import system configuration',
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
|
@ -166,6 +167,7 @@ class ConfigurationMoldTestCase(db_base.DbTestCase):
|
|||
cfg.CONF.molds.storage = 'swift'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 200
|
||||
response.content = "{'key': 'value'}"
|
||||
response.json.return_value = {'key': 'value'}
|
||||
mock_get.return_value = response
|
||||
url = 'https://example.com/file1'
|
||||
|
@ -199,6 +201,7 @@ class ConfigurationMoldTestCase(db_base.DbTestCase):
|
|||
cfg.CONF.molds.password = 'password'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 200
|
||||
response.content = "{'key': 'value'}"
|
||||
response.json.return_value = {'key': 'value'}
|
||||
mock_get.return_value = response
|
||||
url = 'https://example.com/file2'
|
||||
|
@ -217,6 +220,7 @@ class ConfigurationMoldTestCase(db_base.DbTestCase):
|
|||
cfg.CONF.molds.password = None
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 200
|
||||
response.content = "{'key': 'value'}"
|
||||
response.json.return_value = {'key': 'value'}
|
||||
mock_get.return_value = response
|
||||
url = 'https://example.com/file2'
|
||||
|
@ -289,3 +293,31 @@ class ConfigurationMoldTestCase(db_base.DbTestCase):
|
|||
'https://example.com/file2',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
self.assertEqual(mock_get.call_count, 2)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_empty(self, mock_get):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 200
|
||||
response.content = ''
|
||||
mock_get.return_value = response
|
||||
url = 'https://example.com/file2'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(exception.IronicException,
|
||||
molds.get_configuration, task, url)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_invalid_json(self, mock_get):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 200
|
||||
response.content = 'not json'
|
||||
response.json.side_effect = json.decoder.JSONDecodeError(
|
||||
'Expecting value', 'not json', 0)
|
||||
mock_get.return_value = response
|
||||
url = 'https://example.com/file2'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(exception.IronicException,
|
||||
molds.get_configuration, task, url)
|
||||
|
|
|
@ -909,8 +909,8 @@ class DracRedfishManagementTestCase(test_utils.BaseDracTest):
|
|||
|
||||
@mock.patch.object(molds, 'get_configuration', autospec=True)
|
||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||
def test_import_configuration_incorrect_interface(self, mock_get_system,
|
||||
mock_get_configuration):
|
||||
def test_import_configuration_incorrect_schema(self, mock_get_system,
|
||||
mock_get_configuration):
|
||||
task = mock.Mock(node=self.node, context=self.context)
|
||||
fake_manager_oem1 = mock.Mock()
|
||||
fake_manager1 = mock.Mock()
|
||||
|
@ -920,7 +920,7 @@ class DracRedfishManagementTestCase(test_utils.BaseDracTest):
|
|||
'{"oem": {"interface": "idrac-wsman", '
|
||||
'"data": {"prop1": "value1", "prop2": 2}}}')
|
||||
|
||||
self.assertRaises(exception.DracOperationError,
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
self.management.import_configuration, task, 'edge')
|
||||
|
||||
@mock.patch.object(deploy_utils, 'get_async_step_return_state',
|
||||
|
@ -1462,3 +1462,43 @@ class DracRedfishManagementTestCase(test_utils.BaseDracTest):
|
|||
mock_manager_oem.job_service.delete_jobs.assert_called_once_with(
|
||||
job_ids=['JID_CLEARALL'])
|
||||
mock_manager_oem.reset_idrac.assert_called_once_with()
|
||||
|
||||
def test__validate_conf_mold(self):
|
||||
drac_mgmt._validate_conf_mold({'oem': {'interface': 'idrac-redfish',
|
||||
'data': {'SystemConfiguration': {}}}})
|
||||
|
||||
def test__validate_conf_mold_oem_missing(self):
|
||||
self.assertRaisesRegex(
|
||||
exception.InvalidParameterValue,
|
||||
"'oem' is a required property",
|
||||
drac_mgmt._validate_conf_mold,
|
||||
{'bios': {'reset': False}})
|
||||
|
||||
def test__validate_conf_mold_interface_missing(self):
|
||||
self.assertRaisesRegex(
|
||||
exception.InvalidParameterValue,
|
||||
"'interface' is a required property",
|
||||
drac_mgmt._validate_conf_mold,
|
||||
{'oem': {'data': {'SystemConfiguration': {}}}})
|
||||
|
||||
def test__validate_conf_mold_interface_not_supported(self):
|
||||
self.assertRaisesRegex(
|
||||
exception.InvalidParameterValue,
|
||||
"'idrac-redfish' was expected",
|
||||
drac_mgmt._validate_conf_mold,
|
||||
{'oem': {'interface': 'idrac-wsman',
|
||||
'data': {'SystemConfiguration': {}}}})
|
||||
|
||||
def test__validate_conf_mold_data_missing(self):
|
||||
self.assertRaisesRegex(
|
||||
exception.InvalidParameterValue,
|
||||
"'data' is a required property",
|
||||
drac_mgmt._validate_conf_mold,
|
||||
{'oem': {'interface': 'idrac-redfish'}})
|
||||
|
||||
def test__validate_conf_mold_data_empty(self):
|
||||
self.assertRaisesRegex(
|
||||
exception.InvalidParameterValue,
|
||||
"does not have enough properties",
|
||||
drac_mgmt._validate_conf_mold,
|
||||
{'oem': {'interface': 'idrac-redfish', 'data': {}}})
|
||||
|
|
Loading…
Reference in New Issue