Files

453 lines
15 KiB
Python

###
# 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 -*-
"""RMC helper file. Includes RMC errors and caching functionality for monolith."""
# ---------Imports---------
import errno
import hashlib
import json
import logging
import os
from redfish.rest.containers import RestRequest, StaticRestResponse
from redfish.rest.v1 import RestClient
from .ris import RisMonolith
from .sharedtypes import JSONEncoder
# ---------End of imports---------
# ---------Debug logger---------
LOGGER = logging.getLogger(__name__)
# ---------End of debug logger---------
class RdmcError(Exception):
"""Base class for all RDMC Exceptions"""
errcode = 1
def __init__(self, message):
Exception.__init__(self, message)
class InvalidCommandLineError(RdmcError):
"""Raised when user enter incorrect command line arguments"""
pass
class TfaEnablePreRequisiteError(RdmcError):
"""Raised when pre-requisites not met while enabling TFA"""
pass
class FailureDuringCommitError(RdmcError):
"""Raised when there is an error while committing."""
pass
class UserNotAdminError(RdmcError):
"""Raised when user doesn't have admin priviledges, but they are required."""
pass
class IncompatibleiLOVersionError(RdmcError):
"""Raised when iLO version is incompatible."""
pass
class UndefinedClientError(Exception):
"""Raised when there are no clients active (usually when user hasn't logged in)."""
pass
class InstanceNotFoundError(Exception):
"""Raised when attempting to select an instance that does not exist."""
pass
class CurrentlyLoggedInError(Exception):
"""Raised when attempting to select an instance that does not exist"""
pass
class IloLicenseError(Exception):
"""Raised when the proper iLO license is not available for a command"""
pass
class ScepenabledError(Exception):
"""Raised when the generation csr or deletion of https cert is issues when scep is enabled"""
pass
class NothingSelectedError(Exception):
"""Raised when attempting to access an object without first selecting it."""
pass
class NothingSelectedFilterError(Exception):
"""Raised when the filter applied doesn't match any selection (general)."""
pass
class NothingSelectedSetError(Exception):
"""Raised when attempting to access an object without first selecting it (In set)."""
pass
class InvalidSelectionError(Exception):
"""Raised when selection argument fails to match anything."""
pass
class IdTokenError(Exception):
"""Raised when user is not authorized to complete the operation."""
pass
class ValueChangedError(Exception):
"""Raised if user tries to set/commit a value when monolith has older data."""
pass
class LoadSkipSettingError(Exception):
"""Raised when one or more settings are absent in given server."""
pass
class InvalidPathError(Exception):
"""Raised when requested path is not found."""
pass
class UnableToObtainIloVersionError(Exception):
"""Raised when iloversion is missing from default path."""
pass
class ValidationError(Exception):
"""Raised when there is a problem with user input."""
def __init__(self, errlist):
super(ValidationError, self).__init__(errlist)
self._errlist = errlist
def get_errors(self):
"""Returns error list."""
return self._errlist
class IloResponseError(Exception):
"""Raised when iLO returns with a non 2XX response."""
pass
class EmptyRaiseForEAFP(Exception):
"""Raised when you need to check for issues and take different action."""
pass
class IncorrectPropValue(Exception):
"""Raised when you pass an incorrect value to for the associated property."""
pass
class RmcCacheManager(object):
"""Manages caching/uncaching of data for RmcApp.
:param rmc: RmcApp to be managed
:type rmc: :class:`redfish.ris.rmc.RmcApp`
"""
def __init__(self, rmc):
"""Initialize RmcCacheManager
:param rmc: RmcApp to be managed
:type rmc: RmcApp object
"""
self._rmc = rmc
self.encodefunct = lambda data: data
self.decodefunct = lambda data: data
class RmcFileCacheManager(RmcCacheManager):
"""RMC file cache manager.
:param rmc: RmcApp to be managed
:type rmc: :class:`redfish.ris.rmc.RmcApp`
"""
def __init__(self, rmc):
super(RmcFileCacheManager, self).__init__(rmc)
def logout_del_function(self, url=None):
"""Searches for a specific url in cache or returns all urls and returns them for RmcApp
to run logout on, clearing the session.
:param url: The URL to pass back for logout.
:type url: str
"""
if self._rmc.cache:
cachedir = self._rmc.cachedir
indexfn = os.path.join(cachedir, "index") # %s\\index' % cachedir
else:
indexfn = ""
sessionlocs = []
if os.path.isfile(indexfn):
try:
indexfh = open(indexfn, "r")
index_cache = json.load(indexfh)
indexfh.close()
for index in index_cache:
if url:
if url in index["url"]:
os.remove(os.path.join(cachedir, index["href"]))
break
else:
if os.path.isfile(os.path.join(cachedir, index["href"])):
monolith = open(os.path.join(cachedir, index["href"]), "r")
data = json.load(monolith)
monolith.close()
for item in data:
if "login" in item and "session_location" in data["login"]:
if "blobstore" in data["login"]["url"]:
loc = data["login"]["session_location"].split("//")[-1]
sesurl = None
else:
loc = None
if data["login"]["session_location"] is not None:
loc = data["login"]["session_location"].split(data["login"]["url"])[-1]
sesurl = data["login"]["url"]
sessionlocs.append(
(
loc,
sesurl,
self._rmc._cm.decodefunct(data["login"]["session_key"]),
)
)
os.remove(os.path.join(cachedir, index["href"]))
except BaseException as excp:
LOGGER.warning("Unable to read cache data %s", excp)
return sessionlocs
def uncache_rmc(self, creds=None, enc=False):
"""Uncaches monolith data from cache location specified by RmcApp.
:param creds: Dictionary of username and password.
Only required for restoring high security local calls.
:type creds: dict
:param enc: Flag if credentials passed are encoded.
:type enc: bool
"""
cachedir = self._rmc.cachedir
indexfn = "%s/index" % cachedir
if os.path.isfile(indexfn):
try:
indexfh = open(indexfn, "r")
index_cache = json.load(indexfh)
indexfh.close()
for index in index_cache:
clientfn = index["href"]
self._uncache_client(clientfn, creds=creds, enc=enc)
except BaseException as excp:
LOGGER.warning("Unable to read cache data %s", excp)
def _uncache_client(self, cachefn, creds=None, enc=False):
"""Monolith uncache function for parsing and passing all client data and associated
credential attributes.
:param cachefn: The cache file name.
:type cachefn: str.
"""
cachedir = self._rmc.cachedir
clientsfn = "%s/%s" % (cachedir, cachefn)
if os.path.isfile(clientsfn):
try:
clientsfh = open(clientsfn, "r")
client = json.load(clientsfh)
clientsfh.close()
if "login" not in client:
return
login_data = client["login"]
if "url" not in login_data:
return
self._rmc.typepath.getgen(
login_data.get("ilo"),
url=login_data.get("url"),
isredfish=login_data.get("redfish", None),
ca_cert_data=login_data.get("ca_cert_data", {}),
)
if creds and login_data.get("url", "").startswith("blobstore://"):
if enc:
creds["username"] = self._rmc._cm.decodefunct(creds["username"])
creds["password"] = self._rmc._cm.decodefunct(creds["password"])
login_data["username"] = creds["username"]
login_data["password"] = creds["password"]
if isinstance(login_data["username"], bytes):
login_data["username"] = login_data["username"].decode("utf-8")
if isinstance(login_data["password"], bytes):
login_data["password"] = login_data["password"].decode("utf-8")
redfishinst = RestClient(
username=login_data.get("username", "Administrator"),
password=login_data.get("password", None),
base_url=login_data.get("url", None),
biospassword=login_data.get("bios_password", None),
is_redfish=login_data.get("redfish", None),
default_prefix=self._rmc.typepath.defs.startpath,
proxy=login_data.get("proxy", None),
ca_cert_data=login_data.get("ca_cert_data", {}),
)
if login_data.get("authorization_key"):
redfishinst.basic_auth = login_data.get("authorization_key")
elif login_data.get("session_key"):
redfishinst.session_key = self._rmc._cm.decodefunct(login_data.get("session_key"))
if isinstance(redfishinst.session_key, bytes):
redfishinst.session_key = redfishinst.session_key.decode("utf-8")
redfishinst.session_location = login_data.get("session_location")
if "selector" in client:
self._rmc.selector = client["selector"]
if login_data.get("iloversion"):
redfishinst.iloversion = login_data.get("iloversion")
else:
redfishinst.iloversion = None
self._rmc.typepath.iloversion = redfishinst.iloversion
getdata = client["get"]
for key in list(getdata.keys()):
if key == redfishinst.default_prefix:
restreq = RestRequest(method="GET", path=key)
getdata[key]["restreq"] = restreq
redfishinst.root = StaticRestResponse(**getdata[key])
break
self._rmc.monolith = RisMonolith(redfishinst, self._rmc.typepath)
self._rmc.monolith.load_from_dict(client["monolith"])
self._rmc.redfishinst = redfishinst
# make sure root is there
_ = redfishinst.root
self._rmc.typepath.defineregschemapath(redfishinst.root.dict)
except BaseException as excp:
LOGGER.warning("Unable to read cache data %s", excp)
def cache_rmc(self):
"""Saves monolith data to the file path specified in RmcApp."""
if not self._rmc.cache:
return
cachedir = self._rmc.cachedir
if not os.path.isdir(cachedir):
try:
os.makedirs(cachedir)
except OSError as ex:
if ex.errno == errno.EEXIST:
pass
else:
raise
index_map = dict()
index_cache = list()
if self._rmc.redfishinst:
shaobj = hashlib.new("SHA256")
shaobj.update(self._rmc.redfishinst.base_url.encode("utf-8"))
md5str = shaobj.hexdigest()
index_map[self._rmc.redfishinst.base_url] = md5str
index_data = dict(
url=self._rmc.redfishinst.base_url,
href="%s" % md5str,
)
index_cache.append(index_data)
indexfh = open("%s/index" % cachedir, "w")
json.dump(index_cache, indexfh, indent=2, cls=JSONEncoder)
indexfh.close()
if self._rmc.redfishinst:
login_data = dict(
username=None,
password=None,
url=self._rmc.redfishinst.base_url,
session_key=self._rmc._cm.encodefunct(self._rmc.redfishinst.session_key),
session_location=self._rmc.redfishinst.session_location,
authorization_key=self._rmc.redfishinst.basic_auth,
bios_password=self._rmc.redfishinst.bios_password,
redfish=self._rmc.monolith.is_redfish,
ilo=self._rmc.typepath.ilogen,
iloversion=self._rmc.typepath.iloversion,
proxy=self._rmc.redfishinst.proxy,
ca_cert_data=self._rmc.redfishinst._cert_data if self._rmc.redfishinst._cert_data else dict(),
)
clients_data = dict(
selector=self._rmc.selector,
login=login_data,
monolith=self._rmc.monolith,
get=self._rmc.monolith.paths,
)
clientsfh = open("%s/%s" % (cachedir, index_map[self._rmc.redfishinst.base_url]), "w")
if isinstance(clients_data, bytes):
clients_data = clients_data.decode("utf-8")
json.dump(clients_data, clientsfh, indent=2, cls=JSONEncoder)
clientsfh.close()