Ejemplo n.º 1
0
def _parse_common(item, ns):
    """Parse common values that all attributes must have.

    :param item: an XML element.
    :param ns: the namespace to search.
    :returns: a dictionary containing the parsed attributes of the element.
    :raises: DracOperationFailed if the given element had no AttributeName
             value.
    """
    searches = {
        'current_value': './{%s}CurrentValue' % ns,
        'read_only': './{%s}IsReadOnly' % ns,
        'pending_value': './{%s}PendingValue' % ns
    }
    LOG.debug("Handing %(ns)s for %(xml)s", {
        'ns': ns,
        'xml': ET.tostring(item),
    })
    name = item.findtext('./{%s}AttributeName' % ns)
    if not name:
        raise exception.DracOperationFailed(
            message=_('Item has no name: "%s"') % ET.tostring(item))
    res = {}
    res['name'] = name

    for k in searches:
        if k == 'read_only':
            res[k] = item.findtext(searches[k]) == 'true'
        else:
            res[k] = _val_or_none(item.find(searches[k]))
    return res
Ejemplo n.º 2
0
def _get_config(node, resource):
    """Helper for get_config.

    Handles getting BIOS config values for a single namespace

    :param node: an ironic node object.
    :param resource: the namespace.
    :returns: a dictionary that maps the name of each attribute to a dictionary
              of values of that attribute.
    :raises: InvalidParameterValue if some information required to connnect
             to the DRAC is missing on the node or the value of one or more
             required parameters is invalid.
    :raises: DracClientError on an error from pywsman library.
    :raises: DracOperationFailed if the specified resource is unknown.
    """
    res = {}
    client = wsman_client.get_wsman_client(node)
    try:
        doc = client.wsman_enumerate(resource)
    except exception.DracClientError as exc:
        with excutils.save_and_reraise_exception():
            LOG.error(
                _LE('DRAC driver failed to get BIOS settings '
                    'for resource %(resource)s '
                    'from node %(node_uuid)s. '
                    'Reason: %(error)s.'), {
                        'node_uuid': node.uuid,
                        'resource': resource,
                        'error': exc
                    })
    items = doc.find('.//{%s}Items' % resource_uris.CIM_WSMAN)
    for item in items:
        if resource == resource_uris.DCIM_BIOSEnumeration:
            attribute = parse_enumeration(item, resource)
        elif resource == resource_uris.DCIM_BIOSString:
            attribute = parse_string(item, resource)
        elif resource == resource_uris.DCIM_BIOSInteger:
            attribute = parse_integer(item, resource)
        else:
            raise exception.DracOperationFailed(
                message=_('Unknown namespace %(ns)s for item: "%(item)s"') % {
                    'item': ET.tostring(item),
                    'ns': resource
                })
        res[attribute['name']] = attribute
    return res
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
def set_config(task, **kwargs):
    """Sets the pending_value parameter for each of the values passed in.

    :param task: an ironic task object.
    :param kwargs: a dictionary of {'AttributeName': 'NewValue'}
    :raises: DracOperationFailed if any new values are invalid.
    :raises: DracOperationFailed if any of the attributes are read-only.
    :raises: DracOperationFailed if any of the attributes cannot be set for
             any other reason.
    :raises: DracClientError on an error from the pywsman library.
    :returns: A boolean indicating whether commit_config needs to be
              called to make the changes.

    """
    node = task.node
    management.check_for_config_job(node)
    current = get_config(node)
    unknown_keys = set(kwargs) - set(current)
    if unknown_keys:
        LOG.warning(_LW('Ignoring unknown BIOS attributes "%r"'), unknown_keys)

    candidates = set(kwargs) - unknown_keys
    read_only_keys = []
    unchanged_attribs = []
    invalid_attribs_msgs = []
    attrib_names = []

    for k in candidates:
        if str(kwargs[k]) == str(current[k]['current_value']):
            unchanged_attribs.append(k)
        elif current[k]['read_only']:
            read_only_keys.append(k)
        else:
            if 'possible_values' in current[k]:
                if str(kwargs[k]) not in current[k]['possible_values']:
                    m = _('Attribute %(attr)s cannot be set to value %(val)s.'
                          ' It must be in %(ok)r') % {
                              'attr': k,
                              'val': kwargs[k],
                              'ok': current[k]['possible_values']
                          }
                    invalid_attribs_msgs.append(m)
                    continue
            if ('pcre_regex' in current[k]
                    and current[k]['pcre_regex'] is not None):
                regex = re.compile(current[k]['pcre_regex'])
                if regex.search(str(kwargs[k])) is None:
                    # TODO(victor-lowther)
                    # Leave untranslated for now until the unicode
                    # issues that the test suite exposes are straightened out.
                    m = ('Attribute %(attr)s cannot be set to value %(val)s.'
                         ' It must match regex %(re)s.') % {
                             'attr': k,
                             'val': kwargs[k],
                             're': current[k]['pcre_regex']
                         }
                    invalid_attribs_msgs.append(m)
                    continue
            if 'lower_bound' in current[k]:
                lower = current[k]['lower_bound']
                upper = current[k]['upper_bound']
                val = int(kwargs[k])
                if val < lower or val > upper:
                    m = _('Attribute %(attr)s cannot be set to value %(val)d.'
                          ' It must be between %(lower)d and %(upper)d.') % {
                              'attr': k,
                              'val': val,
                              'lower': lower,
                              'upper': upper
                          }
                    invalid_attribs_msgs.append(m)
                    continue
            attrib_names.append(k)

    if unchanged_attribs:
        LOG.warning(_LW('Ignoring unchanged BIOS settings %r'),
                    unchanged_attribs)

    if invalid_attribs_msgs or read_only_keys:
        raise exception.DracOperationFailed(
            _format_error_msg(invalid_attribs_msgs, read_only_keys))

    if not attrib_names:
        return False

    client = wsman_client.get_wsman_client(node)
    selectors = {
        'CreationClassName': 'DCIM_BIOSService',
        'Name': 'DCIM:BIOSService',
        'SystemCreationClassName': 'DCIM_ComputerSystem',
        'SystemName': 'DCIM:ComputerSystem'
    }
    properties = {
        'Target': 'BIOS.Setup.1-1',
        'AttributeName': attrib_names,
        'AttributeValue': map(lambda k: kwargs[k], attrib_names)
    }
    doc = client.wsman_invoke(resource_uris.DCIM_BIOSService, 'SetAttributes',
                              selectors, properties)
    # Yes, we look for RebootRequired.  In this context, that actually means
    # that we need to create a lifecycle controller config job and then reboot
    # so that the lifecycle controller can commit the BIOS config changes that
    # we have proposed.
    set_results = doc.findall('.//{%s}RebootRequired' %
                              resource_uris.DCIM_BIOSService)
    return any(str(res.text) == 'Yes' for res in set_results)
