iLO-manager first main commitgit status
This commit is contained in:
1 parent
e6f4cc04ca
commit
599d8da562
3271 files changed
+550655
No files matched your search
@@ -0,0 +1,558 @@
|
||||
###
|
||||
# Copyright 2020 Hewlett Packard Enterprise, Inc. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
###
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Utility functions for internal and external use. Contains general json navigating functions as
|
||||
well as some monolith utility functions."""
|
||||
import copy
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
if six.PY3:
|
||||
from functools import reduce
|
||||
|
||||
try:
|
||||
from collections import Mapping
|
||||
except ImportError:
|
||||
from collections.abc import Mapping
|
||||
|
||||
import jsonpath_rw
|
||||
from six import iterkeys, string_types
|
||||
|
||||
from redfish.ris.rmc_helper import IncorrectPropValue
|
||||
|
||||
try:
|
||||
# itertools ifilter compatibility for python 2
|
||||
from future_builtins import filter
|
||||
except ImportError:
|
||||
# filter function provides the same functionality in python 3
|
||||
pass
|
||||
|
||||
# ---------Debug logger---------
|
||||
|
||||
LOGGER = logging.getLogger()
|
||||
|
||||
|
||||
# ---------End of debug logger---------
|
||||
|
||||
|
||||
def print_handler(msg, stderr=False):
|
||||
"""Helper function for handling warning messages appropriately. If LOGGER level is set to 40
|
||||
print out the warnings, else log them as a warning.
|
||||
|
||||
:param msg: The warning message.
|
||||
:type msg: str
|
||||
"""
|
||||
# if override:
|
||||
if not stderr:
|
||||
sys.stdout.write(msg)
|
||||
else:
|
||||
sys.stderr.write(msg)
|
||||
# else:
|
||||
# if LOGGER.getEffectiveLevel() == 40:
|
||||
# LOGGER.warning(msg)
|
||||
|
||||
|
||||
def warning_handler(msg, override=False):
|
||||
"""Helper function for handling warning messages appropriately. If LOGGER level is set to 40
|
||||
print out the warnings, else log them as a warning.
|
||||
|
||||
:param msg: The warning message.
|
||||
:type msg: str
|
||||
"""
|
||||
# if override:
|
||||
# sys.stdout.write(msg)
|
||||
# else:
|
||||
# if LOGGER.getEffectiveLevel() == 40:
|
||||
LOGGER.warning(msg)
|
||||
|
||||
|
||||
def validate_headers(instance, verbose=False):
|
||||
"""Validates an instance is patchable.
|
||||
|
||||
:param instance: Instance of the property to check.
|
||||
:type instance: :class:`redfish.ris.RisMonolithMemberv100`
|
||||
:param verbose: Flag to print or log more information.
|
||||
:type verbose: bool
|
||||
:returns: True if the setting is not patchable, False if it is.
|
||||
"""
|
||||
skip = False
|
||||
try:
|
||||
headervals = instance.resp.getheaders()
|
||||
for kii, val in headervals.items():
|
||||
if kii.lower() == "allow":
|
||||
if "PATCH" not in val:
|
||||
if verbose:
|
||||
warning_handler("Skipping read-only path: %s\n" % instance.resp.request.path)
|
||||
skip = True
|
||||
except:
|
||||
pass
|
||||
return skip
|
||||
|
||||
|
||||
def merge_dict(currdict, newdict):
|
||||
"""Merges dictionaries together.
|
||||
|
||||
:param currdict: Dictionary that will absorb the second.
|
||||
:type currdict: dict
|
||||
:param newdict: Dictionary to merge into the first.
|
||||
:type newdict: dict
|
||||
"""
|
||||
for k, itemv2 in list(newdict.items()):
|
||||
itemv1 = currdict.get(k)
|
||||
|
||||
if isinstance(itemv1, Mapping) and isinstance(itemv2, Mapping):
|
||||
merge_dict(itemv1, itemv2)
|
||||
elif isinstance(itemv1, list) and isinstance(itemv2, list):
|
||||
j = 0
|
||||
for i in itemv2:
|
||||
if isinstance(i, dict):
|
||||
for k, v in i.items():
|
||||
if k not in [d.keys() for d in itemv1]:
|
||||
itemv1[j][k] = v
|
||||
j = j + 1
|
||||
else:
|
||||
itemv1.append(i)
|
||||
else:
|
||||
currdict[k] = itemv2
|
||||
|
||||
|
||||
def get_errmsg_type(results):
|
||||
"""Return the registry type of a response.
|
||||
|
||||
:param results: rest response.
|
||||
:type results: :class:`redfish.rest.containers.RestResponse`
|
||||
:returns: A Registry Id type string, None if not match is found, or no_id if the
|
||||
response is not an error message
|
||||
:rtype: None or string
|
||||
"""
|
||||
|
||||
message_type = None
|
||||
try:
|
||||
jsonpath_expr = jsonpath_rw.parse("$..MessageId")
|
||||
messageid = [match.value for match in jsonpath_expr.find(results.dict)]
|
||||
if not messageid:
|
||||
jsonpath_expr = jsonpath_rw.parse("$..MessageID")
|
||||
messageid = [match.value for match in jsonpath_expr.find(results.dict)]
|
||||
if messageid:
|
||||
message_type = messageid[0].split(".")[0]
|
||||
except:
|
||||
pass
|
||||
|
||||
return message_type
|
||||
|
||||
|
||||
def filter_output(output, sel, val):
|
||||
"""Filters a list of dictionaries based on a key:value pair only returning the dictionaries
|
||||
that include the key and value.
|
||||
|
||||
:param output: List of dictionaries to check for the key:value.
|
||||
:type output: list
|
||||
:param sel: the key for the property to be filtered by.
|
||||
:type sel: str
|
||||
:param val: value for the property be filtered by.
|
||||
:type val: str
|
||||
:returns: A filtered list from output parameter
|
||||
:rtype: list
|
||||
"""
|
||||
# TODO: check if this can be replaced by navigatejson
|
||||
newoutput = []
|
||||
if isinstance(output, list):
|
||||
for entry in output:
|
||||
if isinstance(entry, dict):
|
||||
if "/" in sel:
|
||||
sellist = sel.split("/")
|
||||
newentry = copy.copy(entry)
|
||||
|
||||
for item in sellist:
|
||||
if item in list(newentry.keys()):
|
||||
if item == sellist[-1] and str(newentry[item]) == str(val):
|
||||
newoutput.append(entry)
|
||||
else:
|
||||
newentry = newentry[item]
|
||||
else:
|
||||
if sel in list(entry.keys()) and entry[sel] == val:
|
||||
newoutput.append(entry)
|
||||
else:
|
||||
return output
|
||||
|
||||
return newoutput
|
||||
|
||||
|
||||
def checkallowablevalues(newdict=None, oridict=None):
|
||||
"""Validate dictionary changes with Redfish allowable values. This will raise an
|
||||
:class:`redfish.ris.rmc_helper.IncorrectPropValue` error if the dictionary is not valid.
|
||||
|
||||
:param newdict: dictionary with only the properties that have changed.
|
||||
:type newdict: dict
|
||||
:param oridict: Full dictionary with current state. (Includes @Redfish.AllowableValues)
|
||||
:type oridict: dict
|
||||
"""
|
||||
for strmatch in re.finditer("@Redfish.AllowableValues", str(oridict)):
|
||||
propname = str(oridict)[: strmatch.start()].split("'")[-1]
|
||||
strtomatch = "$..'{0}@Redfish.AllowableValues'".format(propname)
|
||||
jsonpath_expr = jsonpath_rw.parse(strtomatch)
|
||||
matches = jsonpath_expr.find(oridict)
|
||||
if matches:
|
||||
for match in matches:
|
||||
fullpath = str(match.full_path)
|
||||
if "Actions" in fullpath:
|
||||
continue
|
||||
checkpath = fullpath.split("@Redfish.AllowableValues")[0]
|
||||
jexpr2 = jsonpath_rw.parse(checkpath)
|
||||
valmatches = jexpr2.find(newdict)
|
||||
if valmatches:
|
||||
for mat in valmatches:
|
||||
res = [val for val in match.value if mat.value.lower() == val.lower()]
|
||||
if not res:
|
||||
raise IncorrectPropValue(
|
||||
"Incorrect Value "
|
||||
"entered. Please enter one of the below "
|
||||
"values for {0}:\n{1}".format("/".join(checkpath.split(".")), str(match.value)[1:-1])
|
||||
)
|
||||
|
||||
|
||||
def navigatejson(selector, currdict, val=None):
|
||||
"""Function for navigating the json dictionary. Searches a dictionary for specific keys
|
||||
and possibly values, returning only the dictionary sections for the requested keys and values.
|
||||
|
||||
:param selector: the property required from current dictionary.
|
||||
:type selector: list
|
||||
:param val: value to be filtered by.
|
||||
:type val: str or int or bool
|
||||
:param currdict: json dictionary of list to be filtered
|
||||
:type currdict: json dictionary/list
|
||||
:returns: returns a dictionary of selected items
|
||||
"""
|
||||
# TODO: Check for val of different types(bool, int, etc)
|
||||
temp_dict = dict()
|
||||
createdict = lambda y, x: {x: y}
|
||||
getkey = lambda cdict, sel: next((item for item in iterkeys(cdict) if sel.lower() == item.lower()), sel)
|
||||
getval = lambda cdict, sele: [cdict[sel] if sel in cdict else "~!@#$%^&*)()" for sel in [getkey(cdict, sele)]][0]
|
||||
fullbreak = False
|
||||
seldict = copy.deepcopy(currdict)
|
||||
for ind, sel in enumerate(selector):
|
||||
if isinstance(seldict, dict):
|
||||
selector[ind] = getkey(seldict, sel)
|
||||
seldict = getval(seldict, sel)
|
||||
if seldict == "~!@#$%^&*)()":
|
||||
return None
|
||||
if val and ind == len(selector) - 1:
|
||||
cval = ",".join(seldict) if isinstance(seldict, (list, tuple)) else seldict
|
||||
if not (
|
||||
(val[-1] == "*" and str(cval).lower().startswith(val[:-1].lower()))
|
||||
or str(cval).lower() == val.lower()
|
||||
):
|
||||
fullbreak = True
|
||||
elif isinstance(seldict, (list, tuple)):
|
||||
returndict = []
|
||||
for items in seldict:
|
||||
correctcase = selector[ind:]
|
||||
returnseldict = navigatejson(correctcase, items)
|
||||
selector[ind:] = correctcase
|
||||
if returnseldict is not None:
|
||||
returndict.append(returnseldict)
|
||||
if returndict:
|
||||
seldict = returndict
|
||||
else:
|
||||
fullbreak = True
|
||||
if seldict:
|
||||
seldict = {selector[ind - 1]: seldict}
|
||||
selsdict = reduce(createdict, [seldict] + selector[: ind - 1][::-1])
|
||||
merge_dict(temp_dict, selsdict)
|
||||
return temp_dict
|
||||
else:
|
||||
break
|
||||
else:
|
||||
fullbreak = True
|
||||
break
|
||||
if fullbreak:
|
||||
return None
|
||||
else:
|
||||
selsdict = reduce(createdict, [seldict] + selector[::-1])
|
||||
merge_dict(temp_dict, selsdict)
|
||||
return temp_dict
|
||||
|
||||
|
||||
def iterateandclear(dictbody, proplist):
|
||||
"""Iterate over a dictionary and remove listed properties.
|
||||
|
||||
:param dictbody: json body
|
||||
:type dictbody: dict or list
|
||||
:param proplist: property list
|
||||
:type proplist: list
|
||||
"""
|
||||
if isinstance(dictbody, dict):
|
||||
_ = [dictbody.pop(key) for key in proplist if key in dictbody]
|
||||
for key in dictbody:
|
||||
dictbody[key] = iterateandclear(dictbody[key], proplist)
|
||||
if isinstance(dictbody, list):
|
||||
for ind, val in enumerate(dictbody):
|
||||
dictbody[ind] = iterateandclear(val, proplist)
|
||||
return dictbody
|
||||
|
||||
|
||||
def skipnonsettingsinst(instances):
|
||||
"""Removes non /settings sections. Useful for only returning settings monolith members.
|
||||
Example: Members with paths `/redfish/v1/systems/1/bios/` and
|
||||
`/redfish/v1/systems/1/bios/settings`
|
||||
will return only the `/redfish/v1/systems/1/bios/settings` member.
|
||||
|
||||
:param instances: list of :class:`redfish.ris.ris.RisMonolithMemberv100`
|
||||
instances to check for settings paths.
|
||||
:type instances: list
|
||||
:returns: list of :class:`redfish.ris.ris.RisMonolithMemberv100` setting instances
|
||||
:rtype: list
|
||||
"""
|
||||
instpaths = [inst.path.lower() for inst in instances]
|
||||
cond = list(filter(lambda x: x.endswith(("/settings", "settings/")), instpaths))
|
||||
paths = [path.split("settings/")[0].split("/settings")[0] for path in cond]
|
||||
newinst = [inst for inst in instances if inst.path.lower() not in paths]
|
||||
return newinst
|
||||
|
||||
|
||||
def getattributeregistry(instances, adict=None):
|
||||
# add try except return {} after test
|
||||
"""Gets an attribute registry in given monolith instances.
|
||||
|
||||
:param instances: list of :class:`redfish.ris.ris.RisMonolithMemberv100` instances to be
|
||||
checked for attribute registry.
|
||||
:type instances: list
|
||||
:param adict: A dictionary containing an AttributeRegistry
|
||||
:type adict: dict
|
||||
:return: returns a dictionary containing the attribute registry string(s)
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
if adict:
|
||||
return adict.get("AttributeRegistry", None)
|
||||
newdict = {}
|
||||
for inst in instances:
|
||||
try:
|
||||
if "AttributeRegistry" in inst.resp.dict:
|
||||
if inst.defpath is not None:
|
||||
if not ("bios/settings" in inst.defpath):
|
||||
newdict[inst.maj_type] = inst.resp.obj["AttributeRegistry"]
|
||||
return newdict
|
||||
newdict[inst.maj_type] = inst.resp.obj["AttributeRegistry"]
|
||||
except AttributeError:
|
||||
LOGGER.warning("Invalid/Unpopulated Response: %s\nType:%s\nPath:%s\n" % (inst.resp, inst.type, inst.path))
|
||||
return newdict
|
||||
|
||||
|
||||
def diffdict(newdict=None, oridict=None, settingskipped=[False]):
|
||||
"""Diff two dictionaries, returning only the values that are different between the two.
|
||||
|
||||
:param newdict: The first dictionary to check for differences.
|
||||
:type newdict: dict
|
||||
:param oridict: The second dictionary to check for differences.
|
||||
:type oridict: dict
|
||||
:param settingskipped: Flag to determine if any settings were missing.
|
||||
:type settingskipped: list
|
||||
:returns: dictionary with only the properties that have changed.
|
||||
:rtype: dict
|
||||
"""
|
||||
try:
|
||||
if newdict == oridict:
|
||||
return {}
|
||||
except:
|
||||
try:
|
||||
if set(newdict) == set(oridict):
|
||||
return {}
|
||||
except:
|
||||
pass
|
||||
|
||||
newdictkeys = list(newdict.keys())
|
||||
newdictlist = []
|
||||
if type(oridict) is list:
|
||||
oridict = oridict[0]
|
||||
newdictlist.append(newdict)
|
||||
oridictkeys = list(oridict.keys())
|
||||
newdictkeyslower = [ki.lower() for ki in newdictkeys]
|
||||
oridictkeyslower = [ki.lower() for ki in list(oridict.keys())]
|
||||
missingkeys = list(set(newdictkeyslower) - set(oridictkeyslower))
|
||||
for kis in missingkeys:
|
||||
del newdict[newdictkeys[newdictkeyslower.index(kis)]]
|
||||
warning_handler("Attribute {0} not found in the selection...".format(kis))
|
||||
settingskipped = [True]
|
||||
for key, val in list(newdict.items()):
|
||||
if key not in oridict:
|
||||
keycase = oridictkeys[oridictkeyslower.index(key.lower())]
|
||||
del newdict[key]
|
||||
key = keycase
|
||||
newdict[key] = val
|
||||
if isinstance(val, dict):
|
||||
res = diffdict(newdict[key], oridict[key])
|
||||
if res:
|
||||
newdict[key] = res
|
||||
else:
|
||||
del newdict[key]
|
||||
elif isinstance(val, list):
|
||||
if val == oridict[key]:
|
||||
del newdict[key]
|
||||
continue
|
||||
if len(val) == 1 and isinstance(val[0], dict):
|
||||
if newdict[key] and oridict[key]:
|
||||
res = diffdict(newdict[key][0], oridict[key][0], settingskipped)
|
||||
if res:
|
||||
newdict[key][0] = res
|
||||
else:
|
||||
del newdict[key]
|
||||
if [li for li in val if not isinstance(li, string_types)]:
|
||||
continue
|
||||
else:
|
||||
if val:
|
||||
if [va.lower() for va in val] == [va.lower() if va else va for va in oridict[key]]:
|
||||
del newdict[key]
|
||||
# TODO: check if lowercase is correct or buggy for string types
|
||||
elif isinstance(val, (string_types, int, type(None))):
|
||||
if newdict[key] == oridict[key]:
|
||||
del newdict[key]
|
||||
if not newdictlist:
|
||||
return newdict
|
||||
else:
|
||||
return newdictlist
|
||||
|
||||
|
||||
def json_traversal(data, key_to_find, ret_dict=False):
|
||||
"""
|
||||
PENDING MODIFICATION TO MORE GENERALIZED NOTATION
|
||||
|
||||
Recursive function to traverse a JSON resposne object and retrieve the array of
|
||||
relevant data (value or full key/value pair). Only a single key needs to be found within the
|
||||
dictionary in order to return a valid dictionary or value.
|
||||
|
||||
#Intended Usage:
|
||||
- Error response message parsing
|
||||
- Checkreadunique in Validation
|
||||
|
||||
:param data: json data to be parsed
|
||||
:type data: JSON error response object
|
||||
:param key_to_find: JSON key to be found
|
||||
:type key_to_find: String
|
||||
:param ret_dict: return dictionary instead of just value
|
||||
:type ret_dict: boolean
|
||||
:returns: value or dictionary containing 'key_to_find'
|
||||
(and all additional keys at the same level).
|
||||
"""
|
||||
|
||||
try:
|
||||
for i, _iter in enumerate(data):
|
||||
try:
|
||||
if _iter == data:
|
||||
return None
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if key_to_find.lower() == _iter.lower():
|
||||
if ret_dict:
|
||||
return data
|
||||
else:
|
||||
return data[_iter]
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if key_to_find.lower() in [str(_.lower()) for _ in _iter.keys()]:
|
||||
if ret_dict:
|
||||
return data
|
||||
else:
|
||||
return data[_iter]
|
||||
except Exception:
|
||||
pass
|
||||
_tmp = None
|
||||
try:
|
||||
if isinstance(data[_iter], dict):
|
||||
for k in data[_iter].keys():
|
||||
if k.lower() == key_to_find.lower():
|
||||
if ret_dict:
|
||||
return data[_iter]
|
||||
else:
|
||||
return data[_iter][k]
|
||||
_tmp = json_traversal(data[_iter], key_to_find, ret_dict)
|
||||
elif isinstance(data[_iter], list) or isinstance(data[_iter], tuple):
|
||||
try:
|
||||
_tmp = json_traversal(data[i], key_to_find, ret_dict)
|
||||
except Exception:
|
||||
_tmp = json_traversal(data[_iter], key_to_find, ret_dict)
|
||||
except Exception:
|
||||
_tmp = json_traversal(data[i], key_to_find, ret_dict)
|
||||
finally:
|
||||
if _tmp:
|
||||
return _tmp
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def json_traversal_delete_empty(data, old_key=None, _iter=None, remove_list=None):
|
||||
"""
|
||||
Recursive function to traverse a dictionary and delete things which
|
||||
match elements in the remove_list
|
||||
|
||||
:param data: to be truncated
|
||||
:type data: list or dict
|
||||
:param old_key: key from previous recursive call (higher in stack)
|
||||
:type old_key: dictionary key
|
||||
:param _iter: iterator tracker for list (tracks iteration across
|
||||
recursive calls)
|
||||
:type _iter: numerical iterator
|
||||
:param remove_list: list of items to be removed
|
||||
:type: list
|
||||
:returns: none
|
||||
"""
|
||||
|
||||
if not remove_list:
|
||||
remove_list = ["NONE", None, "", {}, [], "::", "0.0.0.0", "Unknown"]
|
||||
list_quick_scan = False
|
||||
|
||||
if isinstance(data, list):
|
||||
if _iter is None:
|
||||
for idx, val in enumerate(data):
|
||||
if idx is (len(data) - 1):
|
||||
list_quick_scan = True
|
||||
|
||||
json_traversal_delete_empty(val, old_key, idx, remove_list)
|
||||
|
||||
if list_quick_scan:
|
||||
for j in remove_list:
|
||||
for _ in range(data.count(j)):
|
||||
data.remove(j)
|
||||
|
||||
elif isinstance(data, dict):
|
||||
delete_list = []
|
||||
for key, value in data.items():
|
||||
if (
|
||||
(isinstance(value, dict) and len(value) < 1)
|
||||
or (isinstance(value, list) and len(value) < 1)
|
||||
or None
|
||||
or value in remove_list
|
||||
or key in remove_list
|
||||
):
|
||||
delete_list.append(key)
|
||||
|
||||
else:
|
||||
json_traversal_delete_empty(value, key, remove_list=remove_list)
|
||||
# would be great to not need this section; however,
|
||||
# since recursive deletion is not possible, this is needed
|
||||
# if you can figure out how to pass by reference then fix me!
|
||||
if (isinstance(value, dict) and len(value) < 1) or None or value in remove_list:
|
||||
delete_list.append(key)
|
||||
for dl_entry in delete_list:
|
||||
try:
|
||||
del data[dl_entry]
|
||||
except KeyError:
|
||||
pass
|
||||
Reference in new issue
Block a user