XenAPI: Add support for XenServer VMs
Ironic should be able to control power of the VMs hosted on a XenServer host. This change adds such support to the ssh driver. Blueprint: xenserver-power-control Change-Id: I30b4d0c3f2685170f8c4f6ec834e5395d6bb2499
This commit is contained in:
parent
93f5a12a99
commit
a31f3679b8
@ -118,8 +118,17 @@ CIMC driver
|
|||||||
|
|
||||||
OneView driver
|
OneView driver
|
||||||
--------------
|
--------------
|
||||||
|
=======
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
../drivers/oneview
|
../drivers/oneview
|
||||||
|
|
||||||
|
|
||||||
|
XenServer ssh driver
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
../drivers/xenserver
|
||||||
|
41
doc/source/drivers/xenserver.rst
Normal file
41
doc/source/drivers/xenserver.rst
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
.. _xenserver:
|
||||||
|
.. _bug 1498576: https://bugs.launchpad.net/diskimage-builder/+bug/1498576
|
||||||
|
|
||||||
|
=================
|
||||||
|
XenServer drivers
|
||||||
|
=================
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
XenServer drivers can be used to deploy hosts with Ironic by using XenServer
|
||||||
|
VMs to simulate bare metal nodes.
|
||||||
|
|
||||||
|
Ironic provides support via the ``pxe_ssh`` and ``agent_ssh`` drivers for
|
||||||
|
using a XenServer VM as a bare metal target and do provisioning on it. It
|
||||||
|
works by connecting via SSH into the XenServer host and running commands using
|
||||||
|
the 'xe' command.
|
||||||
|
|
||||||
|
This is particularly useful for deploying overclouds that use XenServer for VM
|
||||||
|
hosting as the Compute node must be run as a virtual machine on the XenServer
|
||||||
|
host it will be controlling. In this case, one VM per hypervisor needs to be
|
||||||
|
installed.
|
||||||
|
|
||||||
|
This support has been tested with XenServer 6.5.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
* Install the VMs using the "Other Install Media" template, which will ensure
|
||||||
|
that they are HVM guests
|
||||||
|
|
||||||
|
* Set the HVM guests to boot from network first
|
||||||
|
|
||||||
|
* If your generated initramfs does not have the fix for `bug 1498576`_,
|
||||||
|
disable the Xen PV drivers as a work around
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
xe vm-param-set uuid=<uuid> xenstore-data:vm-data="vm_data/disable_pf: 1"
|
||||||
|
|
||||||
|
|
@ -25,6 +25,7 @@ Currently supported environments are:
|
|||||||
Virsh (virsh)
|
Virsh (virsh)
|
||||||
VMware (vmware)
|
VMware (vmware)
|
||||||
Parallels (parallels)
|
Parallels (parallels)
|
||||||
|
XenServer (xenserver)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -71,7 +72,7 @@ REQUIRED_PROPERTIES = {
|
|||||||
"Required."),
|
"Required."),
|
||||||
'ssh_username': _("username to authenticate as. Required."),
|
'ssh_username': _("username to authenticate as. Required."),
|
||||||
'ssh_virt_type': _("virtualization software to use; one of vbox, virsh, "
|
'ssh_virt_type': _("virtualization software to use; one of vbox, virsh, "
|
||||||
"vmware, parallels. Required.")
|
"vmware, parallels, xenserver. Required.")
|
||||||
}
|
}
|
||||||
OTHER_PROPERTIES = {
|
OTHER_PROPERTIES = {
|
||||||
'ssh_key_contents': _("private key(s). One of this, ssh_key_filename, "
|
'ssh_key_contents': _("private key(s). One of this, ssh_key_filename, "
|
||||||
@ -107,6 +108,12 @@ def _get_boot_device_map(virt_type):
|
|||||||
boot_devices.PXE: 'net',
|
boot_devices.PXE: 'net',
|
||||||
boot_devices.CDROM: 'dvd',
|
boot_devices.CDROM: 'dvd',
|
||||||
}
|
}
|
||||||
|
elif virt_type == 'xenserver':
|
||||||
|
return {
|
||||||
|
boot_devices.DISK: 'c',
|
||||||
|
boot_devices.PXE: 'n',
|
||||||
|
boot_devices.CDROM: 'd',
|
||||||
|
}
|
||||||
elif virt_type == 'parallels':
|
elif virt_type == 'parallels':
|
||||||
return {
|
return {
|
||||||
boot_devices.DISK: 'hdd0',
|
boot_devices.DISK: 'hdd0',
|
||||||
@ -228,6 +235,32 @@ def _get_command_sets(virt_type):
|
|||||||
"{_BaseCmd_} list -i {_NodeName_} | "
|
"{_BaseCmd_} list -i {_NodeName_} | "
|
||||||
"awk '/^Boot order:/ {print $3}'"),
|
"awk '/^Boot order:/ {print $3}'"),
|
||||||
}
|
}
|
||||||
|
elif virt_type == 'xenserver':
|
||||||
|
return {
|
||||||
|
'base_cmd': 'LC_ALL=C /opt/xensource/bin/xe',
|
||||||
|
# Note(bobba): XenServer appears to have a condition where
|
||||||
|
# vm-start can return before the power-state
|
||||||
|
# has been updated to 'running'. Ironic
|
||||||
|
# expects the power-state to be updated
|
||||||
|
# immediately, so may find that power-state
|
||||||
|
# is still 'halted' and attempt to start the
|
||||||
|
# VM a second time. Sleep to avoid the race.
|
||||||
|
'start_cmd': 'vm-start uuid={_NodeName_} && sleep 10s',
|
||||||
|
'stop_cmd': 'vm-shutdown uuid={_NodeName_} force=true',
|
||||||
|
'list_all': "vm-list --minimal | tr ',' '\n'",
|
||||||
|
'list_running': (
|
||||||
|
"vm-list power-state=running --minimal |"
|
||||||
|
" tr ',' '\n'"),
|
||||||
|
'get_node_macs': (
|
||||||
|
"vif-list vm-uuid={_NodeName_}"
|
||||||
|
" params=MAC --minimal | tr ',' '\n'"),
|
||||||
|
'set_boot_device': (
|
||||||
|
"{_BaseCmd_} vm-param-set uuid={_NodeName_}"
|
||||||
|
" HVM-boot-params:order='{_BootDevice_}'"),
|
||||||
|
'get_boot_device': (
|
||||||
|
"{_BaseCmd_} vm-param-get uuid={_NodeName_}"
|
||||||
|
" --param-name=HVM-boot-params param-key=order | cut -b 1"),
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
raise exception.InvalidParameterValue(_(
|
raise exception.InvalidParameterValue(_(
|
||||||
"SSHPowerDriver '%(virt_type)s' is not a valid virt_type, ") %
|
"SSHPowerDriver '%(virt_type)s' is not a valid virt_type, ") %
|
||||||
|
@ -185,6 +185,10 @@ class SSHValidateParametersTestCase(db_base.DbTestCase):
|
|||||||
boot_map = ssh._get_boot_device_map('vbox')
|
boot_map = ssh._get_boot_device_map('vbox')
|
||||||
self.assertEqual('net', boot_map[boot_devices.PXE])
|
self.assertEqual('net', boot_map[boot_devices.PXE])
|
||||||
|
|
||||||
|
def test__get_boot_device_map_xenserver(self):
|
||||||
|
boot_map = ssh._get_boot_device_map('xenserver')
|
||||||
|
self.assertEqual('n', boot_map[boot_devices.PXE])
|
||||||
|
|
||||||
def test__get_boot_device_map_exception(self):
|
def test__get_boot_device_map_exception(self):
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
ssh._get_boot_device_map,
|
ssh._get_boot_device_map,
|
||||||
@ -859,6 +863,23 @@ class SSHDriverTestCase(db_base.DbTestCase):
|
|||||||
'edit %s') % fake_name
|
'edit %s') % fake_name
|
||||||
mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
|
mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
|
||||||
|
|
||||||
|
@mock.patch.object(ssh, '_get_connection', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_ssh_execute', autospec=True)
|
||||||
|
def test_management_interface_set_boot_device_xenserver_ok(self,
|
||||||
|
mock_exc,
|
||||||
|
mock_h,
|
||||||
|
mock_get_conn):
|
||||||
|
fake_name = 'fake-name'
|
||||||
|
mock_h.return_value = fake_name
|
||||||
|
mock_get_conn.return_value = self.sshclient
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.node['driver_info']['ssh_virt_type'] = 'xenserver'
|
||||||
|
self.driver.management.set_boot_device(task, boot_devices.PXE)
|
||||||
|
expected_cmd = ("LC_ALL=C /opt/xensource/bin/xe vm-param-set uuid=%s "
|
||||||
|
"HVM-boot-params:order='n'") % fake_name
|
||||||
|
mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
|
||||||
|
|
||||||
def test_set_boot_device_bad_device(self):
|
def test_set_boot_device_bad_device(self):
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
@ -941,6 +962,25 @@ class SSHDriverTestCase(db_base.DbTestCase):
|
|||||||
'print; }\' Q="\'" RS="[<>]" | head -1') % fake_name
|
'print; }\' Q="\'" RS="[<>]" | head -1') % fake_name
|
||||||
mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
|
mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
|
||||||
|
|
||||||
|
@mock.patch.object(ssh, '_get_connection', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_ssh_execute', autospec=True)
|
||||||
|
def test_management_interface_get_boot_device_xenserver(self, mock_exc,
|
||||||
|
mock_h,
|
||||||
|
mock_get_conn):
|
||||||
|
fake_name = 'fake-name'
|
||||||
|
mock_h.return_value = fake_name
|
||||||
|
mock_exc.return_value = ('n', '')
|
||||||
|
mock_get_conn.return_value = self.sshclient
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.node['driver_info']['ssh_virt_type'] = 'xenserver'
|
||||||
|
result = self.driver.management.get_boot_device(task)
|
||||||
|
self.assertEqual(boot_devices.PXE, result['boot_device'])
|
||||||
|
expected_cmd = ('LC_ALL=C /opt/xensource/bin/xe vm-param-get '
|
||||||
|
'uuid=%s --param-name=HVM-boot-params '
|
||||||
|
'param-key=order | cut -b 1') % fake_name
|
||||||
|
mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
|
||||||
|
|
||||||
@mock.patch.object(ssh, '_get_connection', autospec=True)
|
@mock.patch.object(ssh, '_get_connection', autospec=True)
|
||||||
@mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
|
@mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
|
||||||
def test_get_boot_device_not_supported(self, mock_h, mock_get_conn):
|
def test_get_boot_device_not_supported(self, mock_h, mock_get_conn):
|
||||||
@ -972,6 +1012,57 @@ class SSHDriverTestCase(db_base.DbTestCase):
|
|||||||
"echo '\"%(node)s\"' || true") % {'node': nodename}
|
"echo '\"%(node)s\"' || true") % {'node': nodename}
|
||||||
mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
|
mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
|
||||||
|
|
||||||
|
@mock.patch.object(ssh, '_get_connection', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_ssh_execute', autospec=True)
|
||||||
|
def test_get_power_state_xenserver(self, mock_exc, mock_h, mock_get_conn):
|
||||||
|
# To see replacing {_NodeName_} in xenserver's list_running
|
||||||
|
nodename = 'fakevm'
|
||||||
|
mock_h.return_value = nodename
|
||||||
|
mock_get_conn.return_value = self.sshclient
|
||||||
|
mock_exc.return_value = (nodename, '')
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.node['driver_info']['ssh_virt_type'] = 'xenserver'
|
||||||
|
power_state = self.driver.power.get_power_state(task)
|
||||||
|
self.assertEqual(states.POWER_ON, power_state)
|
||||||
|
expected_cmd = ("LC_ALL=C /opt/xensource/bin/xe "
|
||||||
|
"vm-list power-state=running --minimal | tr ',' '\n'")
|
||||||
|
mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
|
||||||
|
|
||||||
|
@mock.patch.object(ssh, '_get_connection', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_ssh_execute', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_get_power_status', autospec=True)
|
||||||
|
def test_start_command_xenserver(self, mock_power, mock_exc, mock_h,
|
||||||
|
mock_get_conn):
|
||||||
|
mock_power.side_effect = [states.POWER_OFF, states.POWER_ON]
|
||||||
|
nodename = 'fakevm'
|
||||||
|
mock_h.return_value = nodename
|
||||||
|
mock_get_conn.return_value = self.sshclient
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.node['driver_info']['ssh_virt_type'] = 'xenserver'
|
||||||
|
self.driver.power.set_power_state(task, states.POWER_ON)
|
||||||
|
expected_cmd = ("LC_ALL=C /opt/xensource/bin/xe "
|
||||||
|
"vm-start uuid=fakevm && sleep 10s")
|
||||||
|
mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
|
||||||
|
|
||||||
|
@mock.patch.object(ssh, '_get_connection', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_get_hosts_name_for_node', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_ssh_execute', autospec=True)
|
||||||
|
@mock.patch.object(ssh, '_get_power_status', autospec=True)
|
||||||
|
def test_stop_command_xenserver(self, mock_power, mock_exc, mock_h,
|
||||||
|
mock_get_conn):
|
||||||
|
mock_power.side_effect = [states.POWER_ON, states.POWER_OFF]
|
||||||
|
nodename = 'fakevm'
|
||||||
|
mock_h.return_value = nodename
|
||||||
|
mock_get_conn.return_value = self.sshclient
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.node['driver_info']['ssh_virt_type'] = 'xenserver'
|
||||||
|
self.driver.power.set_power_state(task, states.POWER_OFF)
|
||||||
|
expected_cmd = ("LC_ALL=C /opt/xensource/bin/xe "
|
||||||
|
"vm-shutdown uuid=fakevm force=true")
|
||||||
|
mock_exc.assert_called_once_with(mock.ANY, expected_cmd)
|
||||||
|
|
||||||
def test_management_interface_validate_good(self):
|
def test_management_interface_validate_good(self):
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
task.driver.management.validate(task)
|
task.driver.management.validate(task)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user