Add XClarity Driver

This patch proposes to add new interfaces for management and power
for the Lenovo XClarity Driver.

Change-Id: Ic2743f9a4959a6165a7ec40f4772afb231205f36
Closes-Bug: #1702508
This commit is contained in:
Rushil Chugh 2017-11-13 11:38:17 -05:00
parent 4a74c34061
commit 346a9a3bfc
20 changed files with 977 additions and 0 deletions

View File

@ -11,6 +11,7 @@ python-oneviewclient<3.0.0,>=2.5.2
python-scciclient>=0.6.0
UcsSdk==0.8.2.2
python-dracclient>=1.3.0
python-xclarityclient>=0.1.6
# The CIMC drivers use the Cisco IMC SDK version 0.7.2 or greater
ImcSdk>=0.7.2

View File

@ -4224,3 +4224,24 @@
# for endpoint URL discovery. Mutually exclusive with
# min_version and max_version (string value)
#version = <None>
[xclarity]
#
# From ironic
#
# IP address of XClarity controller. (string value)
#manager_ip = <None>
# Username to access the XClarity controller. (string value)
#username = <None>
# Password for XClarity controller username. (string value)
#password = <None>
# Port to be used for XClarity operations. (port value)
# Minimum value: 0
# Maximum value: 65535
#port = 443

View File

@ -44,6 +44,7 @@ from ironic.conf import redfish
from ironic.conf import service_catalog
from ironic.conf import snmp
from ironic.conf import swift
from ironic.conf import xclarity
CONF = cfg.CONF
@ -76,3 +77,4 @@ redfish.register_opts(CONF)
service_catalog.register_opts(CONF)
snmp.register_opts(CONF)
swift.register_opts(CONF)
xclarity.register_opts(CONF)

View File

@ -61,6 +61,7 @@ _opts = [
('service_catalog', ironic.conf.service_catalog.list_opts()),
('snmp', ironic.conf.snmp.opts),
('swift', ironic.conf.swift.list_opts()),
('xclarity', ironic.conf.xclarity.opts),
]

33
ironic/conf/xclarity.py Normal file
View File

@ -0,0 +1,33 @@
# Copyright 2017 LENOVO Development Company, LP
#
# 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.
from oslo_config import cfg
from ironic.common.i18n import _
opts = [
cfg.StrOpt('manager_ip',
help=_('IP address of XClarity controller.')),
cfg.StrOpt('username',
help=_('Username to access the XClarity controller.')),
cfg.StrOpt('password',
help=_('Password for XClarity controller username.')),
cfg.PortOpt('port',
default=443,
help=_('Port to be used for XClarity operations.')),
]
def register_opts(conf):
conf.register_opts(opts, group='xclarity')

View File

