add ironic baremetal driver
This commit is contained in:
parent
48641cf945
commit
b5fca17987
21
makerpm
21
makerpm
@ -106,7 +106,23 @@ function makexcat {
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# make ironic rpm for ironic baremetal driver
|
||||
function makeironic {
|
||||
RPMNAME="$1"
|
||||
ARCH="$2"
|
||||
cd `dirname $0`/$RPMNAME
|
||||
cp -rf ironic_baremetal /tmp/
|
||||
cd /tmp/ironic_baremetal
|
||||
git init
|
||||
git add *
|
||||
git commit -a -m "generate rpm"
|
||||
python setup.py bdist_rpm
|
||||
rm -rf $RPMROOT/RPMS/$ARCH/
|
||||
mkdir -p $RPMROOT/RPMS/$ARCH/
|
||||
cp -rf dist/*.rpm $RPMROOT/RPMS/$ARCH/
|
||||
rm -rf /tmp/ironic_baremetal
|
||||
}
|
||||
|
||||
|
||||
# Make the xCAT-nbroot-core rpm
|
||||
function makenbroot {
|
||||
@ -206,7 +222,6 @@ else # linux
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [ "$1" = "xCAT" -o "$1" = "xCATsn" -o "$1" = "xCAT-buildkit" -o "$1" = "xCAT-OpenStack" ]; then
|
||||
exportEmbed $3
|
||||
makexcat $1 $2
|
||||
@ -219,6 +234,8 @@ elif [ "$1" = "xCAT-genesis-builder" ]; then
|
||||
elif [ "$1" = "xCAT-genesis-scripts" ]; then
|
||||
exportEmbed $3
|
||||
makegenesisscripts $1 $2
|
||||
elif [ "$1" = "xCAT-OpenStack-ironic" ]; then
|
||||
makeironic $1 $2
|
||||
else # must be one of the noarch rpms
|
||||
exportEmbed $2
|
||||
makenoarch $1
|
||||
|
6
xCAT-OpenStack-ironic/ironic_baremetal/MANIFEST.in
Normal file
6
xCAT-OpenStack-ironic/ironic_baremetal/MANIFEST.in
Normal file
@ -0,0 +1,6 @@
|
||||
include AUTHORS
|
||||
include ChangeLog
|
||||
exclude .gitignore
|
||||
exclude .gitreview
|
||||
|
||||
global-exclude *.pyc
|
44
xCAT-OpenStack-ironic/ironic_baremetal/README.rst
Normal file
44
xCAT-OpenStack-ironic/ironic_baremetal/README.rst
Normal file
@ -0,0 +1,44 @@
|
||||
xCAT Driver for ironic x86/64 machine
|
||||
==================================
|
||||
|
||||
xCAT is a Extreme Cluster/Cloud Administration Toolkit. We can use xcat
|
||||
to do :
|
||||
1 hardward discoveery
|
||||
2 remote hardware control
|
||||
3 remote sonsole
|
||||
4 hardware inventory
|
||||
5 firmware flashing
|
||||
|
||||
Ironic is a project in Openstack, it will replace the nova-baremetal in juno release. Ironic's design is very flexable, we can add driver to extend function
|
||||
without change any code in Openstack. Ironic xCAT driver takes the advantage of xcat and openstack, we can use it to deploy the baremetal machine very easily.
|
||||
|
||||
Before using this driver, we must setup the openstack environment at least for two nodes( ironic conductor and neutron network node can't setup on the same node)
|
||||
Ironic conductor and the baremetal node( waiting for deploy) must in the same vlan
|
||||
|
||||
Add the follows in the ironic egg-info entry_points.txt file (ironic.drivers section)
|
||||
|
||||
pxe_xcat = ironic.drivers.xcat:XCATBaremetalDriver
|
||||
|
||||
When the openstack with ironic is ready, just execute command in the ironic_xcat directory as follows:
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
Restart the ironic-conductor process
|
||||
|
||||
Initialize the xcat environment according to http://sourceforge.net/p/xcat/wiki/XCAT_iDataPlex_Cluster_Quick_Start/
|
||||
Using xCAT baremetal driver need config site table and run copycds to generate image. The node definition is not requirement.
|
||||
|
||||
Ironic use neutron as the network service.
|
||||
Check the openvswitch config on the network node ,make sure brbm bridge connect to the baremetal node.
|
||||
|
||||
==================================================================================
|
||||
Some Example to use the xCAT baremetal driver.
|
||||
|
||||
$touch /tmp/rhelhpc6.5-x86_64-install-compute.qcow2;glance image-create --name rhelhpc6.5-x86_64-install-compute --public --disk-format qcow2 --container-format bare --property xcat_image_name='rhels6.4-x86_64-install-compute' < /tmp/rhelhpc6.5-x86_64-install-compute.qcow2
|
||||
--name rhelhpc6.5-x86_64-install-compute is the image name in xcat. You can use lsdef -t osimage on the ironic-conductor node which xcat is installed.
|
||||
|
||||
$ ironic node-create --driver pxe_xcat -i ipmi_address=xxx.xxx.xxx.xxx -i ipmi_username=userid -i ipmi_password=password -i xcat_node=x3550m4n02 -i xcatmaster=10.1.0.241 -i netboot=xnba -i ipmi_terminal_port=0 -p memory_mb=2048 -p cpus=8
|
||||
|
||||
$ ironic port-create --address ff:ff:ff:ff:ff:ff --node_uuid <ironic node uuid>
|
||||
|
||||
$ nova boot --flavor baremetal --image <image-id> testing --nic net-id=<internal network id>
|
@ -0,0 +1,25 @@
|
||||
"""xCAT baremtal exceptions.
|
||||
"""
|
||||
|
||||
from oslo.config import cfg
|
||||
import six
|
||||
|
||||
from ironic.openstack.common.gettextutils import _
|
||||
from ironic.openstack.common import log as logging
|
||||
from ironic.common.exception import IronicException
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
class xCATCmdFailure(IronicException):
|
||||
message = _("xcat call failed: %(cmd)s %(node)s %(args)s.")
|
||||
|
||||
class xCATDeploymentFailure(IronicException):
|
||||
message = _("xCAT node deployment failed for node %(node)s:%(error)s")
|
||||
|
||||
class GetNetworkFixedIPFailure(IronicException):
|
||||
message = _("get fixed ip failed for mac %(mac_address)s")
|
||||
|
||||
class GetNetworkIdFailure(IronicException):
|
||||
message = _("get node network in failed for mac %(mac_address)s")
|
||||
|
||||
class FailedToGetInfoOnPort(IronicException):
|
||||
message = _("Show info on port: %(port_id)s failed.")
|
@ -0,0 +1,41 @@
|
||||
"""
|
||||
Get the network from neutron
|
||||
This is a xcat patch for the ironic/common/neutron.py
|
||||
"""
|
||||
|
||||
from neutronclient.common import exceptions as neutron_client_exc
|
||||
from ironic.common import exception
|
||||
from ironic.openstack.common import log as logging
|
||||
from ironic.common import neutron
|
||||
from ironic.drivers.modules import xcat_exception
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
def get_vif_port_info(task, port_id):
|
||||
""" Get detail port info from neutron with a given port id """
|
||||
api = neutron.NeutronAPI(task.context)
|
||||
try:
|
||||
port_info = api.client.show_port(port_id)
|
||||
except neutron_client_exc.NeutronClientException:
|
||||
LOG.exception(_("Failed to get port info %s."), port_id)
|
||||
raise exception.FailedToGetInfoOnPort(port_id=port_id)
|
||||
return port_info
|
||||
|
||||
|
||||
def get_ports_info_from_neutron(task):
|
||||
""" Get neutron port info from neutron about this task """
|
||||
vifs = neutron.get_node_vif_ids(task)
|
||||
if not vifs:
|
||||
LOG.warning(_("No VIFs found for node %(node)s when attempting to "
|
||||
"update Neutron DHCP BOOT options."),
|
||||
{'node': task.node.uuid})
|
||||
return
|
||||
failures = []
|
||||
vif_ports_info = {}
|
||||
for port_id, port_vif in vifs.iteritems():
|
||||
try:
|
||||
vif_ports_info[port_id] = get_vif_port_info(task,port_vif)
|
||||
except xcat_exception.FailedToGetInfoOnPort(port_id=port_vif):
|
||||
failures.append(port_vif)
|
||||
return vif_ports_info
|
||||
|
@ -0,0 +1,453 @@
|
||||
"""
|
||||
pxe procedure for the xcat baremetal driver
|
||||
use xcat to config dhcp and tftp
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import paramiko
|
||||
import datetime
|
||||
from oslo.config import cfg
|
||||
from ironic.common import exception
|
||||
from ironic.common import image_service as service
|
||||
from ironic.common import keystone
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.conductor import utils as manager_utils
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers import utils as driver_utils
|
||||
from ironic.openstack.common import log as logging
|
||||
from ironic.openstack.common import strutils
|
||||
from ironic.drivers.modules import xcat_neutron
|
||||
from ironic.drivers.modules import xcat_util
|
||||
from ironic.openstack.common import loopingcall
|
||||
from nova.openstack.common import timeutils
|
||||
from ironic.openstack.common import lockutils
|
||||
from ironic.drivers.modules import xcat_exception
|
||||
|
||||
|
||||
pxe_opts = [
|
||||
cfg.StrOpt('pxe_append_params',
|
||||
default='nofb nomodeset vga=normal',
|
||||
help='Additional append parameters for baremetal PXE boot.'),
|
||||
cfg.StrOpt('default_ephemeral_format',
|
||||
default='ext4',
|
||||
help='Default file system format for ephemeral partition, '
|
||||
'if one is created.'),
|
||||
]
|
||||
xcat_opts = [
|
||||
cfg.StrOpt('network_node_ip',
|
||||
default='127.0.0.1',
|
||||
help='IP address of neutron network node'),
|
||||
cfg.StrOpt('ssh_user',
|
||||
default='root',
|
||||
help='Username of neutron network node.'),
|
||||
cfg.StrOpt('ssh_password',
|
||||
default='cluster',
|
||||
help='Password of neutron network node'),
|
||||
cfg.IntOpt('ssh_port',
|
||||
default=22,
|
||||
help='ssh connection port for the neutron '),
|
||||
cfg.StrOpt('host_filepath',
|
||||
default='/etc/hosts',
|
||||
help='host file of server'),
|
||||
cfg.IntOpt('deploy_timeout',
|
||||
default=3600,
|
||||
help='max depolyment time(seconds) for the xcat driver'),
|
||||
cfg.IntOpt('deploy_checking_interval',
|
||||
default=30,
|
||||
help='interval time(seconds) to check the xcat deploy state'),
|
||||
]
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(pxe_opts, group='pxe')
|
||||
CONF.register_opts(xcat_opts, group='xcat')
|
||||
CONF.import_opt('use_ipv6', 'ironic.netconf')
|
||||
|
||||
EM_SEMAPHORE = 'xcat_pxe'
|
||||
|
||||
def _check_for_missing_params(info_dict, param_prefix=''):
|
||||
missing_info = []
|
||||
for label, value in info_dict.items():
|
||||
if not value:
|
||||
missing_info.append(param_prefix + label)
|
||||
|
||||
if missing_info:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Can not validate PXE bootloader. The following parameters "
|
||||
"were not passed to ironic: %s") % missing_info)
|
||||
|
||||
def _parse_driver_info(node):
|
||||
"""Gets the driver specific Node deployment info.
|
||||
|
||||
This method validates whether the 'driver_info' property of the
|
||||
supplied node contains the required information for this driver to
|
||||
deploy images to the node.
|
||||
|
||||
:param node: a single Node.
|
||||
:returns: A dict with the driver_info values.
|
||||
"""
|
||||
info = node.driver_info
|
||||
d_info = {}
|
||||
d_info['xcat_node'] = info.get('xcat_node')
|
||||
return d_info
|
||||
|
||||
def _parse_instance_info(node):
|
||||
"""Gets the instance specific Node deployment info.
|
||||
|
||||
This method validates whether the 'instance_info' property of the
|
||||
supplied node contains the required information for this driver to
|
||||
deploy images to the node.
|
||||
|
||||
:param node: a single Node.
|
||||
:returns: A dict with the instance_info values.
|
||||
"""
|
||||
|
||||
info = node.instance_info
|
||||
i_info = {}
|
||||
i_info['image_source'] = info.get('image_source')
|
||||
i_info['root_gb'] = info.get('root_gb')
|
||||
i_info['image_file'] = i_info['image_source']
|
||||
|
||||
_check_for_missing_params(i_info)
|
||||
|
||||
# Internal use only
|
||||
i_info['deploy_key'] = info.get('deploy_key')
|
||||
|
||||
i_info['swap_mb'] = info.get('swap_mb', 0)
|
||||
i_info['ephemeral_gb'] = info.get('ephemeral_gb', 0)
|
||||
i_info['ephemeral_format'] = info.get('ephemeral_format')
|
||||
|
||||
err_msg_invalid = _("Can not validate PXE bootloader. Invalid parameter "
|
||||
"%(param)s. Reason: %(reason)s")
|
||||
for param in ('root_gb', 'swap_mb', 'ephemeral_gb'):
|
||||
try:
|
||||
int(i_info[param])
|
||||
except ValueError:
|
||||
reason = _("'%s' is not an integer value.") % i_info[param]
|
||||
raise exception.InvalidParameterValue(err_msg_invalid %
|
||||
{'param': param, 'reason': reason})
|
||||
|
||||
if i_info['ephemeral_gb'] and not i_info['ephemeral_format']:
|
||||
i_info['ephemeral_format'] = CONF.pxe.default_ephemeral_format
|
||||
|
||||
preserve_ephemeral = info.get('preserve_ephemeral', False)
|
||||
try:
|
||||
i_info['preserve_ephemeral'] = strutils.bool_from_string(
|
||||
preserve_ephemeral, strict=True)
|
||||
except ValueError as e:
|
||||
raise exception.InvalidParameterValue(err_msg_invalid %
|
||||
{'param': 'preserve_ephemeral', 'reason': e})
|
||||
return i_info
|
||||
|
||||
|
||||
def _parse_deploy_info(node):
|
||||
"""Gets the instance and driver specific Node deployment info.
|
||||
|
||||
This method validates whether the 'instance_info' and 'driver_info'
|
||||
property of the supplied node contains the required information for
|
||||
this driver to deploy images to the node.
|
||||
|
||||
:param node: a single Node.
|
||||
:returns: A dict with the instance_info and driver_info values.
|
||||
"""
|
||||
info = {}
|
||||
info.update(_parse_instance_info(node))
|
||||
info.update(_parse_driver_info(node))
|
||||
return info
|
||||
|
||||
def _validate_glance_image(ctx, deploy_info):
|
||||
"""Validate the image in Glance.
|
||||
|
||||
Check if the image exist in Glance and if it contains the
|
||||
'kernel_id' and 'ramdisk_id' properties.
|
||||
|
||||
:raises: InvalidParameterValue.
|
||||
"""
|
||||
image_id = deploy_info['image_source']
|
||||
if not image_id:
|
||||
raise exception.ImageNotFound
|
||||
|
||||
class PXEDeploy(base.DeployInterface):
|
||||
"""PXE Deploy Interface: just a stub until the real driver is ported."""
|
||||
|
||||
def validate(self, task):
|
||||
"""Validate the deployment information for the task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue.
|
||||
"""
|
||||
node = task.node
|
||||
if not driver_utils.get_node_mac_addresses(task):
|
||||
raise exception.InvalidParameterValue(_("Node %s does not have "
|
||||
"any port associated with it.") % node.uuid)
|
||||
|
||||
d_info = _parse_deploy_info(node)
|
||||
# Try to get the URL of the Ironic API
|
||||
try:
|
||||
# TODO(lucasagomes): Validate the format of the URL
|
||||
CONF.conductor.api_url or keystone.get_service_url()
|
||||
except (exception.CatalogFailure,
|
||||
exception.CatalogNotFound,
|
||||
exception.CatalogUnauthorized):
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Couldn't get the URL of the Ironic API service from the "
|
||||
"configuration file or keystone catalog."))
|
||||
|
||||
_validate_glance_image(task.context, d_info)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def deploy(self, task):
|
||||
"""Start deployment of the task's node'.
|
||||
|
||||
Config host file and xcat dhcp, generate image info for xcat
|
||||
and issues a reboot request to the power driver.
|
||||
This causes the node to boot into the deployment ramdisk and triggers
|
||||
the next phase of PXE-based deployment via
|
||||
VendorPassthru._continue_deploy().
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:returns: deploy state DEPLOYDONE.
|
||||
"""
|
||||
|
||||
d_info = _parse_deploy_info(task.node)
|
||||
if not task.node.instance_info.get('fixed_ip_address') or not task.node.instance_info.get('image_name'):
|
||||
raise exception.InvalidParameterValue
|
||||
self._config_host_file(d_info,task.node.instance_info.get('fixed_ip_address'))
|
||||
self._make_dhcp()
|
||||
self._nodeset_osimage(d_info,task.node.instance_info.get('image_name'))
|
||||
manager_utils.node_set_boot_device(task, 'pxe', persistent=True)
|
||||
manager_utils.node_power_action(task, states.REBOOT)
|
||||
try:
|
||||
self._wait_for_node_deploy(task)
|
||||
except xcat_exception.xCATDeploymentFailure:
|
||||
LOG.info(_("xcat deployment failed"))
|
||||
return states.ERROR
|
||||
|
||||
return states.DEPLOYDONE
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def tear_down(self, task):
|
||||
"""Tear down a previous deployment on the task's node.
|
||||
|
||||
Power off the node. All actual clean-up is done in the clean_up()
|
||||
method which should be called separately.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:returns: deploy state DELETED.
|
||||
"""
|
||||
manager_utils.node_power_action(task, states.POWER_OFF)
|
||||
return states.DELETED
|
||||
|
||||
def prepare(self, task):
|
||||
"""Prepare the deployment environment for this task's node.
|
||||
Get the image info from glance, config the mac for the xcat
|
||||
use ssh and iptables to disable dhcp on network node
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
"""
|
||||
# TODO(deva): optimize this if rerun on existing files
|
||||
d_info = _parse_deploy_info(task.node)
|
||||
i_info = task.node.instance_info
|
||||
image_id = d_info['image_source']
|
||||
try:
|
||||
glance_service = service.Service(version=1, context=task.context)
|
||||
image_name = glance_service.show(image_id)['name']
|
||||
i_info['image_name'] = image_name
|
||||
except (exception.GlanceConnectionFailed,
|
||||
exception.ImageNotAuthorized,
|
||||
exception.Invalid):
|
||||
LOG.warning(_("Failed to connect to Glance to get the properties "
|
||||
"of the image %s") % image_id)
|
||||
|
||||
node_mac_addresses = driver_utils.get_node_mac_addresses(task)
|
||||
vif_ports_info = xcat_neutron.get_ports_info_from_neutron(task)
|
||||
try:
|
||||
network_info = self._get_deploy_network_info(vif_ports_info, node_mac_addresses)
|
||||
except (xcat_exception.GetNetworkFixedIPFailure,xcat_exception.GetNetworkIdFailure):
|
||||
LOG.error(_("Failed to get network info"))
|
||||
return
|
||||
if not network_info:
|
||||
LOG.error(_("Failed to get network info"))
|
||||
return
|
||||
|
||||
fixed_ip_address = network_info['fixed_ip_address']
|
||||
deploy_mac_address = network_info['mac_address']
|
||||
network_id = network_info['network_id']
|
||||
|
||||
i_info['fixed_ip_address'] = fixed_ip_address
|
||||
i_info['network_id'] = network_id
|
||||
i_info['deploy_mac_address'] = deploy_mac_address
|
||||
|
||||
# use iptables to drop the dhcp mac of baremetal machine
|
||||
self._ssh_append_dhcp_rule(CONF.xcat.network_node_ip,CONF.xcat.ssh_port,CONF.xcat.ssh_user,
|
||||
CONF.xcat.ssh_password,network_id,deploy_mac_address)
|
||||
self._chdef_node_mac_address(d_info,deploy_mac_address)
|
||||
|
||||
def clean_up(self, task):
|
||||
"""Clean up the deployment environment for the task's node.
|
||||
|
||||
Unlinks TFTP and instance images and triggers image cache cleanup.
|
||||
Removes the TFTP configuration files for this node. As a precaution,
|
||||
this method also ensures the keystone auth token file was removed.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
"""
|
||||
pass
|
||||
|
||||
def take_over(self, task):
|
||||
pass
|
||||
|
||||
def _get_deploy_network_info(self, vif_ports_info, valid_node_mac_addrsses):
|
||||
"""Get network info from mac address of ironic node.
|
||||
:param vif_ports_info: info collection from neutron ports
|
||||
:param valid_node_mac_addrsses: mac address from ironic node
|
||||
:raises: GetNetworkFixedIpFailure if search the fixed ip from mac address failure
|
||||
:raises: GetNetworkIdFailure if search the network id from mac address failure
|
||||
"""
|
||||
network_info = {}
|
||||
for port_info in vif_ports_info.values():
|
||||
if(port_info['port']['mac_address'] in valid_node_mac_addrsses ):
|
||||
network_info['fixed_ip_address'] = port_info['port']['fixed_ips'][0]['ip_address']
|
||||
if not network_info['fixed_ip_address']:
|
||||
raise xcat_exception.GetNetworkFixedIPFailure(mac_address=port_info['port']['mac_address'])
|
||||
network_info['mac_address'] = port_info['port']['mac_address']
|
||||
network_info['network_id'] = port_info['port']['network_id']
|
||||
if not network_info['network_id']:
|
||||
raise xcat_exception.GetNetworkIdFailure(mac_address=port_info['port']['mac_address'])
|
||||
network_info['port_id'] = port_info['port']['id']
|
||||
return network_info
|
||||
return network_info
|
||||
|
||||
def _chdef_node_mac_address(self, driver_info, deploy_mac):
|
||||
""" run chdef command to set mac address"""
|
||||
cmd = 'chdef'
|
||||
args = 'mac='+ deploy_mac
|
||||
try:
|
||||
out_err = xcat_util.exec_xcatcmd(driver_info, cmd, args)
|
||||
LOG.info(_("xcat chdef cmd exetute output: %(out_err)s") % {'out_err':out_err})
|
||||
except xcat_exception.xCATCmdFailure as e:
|
||||
LOG.warning(_("xcat chdef failed for node %(xcat_node)s with "
|
||||
"error: %(error)s.")
|
||||
% {'xcat_node': driver_info['xcat_node'], 'error': e})
|
||||
raise exception.IPMIFailure(cmd=cmd)
|
||||
|
||||
@lockutils.synchronized(EM_SEMAPHORE, 'xcat-hosts-')
|
||||
def _config_host_file(self, driver_info, deploy_ip):
|
||||
""" append node and ip infomation to host file"""
|
||||
with open(CONF.xcat.host_filepath,"r+") as f:
|
||||
lines = []
|
||||
for line in f:
|
||||
temp = line.split('#')
|
||||
if temp[0].strip():
|
||||
host_name = xcat_util._tsplit(temp[0].strip(),(' ','\t'))[1]
|
||||
if driver_info['xcat_node'] not in host_name:
|
||||
lines.append(line)
|
||||
|
||||
# append a new line to host file
|
||||
line = "%s\t%s\n" %(deploy_ip,driver_info['xcat_node'])
|
||||
lines.append(line)
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
for line in lines:
|
||||
f.write(line)
|
||||
|
||||
def _nodeset_osimage(self, driver_info, image_name):
|
||||
"""run nodeset command to config the image for the xcat node
|
||||
:param driver_info: xcat node deploy info
|
||||
:param image_name: image for the xcat deployment
|
||||
"""
|
||||
cmd = 'nodeset'
|
||||
args = 'osimage='+ image_name
|
||||
try:
|
||||
xcat_util.exec_xcatcmd(driver_info, cmd, args)
|
||||
except xcat_exception.xCATCmdFailure as e:
|
||||
LOG.warning(_("xcat nodeset failed for node %(xcat_node)s with "
|
||||
"error: %(error)s.")
|
||||
% {'xcat_node': driver_info['xcat_node'], 'error': e})
|
||||
|
||||
def _make_dhcp(self):
|
||||
"""run makedhcp command to setup dhcp environment for the xcat node"""
|
||||
cmd = ['makedhcp',
|
||||
'-n'
|
||||
]
|
||||
try:
|
||||
out, err = utils.execute(*cmd)
|
||||
LOG.info(_(" excute cmd: %(cmd)s \n output: %(out)s \n. Error: %(err)s \n"),
|
||||
{'cmd':cmd,'out': out, 'err': err})
|
||||
except Exception as e:
|
||||
LOG.error(_("Unable to execute %(cmd)s. Exception: %(exception)s"),
|
||||
{'cmd': cmd, 'exception': e})
|
||||
# makedhcp -a
|
||||
cmd = ['makedhcp',
|
||||
'-a'
|
||||
]
|
||||
try:
|
||||
out, err = utils.execute(*cmd)
|
||||
LOG.info(_(" excute cmd: %(cmd)s \n output: %(out)s \n. Error: %(err)s \n"),
|
||||
{'cmd':cmd,'out': out, 'err': err})
|
||||
except Exception as e:
|
||||
LOG.error(_("Unable to execute %(cmd)s. Exception: %(exception)s"),
|
||||
{'cmd': cmd, 'exception': e})
|
||||
|
||||
def _ssh_append_dhcp_rule(self,ip,port,username,password,network_id,mac_address):
|
||||
""" drop the dhcp package in network node to avoid of confilct of dhcp """
|
||||
netns = 'qdhcp-%s' %network_id
|
||||
append_cmd = 'sudo ip netns exec %s iptables -A INPUT -m mac --mac-source %s -j DROP' % \
|
||||
(netns,mac_address)
|
||||
cmd = [append_cmd]
|
||||
xcat_util.xcat_ssh(ip,port,username,password,cmd)
|
||||
|
||||
def _ssh_delete_dhcp_rule(self,ip,port,username,password,network_id,mac_address):
|
||||
""" delete the iptable rule on network node to recover the environment"""
|
||||
netns = 'qdhcp-%s' %network_id
|
||||
cancel_cmd = 'sudo ip netns exec %s iptables -D INPUT -m mac --mac-source %s -j DROP' % \
|
||||
(netns,mac_address)
|
||||
cmd = [cancel_cmd]
|
||||
xcat_util.xcat_ssh(ip,port,username,password,cmd)
|
||||
|
||||
def _wait_for_node_deploy(self, task):
|
||||
"""Wait for xCAT node deployment to complete."""
|
||||
locals = {'errstr':''}
|
||||
driver_info = _parse_deploy_info(task.node)
|
||||
node_mac_addrsses = driver_utils.get_node_mac_addresses(task)
|
||||
i_info = task.node.instance_info
|
||||
|
||||
def _wait_for_deploy():
|
||||
out,err = xcat_util.exec_xcatcmd(driver_info,'nodels','nodelist.status')
|
||||
if err:
|
||||
locals['errstr'] = _("Error returned when quering node status"
|
||||
" for node %s:%s") % (driver_info['xcat_node'], err)
|
||||
LOG.warning(locals['errstr'])
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if out:
|
||||
node,status = out.split(": ")
|
||||
status = status.strip()
|
||||
if status == "booted":
|
||||
LOG.info(_("Deployment for node %s completed.")
|
||||
% driver_info['xcat_node'])
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
if (CONF.xcat.deploy_timeout and
|
||||
timeutils.utcnow() > expiration):
|
||||
locals['errstr'] = _("Timeout while waiting for"
|
||||
" deployment of node %s.") % driver_info['xcat_node']
|
||||
LOG.warning(locals['errstr'])
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
expiration = timeutils.utcnow() + datetime.timedelta(
|
||||
seconds=CONF.xcat.deploy_timeout)
|
||||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_deploy)
|
||||
# default check every 10 seconds
|
||||
timer.start(interval=CONF.xcat.deploy_checking_interval).wait()
|
||||
|
||||
if locals['errstr']:
|
||||
raise xcat_exception.xCATDeploymentFailure(locals['errstr'])
|
||||
# deploy end, delete the dhcp rule for xcat
|
||||
self._ssh_delete_dhcp_rule(CONF.xcat.network_node_ip,CONF.xcat.ssh_port,CONF.xcat.ssh_user,
|
||||
CONF.xcat.ssh_password,i_info['network_id'],node_mac_addrsses[0])
|
||||
|
||||
|
@ -0,0 +1,479 @@
|
||||
|
||||
"""
|
||||
IPMI power manager driver.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import stat
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import console_utils
|
||||
from ironic.openstack.common import excutils
|
||||
from ironic.openstack.common import log as logging
|
||||
from ironic.openstack.common import loopingcall
|
||||
from ironic.openstack.common import processutils
|
||||
from ironic.drivers.modules import xcat_exception
|
||||
from ironic.drivers.modules import xcat_util
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('retry_timeout',
|
||||
'ironic.drivers.modules.ipminative',
|
||||
group='ipmi')
|
||||
CONF.import_opt('min_command_interval',
|
||||
'ironic.drivers.modules.ipminative',
|
||||
group='ipmi')
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
VALID_BOOT_DEVICES = ['net', 'hd', 'cd', 'floppy', 'def', 'stat']
|
||||
VALID_PRIV_LEVELS = ['ADMINISTRATOR', 'CALLBACK', 'OPERATOR', 'USER']
|
||||
TIMING_SUPPORT = None
|
||||
|
||||
|
||||
def _is_timing_supported(is_supported=None):
|
||||
# shim to allow module variable to be mocked in unit tests
|
||||
global TIMING_SUPPORT
|
||||
|
||||
if (TIMING_SUPPORT is None) and (is_supported is not None):
|
||||
TIMING_SUPPORT = is_supported
|
||||
return TIMING_SUPPORT
|
||||
|
||||
|
||||
def check_timing_support():
|
||||
"""Check the installed version of ipmitool for -N -R option support.
|
||||
|
||||
Support was added in 1.8.12 for the -N -R options, which enable
|
||||
more precise control over timing of ipmi packets. Prior to this,
|
||||
the default behavior was to retry each command up to 18 times at
|
||||
1 to 5 second intervals.
|
||||
http://ipmitool.cvs.sourceforge.net/viewvc/ipmitool/ipmitool/ChangeLog?revision=1.37 # noqa
|
||||
|
||||
This method updates the module-level TIMING_SUPPORT variable so that
|
||||
it is accessible by any driver interface class in this module. It is
|
||||
intended to be called from the __init__ method of such classes only.
|
||||
|
||||
:returns: boolean indicating whether support for -N -R is present
|
||||
:raises: OSError
|
||||
"""
|
||||
if _is_timing_supported() is None:
|
||||
# Directly check ipmitool for support of -N and -R options. Because
|
||||
# of the way ipmitool processes' command line options, if the local
|
||||
# ipmitool does not support setting the timing options, the command
|
||||
# below will fail.
|
||||
try:
|
||||
out, err = utils.execute(*['ipmitool', '-N', '0', '-R', '0', '-h'])
|
||||
except processutils.ProcessExecutionError:
|
||||
# the local ipmitool does not support the -N and -R options.
|
||||
_is_timing_supported(False)
|
||||
else:
|
||||
# looks like ipmitool supports timing options.
|
||||
_is_timing_supported(True)
|
||||
|
||||
|
||||
def _console_pwfile_path(uuid):
|
||||
"""Return the file path for storing the ipmi password for a console."""
|
||||
file_name = "%(uuid)s.pw" % {'uuid': uuid}
|
||||
return os.path.join(tempfile.gettempdir(), file_name)
|
||||
|
||||
def _parse_driver_info(node):
|
||||
"""Gets the parameters required for ipmitool to access the node.
|
||||
|
||||
:param node: the Node of interest.
|
||||
:returns: dictionary of parameters.
|
||||
:raises: InvalidParameterValue if any required parameters are missing.
|
||||
|
||||
"""
|
||||
info = node.driver_info or {}
|
||||
address = info.get('ipmi_address')
|
||||
username = info.get('ipmi_username')
|
||||
password = info.get('ipmi_password')
|
||||
port = info.get('ipmi_terminal_port')
|
||||
priv_level = info.get('ipmi_priv_level', 'ADMINISTRATOR')
|
||||
xcat_node = info.get('xcat_node')
|
||||
xcatmaster = info.get('xcatmaster')
|
||||
netboot = info.get('netboot')
|
||||
|
||||
if port:
|
||||
try:
|
||||
port = int(port)
|
||||
except ValueError:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"IPMI terminal port is not an integer."))
|
||||
|
||||
if not address:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"IPMI address not supplied to xcat driver."))
|
||||
|
||||
if priv_level not in VALID_PRIV_LEVELS:
|
||||
valid_priv_lvls = ', '.join(VALID_PRIV_LEVELS)
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Invalid privilege level value:%(priv_level)s, the valid value"
|
||||
" can be one of %(valid_levels)s") %
|
||||
{'priv_level': priv_level, 'valid_levels': valid_priv_lvls})
|
||||
|
||||
if not xcat_node:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"xcat node name not supplied to xcat driver"))
|
||||
|
||||
if not xcatmaster:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"xcatmaster not supplied to xcat driver"))
|
||||
|
||||
if not netboot:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"netboot not supplied to xcat driver"))
|
||||
|
||||
return {
|
||||
'address': address,
|
||||
'username': username,
|
||||
'password': password,
|
||||
'port': port,
|
||||
'uuid': node.uuid,
|
||||
'priv_level': priv_level,
|
||||
'xcat_node': xcat_node,
|
||||
'xcatmaster': xcatmaster,
|
||||
'netboot': netboot
|
||||
}
|
||||
def chdef_node(driver_info):
|
||||
"""Run the chdef command in xcat, config the node
|
||||
:param driver_info: driver_info for the xcat node
|
||||
"""
|
||||
cmd = 'chdef'
|
||||
args = 'mgt=ipmi' + \
|
||||
' bmc=' + driver_info['address'] + \
|
||||
' bmcusername=' + driver_info['username'] + \
|
||||
' bmcpassword=' + driver_info['password'] + \
|
||||
' xcatmaster=' + driver_info['xcatmaster']+ \
|
||||
' netboot=' + driver_info['netboot']+ \
|
||||
' primarynic=mac'+ \
|
||||
' installnic=mac'+ \
|
||||
' monserver=' + driver_info['xcatmaster'] + \
|
||||
' nfsserver=' + driver_info['xcatmaster'] + \
|
||||
' serialflow=hard'+ \
|
||||
' serialspeed=115200' + \
|
||||
' serialport=' + str(driver_info['port']);
|
||||
|
||||
try:
|
||||
xcat_util.exec_xcatcmd(driver_info, cmd, args)
|
||||
except xcat_exception.xCATCmdFailure as e:
|
||||
LOG.warning(_("xcat chdef failed for node %(node_id)s with "
|
||||
"error: %(error)s.")
|
||||
% {'node_id': driver_info['uuid'], 'error': e})
|
||||
|
||||
def _sleep_time(iter):
|
||||
"""Return the time-to-sleep for the n'th iteration of a retry loop.
|
||||
This implementation increases exponentially.
|
||||
|
||||
:param iter: iteration number
|
||||
:returns: number of seconds to sleep
|
||||
|
||||
"""
|
||||
if iter <= 1:
|
||||
return 1
|
||||
return iter ** 2
|
||||
|
||||
|
||||
def _set_and_wait(target_state, driver_info):
|
||||
"""Helper function for DynamicLoopingCall.
|
||||
|
||||
This method changes the power state and polls the BMCuntil the desired
|
||||
power state is reached, or CONF.ipmi.retry_timeout would be exceeded by the
|
||||
next iteration.
|
||||
|
||||
This method assumes the caller knows the current power state and does not
|
||||
check it prior to changing the power state. Most BMCs should be fine, but
|
||||
if a driver is concerned, the state should be checked prior to calling this
|
||||
method.
|
||||
|
||||
:param target_state: desired power state
|
||||
:param driver_info: the ipmitool parameters for accessing a node.
|
||||
:returns: one of ironic.common.states
|
||||
:raises: IPMIFailure on an error from ipmitool (from _power_status call).
|
||||
|
||||
"""
|
||||
if target_state == states.POWER_ON:
|
||||
state_name = "on"
|
||||
elif target_state == states.POWER_OFF:
|
||||
state_name = "off"
|
||||
|
||||
def _wait(mutable):
|
||||
try:
|
||||
# Only issue power change command once
|
||||
if mutable['iter'] < 0:
|
||||
xcat_util.exec_xcatcmd(driver_info,'rpower',state_name)
|
||||
else:
|
||||
mutable['power'] = _power_status(driver_info)
|
||||
except Exception:
|
||||
# Log failures but keep trying
|
||||
LOG.warning(_("xcat rpower %(state)s failed for node %(node)s."),
|
||||
{'state': state_name, 'node': driver_info['uuid']})
|
||||
finally:
|
||||
mutable['iter'] += 1
|
||||
|
||||
if mutable['power'] == target_state:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
|
||||
sleep_time = _sleep_time(mutable['iter'])
|
||||
if (sleep_time + mutable['total_time']) > CONF.ipmi.retry_timeout:
|
||||
# Stop if the next loop would exceed maximum retry_timeout
|
||||
LOG.error(_('xcat rpower %(state)s timed out after '
|
||||
'%(tries)s retries on node %(node_id)s.'),
|
||||
{'state': state_name, 'tries': mutable['iter'],
|
||||
'node_id': driver_info['uuid']})
|
||||
mutable['power'] = states.ERROR
|
||||
raise loopingcall.LoopingCallDone()
|
||||
else:
|
||||
mutable['total_time'] += sleep_time
|
||||
return sleep_time
|
||||
|
||||
# Use mutable objects so the looped method can change them.
|
||||
# Start 'iter' from -1 so that the first two checks are one second apart.
|
||||
status = {'power': None, 'iter': -1, 'total_time': 0}
|
||||
|
||||
timer = loopingcall.DynamicLoopingCall(_wait, status)
|
||||
timer.start().wait()
|
||||
return status['power']
|
||||
|
||||
def _power_on(driver_info):
|
||||
"""Turn the power ON for this node.
|
||||
|
||||
:param driver_info: the xcat parameters for accessing a node.
|
||||
:returns: one of ironic.common.states POWER_ON or ERROR.
|
||||
:raises: IPMIFailure on an error from ipmitool (from _power_status call).
|
||||
|
||||
"""
|
||||
return _set_and_wait(states.POWER_ON, driver_info)
|
||||
|
||||
|
||||
def _power_off(driver_info):
|
||||
"""Turn the power OFF for this node.
|
||||
|
||||
:param driver_info: the xcat parameters for accessing a node.
|
||||
:returns: one of ironic.common.states POWER_OFF or ERROR.
|
||||
:raises: IPMIFailure on an error from ipmitool (from _power_status call).
|
||||
|
||||
"""
|
||||
return _set_and_wait(states.POWER_OFF, driver_info)
|
||||
|
||||
def _power_status(driver_info):
|
||||
"""Get the power status for a node.
|
||||
|
||||
:param driver_info: the xcat access parameters for a node.
|
||||
:returns: one of ironic.common.states POWER_OFF, POWER_ON or ERROR.
|
||||
:raises: IPMIFailure on an error from ipmitool.
|
||||
|
||||
"""
|
||||
cmd = "rpower"
|
||||
try:
|
||||
out_err = xcat_util.exec_xcatcmd(driver_info,cmd,'status')
|
||||
except Exception as e:
|
||||
LOG.warning(_("xcat rpower status failed for node %(node_id)s with "
|
||||
"error: %(error)s.")
|
||||
% {'node_id': driver_info['uuid'], 'error': e})
|
||||
|
||||
if out_err[0].split(' ')[1].strip() == "on":
|
||||
return states.POWER_ON
|
||||
elif out_err[0].split(' ')[1].strip() == "off":
|
||||
return states.POWER_OFF
|
||||
else:
|
||||
return states.ERROR
|
||||
|
||||
|
||||
class XcatPower(base.PowerInterface):
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
check_timing_support()
|
||||
except OSError:
|
||||
raise exception.DriverLoadError(
|
||||
driver=self.__class__.__name__,
|
||||
reason="Unable to locate usable xcat command in "
|
||||
"the system path when checking xcat version")
|
||||
|
||||
def validate(self, task):
|
||||
"""Validate driver_info for xcat driver.
|
||||
|
||||
Check that node['driver_info'] contains IPMI credentials.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue if required ipmi parameters are missing.
|
||||
|
||||
"""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
try:
|
||||
chdef_node(driver_info)
|
||||
except exception:
|
||||
LOG.error(_("chdef xcat info error!"))
|
||||
|
||||
def get_power_state(self, task):
|
||||
"""Get the current power state of the task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:returns: one of ironic.common.states POWER_OFF, POWER_ON or ERROR.
|
||||
|
||||
"""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
return _power_status(driver_info)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def set_power_state(self, task, pstate):
|
||||
"""Turn the power on or off.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:param pstate: The desired power state, one of ironic.common.states
|
||||
POWER_ON, POWER_OFF.
|
||||
:raises: InvalidParameterValue if required ipmi parameters are missing
|
||||
or if an invalid power state was specified.
|
||||
:raises: PowerStateFailure if the power couldn't be set to pstate.
|
||||
|
||||
"""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
|
||||
if pstate == states.POWER_ON:
|
||||
state = _power_on(driver_info)
|
||||
elif pstate == states.POWER_OFF:
|
||||
state = _power_off(driver_info)
|
||||
else:
|
||||
raise exception.InvalidParameterValue(_("set_power_state called "
|
||||
"with invalid power state %s.") % pstate)
|
||||
if state != pstate:
|
||||
raise exception.PowerStateFailure(pstate=pstate)
|
||||
|
||||
@task_manager.require_exclusive_lock
|
||||
def reboot(self, task):
|
||||
"""Cycles the power to the task's node.
|
||||
|
||||
:param task: a TaskManager instance containing the node to act on.
|
||||
:raises: InvalidParameterValue if required ipmi parameters are missing.
|
||||
:raises: PowerStateFailure if the final state of the node is not
|
||||
POWER_ON.
|
||||
|
||||
"""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
_power_off(driver_info)
|
||||
state = _power_on(driver_info)
|
||||
|
||||
if state != states.POWER_ON:
|
||||
raise exception.PowerStateFailure(pstate=states.POWER_ON)
|
||||
|
||||
class VendorPassthru(base.VendorInterface):
|
||||
@task_manager.require_exclusive_lock
|
||||
def _set_boot_device(self, task, device, persistent=False):
|
||||
"""Set the boot device for a node.
|
||||
|
||||
:param task: a TaskManager instance.
|
||||
:param device: Boot device. One of [net, hd, cd, floppy, def, stat].
|
||||
:param persistent: Whether to set next-boot, or make the change
|
||||
permanent. Default: False.
|
||||
:raises: InvalidParameterValue if an invalid boot device is specified
|
||||
or if required ipmi parameters are missing.
|
||||
:raises: IPMIFailure on an error from ipmitool.
|
||||
|
||||
"""
|
||||
if device not in VALID_BOOT_DEVICES:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Invalid boot device %s specified.") % device)
|
||||
cmd = "rsetboot"
|
||||
if persistent:
|
||||
cmd = cmd + " options=persistent"
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
try:
|
||||
xcat_util.exec_xcatcmd(driver_info, cmd, device)
|
||||
# TODO(deva): validate (out, err) and add unit test for failure
|
||||
except xcat_exception.xCATCmdFailure:
|
||||
LOG.error(_("rsetboot %(node)s %(device)s"),{'node':driver_info['xcat_node]'],
|
||||
'device':device})
|
||||
|
||||
|
||||
def validate(self, task, **kwargs):
|
||||
""" run chdef command to config xcat node infomation """
|
||||
method = kwargs['method']
|
||||
if method == 'set_boot_device':
|
||||
device = kwargs.get('device')
|
||||
if device not in VALID_BOOT_DEVICES:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Invalid boot device %s specified.") % device)
|
||||
else:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Unsupported method (%s) passed to xcat driver.")
|
||||
% method)
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
chdef_node(driver_info)
|
||||
|
||||
def vendor_passthru(self, task, **kwargs):
|
||||
method = kwargs['method']
|
||||
if method == 'set_boot_device':
|
||||
return self._set_boot_device(
|
||||
task,
|
||||
kwargs.get('device'),
|
||||
kwargs.get('persistent', False))
|
||||
|
||||
|
||||
class IPMIShellinaboxConsole(base.ConsoleInterface):
|
||||
"""A ConsoleInterface that uses ipmitool and shellinabox."""
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
check_timing_support()
|
||||
except OSError:
|
||||
raise exception.DriverLoadError(
|
||||
driver=self.__class__.__name__,
|
||||
reason="Unable to locate usable xcat command in "
|
||||
"the system path when checking xcat version")
|
||||
|
||||
def validate(self, task):
|
||||
"""Validate the Node console info.
|
||||
|
||||
:param task: a task from TaskManager.
|
||||
:raises: InvalidParameterValue
|
||||
"""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
if not driver_info['xcat_node']:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"xcat node name not supplied to xcat baremetal driver."))
|
||||
if not driver_info['port']:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"IPMI terminal port not supplied to IPMI driver."))
|
||||
|
||||
def start_console(self, task):
|
||||
"""Start a remote console for the node."""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
|
||||
path = _console_pwfile_path(driver_info['uuid'])
|
||||
pw_file = console_utils.make_persistent_password_file(
|
||||
path, driver_info['password'])
|
||||
|
||||
ipmi_cmd = "/:%(uid)s:%(gid)s:HOME:ipmitool -H %(address)s" \
|
||||
" -I lanplus -U %(user)s -f %(pwfile)s" \
|
||||
% {'uid': os.getuid(),
|
||||
'gid': os.getgid(),
|
||||
'address': driver_info['address'],
|
||||
'user': driver_info['username'],
|
||||
'pwfile': pw_file}
|
||||
if CONF.debug:
|
||||
ipmi_cmd += " -v"
|
||||
ipmi_cmd += " sol activate"
|
||||
console_utils.start_shellinabox_console(driver_info['uuid'],
|
||||
driver_info['port'],
|
||||
ipmi_cmd)
|
||||
|
||||
def stop_console(self, task):
|
||||
"""Stop the remote console session for the node."""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
console_utils.stop_shellinabox_console(driver_info['uuid'])
|
||||
utils.unlink_without_raise(_console_pwfile_path(driver_info['uuid']))
|
||||
|
||||
def get_console(self, task):
|
||||
"""Get the type and connection information about the console."""
|
||||
driver_info = _parse_driver_info(task.node)
|
||||
url = console_utils.get_shellinabox_console_url(driver_info['port'])
|
||||
return {'type': 'shellinabox', 'url': url}
|
@ -0,0 +1,110 @@
|
||||
"""
|
||||
util for xcat baremetal driver
|
||||
exec_xcatcmd
|
||||
xcat_ssh to excute remote cmd
|
||||
"""
|
||||
import paramiko
|
||||
import time
|
||||
import socket
|
||||
from ironic.openstack.common import log as logging
|
||||
from oslo.config import cfg
|
||||
from ironic.drivers.modules import xcat_exception
|
||||
from ironic.common import utils
|
||||
|
||||
xcat_opts = [
|
||||
cfg.IntOpt('ssh_session_timeout',
|
||||
default=10,
|
||||
help='ssh session time'),
|
||||
cfg.FloatOpt('ssh_shell_wait',
|
||||
default=0.5,
|
||||
help='wait time for the ssh cmd excute'),
|
||||
cfg.IntOpt('ssh_buf_size',
|
||||
default=65535,
|
||||
help='Maximum size (in charactor) of cache for ssh, '
|
||||
'including those in use'),
|
||||
cfg.StrOpt('ssh_key',
|
||||
default=None,
|
||||
help='ssh private key to login '),
|
||||
cfg.StrOpt('ssh_key_pass',
|
||||
default=None,
|
||||
help='Maximum size (in charactor) of cache for ssh, '
|
||||
'including those in use'),
|
||||
]
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(xcat_opts, group='xcat')
|
||||
|
||||
LAST_CMD_TIME = {}
|
||||
|
||||
def xcat_ssh(ip,port,username,password,cmd):
|
||||
""" exec remote command with ssh """
|
||||
key =None
|
||||
if CONF.xcat.ssh_key:
|
||||
try:
|
||||
key=paramiko.RSAKey.from_private_key_file(CONF.xcat.ssh_key)
|
||||
except paramiko.PasswordRequiredException:
|
||||
if not CONF.ssh_key_pass:
|
||||
raise Exception.message("no pubkey password")
|
||||
key = paramiko.RSAKey.from_private_key_file(
|
||||
CONF.xcat.ssh_key, CONF.xcat.ssh_key.ssh_key_pass)
|
||||
s = paramiko.SSHClient()
|
||||
s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
try:
|
||||
s.connect(ip,port,username=username,password=password,pkey=key,
|
||||
timeout=CONF.xcat.ssh_session_timeout)
|
||||
except socket.timeout as e:
|
||||
LOG.error(_("Unable to connect to the ssh server Exception: %(exception)s"),
|
||||
{'exception': e})
|
||||
chan = s.invoke_shell()
|
||||
output = chan.recv(CONF.xcat.ssh_buf_size)
|
||||
while not output.rstrip().endswith('#') and not output.rstrip().endswith('$'):
|
||||
output = chan.recv(CONF.xcat.ssh_buf_size)
|
||||
for c in cmd :
|
||||
_xcat_ssh_exec(chan,c,password)
|
||||
|
||||
def _xcat_ssh_exec(chan,cmd,password):
|
||||
""" exec ssh command """
|
||||
chan.send(cmd + '\n')
|
||||
time.sleep(CONF.xcat.ssh_shell_wait)
|
||||
ret = chan.recv(CONF.xcat.ssh_buf_size)
|
||||
if 'password' in ret and ret.rstrip().endswith(':'):
|
||||
chan.send(password + '\n')
|
||||
output = chan.recv(CONF.xcat.ssh_buf_size)
|
||||
while not output.rstrip().endswith('#') and not output.rstrip().endswith('$'):
|
||||
output = chan.recv(CONF.xcat.ssh_buf_size)
|
||||
return output
|
||||
|
||||
def _tsplit(string, delimiters):
|
||||
""" Behaves str.split but supports multiple delimiters. """
|
||||
delimiters = tuple(delimiters)
|
||||
stack = [string,]
|
||||
for delimiter in delimiters:
|
||||
for i, substring in enumerate(stack):
|
||||
substack = substring.split(delimiter)
|
||||
stack.pop(i)
|
||||
for j, _substring in enumerate(substack):
|
||||
stack.insert(i+j, _substring)
|
||||
return stack
|
||||
|
||||
def exec_xcatcmd(driver_info, command, args):
|
||||
""" excute xcat cmd """
|
||||
cmd = [command,
|
||||
driver_info['xcat_node']
|
||||
]
|
||||
cmd.extend(args.split(" "))
|
||||
# NOTE: ensure that no communications are excuted more
|
||||
# often than once every min_command_interval seconds.
|
||||
time_till_next_poll = CONF.ipmi.min_command_interval - (
|
||||
time.time() - LAST_CMD_TIME.get(driver_info['xcat_node'], 0))
|
||||
if time_till_next_poll > 0:
|
||||
time.sleep(time_till_next_poll)
|
||||
try:
|
||||
out, err = utils.execute(*cmd)
|
||||
if err:
|
||||
raise xcat_exception.xCATCmdFailure(cmd=cmd,node=driver_info['xcat_node'],
|
||||
args=args)
|
||||
finally:
|
||||
LAST_CMD_TIME[driver_info['xcat_node']] = time.time()
|
||||
return out, err
|
@ -0,0 +1,30 @@
|
||||
"""
|
||||
XCATBaremetalDriver
|
||||
use xcat to deploy a baremetal machine
|
||||
"""
|
||||
|
||||
|
||||
from ironic.drivers import base
|
||||
from ironic.drivers.modules import ipmitool
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic.drivers.modules import xcat_pxe
|
||||
from ironic.drivers import utils
|
||||
from ironic.drivers.modules import xcat_rpower
|
||||
|
||||
|
||||
class XCATBaremetalDriver(base.BaseDriver):
|
||||
"""xCAT driver
|
||||
This driver implements the `core` functionality, combinding
|
||||
:class:`ironic.drivers.xcat_rpower.XcatPower` for power on/off and reboot with
|
||||
:class:`ironic.driver.xcat_pxe.PXEDeploy` for image deployment. Implementations are in
|
||||
those respective classes; this class is merely the glue between them.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.power = xcat_rpower.XcatPower()
|
||||
self.console = ipmitool.IPMIShellinaboxConsole()
|
||||
self.deploy = xcat_pxe.PXEDeploy()
|
||||
self.pxe_vendor = pxe.VendorPassthru()
|
||||
self.ipmi_vendor = ipmitool.VendorPassthru()
|
||||
self.mapping = {'pass_deploy_info': self.pxe_vendor,
|
||||
'set_boot_device': self.ipmi_vendor}
|
||||
self.vendor = utils.MixinVendorInterface(self.mapping)
|
33
xCAT-OpenStack-ironic/ironic_baremetal/openstack-common.conf
Normal file
33
xCAT-OpenStack-ironic/ironic_baremetal/openstack-common.conf
Normal file
@ -0,0 +1,33 @@
|
||||
[DEFAULT]
|
||||
|
||||
# The list of modules to copy from oslo-incubator
|
||||
module=cliutils
|
||||
module=config.generator
|
||||
module=context
|
||||
module=db
|
||||
module=db.sqlalchemy
|
||||
module=db.sqlalchemy.migration_cli
|
||||
module=eventlet_backdoor
|
||||
module=excutils
|
||||
module=fileutils
|
||||
module=gettextutils
|
||||
module=importutils
|
||||
module=jsonutils
|
||||
module=local
|
||||
module=lockutils
|
||||
module=log
|
||||
module=loopingcall
|
||||
module=network_utils
|
||||
module=periodic_task
|
||||
module=policy
|
||||
module=processutils
|
||||
module=service
|
||||
module=strutils
|
||||
module=timeutils
|
||||
module=versionutils
|
||||
|
||||
# Tools
|
||||
|
||||
|
||||
# The base module to hold the copy of openstack.common
|
||||
base=ironic
|
34
xCAT-OpenStack-ironic/ironic_baremetal/setup.cfg
Normal file
34
xCAT-OpenStack-ironic/ironic_baremetal/setup.cfg
Normal file
@ -0,0 +1,34 @@
|
||||
[metadata]
|
||||
name = ironic
|
||||
version = 2014.2
|
||||
summary = OpenStack Bare Metal Provisioning
|
||||
description-file =
|
||||
README.rst
|
||||
author = chenglch
|
||||
author-email = chenglch@cn.ibm.com
|
||||
home-page = http://xcat.sf.net/
|
||||
classifier =
|
||||
Environment :: OpenStack
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 2.6
|
||||
|
||||
[files]
|
||||
packages =
|
||||
ironic
|
||||
|
||||
[entry_points]
|
||||
ironic.drivers =
|
||||
pxe_xcat = ironic.drivers.xcat:XCATBaremetalDriver
|
||||
|
||||
[pbr]
|
||||
autodoc_index_modules = True
|
||||
|
||||
[global]
|
||||
setup-hooks =
|
||||
pbr.hooks.setup_hook
|
30
xCAT-OpenStack-ironic/ironic_baremetal/setup.py
Normal file
30
xCAT-OpenStack-ironic/ironic_baremetal/setup.py
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2013 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.
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
pbr=True)
|
22
xCAT-OpenStack-ironic/ironic_baremetal/test-requirements.txt
Normal file
22
xCAT-OpenStack-ironic/ironic_baremetal/test-requirements.txt
Normal file
@ -0,0 +1,22 @@
|
||||
hacking>=0.8.0,<0.9
|
||||
coverage>=3.6
|
||||
discover
|
||||
fixtures>=0.3.14
|
||||
mock>=1.0
|
||||
Babel>=1.3
|
||||
MySQL-python
|
||||
psycopg2
|
||||
python-ironicclient
|
||||
python-subunit>=0.0.18
|
||||
testrepository>=0.0.18
|
||||
testtools>=0.9.34
|
||||
|
||||
# Doc requirements
|
||||
sphinx>=1.1.2,!=1.2.0,<1.3
|
||||
sphinxcontrib-pecanwsme>=0.8
|
||||
oslosphinx
|
||||
|
||||
# Required for Nova unit tests in ironic/nova/tests/ and can be removed
|
||||
# once the driver code lands in Nova.
|
||||
http://tarballs.openstack.org/nova/nova-master.tar.gz#egg=nova
|
||||
mox>=0.5.3
|
Loading…
Reference in New Issue
Block a user