Ejemplo n.º 5
0
def get_config(node):
    """Get the BIOS configuration from a Dell server using WSMAN

    :param node: an ironic node object.
    :raises: DracClientError on an error from pywsman.
    :raises: DracOperationFailed when a BIOS setting cannot be parsed.
    :returns: a dictionary containing BIOS settings in the form of:
        {'EnumAttrib': {'name': 'EnumAttrib',
                        'current_value': 'Value',
                        'pending_value': 'New Value', # could also be None
                        'read_only': False,
                        'possible_values': ['Value', 'New Value', 'None']},
         'StringAttrib': {'name': 'StringAttrib',
                          'current_value': 'Information',
                          'pending_value': None,
                          'read_only': False,
                          'min_length': 0,
                          'max_length': 255,
                          'pcre_regex': '^[0-9A-Za-z]{0,255}$'},
         'IntegerAttrib': {'name': 'IntegerAttrib',
                           'current_value': 0,
                           'pending_value': None,
                           'read_only': True,
                           'lower_bound': 0,
                           'upper_bound': 65535}
        }

    The above values are only examples, of course.  BIOS attributes exposed via
    this API will always be either an enumerated attribute, a string attribute,
    or an integer attribute.  All attributes have the following parameters:
    :name: is the name of the BIOS attribute.
    :current_value: is the current value of the attribute.
                    It will always be either an integer or a string.
    :pending_value: is the new value that we want the attribute to have.
                    None means that there is no pending value.
    :read_only: indicates whether this attribute can be changed.  Trying to
                change a read-only value will result in an error.
                The read-only flag can change depending on other attributes.
                A future version of this call may expose the dependencies
                that indicate when that may happen.

    Enumerable attributes also have the following parameters:
    :possible_values: is an array of values it is permissible to set
                      the attribute to.

    String attributes also have the following parameters:
    :min_length: is the minimum length of the string.
    :max_length: is the maximum length of the string.
    :pcre_regex: is a PCRE compatible regular expression that the string
                 must match.  It may be None if the string is read only
                 or if the string does not have to match any particular
                 regular expression.

    Integer attributes also have the following parameters:
    :lower_bound: is the minimum value the attribute can have.
    :upper_bound: is the maximum value the attribute can have.

    """
    res = {}
    for ns in [
            resource_uris.DCIM_BIOSEnumeration, resource_uris.DCIM_BIOSString,
            resource_uris.DCIM_BIOSInteger
    ]:
        attribs = _get_config(node, ns)
        if not set(res).isdisjoint(set(attribs)):
            raise exception.DracOperationFailed(
                message=_('Colliding attributes %r') %
                (set(res) & set(attribs)))
        res.update(attribs)
    return res
Ejemplo n.º 6
0
    def wsman_invoke(self,
                     resource_uri,
                     method,
                     selectors=None,
                     properties=None,
                     expected_return=None):
        """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: 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, str(name), str(item))
                else:
                    xml_root.add(resource_uri, name, value)
            LOG.debug(
                ('WSMAN invoking: %(resource_uri)s:%(method)s'
                 '\nselectors: %(selectors)r\nxml: %(xml)s'), {
                     'resource_uri': resource_uri,
                     'method': method,
                     'selectors': selectors,
                     'xml': xml_root.string()
                 })

        else:
            xml_doc = None

            for name, value in properties.items():
                options.add_property(name, value)

            LOG.debug(
                ('WSMAN invoking: %(resource_uri)s:%(method)s'
                 '\nselectors: %(selectors)r\properties: %(props)r') % {
                     'resource_uri': resource_uri,
                     'method': method,
                     'selectors': selectors,
                     'props': properties
                 })

        doc = retry_on_empty_response(self.client, 'invoke', options,
                                      resource_uri, method, xml_doc)
        root = self._get_root(doc)
        LOG.debug("WSMAN invoke returned raw XML: %s",
                  ElementTree.tostring(root))

        return_value = drac_common.find_xml(root, 'ReturnValue',
                                            resource_uri).text
        if return_value == RET_ERROR:
            messages = drac_common.find_xml(root, 'Message', resource_uri,
                                            True)
            message_args = drac_common.find_xml(root, 'MessageArguments',
                                                resource_uri, True)

            if message_args:
                messages = [
                    m.text % p.text for (m, p) in zip(messages, message_args)
                ]
            else:
                messages = [m.text for m in messages]

            raise exception.DracOperationFailed(message='%r' % messages)

        if expected_return and return_value != expected_return:
            raise exception.DracUnexpectedReturnValue(
                expected_return_value=expected_return,
                actual_return_value=return_value)

        return root