@ -0,0 +1,138 @@
# Copyright 2017 Lenovo, Inc.
#
# 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.
from oslo_log import log as logging
from oslo_utils import importutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import states
from ironic.conf import CONF
LOG = logging.getLogger(__name__)
client = importutils.try_import('xclarity_client.client')
xclarity_client_constants = importutils.try_import('xclarity_client.constants')
xclarity_client_exceptions = importutils.try_import(
'xclarity_client.exceptions')
REQUIRED_ON_DRIVER_INFO = {
'xclarity_hardware_id': _("XClarity Server Hardware ID. "
"Required in driver_info."),
}
COMMON_PROPERTIES = {
'xclarity_address': _("IP address of the XClarity node."),
'xclarity_username': _("Username for the XClarity with administrator "
"privileges."),
'xclarity_password': _("Password for xclarity_username."),
'xclarity_port': _("Port to be used for xclarity_username."),
}
COMMON_PROPERTIES.update(REQUIRED_ON_DRIVER_INFO)
def get_properties():
return COMMON_PROPERTIES
def get_xclarity_client():
"""Generates an instance of the XClarity client.
Generates an instance of the XClarity client using the imported
xclarity_client library.
:returns: an instance of the XClarity client
:raises: XClarityError if can't get to the XClarity client
"""
try:
xclarity_client = client.Client(
ip=CONF.xclarity.manager_ip,
username=CONF.xclarity.username,
password=CONF.xclarity.password,
port=CONF.xclarity.port
)
except xclarity_client_exceptions.XClarityError as exc:
msg = (_("Error getting connection to XClarity manager IP: %(ip)s. "
"Error: %(exc)s"), {'ip': CONF.xclarity.manager_ip,
'exc': exc})
raise XClarityError(error=msg)
return xclarity_client
def get_server_hardware_id(node):
"""Validates node configuration and returns xclarity hardware id.
Validates whether node configutation is consistent with XClarity and
returns the XClarity Hardware ID for a specific node.
:param: node: node object to get information from
:returns: the XClarity Hardware ID for a specific node
:raises: MissingParameterValue if unable to validate XClarity Hardware ID
"""
xclarity_hardware_id = node.driver_info.get('xclarity_hardware_id')
if not xclarity_hardware_id:
msg = (_("Error validating node driver info, "
"server uuid: %s missing xclarity_hardware_id") %
node.uuid)
raise exception.MissingParameterValue(error=msg)
return xclarity_hardware_id
def translate_xclarity_power_state(power_state):
"""Translates XClarity's power state strings to be consistent with Ironic.
:param: power_state: power state string to be translated
:returns: the translated power state
"""
power_states_map = {
xclarity_client_constants.STATE_POWER_ON: states.POWER_ON,
xclarity_client_constants.STATE_POWER_OFF: states.POWER_OFF,
}
return power_states_map.get(power_state, states.ERROR)
def translate_xclarity_power_action(power_action):
"""Translates ironic's power action strings to XClarity's format.
:param: power_action: power action string to be translated
:returns: the power action translated
"""
power_action_map = {
states.POWER_ON: xclarity_client_constants.ACTION_POWER_ON,
states.POWER_OFF: xclarity_client_constants.ACTION_POWER_OFF,
states.REBOOT: xclarity_client_constants.ACTION_REBOOT
}
return power_action_map[power_action]
def is_node_managed_by_xclarity(xclarity_client, node):
"""Determines whether dynamic allocation is enabled for a specifc node.
:param: xclarity_client: an instance of the XClarity client
:param: node: node object to get information from
:returns: Boolean depending on whether node is managed by XClarity
"""
try:
hardware_id = get_server_hardware_id(node)
return xclarity_client.is_node_managed(hardware_id)
except exception.MissingParameterValue:
return False
class XClarityError(exception.IronicException):
_msg_fmt = _("XClarity exception occurred. Error: %(error)s")

View File

