Merge "Implements SeaMicro VendorPassThru functionality"

This commit is contained in:
Jenkins
2014-03-04 15:57:32 +00:00
committed by Gerrit Code Review
5 changed files with 477 additions and 8 deletions

View File

@@ -344,3 +344,8 @@ class NodeLocked(TemporaryFailure):
class NoFreeConductorWorker(TemporaryFailure):
message = _('Requested action cannot be performed due to lack of free '
'conductor workers.')
code = 503 # Service Unavailable (temporary).
class VendorPassthruException(IronicException):
pass

View File

@@ -82,6 +82,7 @@ class FakeSeaMicroDriver(base.BaseDriver):
self.power = seamicro.Power()
self.deploy = fake.FakeDeploy()
self.rescue = self.deploy
a = fake.FakeVendorA()
b = fake.FakeVendorB()
self.vendor = fake.MultipleVendorInterface(a, b)
self.seamicro_vendor = seamicro.VendorPassthru()
self.pxe_vendor = pxe.VendorPassthru()
self.vendor = seamicro.SeaMicroPXEMultipleVendorInterface(
self.seamicro_vendor, self.pxe_vendor)

View File

@@ -16,6 +16,8 @@ Ironic SeaMicro interfaces.
Provides basic power control of servers in SeaMicro chassis via
python-seamicroclient.
Provides vendor passthru methods for SeaMicro specific functionality.
"""
from oslo.config import cfg
@@ -47,6 +49,11 @@ CONF.register_opts(opts, opt_group)
LOG = logging.getLogger(__name__)
VENDOR_PASSTHRU_METHODS = ['attach_volume', 'set_boot_device',
'set_node_vlan_id']
VALID_BOOT_DEVICES = ['pxe', 'disk']
def _get_client(*args, **kwargs):
"""Creates the python-seamicro_client
@@ -106,6 +113,13 @@ def _get_server(driver_info):
return s_client.servers.get(driver_info['server_id'])
def _get_volume(driver_info, volume_id):
"""Get volume from volume_id."""
s_client = _get_client(**driver_info)
return s_client.volumes.get(volume_id)
def _get_power_status(node):
"""Get current power state of this node
@@ -243,6 +257,46 @@ def _reboot(node, timeout=None):
return state[0]
def _validate_volume(driver_info, volume_id):
"""Validates if volume is in Storage pools designated for ironic."""
volume = _get_volume(driver_info, volume_id)
# Check if the ironic <scard>/ironic-<pool_id>/<volume_id> naming scheme
# is present in volume id
try:
pool_id = volume.id.split('/')[1].lower()
except IndexError:
pool_id = ""
if "ironic-" in pool_id:
return True
else:
raise exception.InvalidParameterValue(_(
"Invalid volume id specified"))
def _get_pools(driver_info, filters=None):
"""Get SeaMicro storage pools matching given filters."""
s_client = _get_client(**driver_info)
return s_client.pools.list(filters=filters)
def _create_volume(driver_info, volume_size):
"""Create volume in the SeaMicro storage pools designated for ironic."""
ironic_pools = _get_pools(driver_info, filters={'id': 'ironic-'})
if ironic_pools is None:
raise exception.VendorPassthruException(_(
"No storage pools found for ironic"))
least_used_pool = sorted(ironic_pools,
key=lambda x: x.freeSize)[0]
return _get_client(**driver_info).volumes.create(volume_size,
least_used_pool)
class Power(base.PowerInterface):
"""SeaMicro Power Interface.
@@ -307,3 +361,141 @@ class Power(base.PowerInterface):
if state != states.POWER_ON:
raise exception.PowerStateFailure(pstate=states.POWER_ON)
class VendorPassthru(base.VendorInterface):
"""SeaMicro vendor-specific methods."""
def validate(self, node, **kwargs):
method = kwargs['method']
if method in VENDOR_PASSTHRU_METHODS:
return True
else:
raise exception.InvalidParameterValue(_(
"Unsupported method (%s) passed to SeaMicro driver.")
% method)
def vendor_passthru(self, task, node, **kwargs):
"""Dispatch vendor specific method calls."""
method = kwargs['method']
if method in VENDOR_PASSTHRU_METHODS:
return getattr(self, "_" + method)(task, node, **kwargs)
def _set_node_vlan_id(self, task, node, **kwargs):
"""Sets a untagged vlan id for NIC 0 of node.
@kwargs vlan_id: id of untagged vlan for NIC 0 of node
"""
vlan_id = kwargs.get('vlan_id')
if not vlan_id:
raise exception.InvalidParameterValue(_("No vlan id provided"))
seamicro_info = _parse_driver_info(node)
try:
server = _get_server(seamicro_info)
# remove current vlan for server
if len(server.nic['0']['untaggedVlan']) > 0:
server.unset_untagged_vlan(server.nic['0']['untaggedVlan'])
server = server.refresh(5)
server.set_untagged_vlan(vlan_id)
except seamicro_client_exception.ClientException as ex:
LOG.error(_("SeaMicro client exception: %s"), ex.message)
raise exception.VendorPassthruException(message=ex.message)
properties = node.properties
properties['seamicro_vlan_id'] = vlan_id
node.properties = properties
node.save(task.context)
def _attach_volume(self, task, node, **kwargs):
"""Attach volume from SeaMicro storage pools for ironic to node.
If kwargs['volume_id'] not given, Create volume in SeaMicro
storage pool and attach to node.
@kwargs volume_id: id of pre-provisioned volume that is to be attached
as root volume of node
@kwargs volume_size: size of new volume to be created and attached
as root volume of node
"""
seamicro_info = _parse_driver_info(node)
volume_id = kwargs.get('volume_id')
if volume_id is None:
volume_size = kwargs.get('volume_size')
if volume_size is None:
raise exception.InvalidParameterValue(
_("No volume size provided for creating volume"))
volume_id = _create_volume(seamicro_info, volume_size)
if _validate_volume(seamicro_info, volume_id):
try:
server = _get_server(seamicro_info)
server.detach_volume()
server = server.refresh(5)
server.attach_volume(volume_id)
except seamicro_client_exception.ClientException as ex:
LOG.error(_("SeaMicro client exception: %s"), ex.message)
raise exception.VendorPassthruException(message=ex.message)
properties = node.properties
properties['seamicro_volume_id'] = volume_id
node.properties = properties
node.save(task.context)
def _set_boot_device(self, task, node, **kwargs):
"""Set the boot device of the node.
@kwargs boot_device: Boot device. One of [pxe, disk]
"""
boot_device = kwargs.get('boot_device')
if boot_device is None:
raise exception.InvalidParameterValue(_("No boot device provided"))
if boot_device not in VALID_BOOT_DEVICES:
raise exception.InvalidParameterValue(_("Boot device is invalid"))
seamicro_info = _parse_driver_info(node)
try:
server = _get_server(seamicro_info)
if boot_device == "disk":
boot_device = "hd0"
server.set_boot_order(boot_device)
except seamicro_client_exception.ClientException as ex:
LOG.error(_("set_boot_device error: %s"), ex.message)
raise exception.VendorPassthruException(message=ex.message)
class SeaMicroPXEMultipleVendorInterface(base.VendorInterface):
"""Wrapper around SeaMicro and PXE VendorInterfaces."""
def __init__(self, seamicro_vendor, pxe_vendor):
self.seamicro_vendor = seamicro_vendor
self.pxe_vendor = pxe_vendor
self.mapping = dict((method, self.seamicro_vendor)
for method in VENDOR_PASSTHRU_METHODS)
def _map(self, **kwargs):
"""Use SeaMicro interface if method is supported by SeaMicro,
else use PXE.
:returns: an instance of a VendorInterface
:raises: InvalidParameterValue if **kwargs does not contain 'method'
or if the method can not be mapped to an interface.
"""
method = kwargs.get('method')
return self.mapping.get(method) or self.pxe_vendor
def validate(self, *args, **kwargs):
"""Call validate on the appropriate interface only."""
route = self._map(**kwargs)
route.validate(*args, **kwargs)
def vendor_passthru(self, task, node, **kwargs):
"""Call vendor_passthru on the appropriate interface only."""
route = self._map(**kwargs)
return route.vendor_passthru(task, node, **kwargs)

