diff --git a/pyghmi/redfish/command.py b/pyghmi/redfish/command.py index 0b28ccfe..ccb12f9b 100644 --- a/pyghmi/redfish/command.py +++ b/pyghmi/redfish/command.py @@ -145,80 +145,6 @@ class SensorReading(object): self.unavailable = unavailable -class AttrDependencyHandler(object): - def __init__(self, dependencies, currsettings, pendingsettings): - self.dependencymap = {} - for dep in dependencies.get('Dependencies', [{}]): - if 'Dependency' not in dep: - continue - if dep['Type'] != 'Map': - continue - if dep['DependencyFor'] in self.dependencymap: - self.dependencymap[ - dep['DependencyFor']].append(dep['Dependency']) - else: - self.dependencymap[ - dep['DependencyFor']] = [dep['Dependency']] - self.curr = currsettings - self.pend = pendingsettings - self.reg = dependencies['Attributes'] - - def get_overrides(self, setting): - overrides = {} - blameattrs = [] - if setting not in self.dependencymap: - return {}, [] - for depinfo in self.dependencymap[setting]: - lastoper = None - lastcond = None - for mapfrom in depinfo.get('MapFrom', []): - if lastcond is not None and not lastoper: - break # MapTerm required to make sense of this, give up - currattr = mapfrom['MapFromAttribute'] - blameattrs.append(currattr) - currprop = mapfrom['MapFromProperty'] - if currprop == 'CurrentValue': - if currattr in self.pend: - currval = self.pend[currattr] - else: - currval = self.curr[currattr] - else: - currval = self.reg[currattr][currprop] - lastcond = self.process(currval, mapfrom, lastcond, lastoper) - lastoper = mapfrom.get('MapTerms', None) - if lastcond: - if setting not in overrides: - overrides[setting] = {} - if depinfo['MapToAttribute'] not in overrides[setting]: - overrides[depinfo['MapToAttribute']] = {} - overrides[depinfo['MapToAttribute']][ - depinfo['MapToProperty']] = depinfo['MapToValue'] - return overrides, blameattrs - - def process(self, currval, mapfrom, lastcond, lastoper): - newcond = None - mfc = mapfrom['MapFromCondition'] - if mfc == 'EQU': - newcond = currval == mapfrom['MapFromValue'] - if mfc == 'NEQ': - newcond = currval != mapfrom['MapFromValue'] - if mfc == 'GEQ': - newcond = float(currval) >= float(mapfrom['MapFromValue']) - if mfc == 'GTR': - newcond = float(currval) > float(mapfrom['MapFromValue']) - if mfc == 'LEQ': - newcond = float(currval) <= float(mapfrom['MapFromValue']) - if mfc == 'LSS': - newcond = float(currval) < float(mapfrom['MapFromValue']) - if lastcond is not None: - if lastoper == 'AND': - return lastcond and newcond - elif lastoper == 'OR': - return lastcond or newcond - return None - return newcond - - class Command(object): def __init__(self, bmc, userid, password, verifycallback, sysurl=None, @@ -902,40 +828,6 @@ class Command(object): summary['badreadings'].append(unkinf) return summary - def _get_biosreg(self, url): - addon = {} - valtodisplay = {} - displaytoval = {} - reg = self._do_web_request(url) - reg = reg['RegistryEntries'] - for attr in reg['Attributes']: - vals = attr.get('Value', []) - if vals: - valtodisplay[attr['AttributeName']] = {} - displaytoval[attr['AttributeName']] = {} - for val in vals: - valtodisplay[ - attr['AttributeName']][val['ValueName']] = val[ - 'ValueDisplayName'] - displaytoval[ - attr['AttributeName']][val['ValueDisplayName']] = val[ - 'ValueName'] - defaultval = attr.get('DefaultValue', None) - defaultval = valtodisplay.get(attr['AttributeName'], {}).get( - defaultval, defaultval) - if attr['Type'] == 'Integer' and defaultval: - defaultval = int(defaultval) - if attr['Type'] == 'Boolean': - vals = [{'ValueDisplayName': 'True'}, - {'ValueDisplayName': 'False'}] - addon[attr['AttributeName']] = { - 'default': defaultval, - 'help': attr.get('HelpText', None), - 'sortid': attr.get('DisplayOrder', None), - 'possible': [x['ValueDisplayName'] for x in vals], - } - return addon, valtodisplay, displaytoval, reg - def get_bmc_configuration(self): """Get miscellaneous BMC configuration @@ -960,6 +852,9 @@ class Command(object): # For now, this is a stub, no implementation for redfish currently return self.oem.set_bmc_configuration(changeset) + def set_system_configuration(self, changeset): + return self.oem.set_system_configuration(changeset, self) + def clear_bmc_configuration(self): """Reset BMC to factory default @@ -971,64 +866,7 @@ class Command(object): 'Clear BMC configuration not supported in redfish yet') def get_system_configuration(self, hideadvanced=True): - return self._getsyscfg()[0] - - def _getsyscfg(self): - biosinfo = self._do_web_request(self._biosurl, cache=False) - reginfo = ({}, {}, {}, {}) - extrainfo = {} - valtodisplay = {} - self.attrdeps = {'Dependencies': [], 'Attributes': []} - if 'AttributeRegistry' in biosinfo: - overview = self._do_web_request('/redfish/v1/') - reglist = overview['Registries']['@odata.id'] - reglist = self._do_web_request(reglist) - regurl = None - for cand in reglist.get('Members', []): - cand = cand.get('@odata.id', '') - candname = cand.split('/')[-1] - if candname == '': # implementation uses trailing slash - candname = cand.split('/')[-2] - if candname == biosinfo['AttributeRegistry']: - regurl = cand - break - if not regurl: - # Workaround a vendor bug where they link to a - # non-existant name - for cand in reglist.get('Members', []): - cand = cand.get('@odata.id', '') - candname = cand.split('/')[-1] - candname = candname.split('.')[0] - if candname == biosinfo[ - 'AttributeRegistry'].split('.')[0]: - regurl = cand - break - if regurl: - reginfo = self._do_web_request(regurl) - for reg in reginfo.get('Location', []): - if reg.get('Language', 'en').startswith('en'): - reguri = reg['Uri'] - reginfo = self._get_biosreg(reguri) - extrainfo, valtodisplay, _, self.attrdeps = reginfo - currsettings = {} - try: - pendingsettings = self._do_web_request(self._setbiosurl) - except exc.UnsupportedFunctionality: - pendingsettings = {} - pendingsettings = pendingsettings.get('Attributes', {}) - for setting in biosinfo.get('Attributes', {}): - val = biosinfo['Attributes'][setting] - currval = val - if setting in pendingsettings: - val = pendingsettings[setting] - val = valtodisplay.get(setting, {}).get(val, val) - currval = valtodisplay.get(setting, {}).get(currval, currval) - val = {'value': val} - if currval != val['value']: - val['active'] = currval - val.update(**extrainfo.get(setting, {})) - currsettings[setting] = val - return currsettings, reginfo + return self.oem.get_system_configuration(hideadvanced, self) def clear_system_configuration(self): """Clear the BIOS/UEFI configuration @@ -1057,83 +895,6 @@ class Command(object): parms = {'Action': 'Bios.ResetBios'} self._do_web_request(rb, parms) - def set_system_configuration(self, changeset): - while True: - try: - self._set_system_configuration(changeset) - return - except exc.RedfishError as re: - if ('etag' not in re.msgid.lower() - and 'PreconditionFailed' not in re.msgid): - raise - - def _set_system_configuration(self, changeset): - currsettings, reginfo = self._getsyscfg() - rawsettings = self._do_web_request(self._biosurl, cache=False) - rawsettings = rawsettings.get('Attributes', {}) - pendingsettings = self._do_web_request(self._setbiosurl) - etag = pendingsettings.get('@odata.etag', None) - pendingsettings = pendingsettings.get('Attributes', {}) - dephandler = AttrDependencyHandler(self.attrdeps, rawsettings, - pendingsettings) - for change in list(changeset): - if change not in currsettings: - found = False - for attr in currsettings: - if fnmatch(attr.lower(), change.lower()): - found = True - changeset[attr] = changeset[change] - if fnmatch(attr.lower(), - change.replace('.', '_').lower()): - found = True - changeset[attr] = changeset[change] - if found: - del changeset[change] - for change in changeset: - changeval = changeset[change] - overrides, blameattrs = dephandler.get_overrides(change) - meta = {} - for attr in self.attrdeps['Attributes']: - if attr['AttributeName'] == change: - meta = dict(attr) - break - meta.update(**overrides.get(change, {})) - if meta.get('ReadOnly', False) or meta.get('GrayOut', False): - errstr = '{0} is read only'.format(change) - if blameattrs: - errstr += (' due to one of the following settings: ' - '{0}'.format(','.join(sorted(blameattrs))) - ) - raise exc.InvalidParameterValue(errstr) - if (currsettings.get(change, {}).get('possible', []) - and changeval not in currsettings[change]['possible']): - normval = changeval.lower() - normval = re.sub(r'\s+', ' ', normval) - if not normval.endswith('*'): - normval += '*' - for cand in currsettings[change]['possible']: - if fnmatch(cand.lower().replace(' ', ''), - normval.replace(' ', '')): - changeset[change] = cand - break - else: - raise exc.InvalidParameterValue( - '{0} is not a valid value for {1} ({2})'.format( - changeval, change, ','.join( - currsettings[change]['possible']))) - if changeset[change] in reginfo[2].get(change, {}): - changeset[change] = reginfo[2][change][changeset[change]] - for regentry in reginfo[3].get('Attributes', []): - if change in (regentry.get('AttributeName', ''), - regentry.get('DisplayName', '')): - if regentry.get('Type', None) == 'Integer': - changeset[change] = int(changeset[change]) - if regentry.get('Type', None) == 'Boolean': - changeset[change] = _to_boolean(changeset[change]) - redfishsettings = {'Attributes': changeset} - self._do_web_request(self._setbiosurl, redfishsettings, 'PATCH', - etag=etag) - def set_net_configuration(self, ipv4_address=None, ipv4_configuration=None, ipv4_gateway=None, name=None): patch = {} diff --git a/pyghmi/redfish/oem/generic.py b/pyghmi/redfish/oem/generic.py index 22cab09c..9f46550c 100644 --- a/pyghmi/redfish/oem/generic.py +++ b/pyghmi/redfish/oem/generic.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from fnmatch import fnmatch import json import os +import re import pyghmi.exceptions as exc import pyghmi.media as media @@ -47,6 +49,80 @@ boot_devices_read = { } +class AttrDependencyHandler(object): + def __init__(self, dependencies, currsettings, pendingsettings): + self.dependencymap = {} + for dep in dependencies.get('Dependencies', [{}]): + if 'Dependency' not in dep: + continue + if dep['Type'] != 'Map': + continue + if dep['DependencyFor'] in self.dependencymap: + self.dependencymap[ + dep['DependencyFor']].append(dep['Dependency']) + else: + self.dependencymap[ + dep['DependencyFor']] = [dep['Dependency']] + self.curr = currsettings + self.pend = pendingsettings + self.reg = dependencies['Attributes'] + + def get_overrides(self, setting): + overrides = {} + blameattrs = [] + if setting not in self.dependencymap: + return {}, [] + for depinfo in self.dependencymap[setting]: + lastoper = None + lastcond = None + for mapfrom in depinfo.get('MapFrom', []): + if lastcond is not None and not lastoper: + break # MapTerm required to make sense of this, give up + currattr = mapfrom['MapFromAttribute'] + blameattrs.append(currattr) + currprop = mapfrom['MapFromProperty'] + if currprop == 'CurrentValue': + if currattr in self.pend: + currval = self.pend[currattr] + else: + currval = self.curr[currattr] + else: + currval = self.reg[currattr][currprop] + lastcond = self.process(currval, mapfrom, lastcond, lastoper) + lastoper = mapfrom.get('MapTerms', None) + if lastcond: + if setting not in overrides: + overrides[setting] = {} + if depinfo['MapToAttribute'] not in overrides[setting]: + overrides[depinfo['MapToAttribute']] = {} + overrides[depinfo['MapToAttribute']][ + depinfo['MapToProperty']] = depinfo['MapToValue'] + return overrides, blameattrs + + def process(self, currval, mapfrom, lastcond, lastoper): + newcond = None + mfc = mapfrom['MapFromCondition'] + if mfc == 'EQU': + newcond = currval == mapfrom['MapFromValue'] + if mfc == 'NEQ': + newcond = currval != mapfrom['MapFromValue'] + if mfc == 'GEQ': + newcond = float(currval) >= float(mapfrom['MapFromValue']) + if mfc == 'GTR': + newcond = float(currval) > float(mapfrom['MapFromValue']) + if mfc == 'LEQ': + newcond = float(currval) <= float(mapfrom['MapFromValue']) + if mfc == 'LSS': + newcond = float(currval) < float(mapfrom['MapFromValue']) + if lastcond is not None: + if lastoper == 'AND': + return lastcond and newcond + elif lastoper == 'OR': + return lastcond or newcond + return None + return newcond + + class OEMHandler(object): hostnic = None @@ -128,6 +204,179 @@ class OEMHandler(object): raise exc.UnsupportedFunctionality( 'Platform does not support setting bmc attributes') + def _get_biosreg(self, url, fishclient): + addon = {} + valtodisplay = {} + displaytoval = {} + reg = fishclient._do_web_request(url) + reg = reg['RegistryEntries'] + for attr in reg['Attributes']: + vals = attr.get('Value', []) + if vals: + valtodisplay[attr['AttributeName']] = {} + displaytoval[attr['AttributeName']] = {} + for val in vals: + valtodisplay[ + attr['AttributeName']][val['ValueName']] = val[ + 'ValueDisplayName'] + displaytoval[ + attr['AttributeName']][val['ValueDisplayName']] = val[ + 'ValueName'] + defaultval = attr.get('DefaultValue', None) + defaultval = valtodisplay.get(attr['AttributeName'], {}).get( + defaultval, defaultval) + if attr['Type'] == 'Integer' and defaultval: + defaultval = int(defaultval) + if attr['Type'] == 'Boolean': + vals = [{'ValueDisplayName': 'True'}, + {'ValueDisplayName': 'False'}] + addon[attr['AttributeName']] = { + 'default': defaultval, + 'help': attr.get('HelpText', None), + 'sortid': attr.get('DisplayOrder', None), + 'possible': [x['ValueDisplayName'] for x in vals], + } + return addon, valtodisplay, displaytoval, reg + + def get_system_configuration(self, hideadvanced=True, fishclient=None): + return self._getsyscfg(fishclient)[0] + + def _getsyscfg(self, fishclient): + biosinfo = self._do_web_request(fishclient._biosurl, cache=False) + reginfo = ({}, {}, {}, {}) + extrainfo = {} + valtodisplay = {} + self.attrdeps = {'Dependencies': [], 'Attributes': []} + if 'AttributeRegistry' in biosinfo: + overview = fishclient._do_web_request('/redfish/v1/') + reglist = overview['Registries']['@odata.id'] + reglist = fishclient._do_web_request(reglist) + regurl = None + for cand in reglist.get('Members', []): + cand = cand.get('@odata.id', '') + candname = cand.split('/')[-1] + if candname == '': # implementation uses trailing slash + candname = cand.split('/')[-2] + if candname == biosinfo['AttributeRegistry']: + regurl = cand + break + if not regurl: + # Workaround a vendor bug where they link to a + # non-existant name + for cand in reglist.get('Members', []): + cand = cand.get('@odata.id', '') + candname = cand.split('/')[-1] + candname = candname.split('.')[0] + if candname == biosinfo[ + 'AttributeRegistry'].split('.')[0]: + regurl = cand + break + if regurl: + reginfo = fishclient._do_web_request(regurl) + for reg in reginfo.get('Location', []): + if reg.get('Language', 'en').startswith('en'): + reguri = reg['Uri'] + reginfo = self._get_biosreg(reguri, fishclient) + extrainfo, valtodisplay, _, self.attrdeps = reginfo + currsettings = {} + try: + pendingsettings = fishclient._do_web_request( + fishclient._setbiosurl) + except exc.UnsupportedFunctionality: + pendingsettings = {} + pendingsettings = pendingsettings.get('Attributes', {}) + for setting in biosinfo.get('Attributes', {}): + val = biosinfo['Attributes'][setting] + currval = val + if setting in pendingsettings: + val = pendingsettings[setting] + val = valtodisplay.get(setting, {}).get(val, val) + currval = valtodisplay.get(setting, {}).get(currval, currval) + val = {'value': val} + if currval != val['value']: + val['active'] = currval + val.update(**extrainfo.get(setting, {})) + currsettings[setting] = val + return currsettings, reginfo + + def set_system_configuration(self, changeset, fishclient): + while True: + try: + self._set_system_configuration(changeset, fishclient) + return + except exc.RedfishError as re: + if ('etag' not in re.msgid.lower() + and 'PreconditionFailed' not in re.msgid): + raise + + def _set_system_configuration(self, changeset, fishclient): + currsettings, reginfo = self._getsyscfg(fishclient) + rawsettings = fishclient._do_web_request(fishclient._biosurl, + cache=False) + rawsettings = rawsettings.get('Attributes', {}) + pendingsettings = fishclient._do_web_request(fishclient._setbiosurl) + etag = pendingsettings.get('@odata.etag', None) + pendingsettings = pendingsettings.get('Attributes', {}) + dephandler = AttrDependencyHandler(self.attrdeps, rawsettings, + pendingsettings) + for change in list(changeset): + if change not in currsettings: + found = False + for attr in currsettings: + if fnmatch(attr.lower(), change.lower()): + found = True + changeset[attr] = changeset[change] + if fnmatch(attr.lower(), + change.replace('.', '_').lower()): + found = True + changeset[attr] = changeset[change] + if found: + del changeset[change] + for change in changeset: + changeval = changeset[change] + overrides, blameattrs = dephandler.get_overrides(change) + meta = {} + for attr in self.attrdeps['Attributes']: + if attr['AttributeName'] == change: + meta = dict(attr) + break + meta.update(**overrides.get(change, {})) + if meta.get('ReadOnly', False) or meta.get('GrayOut', False): + errstr = '{0} is read only'.format(change) + if blameattrs: + errstr += (' due to one of the following settings: ' + '{0}'.format(','.join(sorted(blameattrs))) + ) + raise exc.InvalidParameterValue(errstr) + if (currsettings.get(change, {}).get('possible', []) + and changeval not in currsettings[change]['possible']): + normval = changeval.lower() + normval = re.sub(r'\s+', ' ', normval) + if not normval.endswith('*'): + normval += '*' + for cand in currsettings[change]['possible']: + if fnmatch(cand.lower().replace(' ', ''), + normval.replace(' ', '')): + changeset[change] = cand + break + else: + raise exc.InvalidParameterValue( + '{0} is not a valid value for {1} ({2})'.format( + changeval, change, ','.join( + currsettings[change]['possible']))) + if changeset[change] in reginfo[2].get(change, {}): + changeset[change] = reginfo[2][change][changeset[change]] + for regentry in reginfo[3].get('Attributes', []): + if change in (regentry.get('AttributeName', ''), + regentry.get('DisplayName', '')): + if regentry.get('Type', None) == 'Integer': + changeset[change] = int(changeset[change]) + if regentry.get('Type', None) == 'Boolean': + changeset[change] = _to_boolean(changeset[change]) + redfishsettings = {'Attributes': changeset} + fishclient._do_web_request( + fishclient._setbiosurl, redfishsettings, 'PATCH', etag=etag) + def attach_remote_media(self, url, username, password, vmurls): return None