@ -0,0 +1,219 @@
# Copyright 2017 Lenovo, Inc.
#
# 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.
from ironic_lib import metrics_utils
from oslo_log import log as logging
from oslo_utils import importutils
from ironic.common import boot_devices
from ironic.common import exception
from ironic.common.i18n import _
from ironic.conductor import task_manager
from ironic.drivers import base
from ironic.drivers.modules.xclarity import common
LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
xclarity_client_exceptions = importutils.try_import(
'xclarity_client.exceptions')
BOOT_DEVICE_MAPPING_TO_XCLARITY = {
boot_devices.PXE: 'PXE Network',
boot_devices.DISK: 'Hard Disk 0',
boot_devices.CDROM: 'CD/DVD Rom',
boot_devices.BIOS: 'Boot To F1'
}
SUPPORTED_BOOT_DEVICES = [
boot_devices.PXE,
boot_devices.DISK,
boot_devices.CDROM,
boot_devices.BIOS,
]
class XClarityManagement(base.ManagementInterface):
def __init__(self):
super(XClarityManagement, self).__init__()
self.xclarity_client = common.get_xclarity_client()
def get_properties(self):
return common.COMMON_PROPERTIES
@METRICS.timer('XClarityManagement.validate')
def validate(self, task):
"""It validates if the node is being used by XClarity.
:param task: a task from TaskManager.
"""
common.is_node_managed_by_xclarity(self.xclarity_client, task.node)
@METRICS.timer('XClarityManagement.get_supported_boot_devices')
def get_supported_boot_devices(self, task):
"""Gets a list of the supported boot devices.
:param task: a task from TaskManager.
:returns: A list with the supported boot devices defined
in :mod:`ironic.common.boot_devices`.
"""
return SUPPORTED_BOOT_DEVICES
def _validate_supported_boot_device(self, task, boot_device):
"""It validates if the boot device is supported by XClarity.
:param task: a task from TaskManager.
:param boot_device: the boot device, one of [PXE, DISK, CDROM, BIOS]
:raises: InvalidParameterValue if the boot device is not supported.
"""
if boot_device not in SUPPORTED_BOOT_DEVICES:
raise exception.InvalidParameterValue(
_("Unsupported boot device %(device)s for node: %(node)s ")
% {"device": boot_device, "node": task.node.uuid}
)
@METRICS.timer('XClarityManagement.get_boot_device')
def get_boot_device(self, task):
"""Get the current boot device for the task's node.
:param task: a task from TaskManager.
:returns: a dictionary containing:
:boot_device: the boot device, one of [PXE, DISK, CDROM, BIOS]
:persistent: Whether the boot device will persist or not
:raises: InvalidParameterValue if the boot device is unknown
:raises: XClarityError if the communication with XClarity fails
"""
server_hardware_id = common.get_server_hardware_id(task.node)
try:
boot_info = (
self.xclarity_client.get_node_all_boot_info(
server_hardware_id)
)
except xclarity_client_exceptions.XClarityError as xclarity_exc:
LOG.error(
"Error getting boot device from XClarity for node %(node)s. "
"Error: %(error)s", {'node': task.node.uuid,
'error': xclarity_exc})
raise common.XClarityError(error=xclarity_exc)
persistent = False
primary = None
boot_order = boot_info['bootOrder']['bootOrderList']
for item in boot_order:
current = item.get('currentBootOrderDevices', None)
boot_type = item.get('bootType', None)
if boot_type == "SingleUse":
persistent = False
primary = current[0]
if primary != 'None':
boot_device = {'boot_device': primary,
'persistent': persistent}
self._validate_whether_supported_boot_device(primary)
return boot_device
elif boot_type == "Permanent":
persistent = True
boot_device = {'boot_device': current[0],
'persistent': persistent}
self._validate_supported_boot_device(task, primary)
return boot_device
@METRICS.timer('XClarityManagement.set_boot_device')
@task_manager.require_exclusive_lock
def set_boot_device(self, task, device, persistent=False):
"""Sets the boot device for a node.
:param task: a task from TaskManager.
:param device: the boot device, one of the supported devices
listed in :mod:`ironic.common.boot_devices`.
:param persistent: Boolean value. True if the boot device will
persist to all future boots, False if not.
Default: False.
:raises: InvalidParameterValue if an invalid boot device is
specified.
:raises: XClarityError if the communication with XClarity fails
"""
self._validate_supported_boot_device(task=task, boot_device=device)
server_hardware_id = task.node.driver_info.get('server_hardware_id')
LOG.debug("Setting boot device to %(device)s for node %(node)s",
{"device": device, "node": task.node.uuid})
self._set_boot_device(task, server_hardware_id, device,
singleuse=not persistent)
@METRICS.timer('XClarityManagement.get_sensors_data')
def get_sensors_data(self, task):
"""Get sensors data.
:param task: a TaskManager instance.
:raises: NotImplementedError
"""
raise NotImplementedError()
def _translate_ironic_to_xclarity(self, boot_device):
"""Translates Ironic boot options to Xclarity boot options.
:param boot_device: Ironic boot_device
:returns: Translated XClarity boot_device.
"""
return BOOT_DEVICE_MAPPING_TO_XCLARITY.get(boot_device)
def _set_boot_device(self, task, server_hardware_id,
new_primary_boot_device, singleuse=False):
"""Set the current boot device for xclarity
:param server_hardware_id: the uri of the server hardware in XClarity
:param new_primary_boot_device: boot device to be set
:param task: a TaskManager instance.
:param singleuse: if this device will be used only once at next boot
"""
boot_info = self.xclarity_client.get_node_all_boot_info(
server_hardware_id)
xclarity_boot_device = self._translate_ironic_to_xclarity(
new_primary_boot_device)
current = []
LOG.debug(
("Setting boot device to %(device)s for XClarity "
"node %(node)s"),
{'device': xclarity_boot_device, 'node': task.node.uuid}
)
for item in boot_info['bootOrder']['bootOrderList']:
if singleuse and item['bootType'] == 'SingleUse':
item['currentBootOrderDevices'][0] = xclarity_boot_device
elif not singleuse and item['bootType'] == 'Permanent':
current = item['currentBootOrderDevices']
if xclarity_boot_device == current[0]:
return
if xclarity_boot_device in current:
current.remove(xclarity_boot_device)
current.insert(0, xclarity_boot_device)
item['currentBootOrderDevices'] = current
try:
self.xclarity_client.set_node_boot_info(server_hardware_id,
boot_info,
xclarity_boot_device,
singleuse)
except xclarity_client_exceptions.XClarityError as xclarity_exc:
LOG.error(
('Error setting boot device %(boot_device)s for the XClarity '
'node %(node)s. Error: %(error)s'),
{'boot_device': xclarity_boot_device, 'node': task.node.uuid,
'error': xclarity_exc}
)
raise common.XClarityError(error=xclarity_exc)

