8c07c4fda3
This patch enforces the rules E123, E126, E127, and E128 in the ironic code base: E123 - closing bracket does not match indentation of opening bracket’s line E126 - continuation line over-indented for hanging indent E127 - continuation line over-indented for visual indent E128 - continuation line under-indented for visual indent This fixes any parts of the current code which fails these rules and removes these rules from the tox.ini flake8 ignore list. Change-Id: Ia96582b5e9abc088d6c1694afc93c59be4a4065c Closes-Bug: 1421522
239 lines
9.1 KiB
Python
239 lines
9.1 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.
|
|
|
|
"""
|
|
Wrapper for pywsman.Client
|
|
"""
|
|
|
|
import time
|
|
from xml.etree import ElementTree
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import importutils
|
|
|
|
from ironic.common import exception
|
|
from ironic.common.i18n import _LW
|
|
from ironic.drivers.modules.drac import common as drac_common
|
|
|
|
pywsman = importutils.try_import('pywsman')
|
|
|
|
opts = [
|
|
cfg.IntOpt('client_retry_count',
|
|
default=5,
|
|
help='In case there is a communication failure, the DRAC '
|
|
'client is going to resend the request as many times as '
|
|
'defined in this setting.'),
|
|
cfg.IntOpt('client_retry_delay',
|
|
default=5,
|
|
help='In case there is a communication failure, the DRAC '
|
|
'client is going to wait for as many seconds as defined '
|
|
'in this setting before resending the request.')
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
opt_group = cfg.OptGroup(name='drac',
|
|
title='Options for the DRAC driver')
|
|
CONF.register_group(opt_group)
|
|
CONF.register_opts(opts, opt_group)
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
_SOAP_ENVELOPE_URI = 'http://www.w3.org/2003/05/soap-envelope'
|
|
|
|
# Filter Dialects, see (Section 2.3.1):
|
|
# http://en.community.dell.com/techcenter/extras/m/white_papers/20439105.aspx
|
|
_FILTER_DIALECT_MAP = {'cql': 'http://schemas.dmtf.org/wbem/cql/1/dsp0202.pdf',
|
|
'wql': 'http://schemas.microsoft.com/wbem/wsman/1/WQL'}
|
|
|
|
# ReturnValue constants
|
|
RET_SUCCESS = '0'
|
|
RET_ERROR = '2'
|
|
RET_CREATED = '4096'
|
|
|
|
|
|
def get_wsman_client(node):
|
|
"""Return a DRAC client object.
|
|
|
|
Given an ironic node object, this method gives back a
|
|
Client object which is a wrapper for pywsman.Client.
|
|
|
|
:param node: an ironic node object.
|
|
:returns: a Client object.
|
|
:raises: InvalidParameterValue if some mandatory information
|
|
is missing on the node or on invalid inputs.
|
|
"""
|
|
driver_info = drac_common.parse_driver_info(node)
|
|
client = Client(**driver_info)
|
|
return client
|
|
|
|
|
|
def retry_on_empty_response(client, action, *args, **kwargs):
|
|
"""Wrapper to retry an action on failure."""
|
|
|
|
func = getattr(client, action)
|
|
for i in range(CONF.drac.client_retry_count):
|
|
response = func(*args, **kwargs)
|
|
if response:
|
|
return response
|
|
else:
|
|
LOG.warning(_LW('Empty response on calling %(action)s on client. '
|
|
'Last error (cURL error code): %(last_error)s, '
|
|
'fault string: "%(fault_string)s" '
|
|
'response_code: %(response_code)s. '
|
|
'Retry attempt %(count)d') %
|
|
{'action': action,
|
|
'last_error': client.last_error(),
|
|
'fault_string': client.fault_string(),
|
|
'response_code': client.response_code(),
|
|
'count': i + 1})
|
|
|
|
time.sleep(CONF.drac.client_retry_delay)
|
|
|
|
|
|
class Client(object):
|
|
|
|
def __init__(self, drac_host, drac_port, drac_path, drac_protocol,
|
|
drac_username, drac_password):
|
|
pywsman_client = pywsman.Client(drac_host, drac_port, drac_path,
|
|
drac_protocol, drac_username,
|
|
drac_password)
|
|
# TODO(ifarkas): Add support for CACerts
|
|
pywsman.wsman_transport_set_verify_peer(pywsman_client, False)
|
|
pywsman.wsman_transport_set_verify_host(pywsman_client, False)
|
|
|
|
self.client = pywsman_client
|
|
|
|
def wsman_enumerate(self, resource_uri, filter_query=None,
|
|
filter_dialect='cql'):
|
|
"""Enumerates a remote WS-Man class.
|
|
|
|
:param resource_uri: URI of the resource.
|
|
:param filter_query: the query string.
|
|
:param filter_dialect: the filter dialect. Valid options are:
|
|
'cql' and 'wql'. Defaults to 'cql'.
|
|
:raises: DracClientError on an error from pywsman library.
|
|
:raises: DracInvalidFilterDialect if an invalid filter dialect
|
|
was specified.
|
|
:returns: an ElementTree object of the response received.
|
|
"""
|
|
options = pywsman.ClientOptions()
|
|
|
|
filter_ = None
|
|
if filter_query is not None:
|
|
try:
|
|
filter_dialect = _FILTER_DIALECT_MAP[filter_dialect]
|
|
except KeyError:
|
|
valid_opts = ', '.join(_FILTER_DIALECT_MAP)
|
|
raise exception.DracInvalidFilterDialect(
|
|
invalid_filter=filter_dialect, supported=valid_opts)
|
|
|
|
filter_ = pywsman.Filter()
|
|
filter_.simple(filter_dialect, filter_query)
|
|
|
|
options.set_flags(pywsman.FLAG_ENUMERATION_OPTIMIZATION)
|
|
options.set_max_elements(100)
|
|
|
|
doc = retry_on_empty_response(self.client, 'enumerate',
|
|
options, filter_, resource_uri)
|
|
root = self._get_root(doc)
|
|
|
|
final_xml = root
|
|
find_query = './/{%s}Body' % _SOAP_ENVELOPE_URI
|
|
insertion_point = final_xml.find(find_query)
|
|
while doc.context() is not None:
|
|
doc = retry_on_empty_response(self.client, 'pull', options, None,
|
|
resource_uri, str(doc.context()))
|
|
root = self._get_root(doc)
|
|
for result in root.findall(find_query):
|
|
for child in list(result):
|
|
insertion_point.append(child)
|
|
|
|
return final_xml
|
|
|
|
def wsman_invoke(self, resource_uri, method, selectors=None,
|
|
properties=None, expected_return_value=RET_SUCCESS):
|
|
"""Invokes a remote WS-Man method.
|
|
|
|
:param resource_uri: URI of the resource.
|
|
:param method: name of the method to invoke.
|
|
:param selectors: dictionary of selectors.
|
|
:param properties: dictionary of properties.
|
|
:param expected_return_value: expected return value.
|
|
:raises: DracClientError on an error from pywsman library.
|
|
:raises: DracOperationFailed on error reported back by DRAC.
|
|
:raises: DracUnexpectedReturnValue on return value mismatch.
|
|
:returns: an ElementTree object of the response received.
|
|
"""
|
|
if selectors is None:
|
|
selectors = {}
|
|
|
|
if properties is None:
|
|
properties = {}
|
|
|
|
options = pywsman.ClientOptions()
|
|
|
|
for name, value in selectors.items():
|
|
options.add_selector(name, value)
|
|
|
|
# NOTE(ifarkas): manually constructing the XML doc should be deleted
|
|
# once pywsman supports passing a list as a property.
|
|
# For now this is only a fallback method: in case no
|
|
# list provided, the supported pywsman API will be used.
|
|
list_included = any([isinstance(prop_item, list) for prop_item
|
|
in properties.values()])
|
|
if list_included:
|
|
xml_doc = pywsman.XmlDoc('%s_INPUT' % method, resource_uri)
|
|
xml_root = xml_doc.root()
|
|
|
|
for name, value in properties.items():
|
|
if isinstance(value, list):
|
|
for item in value:
|
|
xml_root.add(resource_uri, name, item)
|
|
else:
|
|
xml_root.add(resource_uri, name, value)
|
|
|
|
else:
|
|
xml_doc = None
|
|
|
|
for name, value in properties.items():
|
|
options.add_property(name, value)
|
|
|
|
doc = retry_on_empty_response(self.client, 'invoke', options,
|
|
resource_uri, method, xml_doc)
|
|
|
|
root = self._get_root(doc)
|
|
|
|
return_value = drac_common.find_xml(root, 'ReturnValue',
|
|
resource_uri).text
|
|
if return_value != expected_return_value:
|
|
if return_value == RET_ERROR:
|
|
message = drac_common.find_xml(root, 'Message',
|
|
resource_uri).text
|
|
raise exception.DracOperationFailed(message=message)
|
|
else:
|
|
raise exception.DracUnexpectedReturnValue(
|
|
expected_return_value=expected_return_value,
|
|
actual_return_value=return_value)
|
|
|
|
return root
|
|
|
|
def _get_root(self, doc):
|
|
if doc is None or doc.root() is None:
|
|
raise exception.DracClientError(
|
|
last_error=self.client.last_error(),
|
|
fault_string=self.client.fault_string(),
|
|
response_code=self.client.response_code())
|
|
root = doc.root()
|
|
return ElementTree.fromstring(root.string())
|