View File

@@ -93,4 +93,7 @@ class PXEAndSeaMicroDriver(base.BaseDriver):
self.power = seamicro.Power()
self.deploy = pxe.PXEDeploy()
self.rescue = self.deploy
self.vendor = pxe.VendorPassthru()
self.seamicro_vendor = seamicro.VendorPassthru()
self.pxe_vendor = pxe.VendorPassthru()
self.vendor = seamicro.SeaMicroPXEMultipleVendorInterface(
self.seamicro_vendor, self.pxe_vendor)

View File

@@ -12,7 +12,10 @@
"""Test class for Ironic SeaMicro driver."""
import uuid
import mock
from seamicroclient import exceptions as seamicro_client_exception
from ironic.common import driver_factory
from ironic.common import exception
@@ -31,6 +34,7 @@ INFO_DICT = db_utils.get_test_seamicro_info()
class Fake_Server():
def __init__(self, active=False, *args, **kwargs):
self.active = active
self.nic = {'0': {'untaggedVlan': ''}}
def power_on(self):
self.active = True
@@ -41,6 +45,34 @@ class Fake_Server():
def reset(self):
self.active = True
def set_untagged_vlan(self, vlan_id):
return
def attach_volume(self, volume_id):
return
def detach_volume(self):
return
def set_boot_order(self, boot_order):
return
def refresh(self, wait=0):
return self
class Fake_Volume():
def __init__(self, id=None, *args, **kwargs):
if id is None:
self.id = "%s/%s/%s" % ("0", "ironic-p6-6", str(uuid.uuid4()))
else:
self.id = id
class Fake_Pool():
def __init__(self, freeSize=None, *args, **kwargs):
self.freeSize = freeSize
class SeaMicroValidateParametersTestCase(base.TestCase):
@@ -96,10 +128,19 @@ class SeaMicroPrivateMethodsTestCase(base.TestCase):
def setUp(self):
super(SeaMicroPrivateMethodsTestCase, self).setUp()
self.node = db_utils.get_test_node(driver='fake_seamicro',
driver_info=INFO_DICT)
n = {
'driver': 'fake_seamicro',
'driver_info': INFO_DICT
}
self.dbapi = dbapi.get_instance()
self.node = self._create_test_node(**n)
self.Server = Fake_Server
self.Volume = Fake_Volume
self.Pool = Fake_Pool
def _create_test_node(self, **kwargs):
n = db_utils.get_test_node(**kwargs)
return self.dbapi.create_node(n)
@mock.patch.object(seamicro, "_get_server")
def test__get_power_status_on(self, mock_get_server):
@@ -168,6 +209,43 @@ class SeaMicroPrivateMethodsTestCase(base.TestCase):
pstate = seamicro._reboot(self.node, timeout=2)
self.assertEqual(states.ERROR, pstate)
@mock.patch.object(seamicro, "_get_volume")
def test__validate_fail(self, mock_get_volume):
info = seamicro._parse_driver_info(self.node)
volume_id = "0/p6-6/vol1"
volume = self.Volume()
volume.id = volume_id
mock_get_volume.return_value = volume
self.assertRaises(exception.InvalidParameterValue,
seamicro._validate_volume, info, volume_id)
@mock.patch.object(seamicro, "_get_volume")
def test__validate_good(self, mock_get_volume):
info = seamicro._parse_driver_info(self.node)
volume = self.Volume()
mock_get_volume.return_value = volume
valid = seamicro._validate_volume(info, volume.id)
self.assertEqual(valid, True)
@mock.patch.object(seamicro, "_get_pools")
def test__create_volume_fail(self, mock_get_pools):
info = seamicro._parse_driver_info(self.node)
mock_get_pools.return_value = None
self.assertRaises(exception.IronicException,
seamicro._create_volume,
info, 2)
@mock.patch.object(seamicro, "_get_pools")
@mock.patch.object(seamicro, "_get_client")
def test__create_volume_good(self, mock_get_client, mock_get_pools):
info = seamicro._parse_driver_info(self.node)
pools = [self.Pool(1), self.Pool(6), self.Pool(5)]
get_pools_patcher = mock.patch.object(mock_get_client, "volume.create")
get_pools_patcher.start()
mock_get_pools.return_value = pools
seamicro._create_volume(info, 2)
get_pools_patcher.stop()
class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
@@ -178,7 +256,7 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
self.node = db_utils.get_test_node(driver='fake_seamicro',
driver_info=INFO_DICT)
self.dbapi = dbapi.get_instance()
self.dbapi.create_node(self.node)
self.node = self.dbapi.create_node(self.node)
self.parse_drv_info_patcher = mock.patch.object(seamicro,
'_parse_driver_info')
self.parse_drv_info_mock = None
@@ -186,6 +264,7 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
self.get_server_mock = None
self.Server = Fake_Server
self.Volume = Fake_Volume
@mock.patch.object(seamicro, '_reboot')
def test_reboot(self, mock_reboot):
@@ -267,3 +346,192 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase):
task, self.node, states.POWER_OFF)
mock_power_off.assert_called_once_with(self.node)
def test_vendor_passthru_validate_good(self):
with task_manager.acquire(self.context, [self.node['uuid']],
shared=True) as task:
for method in seamicro.VENDOR_PASSTHRU_METHODS:
task.resources[0].driver.vendor.validate(self.node,
**{'method': method})
def test_vendor_passthru_validate_fail(self):
with task_manager.acquire(self.context, [self.node['uuid']],
shared=True) as task:
self.assertRaises(exception.InvalidParameterValue,
task.resources[0].driver.vendor.validate,
task, self.node, **{'method': 'invalid_method'})
@mock.patch.object(seamicro, '_get_server')
def test_set_node_vlan_id_good(self, mock_get_server):
info = seamicro._parse_driver_info(self.node)
vlan_id = "12"
mock_get_server.return_value = self.Server(active="true")
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
kwargs = {'vlan_id': vlan_id, 'method': 'set_node_vlan_id'}
task.resources[0].driver.vendor.\
vendor_passthru(task, self.node, **kwargs)
mock_get_server.assert_called_once_with(info)
def test_set_node_vlan_id_no_input(self):
info = seamicro._parse_driver_info(self.node)
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
self.assertRaises(exception.InvalidParameterValue,
task.resources[0].driver.vendor.
vendor_passthru, task, self.node,
**{'method': 'set_node_vlan_id'})
@mock.patch.object(seamicro, '_get_server')
def test_set_node_vlan_id_fail(self, mock_get_server):
def fake_set_untagged_vlan(self, **kwargs):
raise seamicro_client_exception.ClientException(500)
info = seamicro._parse_driver_info(self.node)
vlan_id = "12"
server = self.Server(active="true")
server.set_untagged_vlan = fake_set_untagged_vlan
mock_get_server.return_value = server
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
kwargs = {'vlan_id': vlan_id, 'method': 'set_node_vlan_id'}
self.assertRaises(exception.IronicException,
task.resources[0].driver.vendor.
vendor_passthru, task, self.node, **kwargs)
mock_get_server.assert_called_once_with(info)
@mock.patch.object(seamicro, '_get_server')
@mock.patch.object(seamicro, '_validate_volume')
def test_attach_volume_with_volume_id_good(self, mock_validate_volume,
mock_get_server):
info = seamicro._parse_driver_info(self.node)
volume_id = '0/ironic-p6-1/vol1'
mock_validate_volume.return_value = True
mock_get_server.return_value = self.Server(active="true")
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
kwargs = {'volume_id': volume_id, 'method': 'attach_volume'}
task.resources[0].driver.vendor.\
vendor_passthru(task, self.node, **kwargs)
mock_get_server.assert_called_once_with(info)
@mock.patch.object(seamicro, '_get_server')
@mock.patch.object(seamicro, '_get_volume')
def test_attach_volume_with_invalid_volume_id_fail(self,
mock_get_volume,
mock_get_server):
info = seamicro._parse_driver_info(self.node)
volume_id = '0/p6-1/vol1'
mock_get_volume.return_value = self.Volume(volume_id)
mock_get_server.return_value = self.Server(active="true")
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
kwargs = {'volume_id': volume_id, 'method': 'attach_volume'}
self.assertRaises(exception.InvalidParameterValue,
task.resources[0].driver.vendor.
vendor_passthru, task, self.node,
**kwargs)
@mock.patch.object(seamicro, '_get_server')
@mock.patch.object(seamicro, '_validate_volume')
def test_attach_volume_fail(self, mock_validate_volume,
mock_get_server):
def fake_attach_volume(self, **kwargs):
raise seamicro_client_exception.ClientException(500)
info = seamicro._parse_driver_info(self.node)
volume_id = '0/p6-1/vol1'
mock_validate_volume.return_value = True
server = self.Server(active="true")
server.attach_volume = fake_attach_volume
mock_get_server.return_value = server
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
kwargs = {'volume_id': volume_id, 'method': 'attach_volume'}
self.assertRaises(exception.IronicException,
task.resources[0].driver.vendor.
vendor_passthru, task, self.node, **kwargs)
mock_get_server.assert_called_once_with(info)
@mock.patch.object(seamicro, '_get_server')
@mock.patch.object(seamicro, '_validate_volume')
@mock.patch.object(seamicro, '_create_volume')
def test_attach_volume_with_volume_size_good(self, mock_create_volume,
mock_validate_volume,
mock_get_server):
info = seamicro._parse_driver_info(self.node)
volume_id = '0/ironic-p6-1/vol1'
volume_size = 2
mock_create_volume.return_value = volume_id
mock_validate_volume.return_value = True
mock_get_server.return_value = self.Server(active="true")
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
kwargs = {'volume_size': volume_size, 'method': "attach_volume"}
task.resources[0].driver.vendor.\
vendor_passthru(task, self.node, **kwargs)
mock_get_server.assert_called_once_with(info)
mock_create_volume.assert_called_once_with(info, volume_size)
def test_attach_volume_with_no_input_fail(self):
info = seamicro._parse_driver_info(self.node)
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
self.assertRaises(exception.InvalidParameterValue,
task.resources[0].driver.vendor.
vendor_passthru, task, self.node,
**{'method': 'attach_volume'})
@mock.patch.object(seamicro, '_get_server')
def test_set_boot_device_good(self, mock_get_server):
info = seamicro._parse_driver_info(self.node)
boot_device = "disk"
mock_get_server.return_value = self.Server(active="true")
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
kwargs = {'boot_device': boot_device, 'method': 'set_boot_device'}
task.resources[0].driver.vendor.\
vendor_passthru(task, self.node, **kwargs)
mock_get_server.assert_called_once_with(info)
def test_set_boot_device_no_input(self):
info = seamicro._parse_driver_info(self.node)
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
self.assertRaises(exception.InvalidParameterValue,
task.resources[0].driver.vendor.
vendor_passthru, task, self.node,
**{'method': 'set_boot_device'})
@mock.patch.object(seamicro, '_get_server')
def test_set_boot_device_invalid_device_fail(self, mock_get_server):
info = seamicro._parse_driver_info(self.node)
boot_device = "invalid_device"
mock_get_server.return_value = self.Server(active="true")
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
kwargs = {'boot_device': boot_device, 'method': 'set_boot_device'}
self.assertRaises(exception.InvalidParameterValue,
task.resources[0].driver.vendor.
vendor_passthru, task, self.node, **kwargs)
@mock.patch.object(seamicro, '_get_server')
def test_set_boot_device_fail(self, mock_get_server):
def fake_set_boot_order(self, **kwargs):
raise seamicro_client_exception.ClientException(500)
info = seamicro._parse_driver_info(self.node)
boot_device = "pxe"
server = self.Server(active="true")
server.set_boot_order = fake_set_boot_order
mock_get_server.return_value = server
with task_manager.acquire(self.context, [info['uuid']],
shared=False) as task:
kwargs = {'boot_device': boot_device, 'method': 'set_boot_device'}
self.assertRaises(exception.IronicException,
task.resources[0].driver.vendor.
vendor_passthru, task, self.node, **kwargs)
mock_get_server.assert_called_once_with(info)