View File

@ -0,0 +1,112 @@
# Copyright 2017 Lenovo, Inc.
#
# 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.
from ironic_lib import metrics_utils
from oslo_log import log as logging
from oslo_utils import importutils
from ironic.common import states
from ironic.conductor import task_manager
from ironic.drivers import base
from ironic.drivers.modules.xclarity import common
LOG = logging.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__)
xclarity_client_exceptions = importutils.try_import(
'xclarity_client.exceptions')
class XClarityPower(base.PowerInterface):
def __init__(self):
super(XClarityPower, self).__init__()
self.xclarity_client = common.get_xclarity_client()
def get_properties(self):
return common.get_properties()
@METRICS.timer('XClarityPower.validate')
def validate(self, task):
"""It validates if the node is being used by XClarity.
:param task: a task from TaskManager.
"""
common.is_node_managed_by_xclarity(self.xclarity_client, task.node)
@METRICS.timer('XClarityPower.get_power_state')
def get_power_state(self, task):
"""Gets the current power state.
:param task: a TaskManager instance.
:returns: one of :mod:`ironic.common.states` POWER_OFF,
POWER_ON or ERROR.
:raises: XClarityError if fails to retrieve power state of XClarity
resource
"""
server_hardware_id = common.get_server_hardware_id(task.node)
try:
power_state = self.xclarity_client.get_node_power_status(
server_hardware_id)
except xclarity_client_exceptions.XClarityException as xclarity_exc:
LOG.error(
("Error getting power state for node %(node)s. Error: "
"%(error)s"),
{'node': task.node.uuid, 'error': xclarity_exc}
)
raise common.XClarityError(error=xclarity_exc)
return common.translate_xclarity_power_state(power_state)
@METRICS.timer('XClarityPower.set_power_state')
@task_manager.require_exclusive_lock
def set_power_state(self, task, power_state):
"""Turn the current power state on or off.
:param task: a TaskManager instance.
:param power_state: The desired power state POWER_ON, POWER_OFF or
REBOOT from :mod:`ironic.common.states`.
:raises: InvalidParameterValue if an invalid power state was specified.
:raises: XClarityError if XClarity fails setting the power state.
"""
if power_state == states.REBOOT:
target_power_state = self.get_power_state(task)
if target_power_state == states.POWER_OFF:
power_state = states.POWER_ON
server_hardware_id = common.get_server_hardware_id(task.node)
LOG.debug("Setting power state of node %(node_uuid)s to "
"%(power_state)s",
{'node_uuid': task.node.uuid, 'power_state': power_state})
try:
self.xclarity_client.set_node_power_status(server_hardware_id,
power_state)
except xclarity_client_exceptions.XClarityError as xclarity_exc:
LOG.error(
"Error setting power state of node %(node_uuid)s to "
"%(power_state)s",
{'node_uuid': task.node.uuid, 'power_state': power_state})
raise common.XClarityError(error=xclarity_exc)
@METRICS.timer('XClarityPower.reboot')
@task_manager.require_exclusive_lock
def reboot(self, task):
"""Reboot the node
:param task: a TaskManager instance.
"""
self.set_power_state(task, states.REBOOT)

