
This will help user to avoid wrong configs, and oslo_config exception ConfigFileValueError will be raised if a wrong value was given in the config file. The list of options: [DEFAULT]/auth_strategy [glance]/auth_strategy [glance]/glance_protocol [neutron]/auth_strategy [amt]/protocol [irmc]/remote_image_share_type [irmc]/port [irmc]/auth_method [irmc]/sensor_method Co-Authored-By: Anton Arefiev <aarefiev@mirantis.com> Closes-Bug: 1502027 Change-Id: I63cdfbf4eb44c07f7fefa8301e9cda1289654c94
257 lines
8.9 KiB
Python
257 lines
8.9 KiB
Python
#
|
|
# 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.
|
|
"""
|
|
Common functionalities for AMT Driver
|
|
"""
|
|
import time
|
|
from xml.etree import ElementTree
|
|
|
|
from oslo_concurrency import processutils
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import importutils
|
|
import six
|
|
|
|
from ironic.common import boot_devices
|
|
from ironic.common import exception
|
|
from ironic.common.i18n import _
|
|
from ironic.common.i18n import _LE
|
|
from ironic.common import utils
|
|
|
|
|
|
pywsman = importutils.try_import('pywsman')
|
|
|
|
_SOAP_ENVELOPE = 'http://www.w3.org/2003/05/soap-envelope'
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
REQUIRED_PROPERTIES = {
|
|
'amt_address': _('IP address or host name of the node. Required.'),
|
|
'amt_password': _('Password. Required.'),
|
|
'amt_username': _('Username to log into AMT system. Required.'),
|
|
}
|
|
OPTIONAL_PROPERTIES = {
|
|
'amt_protocol': _('Protocol used for AMT endpoint. one of http, https; '
|
|
'default is "http". Optional.'),
|
|
}
|
|
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
|
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
|
|
|
opts = [
|
|
cfg.StrOpt('protocol',
|
|
default='http',
|
|
choices=['http', 'https'],
|
|
help=_('Protocol used for AMT endpoint, '
|
|
'support http/https')),
|
|
cfg.IntOpt('awake_interval',
|
|
default=60,
|
|
min=0,
|
|
help=_('Time interval (in seconds) for successive awake call '
|
|
'to AMT interface, this depends on the IdleTimeout '
|
|
'setting on AMT interface. AMT Interface will go to '
|
|
'sleep after 60 seconds of inactivity by default. '
|
|
'IdleTimeout=0 means AMT will not go to sleep at all. '
|
|
'Setting awake_interval=0 will disable awake call.')),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
opt_group = cfg.OptGroup(name='amt',
|
|
title='Options for the AMT power driver')
|
|
CONF.register_group(opt_group)
|
|
CONF.register_opts(opts, opt_group)
|
|
|
|
# TODO(lintan): More boot devices are supported by AMT, but not useful
|
|
# currently. Add them in the future.
|
|
BOOT_DEVICES_MAPPING = {
|
|
boot_devices.PXE: 'Intel(r) AMT: Force PXE Boot',
|
|
boot_devices.DISK: 'Intel(r) AMT: Force Hard-drive Boot',
|
|
boot_devices.CDROM: 'Intel(r) AMT: Force CD/DVD Boot',
|
|
}
|
|
DEFAULT_BOOT_DEVICE = boot_devices.DISK
|
|
|
|
AMT_PROTOCOL_PORT_MAP = {
|
|
'http': 16992,
|
|
'https': 16993,
|
|
}
|
|
|
|
# ReturnValue constants
|
|
RET_SUCCESS = '0'
|
|
|
|
# A dict cache last awake call to AMT Interface
|
|
AMT_AWAKE_CACHE = {}
|
|
|
|
|
|
class Client(object):
|
|
"""AMT client.
|
|
|
|
Create a pywsman client to connect to the target server
|
|
"""
|
|
def __init__(self, address, protocol, username, password):
|
|
port = AMT_PROTOCOL_PORT_MAP[protocol]
|
|
path = '/wsman'
|
|
self.client = pywsman.Client(address, port, path, protocol,
|
|
username, password)
|
|
|
|
def wsman_get(self, resource_uri, options=None):
|
|
"""Get target server info
|
|
|
|
:param options: client options
|
|
:param resource_uri: a URI to an XML schema
|
|
:returns: XmlDoc object
|
|
:raises: AMTFailure if get unexpected response.
|
|
:raises: AMTConnectFailure if unable to connect to the server.
|
|
"""
|
|
if options is None:
|
|
options = pywsman.ClientOptions()
|
|
doc = self.client.get(options, resource_uri)
|
|
item = 'Fault'
|
|
fault = xml_find(doc, _SOAP_ENVELOPE, item)
|
|
if fault is not None:
|
|
LOG.exception(_LE('Call to AMT with URI %(uri)s failed: '
|
|
'got Fault %(fault)s'),
|
|
{'uri': resource_uri, 'fault': fault.text})
|
|
raise exception.AMTFailure(cmd='wsman_get')
|
|
return doc
|
|
|
|
def wsman_invoke(self, options, resource_uri, method, data=None):
|
|
"""Invoke method on target server
|
|
|
|
:param options: client options
|
|
:param resource_uri: a URI to an XML schema
|
|
:param method: invoke method
|
|
:param data: a XmlDoc as invoke input
|
|
:returns: XmlDoc object
|
|
:raises: AMTFailure if get unexpected response.
|
|
:raises: AMTConnectFailure if unable to connect to the server.
|
|
"""
|
|
if data is None:
|
|
doc = self.client.invoke(options, resource_uri, method)
|
|
else:
|
|
doc = self.client.invoke(options, resource_uri, method, data)
|
|
item = "ReturnValue"
|
|
return_value = xml_find(doc, resource_uri, item).text
|
|
if return_value != RET_SUCCESS:
|
|
LOG.exception(_LE("Call to AMT with URI %(uri)s and "
|
|
"method %(method)s failed: return value "
|
|
"was %(value)s"),
|
|
{'uri': resource_uri, 'method': method,
|
|
'value': return_value})
|
|
raise exception.AMTFailure(cmd='wsman_invoke')
|
|
return doc
|
|
|
|
|
|
def parse_driver_info(node):
|
|
"""Parses and creates AMT driver info
|
|
|
|
:param node: an Ironic node object.
|
|
:returns: AMT driver info.
|
|
:raises: MissingParameterValue if any required parameters are missing.
|
|
:raises: InvalidParameterValue if any parameters have invalid values.
|
|
"""
|
|
|
|
info = node.driver_info or {}
|
|
d_info = {}
|
|
missing_info = []
|
|
|
|
for param in REQUIRED_PROPERTIES:
|
|
value = info.get(param)
|
|
if value:
|
|
if not isinstance(value, six.binary_type):
|
|
value = value.encode()
|
|
d_info[param[4:]] = value
|
|
else:
|
|
missing_info.append(param)
|
|
|
|
if missing_info:
|
|
raise exception.MissingParameterValue(_(
|
|
"AMT driver requires the following to be set in "
|
|
"node's driver_info: %s.") % missing_info)
|
|
|
|
d_info['uuid'] = node.uuid
|
|
param = 'amt_protocol'
|
|
protocol = info.get(param, CONF.amt.get(param[4:]))
|
|
if protocol not in AMT_PROTOCOL_PORT_MAP:
|
|
raise exception.InvalidParameterValue(
|
|
_("Invalid protocol %s.") % protocol)
|
|
if not isinstance(value, six.binary_type):
|
|
protocol = protocol.encode()
|
|
d_info[param[4:]] = protocol
|
|
|
|
return d_info
|
|
|
|
|
|
def get_wsman_client(node):
|
|
"""Return a AMT Client object
|
|
|
|
:param node: an Ironic node object.
|
|
:returns: a Client object
|
|
:raises: MissingParameterValue if any required parameters are missing.
|
|
:raises: InvalidParameterValue if any parameters have invalid values.
|
|
"""
|
|
driver_info = parse_driver_info(node)
|
|
client = Client(address=driver_info['address'],
|
|
protocol=driver_info['protocol'],
|
|
username=driver_info['username'],
|
|
password=driver_info['password'])
|
|
return client
|
|
|
|
|
|
def xml_find(doc, namespace, item):
|
|
"""Find the first element with namespace and item, in the XML doc
|
|
|
|
:param doc: a doc object.
|
|
:param namespace: the namespace of the element.
|
|
:param item: the element name.
|
|
:returns: the element object or None
|
|
:raises: AMTConnectFailure if unable to connect to the server.
|
|
"""
|
|
if doc is None:
|
|
raise exception.AMTConnectFailure()
|
|
tree = ElementTree.fromstring(doc.root().string())
|
|
query = ('.//{%(namespace)s}%(item)s' % {'namespace': namespace,
|
|
'item': item})
|
|
return tree.find(query)
|
|
|
|
|
|
def awake_amt_interface(node):
|
|
"""Wake up AMT interface.
|
|
|
|
AMT interface goes to sleep after a period of time if the host is off.
|
|
This method will ping AMT interface to wake it up. Because there is
|
|
no guarantee that the AMT address in driver_info is correct, only
|
|
ping the IP five times which is enough to wake it up.
|
|
|
|
:param node: an Ironic node object.
|
|
:raises: AMTConnectFailure if unable to connect to the server.
|
|
"""
|
|
awake_interval = CONF.amt.awake_interval
|
|
if awake_interval == 0:
|
|
return
|
|
|
|
now = time.time()
|
|
last_awake = AMT_AWAKE_CACHE.get(node.uuid, 0)
|
|
if now - last_awake > awake_interval:
|
|
cmd_args = ['ping', '-i', 0.2, '-c', 5,
|
|
node.driver_info['amt_address']]
|
|
try:
|
|
utils.execute(*cmd_args)
|
|
except processutils.ProcessExecutionError as err:
|
|
LOG.error(_LE('Unable to awake AMT interface on node '
|
|
'%(node_id)s. Error: %(error)s'),
|
|
{'node_id': node.uuid, 'error': err})
|
|
raise exception.AMTConnectFailure()
|
|
else:
|
|
LOG.debug(('Successfully awakened AMT interface on node '
|
|
'%(node_id)s.'), {'node_id': node.uuid})
|
|
AMT_AWAKE_CACHE[node.uuid] = now
|