View File

@ -0,0 +1,35 @@
# Copyright 2017 Lenovo, Inc.
#
# 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.
"""
XClarity Driver and supporting meta-classes.
"""
from ironic.drivers import generic
from ironic.drivers.modules.xclarity import management
from ironic.drivers.modules.xclarity import power
class XClarityHardware(generic.GenericHardware):
"""XClarity hardware type. """
@property
def supported_management_interfaces(self):
"""List of supported management interfaces."""
return [management.XClarityManagement]
@property
def supported_power_interfaces(self):
"""List of supported power interfaces."""
return [power.XClarityPower]

View File

@ -491,6 +491,21 @@ def create_test_node_tag(**kw):
return dbapi.add_node_tag(tag['node_id'], tag['tag'])
def get_test_xclarity_properties():
return {
"cpu_arch": "x86_64",
"cpus": "8",
"local_gb": "10",
"memory_mb": "4096",
}
def get_test_xclarity_driver_info():
return {
'xclarity_hardware_id': 'fake_sh_id',
}
def get_test_node_trait(**kw):
return {
# TODO(mgoddard): Replace None below with the NodeTrait RPC object

View File

@ -0,0 +1,65 @@
# Copyright 2017 Lenovo, Inc.
# All Rights Reserved.
#
# 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_utils import importutils
from ironic.drivers.modules.xclarity import common
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.objects import utils as obj_utils
xclarity_exceptions = importutils.try_import('xclarity_client.exceptions')
xclarity_constants = importutils.try_import('xclarity_client.constants')
class XClarityCommonTestCase(db_base.DbTestCase):
def setUp(self):
super(XClarityCommonTestCase, self).setUp()
self.config(manager_ip='1.2.3.4', group='xclarity')
self.config(username='user', group='xclarity')
self.config(password='password', group='xclarity')
self.node = obj_utils.create_test_node(
self.context, driver='fake-xclarity',
properties=db_utils.get_test_xclarity_properties(),
driver_info=db_utils.get_test_xclarity_driver_info(),
)
def test_get_server_hardware_id(self):
driver_info = self.node.driver_info
driver_info['xclarity_hardware_id'] = 'test'
self.node.driver_info = driver_info
result = common.get_server_hardware_id(self.node)
self.assertEqual(result, 'test')
@mock.patch.object(common, 'get_server_hardware_id',
spec_set=True, autospec=True)
@mock.patch.object(common, 'get_xclarity_client',
spec_set=True, autospec=True)
def test_check_node_managed_by_xclarity(self, mock_xc_client,
mock_validate_driver_info):
driver_info = self.node.driver_info
driver_info['xclarity_hardware_id'] = 'abcd'
self.node.driver_info = driver_info
xclarity_client = mock_xc_client()
mock_validate_driver_info.return_value = '12345'
common.is_node_managed_by_xclarity(xclarity_client,
self.node)
xclarity_client.is_node_managed.assert_called_once_with('12345')

View File

@ -0,0 +1,125 @@
# Copyright 2017 Lenovo, Inc.
# All Rights Reserved.
#
# 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 sys
import six
import mock
from oslo_utils import importutils
from ironic.common import boot_devices
from ironic.conductor import task_manager
from ironic.drivers.modules.xclarity import common
from ironic.drivers.modules.xclarity import management
from ironic.tests.unit.conductor import mgr_utils
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.objects import utils as obj_utils
xclarity_client_exceptions = importutils.try_import(
'xclarity_client.exceptions')
@mock.patch.object(common, 'get_xclarity_client', spect_set=True,
autospec=True)
class XClarityManagementDriverTestCase(db_base.DbTestCase):
def setUp(self):
super(XClarityManagementDriverTestCase, self).setUp()
self.config(enabled_hardware_types=['xclarity'],
enabled_power_interfaces=['xclarity'],
enabled_management_interfaces=['xclarity'])
mgr_utils.mock_the_extension_manager(
driver='xclarity', namespace='ironic.hardware.types')
self.node = obj_utils.create_test_node(
self.context,
driver='xclarity',
driver_info=db_utils.get_test_xclarity_driver_info())
@mock.patch.object(common, 'get_server_hardware_id',
spect_set=True, autospec=True)
def test_validate(self, mock_validate, mock_get_xc_client):
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.management.validate(task)
common.get_server_hardware_id(task.node)
mock_validate.assert_called_with(task.node)
def test_get_properties(self, mock_get_xc_client):
expected = common.REQUIRED_ON_DRIVER_INFO
self.assertItemsEqual(expected,
self.node.driver_info)
@mock.patch.object(management.XClarityManagement, 'get_boot_device',
return_value='pxe')
def test_set_boot_device(self, mock_get_boot_device,
mock_get_xc_client):
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.management.set_boot_device(task, 'pxe')
result = task.driver.management.get_boot_device(task)
self.assertEqual(result, 'pxe')
def test_set_boot_device_fail(self, mock_get_xc_client):
with task_manager.acquire(self.context, self.node.uuid) as task:
xclarity_client_exceptions.XClarityError = Exception
sys.modules['xclarity_client.exceptions'] = (
xclarity_client_exceptions)
if 'ironic.drivers.modules.xclarity' in sys.modules:
six.moves.reload_module(
sys.modules['ironic.drivers.modules.xclarity'])
ex = common.XClarityError('E')
mock_get_xc_client.return_value.set_node_boot_info.side_effect = ex
self.assertRaises(common.XClarityError,
task.driver.management.set_boot_device,
task,
"pxe")
def test_get_supported_boot_devices(self, mock_get_xc_client):
with task_manager.acquire(self.context, self.node.uuid) as task:
expected = [boot_devices.PXE, boot_devices.BIOS,
boot_devices.DISK, boot_devices.CDROM]
self.assertItemsEqual(
expected,
task.driver.management.get_supported_boot_devices(task))
@mock.patch.object(
management.XClarityManagement,
'get_boot_device',
return_value={'boot_device': 'pxe', 'persistent': False})
def test_get_boot_device(self, mock_get_boot_device, mock_get_xc_client):
reference = {'boot_device': 'pxe', 'persistent': False}
with task_manager.acquire(self.context, self.node.uuid) as task:
expected_boot_device = task.driver.management.get_boot_device(
task=task)
self.assertEqual(reference, expected_boot_device)
def test_get_boot_device_fail(self, mock_xc_client):
with task_manager.acquire(self.context, self.node.uuid) as task:
xclarity_client_exceptions.XClarityError = Exception
sys.modules['xclarity_client.exceptions'] = (
xclarity_client_exceptions)
if 'ironic.drivers.modules.xclarity' in sys.modules:
six.moves.reload_module(
sys.modules['ironic.drivers.modules.xclarity'])
ex = common.XClarityError('E')
mock_xc_client.return_value.get_node_all_boot_info.side_effect = ex
self.assertRaises(
common.XClarityError,
task.driver.management.get_boot_device,
task)

View File

@ -0,0 +1,113 @@
# Copyright 2017 Lenovo, Inc.
# All Rights Reserved.
#
# 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.
STATE_POWER_ON = "power on"
STATE_POWER_OFF = "power off"
STATE_POWERING_ON = "power on"
STATE_POWERING_OFF = "power on"
import sys
import six
import mock
from oslo_utils import importutils
from ironic.common import states
from ironic.conductor import task_manager
from ironic.drivers.modules.xclarity import common
from ironic.drivers.modules.xclarity import power
from ironic.tests.unit.conductor import mgr_utils
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.db import utils as db_utils
from ironic.tests.unit.objects import utils as obj_utils
xclarity_constants = importutils.try_import('xclarity_client.constants')
xclarity_client_exceptions = importutils.try_import(
'xclarity_client.exceptions')
@mock.patch.object(common, 'get_xclarity_client',
spect_set=True, autospec=True)
class XClarityPowerDriverTestCase(db_base.DbTestCase):
def setUp(self):
super(XClarityPowerDriverTestCase, self).setUp()
self.config(enabled_hardware_types=['xclarity'],
enabled_power_interfaces=['xclarity'],
enabled_management_interfaces=['xclarity'])
mgr_utils.mock_the_extension_manager(
driver='xclarity', namespace='ironic.hardware.types')
self.node = obj_utils.create_test_node(
self.context,
driver='xclarity',
driver_info=db_utils.get_test_xclarity_driver_info())
def test_get_properties(self, mock_get_xc_client):
expected = common.REQUIRED_ON_DRIVER_INFO
self.assertItemsEqual(expected,
self.node.driver_info)
@mock.patch.object(common, 'get_server_hardware_id',
spect_set=True, autospec=True)
def test_validate(self, mock_validate_driver_info, mock_get_xc_client):
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.power.validate(task)
common.get_server_hardware_id(task.node)
mock_validate_driver_info.assert_called_with(task.node)
@mock.patch.object(power.XClarityPower, 'get_power_state',
return_value=STATE_POWER_ON)
def test_get_power_state(self, mock_get_power_state, mock_get_xc_client):
with task_manager.acquire(self.context, self.node.uuid) as task:
result = power.XClarityPower.get_power_state(task)
self.assertEqual(STATE_POWER_ON, result)
def test_get_power_state_fail(self, mock_xc_client):
with task_manager.acquire(self.context, self.node.uuid) as task:
xclarity_client_exceptions.XClarityError = Exception
sys.modules['xclarity_client.exceptions'] = (
xclarity_client_exceptions)
if 'ironic.drivers.modules.xclarity' in sys.modules:
six.moves.reload_module(
sys.modules['ironic.drivers.modules.xclarity'])
ex = common.XClarityError('E')
mock_xc_client.return_value.get_node_power_status.side_effect = ex
self.assertRaises(common.XClarityError,
task.driver.power.get_power_state,
task)
@mock.patch.object(power.XClarityPower, 'get_power_state',
return_value=states.POWER_ON)
def test_set_power(self, mock_set_power_state, mock_get_xc_client):
with task_manager.acquire(self.context, self.node.uuid) as task:
task.driver.power.set_power_state(task, states.POWER_ON)
expected = task.driver.power.get_power_state(task)
self.assertEqual(expected, states.POWER_ON)
def test_set_power_fail(self, mock_xc_client):
with task_manager.acquire(self.context, self.node.uuid) as task:
xclarity_client_exceptions.XClarityError = Exception
sys.modules['xclarity_client.exceptions'] = (
xclarity_client_exceptions)
if 'ironic.drivers.modules.xclarity' in sys.modules:
six.moves.reload_module(
sys.modules['ironic.drivers.modules.xclarity'])
ex = common.XClarityError('E')
mock_xc_client.return_value.set_node_power_status.side_effect = ex
self.assertRaises(common.XClarityError,
task.driver.power.set_power_state,
task, states.POWER_OFF)

View File

@ -0,0 +1,49 @@
# Copyright 2017 Lenovo, Inc.
#
# 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.
"""
Test class for XClarity Driver
"""
from ironic.conductor import task_manager
from ironic.drivers.modules import agent
from ironic.drivers.modules import iscsi_deploy
from ironic.drivers.modules import pxe
from ironic.drivers.xclarity import management as xc_management
from ironic.drivers.xclarity import power as xc_power
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.objects import utils as obj_utils
class XClarityHardwareTestCase(db_base.DbTestCase):
def setUp(self):
super(XClarityHardwareTestCase, self).setUp()
self.config(enabled_hardware_types=['xclarity'],
enabled_power_interfaces=['xclarity'],
enabled_management_interfaces=['xclarity'])
def test_default_interfaces(self):
node = obj_utils.create_test_node(self.context, driver='xclarity')
with task_manager.acquire(self.context, node.id) as task:
self.assertIsInstance(task.driver.boot,
pxe.PXEBoot)
self.assertIsInstance(task.driver.deploy,
iscsi_deploy.ISCSIDeploy,
agent.AgentDeploy)
self.assertIsInstance(task.driver.management,
xc_management.XClarityManagement)
self.assertIsInstance(task.driver.power,
xc_power.XClarityPower)

View File

@ -162,3 +162,21 @@ SUSHY_CONSTANTS_SPEC = (
'BOOT_SOURCE_ENABLED_CONTINUOUS',
'BOOT_SOURCE_ENABLED_ONCE',
)
XCLARITY_SPEC = (
'client',
'states',
'exceptions',
'models',
'utils',
)
XCLARITY_CLIENT_CLS_SPEC = (
)
XCLARITY_STATES_SPEC = (
'STATE_POWERING_OFF',
'STATE_POWERING_ON',
'STATE_POWER_OFF',
'STATE_POWER_ON',
)

View File

@ -253,3 +253,21 @@ if not sushy:
if 'ironic.drivers.modules.redfish' in sys.modules:
six.moves.reload_module(
sys.modules['ironic.drivers.modules.redfish'])
xclarity_client = importutils.try_import('xclarity_client')
if not xclarity_client:
xclarity_client = mock.MagicMock(spec_set=mock_specs.XCLARITY_SPEC)
sys.modules['xclarity_client'] = xclarity_client
sys.modules['xclarity_client.client'] = xclarity_client.client
states = mock.MagicMock(
spec_set=mock_specs.XCLARITY_STATES_SPEC,
STATE_POWER_ON="power on",
STATE_POWER_OFF="power off",
STATE_POWERING_ON="powering_on",
STATE_POWERING_OFF="powering_off")
sys.modules['xclarity_client.states'] = states
sys.modules['xclarity_client.exceptions'] = xclarity_client.exceptions
sys.modules['xclarity_client.utils'] = xclarity_client.utils
xclarity_client.exceptions.XClarityException = type('XClarityException',
(Exception,), {})
sys.modules['xclarity_client.models'] = xclarity_client.models

View File

@ -0,0 +1,9 @@
---
features:
- |
Adds the new ``xclarity`` hardware type for managing Lenovo server
hardware with the following interfaces:
* management: ``xclarity``
* power: ``xclarity``

View File

@ -127,6 +127,7 @@ ironic.hardware.interfaces.management =
oneview = ironic.drivers.modules.oneview.management:OneViewManagement
redfish = ironic.drivers.modules.redfish.management:RedfishManagement
ucsm = ironic.drivers.modules.ucs.management:UcsManagement
xclarity = ironic.drivers.modules.xclarity.management:XClarityManagement
ironic.hardware.interfaces.network =
flat = ironic.drivers.modules.network.flat:FlatNetwork
@ -144,6 +145,7 @@ ironic.hardware.interfaces.power =
redfish = ironic.drivers.modules.redfish.power:RedfishPower
snmp = ironic.drivers.modules.snmp:SNMPPower
ucsm = ironic.drivers.modules.ucs.power:Power
xclarity = ironic.drivers.modules.xclarity.power:XClarityPower
ironic.hardware.interfaces.raid =
agent = ironic.drivers.modules.agent:AgentRAID
@ -178,6 +180,7 @@ ironic.hardware.types =
oneview = ironic.drivers.oneview:OneViewHardware
redfish = ironic.drivers.redfish:RedfishHardware
snmp = ironic.drivers.snmp:SNMPHardware
xclarity = ironic.drivers.xclarity:XClarityHardware
ironic.database.migration_backend =
sqlalchemy = ironic.db.sqlalchemy.migration