diff --git a/ilorest/__init__.py b/ilorest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/cliutils.py b/ilorest/cliutils.py new file mode 100644 index 0000000..0e738d3 --- /dev/null +++ b/ilorest/cliutils.py @@ -0,0 +1,246 @@ +### +# Copyright 2017 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 -*- +"""Base implementation for cli interaction with Redfish interface""" + +# ---------Imports--------- + +import getpass +import os +import re +import subprocess +import sys + +try: + from rdmc_helper import UI +except ImportError: + from ilorest.rdmc_helper import UI + +if os.name == "nt": + import ctypes + from ctypes import windll, wintypes + +# ---------End of imports--------- + + +class CommandNotFoundException(Exception): + """Exception throw when Command is not found""" + + pass + + +class ResourceAllocationError(Exception): + """Exception throw when Command is not found""" + + pass + + +def get_user_config_dir(): + """Platform specific directory for user configuration. + + :returns: returns the user configuration directory. + :rtype: string + """ + if os.name == "nt": + try: + csidl_appdata = 26 + + shgetfolderpath = windll.shell32.SHGetFolderPathW + shgetfolderpath.argtypes = [ + wintypes.HWND, + ctypes.c_int, + wintypes.HANDLE, + wintypes.DWORD, + wintypes.LPCWSTR, + ] + + path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH) + result = shgetfolderpath(0, csidl_appdata, 0, 0, path_buf) + + if result == 0: + return path_buf.value + except ImportError: + pass + + return os.environ["APPDATA"] + + return os.path.expanduser("~") + + +def is_exe(filename): + """Determine if filename is an executable. + + :param filename: the filename to examine. + :type filename: string + :returns: True if filename is executable False otherwise. + :rtype: boolean + """ + if sys.platform == "win32": + if os.path.exists(filename): + return True + + # windows uses PATHEXT to list valid executable extensions + pathext = os.environ["PATHEXT"].split(os.pathsep) + (_, ext) = os.path.splitext(filename) + + if ext in pathext: + return True + else: + if os.path.exists(filename) and os.access(filename, os.X_OK): + return True + + return False + + +def find_exe(filename, path=None): + """Search path for a executable (aka which) + + :param filename: the filename to search for. + :type filename: string. + :param path: the path(s) to search (default: os.environ['PATH']) + :param path: string separated with os.pathsep + :returns: string with full path to the file or None if not found. + :rtype: string or None. + """ + if path is None: + path = os.environ["PATH"] + + pathlist = path.split(os.pathsep) + + # handle fully qualified paths + if os.path.isfile(filename) and is_exe(filename): + return filename + + for innerpath in pathlist: + foundpath = os.path.join(innerpath, filename) + + if is_exe(foundpath): + return foundpath + + return None + + +def get_terminal_size(): + """Returns the rows and columns of the terminal as a tuple. + + :returns: the row and column count of the terminal + :rtype: tuple (cols, rows) + """ + _tuple = (80, 25) # default + + if os.name == "nt": + pass + else: + which_stty = find_exe("stty") + + if which_stty: + args = [which_stty, "size"] + try: + procs = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError as excp: + raise ResourceAllocationError(str(excp)) + (stdout_s, _) = procs.communicate() + + _ = procs.wait() + # python3 change + if isinstance(stdout_s, bytes): + stdout_s = stdout_s.decode("utf-8") + if stdout_s and re.search(r"^\d+ \d+$", stdout_s): + rows, cols = stdout_s.split() + _tuple = (cols, rows) + + return _tuple + + +class CLI(object): + """Class for building command line interfaces.""" + + def __init__(self, verbosity=1, out=sys.stdout): + self._verbosity = verbosity + self._out = out + cols, rows = get_terminal_size() + self._cols = int(cols) + self._rows = int(rows) + + def verbosity(self, verbosity): + self._verbosity = verbosity + + def get_hrstr(self, character="-"): + """returns a string suitable for use as a horizontal rule. + + :param character: the character to use as the rule. (default -) + :type character: str. + """ + return "%s\n" % (character * self._cols) + + def printer(self, data, flush=True): + """printing wrapper + + :param data: data to be printed to the output stream + :type data: str. + :param flush: flush buffer - True, not flush output buffer - False + :type flush: boolean + """ + + UI(self._verbosity).printer(data, flush) + + def horizontalrule(self, character="-"): + """writes a horizontal rule to the file handle. + + :param character: the character to use as the rule. (default -) + :type character: str. + """ + + self.printer(self.get_hrstr(character=character)) + + def version(self, progname, version, extracontent): + """Prints a version string to fileh. + + :param progname: the name of the program. + :type progname: str. + :param version: the version of the program. + :type version str. + :param fileh: the file handle to write to. Default is sys.stdout. + :type fileh: file object + :returns: None + """ + tmp = "%s version %s\n%s" % (progname, version, extracontent) + self.printer(tmp) + self.horizontalrule() + + def prompt_password(self, msg, default=None): + """Convenient password prompting function + + :param msg: prompt text. + :type msg: str. + :param default: default value if user does not enter anything. + :type default: str. + :returns: string user entered, or default if nothing entered + """ + message = "%s : " % msg + + if default is not None: + message = "%s [%s] : " % (msg, default) + + i = getpass.getpass(message) + + if i is None or len(i) == 0: + i = default + + i = str(i) + + return i.strip() diff --git a/ilorest/config/__init__.py b/ilorest/config/__init__.py new file mode 100644 index 0000000..4886160 --- /dev/null +++ b/ilorest/config/__init__.py @@ -0,0 +1 @@ +"""Config parsing for RDMC""" diff --git a/ilorest/config/config.py b/ilorest/config/config.py new file mode 100644 index 0000000..7edbcb8 --- /dev/null +++ b/ilorest/config/config.py @@ -0,0 +1,159 @@ +### +# Copyright 2016-2021 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 -*- +"""Module for working with global configuration options.""" + +# ---------Imports--------- + +import logging +import os +import re + +import six +from six.moves import configparser + +# ---------End of imports--------- + + +# ---------Debug logger--------- + +LOGGER = logging.getLogger(__name__) + +# ---------End of debug logger--------- + + +class AutoConfigParser(object): + """Auto configuration parser. Properties starting with _ac__ are automatically + serialized to config file""" + + _config_pattern = re.compile(r"_ac__(?P.*)") + + def __init__(self, filename=None): + """Initialize AutoConfigParser + + :param filename: file name to be used for config loading. + :type filename: str. + + """ + self._sectionname = "globals" + self._configfile = filename + + @property + def configfile(self): + """The current configuration file location""" + return self._configfile + + def _get_ac_keys(self): + """Retrieve parse option keys""" + result = [] + for key in six.iterkeys(self.__dict__): + match = AutoConfigParser._config_pattern.search(key) + if match: + result.append(match.group("confkey")) + return result + + def _get(self, key): + """Retrieve parse option key + + :param key: key to retrieve. + :type key: str. + + """ + ackey = "_ac__%s" % key.replace("-", "_") + if ackey in self.__dict__: + return self.__dict__[ackey] + return None + + def _set(self, key, value): + """Set parse option key + + :param key: key to be set. + :type key: str. + :param value: value to be given to key. + :type value: str. + + """ + ackey = "_ac__%s" % key.replace("-", "_") + if ackey in self.__dict__: + self.__dict__[ackey] = value + return None + + def load(self, filename=None): + """Load configuration settings from the file filename, if filename + is None then the value from configfile property is used + + :param filename: file name to be used for config loading. + :type filename: str. + + """ + fname = self.configfile + if filename: + fname = filename + + if not fname or not os.path.isfile(fname): + return + + try: + config = configparser.RawConfigParser() + config.read(fname) + for key in self._get_ac_keys(): + configval = None + try: + configval = config.get(self._sectionname, key) + except configparser.NoOptionError: + # also try with - instead of _ + try: + configval = config.get(self._sectionname, key.replace("_", "-")) + except configparser.NoOptionError: + pass + + if configval: + ackey = "_ac__%s" % key + self.__dict__[ackey] = configval + except configparser.NoOptionError: + pass + except configparser.NoSectionError: + pass + + def save(self, filename=None): + """Save configuration settings from the file filename, if filename + is None then the value from configfile property is used + + :param filename: file name to be used for config saving. + :type filename: str. + + """ + fname = self.configfile + if filename: + fname = filename + + if fname: + return + + config = configparser.RawConfigParser() + try: + config.add_section(self._sectionname) + except configparser.DuplicateSectionError: + pass # ignored + + for key in self._get_ac_keys(): + ackey = "_ac__%s" % key + config.set(self._sectionname, key, str(self.__dict__[ackey])) + + fileh = open(self._configfile, "wb") + config.write(fileh) + fileh.close() diff --git a/ilorest/config/rdmc_config.py b/ilorest/config/rdmc_config.py new file mode 100644 index 0000000..1b8ec10 --- /dev/null +++ b/ilorest/config/rdmc_config.py @@ -0,0 +1,260 @@ +### +# Copyright 2016-2021 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 -*- +"""Rdmc config""" + +import os + +try: + from config.config import AutoConfigParser +except ImportError: + from ilorest.config.config import AutoConfigParser + + +class RdmcConfig(AutoConfigParser): + """Rdmc config class for loading and parsing the .conf file global configuration options. + Uses the AutoConfigParser.""" + + def __init__(self, filename=None): + """Initialize RdmcConfig + + :param filename: file name to be used for Rdmcconfig loading. + :type filename: str + + """ + AutoConfigParser.__init__(self, filename=filename) + self._sectionname = "redfish" + self._configfile = filename + self._ac__logdir = os.getcwd() + self._ac__cache = True + self._ac__url = "" + self._ac__username = "" + self._ac__password = "" + self._ac__sslcert = "" + self._ac__commit = "" + self._ac__format = "" + self._ac__cachedir = "" + self._ac__savefile = "" + self._ac__loadfile = "" + self._ac__user_cert = "" + self._ac__user_root_ca_key = "" + self._ac__user_root_ca_password = "" + + @property + def configfile(self): + """The current configuration file""" + return self._configfile + + @configfile.setter + def configfile(self, config_file): + """Set the current configuration file + + :param config_file: file name to be used for Rmcconfig loading. + :type config_file: str + """ + self._configfile = config_file + + @property + def logdir(self): + """Get the current log directory""" + return self._get("logdir") + + @logdir.setter + def logdir(self, value): + """Set the current log directory + + :param value: current working directory for logging + :type value: str + """ + return self._set("logdir", value) + + @property + def cache(self): + """Get the config file cache status""" + + if isinstance(self._get("cache"), bool): + return self._get("cache") + + return self._get("cache").lower() in ("yes", "true", "t", "1") + + @cache.setter + def cache(self, value): + """Get the config file cache status + + :param value: status of config file cache + :type value: bool + """ + return self._set("cache", value) + + @property + def url(self): + """Get the config file URL""" + url = self._get("url") + url = url[:-1] if url.endswith("/") else url + + return url + + @url.setter + def url(self, value): + """Set the config file URL + + :param value: URL path for the config file + :type value: str + """ + return self._set("url", value) + + @property + def username(self): + """Get the config file user name""" + return self._get("username") + + @username.setter + def username(self, value): + """Set the config file user name + + :param value: user name for config file + :type value: str + """ + return self._set("username", value) + + @property + def password(self): + """Get the config file password""" + return self._get("password") + + @password.setter + def password(self, value): + """Set the config file password + + :param value: password for config file + :type value: str + """ + return self._set("password", value) + + @property + def commit(self): + """Get the config file commit status""" + return self._get("commit") + + @commit.setter + def commit(self, value): + """Set the config file commit status + + :param value: commit status + :type value: str + """ + return self._set("commit", value) + + @property + def format(self): + """Get the config file default format""" + return self._get("format") + + @format.setter + def format(self, value): + """Set the config file default format + + :param value: set the config file format + :type value: str + """ + return self._set("format", value) + + @property + def cachedir(self): + """Get the config file cache directory""" + return self._get("cachedir") + + @cachedir.setter + def cachedir(self, value): + """Set the config file cache directory + + :param value: config file cache directory + :type value: str + """ + return self._set("cachedir", value) + + @property + def defaultsavefilename(self): + """Get the config file default save name""" + return self._get("savefile") + + @defaultsavefilename.setter + def defaultsavefilename(self, value): + """Set the config file default save name + + :param value: config file save name + :type value: str + """ + return self._set("savefile", value) + + @property + def defaultloadfilename(self): + """Get the config file default load name""" + return self._get("loadfile") + + @defaultloadfilename.setter + def defaultloadfilename(self, value): + """Set the config file default load name + + :param value: name of config file to load by default + :type value: str + """ + return self._set("loadfile", value) + + @property + def proxy(self): + """Get proxy value to be set for communication""" + return self._get("proxy") + + @proxy.setter + def proxy(self, value): + """Set proxy value for communication""" + return self._set("proxy", value) + + @property + def ssl_cert(self): + """Get proxy value to be set for communication""" + return self._get("sslcert") + + @ssl_cert.setter + def ssl_cert(self, value): + """Set proxy value for communication""" + return self._set("sslcert", value) + + @property + def user_cert(self): + return self._get("usercert") + + @user_cert.setter + def user_cert(self, value): + return self._set("usercert", value) + + @property + def user_root_ca_key(self): + return self._get("user_root_ca_key") + + @user_root_ca_key.setter + def user_root_ca_key(self, value): + return self._set("user_root_ca_key", value) + + @property + def user_root_ca_password(self): + return self._get("user_root_ca_password") + + @user_root_ca_password.setter + def user_root_ca_password(self, value): + return self._set("user_root_ca_password", value) diff --git a/ilorest/extensions/BIOS_COMMANDS/BiosDefaultsCommand.py b/ilorest/extensions/BIOS_COMMANDS/BiosDefaultsCommand.py new file mode 100644 index 0000000..2e7f9e2 --- /dev/null +++ b/ilorest/extensions/BIOS_COMMANDS/BiosDefaultsCommand.py @@ -0,0 +1,156 @@ +### +# Copyright 2016-2021 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 -*- +""" BiosDefaultsCommand for rdmc """ + +try: + from rdmc_helper import Encryption, InvalidCommandLineErrorOPTS, ReturnCodes +except ImportError: + from ilorest.rdmc_helper import Encryption, InvalidCommandLineErrorOPTS, ReturnCodes + + +class BiosDefaultsCommand: + """Set BIOS settings back to default for the server that is currently + logged in""" + + def __init__(self): + self.ident = { + "name": "biosdefaults", + "usage": None, + "description": "Run to set the currently" + " logged in server's Bios. type settings to defaults\n\texample: " + "biosdefaults\n\n\tRun to set the currently logged in server's " + "Bios. type settings to user defaults\n\texample: biosdefaults " + "--userdefaults\n\n\tRun to set the currently logged in server " + "to manufacturing defaults, including boot order and secure boot." + "\n\texample: biosdefaults --manufacturingdefaults", + "summary": "Set the currently logged in server to default BIOS settings.", + "aliases": [], + "auxcommands": ["SetCommand", "RebootCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main BIOS defaults worker function""" + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.defaultsvalidation(options) + + if options.manufdefaults: + self.rdmc.ui.printer("Resetting BIOS attributes and settings to manufacturing defaults.\n") + elif options.userdefaults: + self.rdmc.ui.printer("Resetting BIOS attributes and settings to user defaults.\n") + + put_path = self.rdmc.app.typepath.defs.biospath + body = None + + if self.rdmc.app.typepath.defs.isgen10 and not options.manufdefaults: + bodydict = self.rdmc.app.get_handler(self.rdmc.app.typepath.defs.biospath, service=True, silent=True).dict + + for item in bodydict["Actions"]: + if "ResetBios" in item: + action = item.split("#")[-1] + path = bodydict["Actions"][item]["target"] + break + + body = {"Action": action} + + if options.userdefaults: + body["ResetType"] = "default.user" + else: + body["ResetType"] = "default" + + self.rdmc.app.post_handler(path, body) + else: + if options.userdefaults: + body = {"BaseConfig": "default.user"} + elif not options.manufdefaults: + body = {"BaseConfig": "default"} + if body: + self.rdmc.app.put_handler( + put_path + "/settings", + body=body, + optionalpassword=options.biospassword, + ) + + if not body and options.manufdefaults: + setstring = "RestoreManufacturingDefaults=Yes --selector=HpBios. --commit" + if options.reboot: + setstring += " --reboot=%s" % options.reboot + + self.auxcommands["set"].run(setstring) + + elif options.reboot: + self.auxcommands["reboot"].run(options.reboot) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def defaultsvalidation(self, options): + """BIOS defaults method validation function""" + self.cmdbase.login_select_validation(self, options) + + if options.encode: + options.biospassword = Encryption.decode_credentials(options.biospassword) + if isinstance(options.biospassword, bytes): + options.biospassword = options.biospassword.decode("utf-8") + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--reboot", + dest="reboot", + help="Use this flag to perform a reboot command function after" + " completion of operations. For help with parameters and" + " descriptions regarding the reboot flag, run help reboot.", + default=None, + ) + customparser.add_argument( + "--userdefaults", + dest="userdefaults", + action="store_true", + help="Sets bios to user defaults instead of manufacturing defaults.", + default=False, + ) + customparser.add_argument( + "--manufacturingdefaults", + dest="manufdefaults", + action="store_true", + help="Reset all configuration settings to manufacturing defaults, " "including boot order and secure boot.", + default=False, + ) diff --git a/ilorest/extensions/BIOS_COMMANDS/BootOrderCommand.py b/ilorest/extensions/BIOS_COMMANDS/BootOrderCommand.py new file mode 100644 index 0000000..450c3a4 --- /dev/null +++ b/ilorest/extensions/BIOS_COMMANDS/BootOrderCommand.py @@ -0,0 +1,738 @@ +### +# Copyright 2016-2021 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 -*- +""" BootOrder Command for rdmc """ + +import ast +import copy +import fnmatch +from functools import reduce + +import six + +try: + from rdmc_helper import ( + UI, + BootOrderMissingEntriesError, + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidOrNothingChangedSettingsError, + ReturnCodes, + ) +except: + from ilorest.rdmc_helper import ( + UI, + BootOrderMissingEntriesError, + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidOrNothingChangedSettingsError, + ReturnCodes, + ) + + +class BootOrderCommand: + """Changes the boot order for the server that is currently logged in""" + + def __init__(self): + self.ident = { + "name": "bootorder", + "usage": None, + "description": "Run without arguments for current boot order and one time boot options." + "\n\texample: bootorder\n\n\tTo set the persistent boot order pick items " + 'from\n\tthe "Current Persistent Boot Order" section.\n\t' + "example: bootorder [5,4,3,2,1] --commit\n\n\tSetting partial" + " boot order is also supported.\n\tMissing entries are " + "concatenated at the end.\n\texample: bootorder [5] --commit\n\n\t" + "You can also set the boot order using partial string matching.\n\t" + "example: bootorder NIC.*v4 HD* Generic.USB.1.1 --commit\n\n\tThis will set " + "All v4 NICs first, followed by all hard drives,\n\tfollowed by Generic.USB.1.1. " + "Everything not listed will be\n\tadded to the end of the boot order." + '\n\n\tTo set one time boot entry pick items from the\n\t"' + 'Continuous and one time boot uefi options" section.\n\t' + "example: bootorder --onetimeboot=Hdd\n\n\tTo set continuous" + ' boot entry pick items from the\n\t"Continuous and one time ' + 'boot uefi options" section.\n\texample: bootorder --' + "continuousboot=Utilities --commit\n\n\tDisable either " + "continuous or one time boot options.\n\texample: bootorder " + "--disablebootflag --commit\n\n\t" + "Changing Secure Boot Keys:\n\tTo manage secure boot keys use" + " the --securebootkeys flag.\n\tTo delete all keys.\n\n\texample:" + " bootorder --securebootkeys=deletepk\n\tFor all possibilities" + " see the --securebootkeys flag \n\tin the options list.\n\n\t" + "NOTE: pick ONETIMEBOOT and " + 'CONTINUOUS items from "Continuous\n\tand one time boot ' + 'options" section. Items in this list represent\n\ta ' + '"clustered" view of the "Continuous and one time boot uefi' + '\n\toptions" section. Example: choosing Pxe will try to Pxe' + ' boot\n\tcapable devices in the order found in the "' + 'Continuous and one\n\ttime boot options".\n\n\t', + "summary": "Displays and sets the current boot order.", + "aliases": [], + "auxcommands": [ + "GetCommand", + "SelectCommand", + "SetCommand", + "RebootCommand", + ], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main boot order worker function""" + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.bootordervalidation(options) + + if options.secureboot: + self.secureboothelper(options.secureboot) + + if options.reboot: + self.auxcommands["reboot"].run(options.reboot) + + return ReturnCodes.SUCCESS + + self.rdmc.app.select(selector="HpeServerBootSettings.", path_refresh=True) + + props = self.rdmc.app.getprops(skipnonsetting=False) + + for prop in props: + bootname = prop.get("Name") + + self.rdmc.ui.printer("bootname is : {0}\n\n".format(bootname)) + + if self.rdmc.app.typepath.defs.isgen10: + if "current" in bootname.lower() or "pending" in bootname.lower(): + try: + bootpath = prop.get("@odata.id") + except: + bootpath = prop.get("links") + else: + bootpath = "/rest/v1/systems/1/bios/boot" + + self.rdmc.ui.printer("bootpath is : {0}\n\n".format(bootpath)) + + bootsources = self.rdmc.app.get_handler(bootpath, service=True, silent=True).dict["BootSources"] + + bootoverride = None + self.auxcommands["select"].selectfunction("HpeBios.") + try: + bootmode = self.auxcommands["get"].getworkerfunction("BootMode", options, results=True, uselist=True) + except: + bootmode = dict() + bootmode["BootMode"] = "Uefi" + + self.auxcommands["select"].selectfunction("ComputerSystem.") + onetimebootsettings = self.auxcommands["get"].getworkerfunction( + ["Boot/" + self.rdmc.app.typepath.defs.bootoverridetargettype], + options, + results=True, + uselist=True, + ) + + bootstatus = self.auxcommands["get"].getworkerfunction( + ["Boot/BootSourceOverrideEnabled"], + options, + results=True, + uselist=True, + ) + + targetstatus = self.auxcommands["get"].getworkerfunction( + ["Boot/BootSourceOverrideTarget"], + options, + results=True, + uselist=True, + ) + + uefitargetstatus = self.auxcommands["get"].getworkerfunction( + ["Boot/UefiTargetBootSourceOverride"], + options, + results=True, + uselist=True, + ) + + currentsettings = self.rdmc.app.get_handler(self.rdmc.app.typepath.defs.systempath, service=True, silent=True) + + if bootmode and bootmode.get("BootMode", None) == "Uefi": + if self.rdmc.app.typepath.defs.isgen9: + uefionetimebootsettings = self.auxcommands["get"].getworkerfunction( + ["Boot/UefiTargetBootSourceOverrideSupported"], + options, + results=True, + uselist=True, + ) + + else: + # Gen 10 + bootsettings = self.auxcommands["get"].getworkerfunction( + ["Boot"], + options, + results=True, + uselist=True, + )["Boot"] + finaluefi = [] + if "UefiTargetBootSourceOverride@Redfish.AllowableValues" in bootsettings: + uefionetimebootsettings = bootsettings["UefiTargetBootSourceOverride@Redfish.AllowableValues"] + + for setting in uefionetimebootsettings: + for source in bootsources: + if "UEFIDevicePath" in source and source["UEFIDevicePath"].endswith(setting): + finaluefi.append(source["StructuredBootString"]) + continue + + uefionetimebootsettings = {"Boot": {"UefiTargetBootSourceOverrideSupported": finaluefi}} + else: + uefionetimebootsettings = None + + if options.onetimeboot is None and options.continuousboot is None and not options.disablebootflag: + self.auxcommands["select"].selectfunction("HpeServerBootSettings.") + bootsettings = self.auxcommands["get"].getworkerfunction( + "PersistentBootConfigOrder", options, results=True, uselist=True + ) + + if not args: + if not options.json: + self.print_out_boot_order( + bootsettings, + onetimebootsettings, + uefionetimebootsettings, + bootmode, + bootsources, + bootstatus, + targetstatus, + ) + elif options.json: + UI().print_out_json(bootsettings) + elif len(args) == 1 and args[0][0] == "[": + bootlist = args[0][1:-1].split(",") + currentlist = bootsettings["PersistentBootConfigOrder"] + + if not isinstance(currentlist, list): + templist = ast.literal_eval(currentlist[1:-1]) + currentlist = [n.strip() for n in templist] + + removallist = copy.deepcopy(currentlist) + + if len(bootlist) > len(currentlist): + raise InvalidCommandLineError("Number of entries is greater than the current boot order length.") + else: + newlist = "[" + + for value, _ in enumerate(bootlist): + try: + newlist += currentlist[int(bootlist[value]) - 1] + except: + raise InvalidCommandLineError( + "Invalid entry " + "number passed to bootorder. Please \n" + " run bootorder without arguments" + " for possible boot \n order numbers. " + ) + + removallist.remove(currentlist[int(bootlist[value]) - 1]) + + if removallist: + newlist += "," + + if not removallist: + newlist += "]" + else: + for value, _ in enumerate(removallist): + newlist += removallist[value] + + if not value == len(removallist) - 1: + newlist += "," + + newlist += "]" + + if options.biospassword: + newlist += " --biospassword " + options.biospassword + + if options.reboot: + newlist += " --commit --reboot " + options.reboot + elif options.commit: + newlist += " --commit" + + self.auxcommands["set"].run("PersistentBootConfigOrder=" + newlist) + else: + currlist = bootsettings["PersistentBootConfigOrder"] + if not isinstance(currlist, list): + templist = ast.literal_eval(currlist[1:-1]) + currlist = [n.strip() for n in templist] + remlist = copy.deepcopy(currlist) + if len(args) > len(currlist): + raise InvalidCommandLineError("Number of entries is " "greater than the current boot order length.") + newlist = [] + for arg in args: + argmatch = [val for val in remlist if fnmatch.fnmatch(val.lower(), arg.lower())] + if not argmatch and not options.ime: + raise InvalidCommandLineError( + "Invalid entry passed: " + "{0}. Please run bootorder to check for possible " + "values and reevaluate.\n".format(arg) + ) + if argmatch: + newlist.extend(argmatch) + _ = [remlist.remove(val) for val in newlist if val in remlist] + newlist.extend(remlist) + strlist = "[" + concatlist = reduce((lambda x, y: x + "," + y), newlist) + strlist = strlist + concatlist + "]" + + if options.biospassword: + strlist += " --biospassword " + options.biospassword + if options.reboot: + strlist += " --commit --reboot " + options.reboot + elif options.commit: + strlist += " --commit" + self.auxcommands["set"].run("PersistentBootConfigOrder=" + strlist) + else: + if options.onetimeboot is not None: + entry = options.onetimeboot + + if not bootstatus["Boot"]["BootSourceOverrideEnabled"] == "Once": + bootoverride = " Boot/BootSourceOverrideEnabled=Once" + elif options.continuousboot is not None: + entry = options.continuousboot + + if not bootstatus["Boot"]["BootSourceOverrideEnabled"] == "Continuous": + bootoverride = " Boot/BootSourceOverrideEnabled=Continuous" + else: + entry = "JacksBootOption" + if not bootstatus["Boot"]["BootSourceOverrideEnabled"] == "Disabled": + if currentsettings.dict["Boot"]["BootSourceOverrideEnabled"] == "Disabled": + bootoverride = "Boot/BootSourceOverrideTarget=None" " Boot/BootSourceOverrideEnabled=Disabled" + else: + bootoverride = "Boot/BootSourceOverrideTarget=None" + + newlist = "" + + if entry.lower() in ( + item.lower() for item in onetimebootsettings["Boot"][self.rdmc.app.typepath.defs.bootoverridetargettype] + ): + if entry and isinstance(entry, six.string_types): + entry = entry.upper() + + entry = self.searchcasestring( + entry, + onetimebootsettings["Boot"][self.rdmc.app.typepath.defs.bootoverridetargettype], + ) + + if not entry == targetstatus["Boot"]["BootSourceOverrideTarget"]: + newlist += " Boot/BootSourceOverrideTarget=" + entry + + if bootoverride: + newlist += bootoverride + + if options.biospassword and newlist: + newlist += " --biospassword " + options.biospassword + + if options.reboot and newlist: + newlist += " --commit --reboot " + options.reboot + elif options.commit and newlist: + newlist += " --commit" + + if newlist: + self.auxcommands["set"].run(newlist) + else: + raise InvalidOrNothingChangedSettingsError("Entry is the current boot setting.") + elif ( + uefionetimebootsettings + and uefionetimebootsettings["Boot"]["UefiTargetBootSourceOverrideSupported"] + and entry in (item for item in uefionetimebootsettings["Boot"]["UefiTargetBootSourceOverrideSupported"]) + ): + if entry and isinstance(entry, six.string_types): + entry = entry.upper() + + entry = self.searchcasestring( + entry, + uefionetimebootsettings["Boot"]["UefiTargetBootSourceOverrideSupported"], + ) + try: + # gen10 + allowable_vals = next( + iter( + self.auxcommands["get"].getworkerfunction( + ["Boot/UefiTargetBootSourceOverride@Redfish.AllowableValues"], + options, + results=True, + uselist=True, + ) + ), + {}, + )["Boot"]["UefiTargetBootSourceOverride@Redfish.AllowableValues"] + for source in bootsources: + if source["StructuredBootString"].upper() == entry.upper(): + for val in allowable_vals: + if "UEFIDevicePath" in source and source["UEFIDevicePath"].endswith(val): + entry = val + break + except KeyError: + pass + + if not entry == uefitargetstatus["Boot"]["UefiTargetBootSourceOverride"]: + newlist += " Boot/UefiTargetBootSourceOverride=" + entry + elif not targetstatus["Boot"]["BootSourceOverrideTarget"] == "UefiTarget": + newlist += " Boot/BootSourceOverrideTarget=UefiTarget" + + if bootoverride: + if self.rdmc.app.typepath.defs.isgen9 and newlist: + if not bootoverride.split("=")[-1] == bootstatus["Boot"]["BootSourceOverrideEnabled"]: + # Preemptively set UefiTargetBootSourceOverride so iLO 4 doesn't complain + self.rdmc.app.patch_handler( + self.rdmc.app.typepath.defs.systempath, + {"Boot": {"UefiTargetBootSourceOverride": entry}}, + silent=True, + service=True, + ) + self.rdmc.app.select(selector=self.rdmc.app.selector, path_refresh=True) + newlist = "" + newlist += bootoverride + else: + newlist += bootoverride + + if options.reboot and newlist: + newlist += " --commit --reboot " + options.reboot + elif options.commit and newlist: + newlist += " --commit" + + if newlist: + try: + self.auxcommands["set"].run(newlist) + except InvalidOrNothingChangedSettingsError: + if self.rdmc.app.typepath.defs.isgen9: + pass + else: + raise + else: + raise InvalidOrNothingChangedSettingsError("Entry is the " "current boot setting.\n") + elif options.disablebootflag: + if bootoverride: + newlist += bootoverride + + if options.reboot: + newlist += " --commit --reboot " + options.reboot + elif options.commit and newlist: + newlist += " --commit" + + if newlist: + self.auxcommands["set"].run(newlist) + else: + raise InvalidOrNothingChangedSettingsError("Entry is the " "current boot setting.\n") + else: + raise InvalidCommandLineError( + "Invalid entry passed for one" + " time boot. Please run boot \n order without" + " arguments to view available options.\n" + ) + + self.cmdbase.logout_routine(self, options) + + # Return code + return ReturnCodes.SUCCESS + + def searchcasestring(self, entry, content): + """Helper function for retrieving correct case for value + + :param entry: entry to correlate case + :type entry: string. + :param content: list of items + :type content: list. + """ + for item in content: + if entry.upper() == item.upper(): + return item + + def secureboothelper(self, securebootoption): + """Helper function for secure boot function + + :param securebootoption: option passed in for secure boot + :type securebootoption: string. + """ + actionlist = ["defaultkeys", "deletekeys", "deletepk"] + + if not securebootoption.lower() in actionlist: + raise InvalidCommandLineError("%s is not a valid option for " "the securebootkeys flag." % securebootoption) + + if securebootoption == actionlist[0]: + action = "ResetAllKeysToDefault" + elif securebootoption == actionlist[1]: + action = "DeleteAllKeys" + elif securebootoption == actionlist[2]: + action = "DeletePK" + + results = self.rdmc.app.select(selector=self.rdmc.app.typepath.defs.hpsecureboot) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + results = results.resp.dict + + try: + for item in results["Actions"]: + if "ResetKeys" in item: + path = results["Actions"][item]["target"] + break + + body = {"ResetKeysType": action} + self.rdmc.app.post_handler(path, body) + except: + if securebootoption == actionlist[0]: + self.auxcommands["select"].selectfunction(self.rdmc.app.typepath.defs.hpsecureboot) + self.auxcommands["set"].run("ResetToDefaultKeys=True --commit") + elif securebootoption == actionlist[1]: + self.auxcommands["select"].selectfunction(self.rdmc.app.typepath.defs.hpsecureboot) + self.auxcommands["set"].run("ResetAllKeys=True --commit") + else: + self.rdmc.ui.warn("DeletePK option is not available on Gen9.\n") + + def print_out_boot_order( + self, + content, + onetimecontent, + uefionetimecontent, + bootmode, + bootsources, + bootstatus, + targetstatus, + ): + """Convert content to human readable and print out to std.out + + :param content: current content + :type content: string. + :param onetimecontent: list of one time boot entries + :type onetimecontent: list. + :param uefionetimecontent: list of uefi one time boot entries + :type uefionetimecontent: list. + :param bootmode: current system boot mode + :type bootmode: string. + :param bootsources: current systems boot sources + :type bootsources: list. + """ + if content is None: + raise BootOrderMissingEntriesError("No entries found in " "current boot order.\n\n") + else: + self.print_boot_helper(content, "\nCurrent Persistent Boot " "Order:", bootsources=bootsources) + + bootstatusval = bootstatus["Boot"]["BootSourceOverrideEnabled"] + boottoval = targetstatus["Boot"]["BootSourceOverrideTarget"] + if bootstatusval == "Continuous": + self.rdmc.ui.printer("Current continuous boot: {0}\n\n".format(boottoval)) + elif bootstatusval == "Once": + self.rdmc.ui.printer("Current one time boot: {0}\n\n".format(boottoval)) + + if onetimecontent is None: + raise BootOrderMissingEntriesError("No entries found for one time boot options.\n\n") + else: + self.print_boot_helper(onetimecontent["Boot"], "Continuous and one time boot options:") + + if bootmode and any([bootmode.get(boot, None) == "Uefi" for boot in bootmode]): + if uefionetimecontent is None: + self.rdmc.ui.printer("Continuous and one time boot uefi options:\n") + self.rdmc.ui.printer( + "No entries found for one-time UEFI options or boot source mode " "is not set to UEFI." + ) + else: + self.print_boot_helper( + uefionetimecontent["Boot"], + "Continuous and one time boot uefi options:", + bootsources=bootsources, + ) + + def print_boot_helper(self, content, outstring, indent=0, bootsources=None): + """Print boot helper + + :param content: current content + :type content: string. + :param outstring: output string + :type outstring: string. + :param indent: indent format + :type indent: string. + :param bootsources: current systems boot sources + :type bootsources: list. + """ + for _, value in list(content.items()): + self.rdmc.ui.printer("\t" * indent + outstring) + + if isinstance(value, list): + count = 1 + + for item in value: + self.rdmc.ui.printer("\n") + + if not item: + item = str("null") + + # if isinstance(item, six.string_types): + bootstring = False + try: + for source in bootsources: + if item == source["StructuredBootString"]: + self.rdmc.ui.printer( + "\t" * indent + + str(count) + + ". " + + str(item) + + " (" + + str(source["BootString"]) + + ")" + ) + bootstring = True + break + + if not bootstring: + self.rdmc.ui.printer("\t" * indent + str(count) + ". " + str(item)) + except: + self.rdmc.ui.printer("\t" * indent + str(count) + ". " + str(item)) + + count += 1 + # else: + # self.print_boot_helper(item, indent+1) + + self.rdmc.ui.printer("\n\n") + + def bootordervalidation(self, options): + """Boot order method validation function + + :param options: command line options + :type options: list. + """ + inputline = list() + + if self.rdmc.config.commit.lower() == "true": + options.commit = True + + self.cmdbase.login_select_validation(self, options) + + if inputline: + self.lobobj.loginfunction(inputline) + + if options.encode: + options.biospassword = Encryption.decode_credentials(options.biospassword) + if isinstance(options.biospassword, bytes): + options.biospassword = options.biospassword.decode("utf-8") + + @staticmethod + def options_argument_group(parser): + """Additional argument + + :param parser: The parser to add the removeprivs option group to + :type parser: ArgumentParser/OptionParser + """ + + parser.add_argument_group( + "GLOBAL OPTION:", + "Option(s) are available " "for all arguments within the scope of this command.", + ) + + parser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + self.options_argument_group(customparser) + + customparser.add_argument( + "--onetimeboot", + dest="onetimeboot", + help="Use this flag to configure a one-time boot option." + " Using this flag will prioritize the provided boot source" + " only on the very next time the server is booted.", + default=None, + ) + customparser.add_argument( + "--continuousboot", + dest="continuousboot", + help="Use this flag to enable a continuous boot option. Using" + " this flag will cause the system to boot to the selected" + " device every time the system boots.", + default=None, + ) + customparser.add_argument( + "--disablebootflag", + dest="disablebootflag", + action="store_true", + help="Use this to disable either continuous or one-time boot modes.", + default=None, + ) + customparser.add_argument( + "--securebootkeys", + dest="secureboot", + help="Use this flag to perform actions on secure boot keys." + "Possible values include defaultkeys: resets all keys to default," + "deletekeys: deletes all keys, deletepk: deletes all product keys.", + default=False, + ) + customparser.add_argument( + "--commit", + dest="commit", + action="store_true", + help="Use this flag when you are ready to commit all pending" + " changes. Note that some changes made in this way will be updated" + " instantly, while others will be reflected the next time the" + " server is started.", + default=None, + ) + customparser.add_argument( + "--reboot", + dest="reboot", + help="Use this flag to perform a reboot command function after" + " completion of operations. For help with parameters and" + " descriptions regarding the reboot flag, run help reboot.", + default=None, + ) + customparser.add_argument( + "--ignorematcherror", + dest="ime", + action="store_true", + help="Use this flag when you want to run multiple matches and " + "not throw an error in case there are no matches found for given " + "expression.", + default=None, + ) diff --git a/ilorest/extensions/BIOS_COMMANDS/IscsiConfigCommand.py b/ilorest/extensions/BIOS_COMMANDS/IscsiConfigCommand.py new file mode 100644 index 0000000..b38d27e --- /dev/null +++ b/ilorest/extensions/BIOS_COMMANDS/IscsiConfigCommand.py @@ -0,0 +1,895 @@ +### +# Copyright 2016-2021 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 -*- +""" IscsiConfig Command for rdmc """ + +import json +import re +import time + +import redfish.ris + +try: + from rdmc_helper import ( + BootOrderMissingEntriesError, + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NicMissingOrConfigurationError, + ReturnCodes, + ) +except: + from ilorest.rdmc_helper import ( + BootOrderMissingEntriesError, + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NicMissingOrConfigurationError, + ReturnCodes, + ) + + +class IscsiConfigCommand: + """Changes the iscsi configuration for the server that is currently logged in""" + + def __init__(self): + self.ident = { + "name": "iscsiconfig", + "usage": None, + "description": "Run without" + " arguments for available NIC sources for iSCSI" + " configuration.\n\texample: iscsiconfig\n\n\tDisplay" + " the current iSCSI configuration:\n\texample: " + "iscsiconfig --list\n\n\tSaving current iSCSI " + "configuration to a file:\n\texample: iscsiconfig " + "--list -f output.txt\n\n\tLoading iSCSI " + "configurations from a file:\n\texample: iscsiconfig " + "--modify output.txt\n\n\tIn order to add a NIC " + 'source to an iSCSI boot attempt you must run \n\t"' + 'iscsiconfig" without any paramters. This will ' + "display a list of NIC\n\tsources which are currently" + " present in the system.\n\tAdding an iSCSI boot " + "attempt:\n\texample: iscsiconfig --add 1\n\n\tIn " + "order to delete an iSCSI boot attempt you must run" + '\n\t"iscsiconfig --list" to view the currently ' + "configured attempts.\n\tOnce you find the attempt " + "you want to delete simply pass the attempt\n\tnumber" + " to the iSCSI delete function.\n\tDeleting an iSCSI " + "boot attempt:\n\texample: iscsiconfig --delete 1", + "summary": "Displays and configures the current iscsi settings.", + "aliases": [], + "auxcommands": [ + "GetCommand", + "SetCommand", + "SelectCommand", + "RebootCommand", + "LogoutCommand", + ], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main iscsi configuration worker function + + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.iscsiconfigurationvalidation(options) + + if self.rdmc.app.typepath.defs.isgen10: + iscsipath = self.gencompatpaths(selector="HpeiSCSISoftwareInitiator.", rel=False) + if "/settings" not in iscsipath: + iscsisettingspath = iscsipath + "settings" + else: + iscsisettingspath = iscsipath + bootpath = self.gencompatpaths(selector="HpeServerBootSettings.") + bootpath = bootpath.replace("/settings", "") + else: + # TODO: update gencompats to handle the nesting of these links within the gen 9 version. + if self.rdmc.app.typepath.defs.biospath[-1] == "/": + iscsipath = self.rdmc.app.typepath.defs.biospath + "iScsi/" + iscsisettingspath = self.rdmc.app.typepath.defs.biospath + "iScsi/settings/" + bootpath = self.rdmc.app.typepath.defs.biospath + "Boot/" + else: + iscsipath = self.rdmc.app.typepath.defs.biospath + "/iScsi" + iscsisettingspath = self.rdmc.app.typepath.defs.biospath + "/iScsi/settings" + bootpath = self.rdmc.app.typepath.defs.biospath + "/Boot" + + if options.list: + self.listoptionhelper(options, iscsipath, iscsisettingspath, bootpath) + elif options.modify: + self.modifyoptionhelper(options, iscsisettingspath) + elif options.add: + self.addoptionhelper(options, iscsipath, iscsisettingspath, bootpath) + elif options.delete: + self.deleteoptionhelper(options, iscsisettingspath) + elif not args: + self.defaultsoptionhelper(options, iscsipath, bootpath) + else: + if len(args) < 2: + self.iscsiconfigurationvalidation(options) + else: + raise InvalidCommandLineError( + "Invalid number of parameters. " "Iscsi configuration takes a maximum of 1 parameter." + ) + + if options.reboot: + self.auxcommands["reboot"].run(options.reboot) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def gencompatpaths(self, selector=None, rel=False): + """Helper function for finding gen compatible paths + + :param selector: the type selection for the get operation + :type selector: str. + :param rel: flag to tell select function to reload selected instance + :type rel: boolean. + :returns: returns urls + """ + + self.rdmc.app.select(selector=selector, path_refresh=rel) + props = self.rdmc.app.getprops(skipnonsetting=False) + for prop in props: + name = prop.get("Name") + if "current" in name.lower() or "pending" in name.lower(): + try: + path = prop.get("@odata.id") + except: + self.rdmc.ui.error("URI path could not be found.") + return path + + def addoptionhelper(self, options, iscsipath, iscsisettingspath, bootpath): + """Helper function to add option for iscsi + + :param options: command line options + :type options: list. + :param iscsipath: current iscsi path + :type iscsipath: str. + :param iscsisettingspath: current iscsi settings path + :type iscsisettingspath: str. + :param bootpath: current boot path + :type bootpath: str. + """ + devicealloc = list() + self.auxcommands["select"].selectfunction("HpeBiosMapping.") + pcisettingsmap = self.auxcommands["get"].getworkerfunction( + "BiosPciSettingsMappings", options, results=True, uselist=True + ) + + for item in pcisettingsmap["BiosPciSettingsMappings"]: + if "Associations" in item: + if "EmbNicEnable" in item["Associations"] or "EmbNicConfig" in item["Associations"]: + _ = [devicealloc.append(x) for x in item["Subinstances"]] + + if ( + re.match("FlexLom[0-9]+Enable", item["Associations"][0]) + or re.match("PciSlot[0-9]+Enable", item["Associations"][0]) + or re.match("Slot[0-9]+NicBoot[0-9]+", item["Associations"][0]) + ): + _ = [devicealloc.append(x) for x in item["Subinstances"]] + + foundlocation = False + iscsibootsources = self.rawdatahandler(action="GET", silent=True, jsonflag=True, path=iscsisettingspath) + count = 0 + attemptinstancenumber = self.bootattemptcounter(iscsibootsources[self.rdmc.app.typepath.defs.iscsisource]) + if self.rdmc.app.typepath.defs.isgen10: + newpcilist = [] + self.auxcommands["select"].selectfunction("HpeServerPciDeviceCollection") + pcideviceslist = next( + iter(self.auxcommands["get"].getworkerfunction("Members", options, results=True, uselist=False)), + None, + ) + for device in pcideviceslist["Members"]: + newpcilist.append(self.rdmc.app.get_handler(device["@odata.id"], silent=True).dict) + pcideviceslist = newpcilist + else: + self.auxcommands["select"].selectfunction(["Collection."]) + pcideviceslist = next( + iter( + self.auxcommands["get"].getworkerfunction( + "Items", + options, + results=True, + uselist=False, + filtervals=("MemberType", "HpServerPciDevice.*"), + ) + ), + None, + )["Items"] + + self.pcidevicehelper(devicealloc, iscsipath, bootpath, pcideviceslist) + + devicealloc_final = self.make_final_list(devicealloc, pcideviceslist) + + for item in iscsibootsources[self.rdmc.app.typepath.defs.iscsisource]: + try: + if not item[self.rdmc.app.typepath.defs.iscsiattemptinstance]: + nicsourcedata = devicealloc_final[int(options.add) - 1]["Associations"] + + iscsibootsources[self.rdmc.app.typepath.defs.iscsisource][count]["iSCSINicSource"] = ( + nicsourcedata[1] if isinstance(nicsourcedata[0], dict) else nicsourcedata[0] + ) + + iscsibootsources[self.rdmc.app.typepath.defs.iscsisource][count][ + self.rdmc.app.typepath.defs.iscsiattemptinstance + ] = int(options.add) + iscsibootsources[self.rdmc.app.typepath.defs.iscsisource][count][ + self.rdmc.app.typepath.defs.iscsiattemptname + ] = str(attemptinstancenumber) + foundlocation = True + break + except Exception: + raise NicMissingOrConfigurationError("Invalid input value for configuring NIC.") + count += 1 + + if foundlocation: + self.rdmc.app.patch_handler( + iscsisettingspath, + iscsibootsources, + optionalpassword=options.biospassword, + ) + else: + raise NicMissingOrConfigurationError("Failed to add NIC. All NICs" " have already been configured.") + + def make_final_list(self, devicealloc, pcideviceslist): + final_devicealloc = [] + for item in devicealloc: + if isinstance(item["Associations"][0], dict): + listval = 1 + else: + listval = 0 + if "Storage" not in item["Associations"][listval]: + for pcidevice in pcideviceslist: + # if item["CorrelatableID"] == pcidevice["UEFIDevicePath"]: + final_devicealloc.append(item) + return final_devicealloc + + def bootattemptcounter(self, bootsources): + """Helper function to count the current boot entries for iscsi + + :param bootsources: current iscsi boot sources + :type bootsources: list. + """ + size = 0 + count = list() + + for item in bootsources: + size += 1 + + if item[self.rdmc.app.typepath.defs.iscsiattemptinstance]: + count.append(int(item[self.rdmc.app.typepath.defs.iscsiattemptinstance])) + + if size == len(count): + raise NicMissingOrConfigurationError("Failed to add NIC. All " "NICs have already been configured.") + + count.sort(key=None, reverse=False) + + if len(count) > 0: + iterate = 0 + + for i in range(1, size + 1, 1): + if iterate < len(count) and i == count[iterate]: + iterate += 1 + else: + return iterate + 1 + else: + return int(1) + + def deleteoptionhelper(self, options, iscsisettingspath): + """Helper function to delete option for iscsi + + :param options: command line options + :type options: list. + :param iscsisettingspath: current iscsi settings path + :type iscsisettingspath: string. + """ + patch = None + + self.auxcommands["select"].selectfunction("HpBaseConfigs.") + contents = self.rdmc.app.getprops(selector="BaseConfigs") + + for content in contents: + for key in content["BaseConfigs"][0]["default"]: + if key == self.rdmc.app.typepath.defs.iscsisource: + patch = content["BaseConfigs"][0]["default"][key] + + if not patch: + raise NicMissingOrConfigurationError("Could not access Base Configurations.") + + self.validateinput(options=options, deleteoption=True) + + foundlocation = False + iscsibootsources = self.rawdatahandler(action="GET", silent=True, jsonflag=False, path=iscsisettingspath) + holdetag = iscsibootsources.getheader("etag") + iscsibootsources = json.loads(iscsibootsources.read) + + try: + count = 0 + for item in iscsibootsources[self.rdmc.app.typepath.defs.iscsisource]: + if item[self.rdmc.app.typepath.defs.iscsiattemptinstance] == int(options.delete): + iscsibootsources[self.rdmc.app.typepath.defs.iscsisource][count] = patch[count] + foundlocation = True + + count += 1 + except Exception: + raise NicMissingOrConfigurationError( + "The NIC targeted for delete" " does not exist. The request for " "delete could not be completed." + ) + + if foundlocation: + self.rdmc.app.put_handler( + iscsisettingspath, + iscsibootsources, + optionalpassword=options.biospassword, + headers={"if-Match": holdetag}, + ) + self.rdmc.app.get_handler(iscsisettingspath, silent=True) + else: + raise NicMissingOrConfigurationError("The given attempt instance does not exist.") + + def listoptionhelper(self, options, iscsipath, iscsisettingspath, bootpath): + """Helper function to list options for iscsi + + :param options: command line options + :type options: list. + :param iscsipath: current iscsi path + :type iscsipath: str. + :param iscsisettingspath: current iscsi settings path + :type iscsisettingspath: str. + :param bootpath: current boot path + :type bootpath: str. + """ + self.auxcommands["select"].selectfunction("HpeBiosMapping.") + pcisettingsmap = self.auxcommands["get"].getworkerfunction( + "BiosPciSettingsMappings", options, results=True, uselist=True + ) + + devicealloc = list() + for item in pcisettingsmap["BiosPciSettingsMappings"]: + if "Associations" in item: + if "EmbNicEnable" in item["Associations"] or "EmbNicConfig" in item["Associations"]: + _ = [devicealloc.append(x) for x in item["Subinstances"]] + + if ( + re.match("FlexLom[0-9]+Enable", item["Associations"][0]) + or re.match("PciSlot[0-9]+Enable", item["Associations"][0]) + or re.match("Slot[0-9]+NicBoot[0-9]+", item["Associations"][0]) + ): + _ = [devicealloc.append(x) for x in item["Subinstances"]] + + if self.rdmc.app.typepath.defs.isgen10: + newpcilist = [] + + self.auxcommands["select"].selectfunction("HpeServerPciDeviceCollection") + pcideviceslist = next( + iter(self.auxcommands["get"].getworkerfunction("Members", options, results=True, uselist=False)), + None, + ) + + for device in pcideviceslist["Members"]: + newpcilist.append(self.rdmc.app.get_handler(device["@odata.id"], silent=True).dict) + + pcideviceslist = newpcilist + else: + self.auxcommands["select"].selectfunction(["Collection."]) + pcideviceslist = next( + iter( + self.auxcommands["get"].getworkerfunction( + "Items", + options, + results=True, + uselist=False, + filtervals=("MemberType", "HpServerPciDevice.*"), + ) + ), + None, + )["Items"] + i = 0 + iscsibootsources = "" + while i < 4: + self.auxcommands["select"].selectfunction("HpiSCSISoftwareInitiator.") + iscsibootsources = self.rawdatahandler(action="GET", silent=True, jsonflag=True, path=iscsisettingspath) + if not ("error" in iscsibootsources): + break + time.sleep(10) + i = i + 1 + self.rdmc.ui.printer("Retrying ...\n\n") + structeredlist = list() + + self.pcidevicehelper(devicealloc, iscsipath, bootpath, pcideviceslist) + if not ("error" in iscsibootsources): + for item in iscsibootsources[self.rdmc.app.typepath.defs.iscsisource]: + if item["iSCSINicSource"]: + for device in devicealloc: + it = device["CorrelatableID"] + it = it.split(",") + del it[-1] + it = ",".join(it) + if not isinstance(device["Associations"][0], list): + listval = 1 if isinstance(device["Associations"][0], dict) else 0 + else: + listval = 0 + if item["iSCSINicSource"] == device["Associations"][listval]: + for pcidevice in pcideviceslist: + pcid = pcidevice["UEFIDevicePath"] + pcid = pcid.split(",") + del pcid[-1] + pcid = ",".join(pcid) + if (device["CorrelatableID"] == pcidevice["UEFIDevicePath"]) or ("Embedded" not in pcidevice["DeviceType"] and (pcid == it)): + if "Storage" not in pcidevice["DeviceType"]: + inputstring = ( + pcidevice["DeviceType"] + + " " + + str(pcidevice["DeviceInstance"]) + + " Port " + + str(pcidevice["DeviceSubInstance"]) + + " : " + + pcidevice.get("Name", "") + ) + structeredlist.append( + { + inputstring: { + str( + "Attempt " + + str(item[self.rdmc.app.typepath.defs.iscsiattemptinstance]) + ): item + } + } + ) + + else: + structeredlist.append({"Not Added": {}}) + + try: + if iscsibootsources is None: + raise BootOrderMissingEntriesError("No entries found for iscsi boot sources \n\n") + elif "error" in iscsibootsources: + raise BootOrderMissingEntriesError( + "/redfish/v1/systems/1/bios/oem/hpe/iscsi/settings URI seems to be not reachable even after" + " 4 retry attempts , kindly retry the command after some time \n\n" + ) + elif not options.filename: + self.print_iscsi_config_helper(structeredlist, "Current iSCSI Attempts: \n") + except Exception as excp: + raise excp + + if structeredlist is None: + self.rdmc.ui.error("No entries found for iscsi boot sources.\n\n") + elif options.filename: + output = json.dumps(structeredlist, indent=2, cls=redfish.ris.JSONEncoder, sort_keys=True) + filehndl = open(options.filename[0], "w") + filehndl.write(output) + filehndl.close() + + self.rdmc.ui.printer("Results written out to '%s'\n" % options.filename[0]) + + def defaultsoptionhelper(self, options, iscsipath, bootpath): + """Helper function for default options for iscsi + + :param options: command line options + :type options: list. + :param iscsipath: current iscsi path + :type iscsipath: str. + :param bootpath: current boot path + :type bootpath: str. + """ + self.auxcommands["select"].selectfunction("HpBiosMapping.") + pcisettingsmap = self.auxcommands["get"].getworkerfunction( + "BiosPciSettingsMappings", options, results=True, uselist=True + ) + + devicealloc = list() + for item in pcisettingsmap["BiosPciSettingsMappings"]: + if "Associations" in item: + # self.rdmc.ui.printer("Assoc 1 : %s\n" % (item["Associations"])) + # self.rdmc.ui.printer("Sub 1 : %s\n" % (item["Subinstances"])) + if "EmbNicEnable" in item["Associations"] or "EmbNicConfig" in item["Associations"]: + _ = [devicealloc.append(x) for x in item["Subinstances"]] + + try: + item["Associations"][0] + # self.rdmc.ui.printer("Assoc 2 : %s\n" % (item["Associations"][0])) + if ( + re.match("FlexLom[0-9]+Enable", item["Associations"][0]) + or re.match("PciSlot[0-9]+Enable", item["Associations"][0]) + or re.match("Slot[0-9]+NicBoot[0-9]+", item["Associations"][0]) + ): + _ = [devicealloc.append(x) for x in item["Subinstances"]] + except IndexError: + pass + # self.rdmc.ui.printer("Device alloc : %s\n" % devicealloc) + + if self.rdmc.app.typepath.defs.isgen10: + newpcilist = [] + self.auxcommands["select"].selectfunction("HpeServerPciDeviceCollection") + pcideviceslist = self.auxcommands["get"].getworkerfunction("Members", options, results=True, uselist=False) + pcideviceslist = pcideviceslist[0] + for device in pcideviceslist["Members"]: + newpcilist.append(self.rdmc.app.get_handler(device["@odata.id"], silent=True).dict) + pcideviceslist = newpcilist + else: + self.auxcommands["select"].selectfunction(["Collection."]) + pcideviceslist = self.auxcommands["get"].getworkerfunction( + "Items", + options, + results=True, + uselist=False, + filtervals=("MemberType", "HpServerPciDevice.*"), + )["Items"] + + self.auxcommands["select"].selectfunction(self.rdmc.app.typepath.defs.hpiscsisoftwareinitiatortype) + iscsiinitiatorname = self.auxcommands["get"].getworkerfunction( + "iSCSIInitiatorName", options, results=True, uselist=True + ) + + disabledlist = self.pcidevicehelper(devicealloc, iscsipath, bootpath, pcideviceslist) + + self.print_out_iscsi_configuration(iscsiinitiatorname, devicealloc, pcideviceslist) + + if disabledlist: + self.print_out_iscsi_configuration(iscsiinitiatorname, disabledlist, pcideviceslist, disabled=True) + + def modifyoptionhelper(self, options, iscsisettingspath): + """Helper function to modify options for iscsi + + :param options: command line options + :type options: list. + :param iscsisettingspath: current iscsi settings path + :type iscsisettingspath: str. + """ + try: + inputfile = open(options.modify, "r") + contentsholder = json.loads(inputfile.read()) + except Exception as excp: + raise InvalidCommandLineError("%s" % excp) + + iscsibootsources = self.rawdatahandler(action="GET", silent=True, jsonflag=True, path=iscsisettingspath) + + count = 0 + resultsdict = list() + + for item in contentsholder: + for entry in item.values(): + enteredsection = False + + for key, value in entry.items(): + enteredsection = True + resultsdict.append( + self.modifyfunctionhelper( + key, + value, + iscsibootsources[self.rdmc.app.typepath.defs.iscsisource], + ) + ) + + if not enteredsection: + resultsdict.append(iscsibootsources[self.rdmc.app.typepath.defs.iscsisource][count]) + + count += 1 + + contentsholder = {self.rdmc.app.typepath.defs.iscsisource: resultsdict} + + self.rdmc.app.patch_handler(iscsisettingspath, contentsholder, optionalpassword=options.biospassword) + self.rdmc.app.get_handler(iscsisettingspath, silent=True) + self.rdmc.ui.printer("Please reboot the server for changes to take effect\n") + + def modifyfunctionhelper(self, key, value, bootsources): + """Helper function to modify the entries for iscsi + + :param key: key to be used for attempt + :type key: string. + :param value: value to apply to attempt + :type value: str. + :param bootsources: current boot sources + :type bootsources: list. + """ + foundoption = False + + for bootsource in bootsources: + if bootsource[self.rdmc.app.typepath.defs.iscsiattemptinstance] == int(key[-1:]): + foundoption = True + break + else: + return value + if foundoption: + return value + + def pcidevicehelper(self, devicealloc, iscsipath, bootpath, pcideviceslist=None, options=None): + """Helper function to check for extra pci devices / identify disabled devices + + :param devicealloc: list of devices allocated + :type devicealloc: list. + :param iscsipath: current iscsi path + :type iscsipath: str. + :param bootpath: current boot path + :type bootpath: str. + :param pcideviceslist: current pci device list + :type pcideviceslist: list. + :param options: command line options + :type options: list. + """ + if not pcideviceslist: + if self.rdmc.app.typepath.defs.isgen10: + newpcilist = [] + self.auxcommands["select"].selectfunction("HpeServerPciDeviceCollection") + pcideviceslist = next( + iter(self.auxcommands["get"].getworkerfunction("Members", options, results=True, uselist=False)), + None, + ) + + for device in pcideviceslist["Members"]: + newpcilist.append(self.rdmc.app.get_handler(device["@odata.id"], silent=True).dict) + + pcideviceslist = newpcilist + else: + self.auxcommands["select"].selectfunction(["Collection."]) + pcideviceslist = next( + iter( + self.auxcommands["get"].getworkerfunction( + "Items", + options, + results=True, + uselist=False, + filtervals=("MemberType", "HpServerPciDevice.*"), + ) + ), + None, + )["Items"] + try: + self.rawdatahandler(action="GET", silent=True, jsonflag=True, path=iscsipath)["iSCSINicSources"] + except: + raise NicMissingOrConfigurationError("No iSCSI nic sources available.") + + _ = [x["UEFIDevicePath"] for x in pcideviceslist] + removal = list() + + bios = self.rawdatahandler(action="GET", silent=True, jsonflag=True, path=bootpath) + + for item in devicealloc: + if isinstance(item["Associations"][0], dict): + if "PreBootNetwork" in list(item["Associations"][0].keys()): + if item["Associations"] and item["Associations"][0]["PreBootNetwork"] in list(bios.keys()): + if bios[item["Associations"][0]["PreBootNetwork"]] == "Disabled": + removal.append(item) + else: + if item["Associations"] and item["Associations"][0] in list(bios.keys()): + if bios[item["Associations"][0]] == "Disabled": + removal.append(item) + + _ = [devicealloc.remove(x) for x in removal] + + return removal + + def print_out_iscsi_configuration(self, iscsiinitiatorname, devicealloc, pcideviceslist, disabled=False): + """Convert content to human readable and print out to std.out + + :param iscsiinitiatorname: iscsi initiator name + :type iscsiinitiatorname: str. + :param devicealloc: list of devices allocated + :type devicealloc: list. + :param pcideviceslist: current pci device list + :type pcideviceslist: list. + :param disabled: command line options + :type disabled: boolean. + """ + try: + if iscsiinitiatorname is None: + BootOrderMissingEntriesError("No entry found for the iscsi initiator name.\n\n") + elif disabled: + pass + else: + self.print_iscsi_config_helper(iscsiinitiatorname["iSCSIInitiatorName"], "\nIscsi Initiator Name: ") + except Exception as excp: + raise excp + + try: + tot_instance = [] + final_device_list = list() + if devicealloc and pcideviceslist: + if disabled: + self.rdmc.ui.printer("\nDisabled iSCSI Boot Network Interfaces: \n") + count = "Disabled" + else: + self.rdmc.ui.printer("Available iSCSI Boot Network Interfaces: \n") + count = 1 + for pcidevice in pcideviceslist: + pcid = pcidevice["UEFIDevicePath"] + pcid = pcid.split(",") + del pcid[-1] + pcid = ",".join(pcid) + for item in devicealloc: + it = item["CorrelatableID"] + it = it.split(",") + del it[-1] + it = ",".join(it) + if pcid == it: + if "Storage" not in pcidevice["DeviceType"]: + device = pcidevice["DeviceType"] + " " + str(pcidevice["DeviceInstance"]) + " Port " + str(item["Subinstance"]) + if device not in final_device_list: + final_device_list.append(device) + temp = dict() + temp["Device"] = device + if "Name" not in pcidevice: + temp["Name"] = pcidevice["StructuredName"] + else: + temp["Name"] = pcidevice["Name"] + tot_instance.append(temp) + count = 0 + for t in tot_instance: + count = count + 1 + self.rdmc.ui.printer( + "[%s] %s : %s\n" + % ( + count, + t["Device"], + t["Name"], + ) + ) + else: + raise BootOrderMissingEntriesError("No entries found for" " iscsi configurations devices.\n") + except Exception as excp: + raise excp + + def print_iscsi_config_helper(self, content, outstring, indent=0): + """Print iscsi configuration helper + + :param content: current content to be output + :type content: string. + :param outstring: current output string + :type outstring: str. + :param indent: current iscsi settings path + :type indent: str. + """ + self.rdmc.ui.printer("\t" * indent + outstring) + + if content: + self.rdmc.ui.print_out_json(content) + else: + self.rdmc.ui.error("\t" * indent + "No entries currently configured.\n") + + self.rdmc.ui.printer("\n\n") + + def rawdatahandler(self, action="None", path=None, silent=True, jsonflag=False): + """Helper function to get and put the raw data + + :param action: current rest action + :type action: list. + :param path: current path + :type path: str. + :param silent: flag to determine silent mode + :type silent: boolean. + :param jsonflag: flag to determine json output + :type jsonflag: boolean. + """ + if action == "GET": + rawdata = self.rdmc.app.get_handler(get_path=path, silent=silent) + + if jsonflag is True: + rawdata = json.loads(rawdata.read) + + return rawdata + + def validateinput(self, deviceallocsize=None, options=None, deleteoption=False): + """Helper function to validate that the input is correct + + :param deviceallocsize: current device allocated size + :type deviceallocsize: str. + :param options: command line options + :type options: list. + :param deleteoption: flag to delete option + :type deleteoption: boolean. + """ + if deviceallocsize: + try: + if int(options.add) - 1 >= deviceallocsize: + raise NicMissingOrConfigurationError("Please verify the " "given input value for configuring NIC.") + except Exception: + raise NicMissingOrConfigurationError("Please verify the " "given input value for configuring NIC.") + if deleteoption: + try: + if int(options.delete) == 0: + raise NicMissingOrConfigurationError( + "Invalid input value." "Please give valid attempt for instance values." + ) + except Exception: + raise NicMissingOrConfigurationError( + "Invalid input value." "Please give valid attempt for instance values." + ) + + def iscsiconfigurationvalidation(self, options): + """iscsi configuration method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + if options.encode: + options.biospassword = Encryption.decode_credentials(options.biospassword) + if isinstance(options.biospassword, bytes): + options.biospassword = options.biospassword.decode("utf-8") + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-f", + "--filename", + dest="filename", + help="Use this flag if you wish to use a different" + " filename than the default one. The default filename is" + " ilorest.json.", + action="append", + default=None, + ) + customparser.add_argument( + "--add", + dest="add", + help="Use this iSCSI configuration option to add an iSCSI" " configuration option.", + default=None, + ) + customparser.add_argument( + "--delete", + dest="delete", + help="Use this iSCSI configuration option to delete an iSCSI" " configuration option.", + default=None, + ) + customparser.add_argument( + "--modify", + dest="modify", + help="Use this iSCSI configuration option to modify an iSCSI" " configuration option.", + default=None, + ) + customparser.add_argument( + "--list", + dest="list", + action="store_true", + help="Use this iSCSI configuration option to list the details" " of the different iSCSI configurations.", + default=None, + ) + customparser.add_argument( + "--reboot", + dest="reboot", + help="Use this flag to perform a reboot command function after" + " completion of operations. For help with parameters and" + " descriptions regarding the reboot flag, run help reboot.", + default=None, + ) diff --git a/ilorest/extensions/BIOS_COMMANDS/SetPasswordCommand.py b/ilorest/extensions/BIOS_COMMANDS/SetPasswordCommand.py new file mode 100644 index 0000000..4c3e074 --- /dev/null +++ b/ilorest/extensions/BIOS_COMMANDS/SetPasswordCommand.py @@ -0,0 +1,225 @@ +### +# Copyright 2016-2021 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 -*- +""" SetPassword Command for rdmc """ + +try: + from rdmc_helper import ( + Encryption, + InvalidCommandLineErrorOPTS, + ReturnCodes, + UnableToDecodeError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + Encryption, + InvalidCommandLineErrorOPTS, + ReturnCodes, + UnableToDecodeError, + ) + + +class SetPasswordCommand: + """Set password class command""" + + def __init__(self): + self.ident = { + "name": "setpassword", + "usage": None, + "description": "Sets the admin password and power-on password\n" + "setpassword --newpassword --currentpassword [OPTIONS]\n\n\t" + "Setting the admin password with no previous password set." + "\n\texample: setpassword --newpassword testnew --currentpassword None\n\n\tSetting the admin " + "password back to nothing.\n\texample: setpassword --newpassword None --currentpassword testnew " + "\n\n\tSetting the power on password.\n\texample: setpassword" + " --newpassword testnew --currentpassword None --poweron\n\tNote: " + "if it is empty password, send None as above.", + "summary": "Sets the admin password and power-on password", + "aliases": [], + "auxcommands": [ + "LoginCommand", + "SetCommand", + "SelectCommand", + "CommitCommand", + "RebootCommand", + "LogoutCommand", + ], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main set password worker function + + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.setpasswordvalidation(options) + + # if not args: + # self.rdmc.ui.printer('Please input the current password.\n') + # tempoldpass = getpass.getpass() + + # if tempoldpass and tempoldpass != '\r': + # tempoldpass = tempoldpass + # else: + # tempoldpass = '""' + + # self.rdmc.ui.printer('Please input the new password.\n') + # tempnewpass = getpass.getpass() + + # if tempnewpass and tempnewpass != '\r': + # tempnewpass = tempnewpass + # else: + # tempnewpass = '""' + # args.extend([tempnewpass, tempoldpass]) + + # if len(args) < 2: + # raise InvalidCommandLineError("Please pass both new password and old password.") + + args = list() + args.append(options.newpassword) + args.append(options.currentpassword) + count = 0 + for arg in args: + if arg: + if arg.lower() == "none" or arg.lower() == "null" or arg is None: + args[count] = "" + elif len(arg) > 2: + if ('"' in arg[0] and '"' in arg[-1]) or ("'" in arg[0] and "'" in arg[-1]): + args[count] = arg[1:-1] + elif len(arg) == 2: + if (arg[0] == '"' and arg[1] == '"') or (arg[0] == "'" and arg[1] == "'"): + args[count] = "" + count += 1 + + if options.encode: + _args = [] + for arg in args: + try: + arg = Encryption.decode_credentials(arg) + if isinstance(arg, bytes): + arg = arg.decode("utf-8") + _args.append(arg) + except UnableToDecodeError: + _args.append(arg) + args = _args + if self.rdmc.app.typepath.defs.isgen10: + bodydict = self.rdmc.app.get_handler(self.rdmc.app.typepath.defs.biospath, service=True, silent=True).dict + + for item in bodydict["Actions"]: + if "ChangePassword" in item: + path = bodydict["Actions"][item]["target"] + break + + if options.poweron: + body = { + "PasswordName": "User", + "OldPassword": args[1], + "NewPassword": args[0], + } + else: + body = { + "PasswordName": "Administrator", + "OldPassword": args[1], + "NewPassword": args[0], + } + + self.rdmc.app.post_handler(path, body) + else: + if options.poweron: + self.auxcommands["select"].run("HpBios.") + self.auxcommands["set"].run("PowerOnPassword=%s OldPowerOnPassword=%s" % (args[0], args[1])) + self.auxcommands["commit"].run("") + else: + self.auxcommands["select"].run("HpBios.") + self.auxcommands["set"].run("AdminPassword=%s OldAdminPassword=%s" % (args[0], args[1])) + self.auxcommands["commit"].run("") + self.rdmc.ui.printer( + "\nThe session will now be terminated.\n" + " login again with updated credentials in order to continue.\n" + ) + self.auxcommands["logout"].run("") + + if options: + if options.reboot: + self.auxcommands["reboot"].run(options.reboot) + + self.cmdbase.logout_routine(self, options) + return ReturnCodes.SUCCESS + + def setpasswordvalidation(self, options): + """Results method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--currentpassword", + dest="currentpassword", + help="Use this flag to provide current password.", + required=True, + ) + + customparser.add_argument( + "--newpassword", + dest="newpassword", + help="Use this flag to provide new password.", + required=True, + ) + + customparser.add_argument( + "--reboot", + dest="reboot", + help="Use this flag to perform a reboot command function after " + "completion of operations. 'REBOOT' is a replaceable parameter " + "that can have multiple values. For help with parameters and " + "descriptions regarding the reboot flag, run help reboot.", + default=None, + ) + customparser.add_argument( + "--poweron", + dest="poweron", + action="store_true", + help="""Use this flag to set power on password instead""", + default=None, + ) diff --git a/ilorest/extensions/BIOS_COMMANDS/__init__.py b/ilorest/extensions/BIOS_COMMANDS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/extensions/COMMANDS/CommitCommand.py b/ilorest/extensions/COMMANDS/CommitCommand.py new file mode 100644 index 0000000..3a7e6fd --- /dev/null +++ b/ilorest/extensions/COMMANDS/CommitCommand.py @@ -0,0 +1,140 @@ +### +# Copyright 2016-2021 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 -*- +""" Commit Command for RDMC """ + +from redfish.ris.rmc_helper import NothingSelectedError + +try: + from rdmc_helper import ( + FailureDuringCommitError, + InvalidCommandLineErrorOPTS, + NoChangesFoundOrMadeError, + NoCurrentSessionEstablished, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + FailureDuringCommitError, + InvalidCommandLineErrorOPTS, + NoChangesFoundOrMadeError, + NoCurrentSessionEstablished, + ReturnCodes, + ) + + +class CommitCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "commit", + "usage": None, + "description": "commit [OPTIONS]\n\n\tRun to apply all changes made during" + " the current session\n\texample: commit", + "summary": "Applies all the changes made during the current session.", + "aliases": [], + "auxcommands": ["LogoutCommand", "RebootCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def commitfunction(self, options=None): + """Main commit worker function + + :param options: command line options + :type options: list. + """ + self.commitvalidation() + + self.rdmc.ui.printer("Committing changes...\n") + + if options: + if options.biospassword: + self.rdmc.app.current_client.bios_password = options.biospassword + try: + failure = False + commit_opp = self.rdmc.app.commit() + for path in commit_opp: + if self.rdmc.opts.verbose: + self.rdmc.ui.printer("Changes are being made to path: %s\n" % path) + if next(commit_opp): + failure = True + except NothingSelectedError: + raise NoChangesFoundOrMadeError("No changes found or made during commit operation.") + else: + if failure: + raise FailureDuringCommitError( + "One or more types failed to commit. Run the " + "status command to see uncommitted data. " + "if you wish to discard failed changes refresh the " + "type using select with the --refresh flag." + ) + + if options.reboot: + self.auxcommands["reboot"].run(options.reboot) + self.auxcommands["logout"].run("") + + def run(self, line, help_disp=False): + """Wrapper function for commit main function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.commitfunction(options) + + # Return code + return ReturnCodes.SUCCESS + + def commitvalidation(self): + """Commit method validation function""" + + try: + _ = self.rdmc.app.current_client + except: + raise NoCurrentSessionEstablished("Please login and make setting" " changes before using commit command.") + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + customparser.add_argument( + "--reboot", + dest="reboot", + help="Use this flag to perform a reboot command function after" + " completion of operations. For help with parameters and" + " descriptions regarding the reboot flag, run help reboot.", + default=None, + ) diff --git a/ilorest/extensions/COMMANDS/GetCommand.py b/ilorest/extensions/COMMANDS/GetCommand.py new file mode 100644 index 0000000..d897ed3 --- /dev/null +++ b/ilorest/extensions/COMMANDS/GetCommand.py @@ -0,0 +1,365 @@ +### +# Copyright 2016-2023 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 -*- +""" Get Command for RDMC """ +import json +from collections import OrderedDict + +import six + +import redfish.ris +from redfish.ris.utils import iterateandclear + +try: + from rdmc_helper import ( + UI, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + UI, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +try: + from rdmc_helper import HARDCODEDLIST +except: + from ilorest.rdmc_helper import HARDCODEDLIST + + +class GetCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "get", + "usage": None, + "description": "To retrieve all" + " the properties run without arguments. \n\t*Note*: " + "a type will need to be selected or this will return an " + "error.\n\texample: get\n\n\tTo retrieve multiple " + "properties use the following example\n\texample: " + "get Temperatures/ReadingCelsius Fans/Name --selector=Thermal." + "\n\n\tTo change output style format provide" + " the json flag\n\texample: get --json", + "summary": "Displays the current value(s) of a" " property(ies) within a selected type.", + "aliases": [], + "auxcommands": ["LogoutCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main get worker function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if getattr(options, "json"): + self.rdmc.json = True + + self.getvalidation(options) + + filtr = (None, None) + if options.filter: + try: + if (str(options.filter)[0] == str(options.filter)[-1]) and str(options.filter).startswith(("'", '"')): + options.filter = options.filter[1:-1] + + (sel, val) = options.filter.split("=") + filtr = (sel.strip(), val.strip()) + + except: + raise InvalidCommandLineError("Invalid filter" " parameter format [filter_attribute]=[filter_value]") + + self.getworkerfunction( + args, + options, + results=None, + uselist=True, + filtervals=filtr, + readonly=options.noreadonly, + ) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def getworkerfunction( + self, + args, + options, + readonly=False, + filtervals=(None, None), + results=None, + uselist=False, + ): + """main get worker function + + :param args: command line arguments + :type args: list. + :param options: command line options + :type options: list. + :param line: command line input + :type line: string. + :param readonly: remove readonly properties + :type readonly: bool + :param filtervals: filter key value pair (Key,Val) + :type filtervals: tuple + :param results: current results collected + :type results: string. + :param uselist: use reserved properties list to filter results + :type uselist: boolean. + """ + nocontent = set() + instances = None + arg = None + + # For rest redfish compatibility of bios. + args = [args] if args and isinstance(args, six.string_types) else args + if self.rdmc.app.selector and "." not in self.rdmc.app.selector: + self.rdmc.app.selector = self.rdmc.app.selector + "." + args = ( + [ + "Attributes/" + arg + if self.rdmc.app.selector.lower().startswith("bios.") and "attributes" not in arg.lower() + else arg + for arg in args + ] + if args + else args + ) + if filtervals[0]: + instances = self.rdmc.app.select(selector=self.rdmc.app.selector, fltrvals=filtervals) + + try: + if "securityservice" in self.rdmc.app.selector.lower(): + url = "/redfish/v1/Managers/1/SecurityService/" + contents = self.rdmc.app.get_handler(url, service=True, silent=True).dict + security_contents = [] + if not args: + UI().print_out_human_readable(contents) + else: + attr = args[0] + contents_lower = {k.lower(): v for k, v in contents.items()} + security_contents.append({attr: contents_lower[attr.lower()]}) + if security_contents: + UI().print_out_human_readable(security_contents) + else: + contents = self.rdmc.app.getprops(props=args, remread=readonly, nocontent=nocontent, insts=instances) + uselist = False if readonly else uselist + except redfish.ris.rmc_helper.EmptyRaiseForEAFP: + contents = self.rdmc.app.getprops(props=args, nocontent=nocontent) + for ind, content in enumerate(contents): + if "bios." in self.rdmc.app.selector.lower() and "Attributes" in list(content.keys()): + content.update(content["Attributes"]) + del content["Attributes"] + contents[ind] = OrderedDict(sorted(list(content.items()), key=lambda x: x[0])) + if uselist: + if not contents: + raise NoContentsFoundForOperationError( + "No get contents found for entry: %s, Check if it " "Oem/Hpe Attribute" % args[0] + ) + contents = contents[0] + contents = { + key: val for key, val in contents.items() if key not in HARDCODEDLIST and "@odata" not in key.lower() + } + if results: + return contents + + contents = contents[0] if (isinstance(contents, list) and len(contents) == 1) else contents + + if options and options.json and contents: + UI().print_out_json(contents) + elif contents: + UI().print_out_human_readable(contents) + else: + try: + if nocontent or not any(next(iter(contents))): + raise Exception() + except Exception: + strtoprint = ", ".join(str(val) for val in nocontent) + if not strtoprint and arg: + strtoprint = arg + raise NoContentsFoundForOperationError("No get contents found for entry: %s" % strtoprint) + else: + raise NoContentsFoundForOperationError("No get contents found for " "selected type.") + if options.logout: + self.auxcommands["logout"].run("") + + def removereserved(self, entry): + """function to remove reserved properties + + :param entry: dictionary to remove reserved properties from + :type entry: dict. + """ + # convert to dict + new = json.loads(json.dumps(entry)) + + new_dict = {key: val for key, val in new.items() if "@odata" not in key.lower()} + + # for key, val in list(entry.items()): + # if key.lower() in HARDCODEDLIST or "@odata" in key.lower(): + # del entry[key] + # elif isinstance(val, list): + # for item in entry[key]: + # if isinstance(item, dict) and item: + # self.removereserved(item) + # if all([True if not test else False for test in entry[key]]): + # del entry[key] + # elif isinstance(val, dict): + # self.removereserved(val) + # if all([True if not test else False for test in entry[key]]): + # del entry[key] + + return new_dict + + def checktoprint(self, options, contents, nocontent, arg): + """function to decide what/how to print + :param options: list of options + :type options: list. + :param contents: dictionary value returned by getprops. + :type contents: dict. + :param nocontent: props not found are added to the list. + :type nocontent: list. + :param arg: string of args + :type arg: string + """ + if options and options.json and contents: + self.rdmc.ui.print_out_json(contents) + elif contents: + self.rdmc.ui.print_out_human_readable(contents) + else: + try: + if nocontent or not any(next(iter(contents))): + raise Exception() + except: + strtoprint = ", ".join(str(val) for val in nocontent) + if not strtoprint and arg: + strtoprint = arg + raise NoContentsFoundForOperationError("No get contents " "found for entry: %s" % strtoprint) + else: + raise NoContentsFoundForOperationError("No get contents " "found for selected type.") + + def collectandclear(self, contents, key, values): + """function to find and remove unneeded values from contents dictionary + :param contents: dictionary value returned by getprops + :type contents: dict. + :param key: string of keys + :type key: string. + :param values: list of values + :type values: list. + """ + clearcontent = contents[0][key] + if isinstance(clearcontent, dict): + keyslist = list(clearcontent.keys()) + else: + keyslist = [clearcontent] + clearedlist = keyslist + for arg in values: + for keys in keyslist: + if str(keys).lower() == str(arg).lower(): + clearedlist.remove(arg) + contents = iterateandclear(contents, clearedlist) + return contents + + def getvalidation(self, options): + """get method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--selector", + dest="selector", + help="Optionally include this flag to select a type to run" + " the current command on. Use this flag when you wish to" + " select a type without entering another command, or if you" + " wish to work with a type that is different from the one" + " you currently have selected.", + default=None, + ) + + customparser.add_argument( + "--filter", + dest="filter", + help="Optionally set a filter value for a filter attribute." + " This uses the provided filter for the currently selected" + " type. Note: Use this flag to narrow down your results. For" + " example, selecting a common type might return multiple" + " objects that are all of that type. If you want to modify" + " the properties of only one of those objects, use the filter" + " flag to narrow down results based on properties." + "\t\t\t\t\t Usage: --filter [ATTRIBUTE]=[VALUE]", + default=None, + ) + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + customparser.add_argument( + "--noreadonly", + dest="noreadonly", + action="store_true", + help="Optionally include this flag if you wish to only show" + " properties that are not read-only. This is useful to see what " + "is configurable with the selected type(s).", + default=False, + ) + customparser.add_argument( + "--refresh", + dest="ref", + action="store_true", + help="Optionally reload the data of selected type and clear " "patches from current selection.", + default=False, + ) diff --git a/ilorest/extensions/COMMANDS/InfoCommand.py b/ilorest/extensions/COMMANDS/InfoCommand.py new file mode 100644 index 0000000..3ef3bce --- /dev/null +++ b/ilorest/extensions/COMMANDS/InfoCommand.py @@ -0,0 +1,183 @@ +### +# Copyright 2016-2021 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 -*- +""" Info Command for RDMC """ + +try: + from rdmc_helper import ( + InfoMissingEntriesError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InfoMissingEntriesError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + +try: + from rdmc_helper import HARDCODEDLIST +except: + from ilorest.rdmc_helper import HARDCODEDLIST + + +class InfoCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "info", + "usage": None, + "description": "Displays detailed " + "information about a property within a selected type" + "\n\texample: info property\n\n\tDisplays detailed " + "information for several properties\n\twithin a selected " + "type\n\texample: info property property/sub-property property\n\n\t" + "Run without arguments to display properties \n\tthat " + "are available for info command\n\texample: info", + "summary": "Displays detailed information about a property within a selected type.", + "aliases": [], + "auxcommands": [], + } + + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, autotest=False, help_disp=False): + """Main info worker function + + :param line: command line input + :type line: string. + :param autotest: flag to determine if running automatictesting + :type autotest: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.infovalidation(options) + + if args: + for item in args: + if self.rdmc.app.selector.lower().startswith("bios.") and "attributes" not in item.lower(): + if not (item.lower() in HARDCODEDLIST or "@odata" in item.lower()): + item = "Attributes/" + item + + outdata = self.rdmc.app.info(props=item, dumpjson=options.json, latestschema=options.latestschema) + + if autotest: + return outdata + if outdata and options.json: + self.rdmc.ui.print_out_json(outdata) + elif outdata: + self.rdmc.ui.printer(outdata) + + if not outdata: + raise InfoMissingEntriesError("There are no valid " "entries for info in the current instance.") + else: + if len(args) > 1 and not item == args[-1]: + self.rdmc.ui.printer("\n************************************" "**************\n") + else: + results = set() + instances = self.rdmc.app.select() + for instance in instances: + currdict = instance.resp.dict + currdict = ( + currdict["Attributes"] + if instance.maj_type.startswith(self.rdmc.app.typepath.defs.biostype) + and currdict.get("Attributes", None) + else currdict + ) + results.update([key for key in currdict if key not in HARDCODEDLIST and "@odata" not in key.lower()]) + + if results and autotest: + return results + elif results: + self.rdmc.ui.printer("Info options:\n") + for item in results: + self.rdmc.ui.printer("%s\n" % item) + else: + raise InfoMissingEntriesError( + "No info items available for this selected type." " Try running with the --latestschema flag." + ) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def infovalidation(self, options): + """Info method validation function + + :param options: command line options + :type options: list. + """ + + if self.rdmc.opts.latestschema: + options.latestschema = True + + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--selector", + dest="selector", + help="Optionally include this flag to select a type to run" + " the current command on. Use this flag when you wish to" + " select a type without entering another command, or if you" + " wish to work with a type that is different from the one" + " you currently have selected.", + default=None, + ) + + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + customparser.add_argument( + "--latestschema", + dest="latestschema", + action="store_true", + help="Optionally use the latest schema instead of the one " + "requested by the file. Note: May cause errors in some data " + "retrieval due to difference in schema versions.", + default=None, + ) diff --git a/ilorest/extensions/COMMANDS/ListCommand.py b/ilorest/extensions/COMMANDS/ListCommand.py new file mode 100644 index 0000000..1a07208 --- /dev/null +++ b/ilorest/extensions/COMMANDS/ListCommand.py @@ -0,0 +1,168 @@ +### +# Copyright 2016-2021 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 -*- +""" List Command for RDMC """ + +try: + from rdmc_helper import ( + UI, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + UI, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class ListCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "list", + "usage": None, + "description": "Displays the current values of the " + "properties within\n\ta selected type including" + " reserved properties\n\texample: list\n\n\tNOTE: If " + "you wish to not list all the reserved properties\n\t " + " run the get command instead", + "summary": "Displays the current value(s) of a" + " property(ies) within a selected type including" + " reserved properties.", + "aliases": ["ls"], + "auxcommands": ["GetCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Wrapper function for main list function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.listvalidation(options) + + fvals = (None, None) + + if options.filter: + try: + if (str(options.filter)[0] == str(options.filter)[-1]) and str(options.filter).startswith(("'", '"')): + options.filter = options.filter[1:-1] + + (sel, val) = options.filter.split("=") + fvals = (sel.strip(), val.strip()) + except: + raise InvalidCommandLineError("Invalid filter" " parameter format [filter_attribute]=[filter_value]") + + if "securityservice" in options.selector.lower(): + url = "/redfish/v1/Managers/1/SecurityService/" + contents = self.rdmc.app.get_handler(url, service=True, silent=True).dict + security_contents = [] + + if not args: + UI().print_out_human_readable(contents) + else: + attr = args[0] + contents_lower = {k.lower(): v for k, v in contents.items()} + security_contents.append({attr: contents_lower[attr.lower()]}) + if security_contents: + UI().print_out_human_readable(security_contents) + else: + self.auxcommands["get"].getworkerfunction(args, options, filtervals=fvals, uselist=False) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def listvalidation(self, options): + """List data validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--selector", + dest="selector", + help="Optionally include this flag to select a type to run" + " the current command on. Use this flag when you wish to" + " select a type without entering another command, or if you" + " wish to work with a type that is different from the one" + " you currently have selected.", + default=None, + ) + + customparser.add_argument( + "--filter", + dest="filter", + help="Optionally set a filter value for a filter attribute." + " This uses the provided filter for the currently selected" + " type. Note: Use this flag to narrow down your results. For" + " example, selecting a common type might return multiple" + " objects that are all of that type. If you want to modify" + " the properties of only one of those objects, use the filter" + " flag to narrow down results based on properties." + "\t\t\t\t\t Usage: --filter [ATTRIBUTE]=[VALUE]", + default=None, + ) + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + customparser.add_argument( + "--refresh", + dest="ref", + action="store_true", + help="Optionally reload the data of selected type and clear " "patches from current selection.", + default=False, + ) diff --git a/ilorest/extensions/COMMANDS/LoadCommand.py b/ilorest/extensions/COMMANDS/LoadCommand.py new file mode 100644 index 0000000..3ecf6c7 --- /dev/null +++ b/ilorest/extensions/COMMANDS/LoadCommand.py @@ -0,0 +1,562 @@ +### +# Copyright 2016-2023 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 -*- +""" Load Command for RDMC """ + +import json +import os +import shlex +import subprocess +import sys +from datetime import datetime + +from six.moves import queue + +import redfish.ris +from redfish.ris.rmc_helper import LoadSkipSettingError + +try: + from rdmc_helper import ( + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + InvalidFileInputError, + InvalidMSCfileInputError, + MultipleServerConfigError, + NoChangesFoundOrMadeError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + InvalidFileInputError, + InvalidMSCfileInputError, + MultipleServerConfigError, + NoChangesFoundOrMadeError, + ReturnCodes, + ) + +try: + from rdmc_helper import HARDCODEDLIST +except: + from ilorest.rdmc_helper import HARDCODEDLIST + +# default file name +__filename__ = "ilorest.json" + + +class LoadCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "load", + "usage": None, + "description": "Run to load the default configuration" + " file\n\texample: load\n\n\tLoad configuration file from a " + "different file\n\tif any property values have changed, the " + "changes are committed and the user is logged out of the server" + "\n\n\texample: load -f output.json\n\n\tLoad configurations to " + "multiple servers\n\texample: load -m mpfilename.txt -f output." + "json\n\n\tNote: multiple server file format (1 server per new " + "line)\n\t--url -u admin -p password\n\t--url" + " -u admin -p password\n\t--url -u admin -p password", + "summary": "Loads the server configuration settings from a file.", + "aliases": [], + "auxcommands": ["CommitCommand", "SelectCommand", "RawPatchCommand"], + } + self.filenames = None + self.mpfilename = None + self.queue = queue.Queue() + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def securebootremovereadonly(self, tmp): + templist = [ + "SecureBootCurrentBoot", + "@odata.etag", + "Id", + "AutoNeg", + "FQDN", + "FullDuplex", + "HostName", + "IPv6Addresses", + "IPv6DefaultGateway", + "LinkStatus", + "MaxIPv6StaticAddresses", + "Name", + "NameServers", + "PermanentMACAddress", + "ConfigurationSettings", + "InterfaceType", + "NICSupportsIPv6", + "DomainName", + "IPV6", + "DNSServers", + "MACAddress", + "VLAN", + "SpeedMbps", + "DateTime" + ] + remove_data = self.rdmc.app.removereadonlyprops(tmp, False, True, templist) + return remove_data + + def run(self, line, help_disp=False): + """Main load worker function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.loadvalidation(options) + returnvalue = False + + if options.mpfilename: + self.rdmc.ui.printer("Loading configuration for multiple servers...\n") + else: + self.rdmc.ui.printer("Loading configuration...\n") + + for files in self.filenames: + if not os.path.isfile(files): + raise InvalidFileInputError( + "File '%s' doesn't exist. Please create file by running save command." % files + ) + if options.encryption: + with open(files, "rb") as myfile: + data = myfile.read() + data = Encryption().decrypt_file(data, options.encryption) + loadcontents = json.loads(data) + else: + try: + with open(files, "r") as myfile: + loadcontents = json.load(myfile) + except: + raise InvalidFileFormattingError("Invalid file formatting found in file %s" % files) + + if options.mpfilename: + mfile = options.mpfilename + outputdir = None + + if options.outdirectory: + outputdir = options.outdirectory + + if self.runmpfunc(mpfile=mfile, lfile=files, outputdir=outputdir): + return ReturnCodes.SUCCESS + else: + raise MultipleServerConfigError("One or more servers failed to load given configuration.") + + results = False + eth_conf = False + validation_errs = [] + + for loadcontent in loadcontents: + for content, loaddict in loadcontent.items(): + inputlist = list() + + if content == "Comments": + continue + + if "EthernetInterface" in content: + if options.force_network_config: + import platform + import tempfile + + patchpath = "/redfish/v1/Managers/1/EthernetInterfaces/1/" + payload = loadcontent[content][patchpath] + remove_odata = self.securebootremovereadonly(payload) + payload.update(remove_odata) + if "StaticNameServers" in payload: + payload["StaticNameServers"] = [item for item in payload["StaticNameServers"] if + item != "::"] + tempdir = "/tmp" if platform.system() == "Darwin" else tempfile.gettempdir() + temp_file = os.path.join(tempdir, "temp_patch.json") + out_file = open(temp_file, "w") + patch_payload = {patchpath: payload} + json.dump(patch_payload, out_file, indent=6) + out_file.close() + self.auxcommands["rawpatch"].run(temp_file + " --service") + os.remove(temp_file) + eth_conf = True + continue + else: + self.rdmc.ui.printer("Skipping network configurations as" + " --force_network_config is not included.\n") + continue + + inputlist.append(content) + if options.biospassword: + inputlist.extend(["--biospassword", options.biospassword]) + + self.auxcommands["select"].selectfunction(inputlist) + if self.rdmc.app.selector.lower() not in content.lower(): + raise InvalidCommandLineError("Selector not found.\n") + + try: + for _, items in loaddict.items(): + remove_odata = self.securebootremovereadonly(items) + items.update(remove_odata) + try: + if self.rdmc.app.loadset( + seldict=items, + latestschema=options.latestschema, + uniqueoverride=options.uniqueoverride, + ): + results = True + except LoadSkipSettingError: + returnvalue = True + results = True + except: + raise + except redfish.ris.ValidationError as excp: + errs = excp.get_errors() + validation_errs.append({self.rdmc.app.selector: errs}) + except: + raise + + try: + if results: + self.auxcommands["commit"].commitfunction(options=options) + reboot_needed = False + for n in inputlist: + n = n.lower() + if "bios" in n or "boot" in n or "iscsi" in n: + reboot_needed = True + if reboot_needed: + self.rdmc.ui.printer("Reboot is required for settings to take effect.\n") + except NoChangesFoundOrMadeError as excp: + if returnvalue: + pass + else: + raise excp + + if validation_errs: + for validation_err in validation_errs: + for err_type in validation_err: + self.rdmc.ui.error("Validation error(s) in type %s:\n" % err_type) + for err in validation_err[err_type]: + if isinstance(err, redfish.ris.RegistryValidationError): + self.rdmc.ui.error(err.message) + try: + if err.reg: + self.rdmc.ui.error(str(err.sel)) + except: + pass + raise redfish.ris.ValidationError() + + if not results: + if eth_conf: + continue + else: + self.rdmc.ui.printer("No differences found from current configuration.\n") + + # Return code + if returnvalue: + return ReturnCodes.LOAD_SKIP_SETTING_ERROR + + return ReturnCodes.SUCCESS + + def loadvalidation(self, options): + """Load method validation function + + :param options: command line options + :type options: list. + """ + + if self.rdmc.opts.latestschema: + options.latestschema = True + + try: + self.cmdbase.login_select_validation(self, options) + except Exception: + if options.mpfilename: + pass + else: + raise + + # filename validations and checks + if options.filename: + self.filenames = options.filename + elif self.rdmc.config: + if self.rdmc.config.defaultloadfilename: + self.filenames = [self.rdmc.config.defaultloadfilename] + + if not self.filenames: + self.filenames = [__filename__] + + def verify_file(self, filedata, inputfile): + """Function used to handle oddly named files and convert to JSON + + :param filedata: input file data + :type filedata: string. + :param inputfile: current input file + :type inputfile: string. + """ + try: + tempholder = json.loads(filedata) + return tempholder + except: + raise InvalidFileFormattingError("Invalid file formatting found in file %s" % inputfile) + + def get_current_selector(self, path=None): + """Returns current selected content minus hard coded list + + :param path: current path + :type path: string. + """ + contents = self.rdmc.app.monolith.path[path] + + if not contents: + contents = list() + + for content in contents: + for k in list(content.keys()): + if k.lower() in HARDCODEDLIST or "@odata" in k.lower(): + del content[k] + + return contents + + def runmpfunc(self, mpfile=None, lfile=None, outputdir=None): + """Main worker function for multi file command + + :param mpfile: configuration file + :type mpfile: string. + :param lfile: custom file name + :type lfile: string. + :param outputdir: custom output directory + :type outputdir: string. + """ + # self.logoutobj.run("") + data = self.validatempfile(mpfile=mpfile, lfile=lfile) + + if not data: + return False + + processes = [] + finalreturncode = True + outputform = "%Y-%m-%d-%H-%M-%S" + + if outputdir: + if outputdir.endswith(('"', "'")) and outputdir.startswith(('"', "'")): + outputdir = outputdir[1:-1] + + if not os.path.isdir(outputdir): + self.rdmc.ui.error("The give output folder path does not exist.\n") + raise InvalidCommandLineErrorOPTS("") + + dirpath = outputdir + else: + dirpath = os.getcwd() + + dirname = "%s_%s" % (datetime.now().strftime(outputform), "MSClogs") + createdir = os.path.join(dirpath, dirname) + os.mkdir(createdir) + + oofile = open(os.path.join(createdir, "CompleteOutputfile.txt"), "w+") + self.rdmc.ui.printer("Create multiple processes to load configuration " "concurrently to all servers...\n") + + while True: + if not self.queue.empty(): + line = self.queue.get() + else: + break + + finput = "\n" + "Output for " + line[line.index("--url") + 1] + ": \n\n" + urlvar = line[line.index("--url") + 1] + + if "python" in os.path.basename(sys.executable.lower()): + # If we are running from source we have to add the python file to the command + listargforsubprocess = [sys.executable, sys.argv[0]] + line + else: + listargforsubprocess = [sys.executable] + line + + if os.name != "nt": + listargforsubprocess = " ".join(listargforsubprocess) + + urlfilename = urlvar.split("//")[-1] + logfile = open(os.path.join(createdir, urlfilename + ".txt"), "w+") + pinput = subprocess.Popen(listargforsubprocess, shell=True, stdout=logfile, stderr=logfile) + + processes.append((pinput, finput, urlfilename, logfile)) + + for pinput, finput, urlfilename, logfile in processes: + pinput.wait() + returncode = pinput.returncode + finalreturncode = finalreturncode and not returncode + + logfile.close() + logfile = open(os.path.join(createdir, urlfilename + ".txt"), "r+") + oofile.write(finput + str(logfile.read())) + oofile.write("-x+x-" * 16) + logfile.close() + + if returncode == 0: + self.rdmc.ui.printer("Loading Configuration for {} : SUCCESS\n".format(urlfilename)) + else: + self.rdmc.ui.error("Loading Configuration for {} : FAILED\n".format(urlfilename)) + self.rdmc.ui.error( + "ILOREST return code : {}.\nFor more details please check " + "{}.txt under {} directory.\n".format(returncode, urlfilename, createdir) + ) + + oofile.close() + + if finalreturncode: + self.rdmc.ui.printer("All servers have been successfully configured.\n") + + return finalreturncode + + def validatempfile(self, mpfile=None, lfile=None): + """Validate temporary file + + :param mpfile: configuration file + :type mpfile: string. + :param lfile: custom file name + :type lfile: string. + """ + self.rdmc.ui.printer("Checking given server information...\n") + + if not mpfile: + return False + + if not os.path.isfile(mpfile): + raise InvalidFileInputError( + "File '%s' doesn't exist, please " "create file by running save command." % mpfile + ) + + try: + with open(mpfile, "r") as myfile: + data = list() + cmdtorun = ["load"] + cmdargs = ["-f", str(lfile)] + globalargs = ["-v", "--nocache"] + + while True: + line = myfile.readline() + + if not line: + break + + if line.endswith(os.linesep): + line.rstrip(os.linesep) + + args = shlex.split(line, posix=False) + + if len(args) < 5: + self.rdmc.ui.error("Incomplete data in input file: {}\n".format(line)) + raise InvalidMSCfileInputError("Please verify the " "contents of the %s file" % mpfile) + else: + linelist = globalargs + cmdtorun + args + cmdargs + line = str(line).replace("\n", "") + self.queue.put(linelist) + data.append(linelist) + except Exception as excp: + raise excp + + if data: + return data + + return False + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-f", + "--filename", + dest="filename", + help="Use this flag if you wish to use a different" + " filename than the default one. The default filename is" + " %s." % __filename__, + action="append", + default=None, + ) + customparser.add_argument( + "-m", + "--multiprocessing", + dest="mpfilename", + help="""use the provided filename to obtain data""", + default=None, + ) + customparser.add_argument( + "--outputdirectory", + dest="outdirectory", + help="""use the provided directory to output data for multiple server configuration""", + default=None, + ) + customparser.add_argument( + "--latestschema", + dest="latestschema", + action="store_true", + help="Optionally use the latest schema instead of the one " + "requested by the file. Note: May cause errors in some data " + "retrieval due to difference in schema versions.", + default=None, + ) + customparser.add_argument( + "--uniqueoverride", + dest="uniqueoverride", + action="store_true", + help="Override the measures stopping the tool from writing " "over items that are system unique.", + default=False, + ) + customparser.add_argument( + "--encryption", + dest="encryption", + help="Optionally include this flag to encrypt/decrypt a file " "using the key provided.", + default=None, + ) + customparser.add_argument( + "--reboot", + dest="reboot", + help="Use this flag to perform a reboot command function after" + " completion of operations. For help with parameters and" + " descriptions regarding the reboot flag, run help reboot.", + default=None, + ) + customparser.add_argument( + "--force_network_config", + dest="force_network_config", + help="Use this flag to force set network configuration." + "Network settings will be skipped if the flag is not included.", + action="store_true", + default=None, + ) + diff --git a/ilorest/extensions/COMMANDS/LoginCommand.py b/ilorest/extensions/COMMANDS/LoginCommand.py new file mode 100644 index 0000000..290fe21 --- /dev/null +++ b/ilorest/extensions/COMMANDS/LoginCommand.py @@ -0,0 +1,350 @@ +### +# Copyright 2016-2021 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 -*- +""" Login Command for RDMC """ + +import getpass +import os +import socket +import redfish.ris + +try: + from rdmc_helper import ( + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + PathUnavailableError, + ReturnCodes, + UsernamePasswordRequiredError, + ) +except ModuleNotFoundError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + PathUnavailableError, + Encryption, + UsernamePasswordRequiredError, + ) + +from redfish.rest.v1 import ServerDownOrUnreachableError + + +class LoginCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "login", + "usage": None, + "description": "To login remotely run using iLO url and iLO credentials" + "\n\texample: login -u " + "-p \n\n\tTo login on a local server run without " + "arguments\n\texample: login" + "\n\n\tTo login through VNIC run using --force_vnic and iLO credentials " + "\n\texample: login --force_vnic -u -p " + "\n\nLogin using OTP can be done in 2 ways." + "\n\n\t To login implicitly, use the tag --wait_for_otp." + "\n\t\texample: login -u -p --wait_for_otp" + "\n\n\n\t To login explicitly, use the tag -o/--otp and enter OTP after." + "\n\t\texample: login -u -p -o " + "\n\n\tNOTE: A [URL] can be specified with " + "an IPv4, IPv6, or hostname address.", + "summary": "Connects to a server, establishes a secure session," " and discovers data from iLO.", + "aliases": [], + "auxcommands": ["LogoutCommand"], + "cert_data": {}, + } + self.cmdbase = None + self.rdmc = None + self.url = None + self.username = None + self.password = None + self.sessionid = None + self.biospassword = None + self.auxcommands = dict() + self.cert_data = dict() + self.login_otp = None + + def run(self, line, help_disp=False): + """wrapper function for main login function + + :param line: command line input + :type line: string. + :param help_disp: flag to determine to display or not + :type help_disp: boolean + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + self.loginfunction(line) + + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + + if not self.rdmc.app.monolith._visited_urls: + self.auxcommands["logout"].run("") + raise PathUnavailableError("The path specified by the --path flag is unavailable.") + except Exception: + raise + + # Return code + return ReturnCodes.SUCCESS + + def perform_login(self, options, skipbuild, user_ca_cert_data): + self.rdmc.app.login( + username=self.username, + password=self.password, + sessionid=self.sessionid, + base_url=self.url, + path=options.path, + skipbuild=skipbuild, + includelogs=options.includelogs, + biospassword=self.biospassword, + is_redfish=self.rdmc.opts.is_redfish, + proxy=self.rdmc.opts.proxy, + user_ca_cert_data=user_ca_cert_data, + json_out=self.rdmc.json, + login_otp=self.login_otp, + log_dir=self.rdmc.log_dir, + ) + + def loginfunction(self, line, skipbuild=None, json_out=False): + """Main worker function for login class + + :param line: entered command line + :type line: list. + :param skipbuild: flag to determine if monolith should be build + :type skipbuild: boolean. + :param json_out: flag to determine if json output neededd + :type skipbuild: boolean. + """ + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineError("Invalid command line arguments") + + self.loginvalidation(options, args) + + # if proxy server provided in command line as --useproxy, it will be used, + # otherwise it will the environment variable setting. + # else proxy will be set as None. + if self.rdmc.opts.proxy: + _ = self.rdmc.opts.proxy + elif "https_proxy" in os.environ and os.environ["https_proxy"]: + _ = os.environ["https_proxy"] + elif "http_proxy" in os.environ and os.environ["http_proxy"]: + _ = os.environ["http_proxy"] + else: + _ = self.rdmc.config.proxy + + no_bundle = False + + if getattr(options, "ca_cert_bundle", False): + user_ca_cert_data = {"ca_certs": options.ca_cert_bundle} + else: + user_ca_cert_data = {} + if getattr(options, "user_certificate", False): + no_bundle = True + user_ca_cert_data.update({"cert_file": options.user_certificate}) + if getattr(options, "user_root_ca_key", False): + no_bundle = True + user_ca_cert_data.update({"key_file": options.user_root_ca_key}) + if getattr(options, "user_root_ca_password", False): + no_bundle = True + user_ca_cert_data.update({"key_password": options.user_root_ca_password}) + + if not no_bundle: + if hasattr(user_ca_cert_data, "ca_certs"): + user_ca_cert_data.pop("ca_certs") + + try: + if getattr(options, "force_vnic", False): + self.rdmc.ui.printer("\nAttempt to login with Vnic...\n") + try: + sock = socket.create_connection((args[0], 443)) + if sock: + sock.close + except: + pass + + self.sessionid = options.sessionid + self.login_otp = options.login_otp + + if "blobstore" in self.url and (options.waitforOTP or options.login_otp): + options.waitforOTP = None + options.login_otp = None + self.rdmc.ui.printer("Warning: For local inband mode, TFA is not supported, options --wait_for_otp " + "and --otp will be ignored\n") + + if options.waitforOTP: + try: + self.perform_login(options, skipbuild, user_ca_cert_data) + except redfish.rest.connections.OneTimePasscodeError: + self.rdmc.ui.printer("One Time Passcode Sent to registered email.\n") + ans = input("Enter OTP: ") + self.login_otp = ans + self.perform_login(options, skipbuild, user_ca_cert_data) + else: + self.perform_login(options, skipbuild, user_ca_cert_data) + except ServerDownOrUnreachableError as excp: + self.rdmc.ui.printer("The following error occurred during login: '%s'\n" % str(excp.__class__.__name__)) + + self.username = None + self.password = None + + # Warning for cache enabled, since we save session in plain text + if not self.rdmc.encoding: + self.rdmc.ui.warn("Cache is activated. Session keys are stored in plaintext.") + + if self.rdmc.opts.debug: + self.rdmc.ui.warn("Logger is activated. Logging is stored in plaintext.") + + if options.selector: + try: + self.rdmc.app.select(selector=options.selector) + + if self.rdmc.opts.verbose: + self.rdmc.ui.printer(("Selected option: '%s'\n" % options.selector)) + except Exception as excp: + raise redfish.ris.InstanceNotFoundError(excp) + + def loginvalidation(self, options, args): + """Login helper function for login validations + + :param options: command line options + :type options: list. + :param args: command line arguments + :type args: list. + """ + # Fill user name/password from config file + if not options.user: + options.user = self.rdmc.config.username + if not options.password: + options.password = self.rdmc.config.password + if not hasattr(options, "user_certificate"): + options.user_certificate = self.rdmc.config.user_cert + if not hasattr(options, "user_root_ca_key"): + options.user_root_ca_key = self.rdmc.config.user_root_ca_key + if not hasattr(options, "user_root_ca_password"): + options.user_root_ca_password = self.rdmc.config.user_root_ca_password + + if ( + options.user + and not options.password + and ( + not hasattr(options, "user_certificate") + or not hasattr(options, "user_root_ca_key") + or hasattr(options, "user_root_ca_password") + ) + ): + # Option for interactive entry of password + tempinput = getpass.getpass().rstrip() + if tempinput: + options.password = tempinput + else: + raise InvalidCommandLineError("Empty or invalid password was entered.") + + if options.user: + self.username = options.user + + if options.password: + self.password = options.password + + if options.encode: + self.username = Encryption.decode_credentials(self.username).decode("utf-8") + self.password = Encryption.decode_credentials(self.password).decode("utf-8") + + if options.biospassword: + self.biospassword = options.biospassword + + # Assignment of url in case no url is entered + if getattr(options, "force_vnic", False): + if not (getattr(options, "ca_cert_bundle", False) or getattr(options, "user_certificate", False)): + if not (self.username and self.password) and not options.sessionid: + raise UsernamePasswordRequiredError("Please provide credentials to login with VNIC") + self.url = "https://16.1.15.1" + else: + self.url = "blobstore://." + + if args: + # Any argument should be treated as an URL + self.url = args[0] + + # Verify that URL is properly formatted for https:// + if "https://" not in self.url: + self.url = "https://" + self.url + + if not ( + hasattr(options, "user_certificate") + or hasattr(options, "user_root_ca_key") + or hasattr(options, "user_root_ca_password") + ): + if not (options.username and options.password): + raise InvalidCommandLineError("Empty username or password was entered.") + else: + # Check to see if there is a URL in config file + if self.rdmc.config.url: + self.url = self.rdmc.config.url + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + + def remove_argument(parser, arg): + for action in parser._actions: + opts = action.option_strings + if (opts and opts[0] == arg) or action.dest == arg: + parser._remove_action(action) + break + + for action in parser._action_groups: + for group_action in action._group_actions: + opts = group_action.option_strings + if (opts and opts[0] == arg) or group_action.dest == arg: + action._group_actions.remove(group_action) + return + + if not customparser: + return + + customparser.add_argument( + "--wait_for_otp", + dest="waitforOTP", + help="Optionally include this flag to implicitly wait for OTP.", + action="store_true", + default=None, + ) + self.cmdbase.add_login_arguments_group(customparser) + remove_argument(customparser, "url") + customparser.add_argument( + "--selector", + dest="selector", + help="Optionally include this flag to select a type to run" + " the current command on. Use this flag when you wish to" + " select a type without entering another command, or if you" + " wish to work with a type that is different from the one" + " you currently have selected.", + default=None, + ) diff --git a/ilorest/extensions/COMMANDS/LogoutCommand.py b/ilorest/extensions/COMMANDS/LogoutCommand.py new file mode 100644 index 0000000..30c527f --- /dev/null +++ b/ilorest/extensions/COMMANDS/LogoutCommand.py @@ -0,0 +1,79 @@ +### +# Copyright 2016-2021 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 -*- +""" Logout Command for RDMC """ + +try: + from rdmc_helper import InvalidCommandLineErrorOPTS, ReturnCodes +except ImportError: + from ilorest.rdmc_helper import InvalidCommandLineErrorOPTS, ReturnCodes + + +class LogoutCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "logout", + "usage": None, + "description": "Run to end the current session and disconnect" " from the server\n\tExample: logout", + "summary": "Ends the current session and disconnects from the server.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + + def logoutfunction(self, line): + """Main logout worker function + + :param line: command line input + :type line: string. + """ + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.rdmc.app.logout("") + + def run(self, line, help_disp=False): + """Wrapper function for main logout function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + self.rdmc.ui.printer("Logging session out.\n") + self.logoutfunction(line) + + # Return code + return ReturnCodes.SUCCESS + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return diff --git a/ilorest/extensions/COMMANDS/PendingChangesCommand.py b/ilorest/extensions/COMMANDS/PendingChangesCommand.py new file mode 100644 index 0000000..b91457a --- /dev/null +++ b/ilorest/extensions/COMMANDS/PendingChangesCommand.py @@ -0,0 +1,201 @@ +### +# Copyright 2016-2021 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 -*- +""" Results Command for rdmc """ + +import copy +import json +import sys + +import jsondiff + +try: + from rdmc_helper import HARDCODEDLIST +except: + from ilorest.rdmc_helper import HARDCODEDLIST +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class PendingChangesCommand: + """PendingChanges class command""" + + def __init__(self): + self.ident = { + "name": "pending", + "usage": None, + "description": "Run to show pending committed changes " + "that will be applied after a reboot.\n\texample: pending", + "summary": "Show the pending changes that will be applied on reboot.", + "aliases": [], + "auxcommands": [], + } + + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Show pending changes of settings objects + + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if args: + raise InvalidCommandLineError("Pending command does not take any arguments.") + self.pendingvalidation(options) + + self.pendingfunction() + sys.stdout.write("\n") + self.cmdbase.logout_routine(self, options) + return ReturnCodes.SUCCESS + + def pendingfunction(self): + """Main pending command worker function""" + settingsuri = [] + ignorekeys = ["@odata.id", "@odata.etag", "@redfish.settings", "oem"] + ignoreuri = [str("hpsut*")] + ignorekeys.extend(HARDCODEDLIST) + + resourcedir = self.rdmc.app.get_handler(self.rdmc.app.monolith._resourcedir, service=True, silent=True) + + for resource in resourcedir.dict["Instances"]: + if (resource["@odata.id"].split("/").__len__() - 1) > 4: + splitstr = resource["@odata.id"].split("/")[5] + for element in ignoreuri: + if "/settings" in resource["@odata.id"] and not self.wildcard_str_match(element, splitstr): + settingsuri.append(resource["@odata.id"]) + + self.rdmc.ui.printer("Current Pending Changes:\n") + + for uri in settingsuri: + diffprint = {} + baseuri = uri.split("settings")[0] + + base = self.rdmc.app.get_handler(baseuri, service=True, silent=True) + settings = self.rdmc.app.get_handler(uri, service=True, silent=True) + + typestring = self.rdmc.app.monolith.typepath.defs.typestring + currenttype = ".".join(base.dict[typestring].split("#")[-1].split(".")[:-1]) + + differences = json.loads(jsondiff.diff(base.dict, settings.dict, syntax="symmetric", dump=True)) + + diffprint = self.recursdict(differences, ignorekeys) + + self.rdmc.ui.printer("\n%s:" % currenttype) + if not diffprint: + self.rdmc.ui.printer("\nNo pending changes found.\n") + else: + self.rdmc.ui.pretty_human_readable(diffprint) + + def wildcard_str_match(self, first, second): + """ + Recursive function for determining match between two strings. Accounts + for wildcard characters + + :param first: comparison string (may contain '*' or '?' for wildcards) + :param type: str (unicode) + :param second: string value to be compared (must not contain '*' or '?') + :param type: str (unicode) + """ + + if not first and not second: + return True + if len(first) > 1 and first[0] == "*" and not second: + return False + if (len(first) > 1 and first[0] == "?") or (first and second and first[0] == second[0]): + return self.wildcard_str_match(first[1:], second[1:]) + if first and first[0] == "*": + return self.wildcard_str_match(first[1:], second) or self.wildcard_str_match(first, second[1:]) + + return False + + def recursdict(self, diff, ignorekeys): + """Recursively get dict ready for printing + + :param diff: diff dict + :type options: dict. + """ + diffprint = copy.deepcopy(diff) + for item in diff: + if item.lower() in ignorekeys: + diffprint.pop(item) + elif item == "$delete": + for ditem in diff[item]: + if isinstance(diff[item], list): + continue + if isinstance(diff[item][ditem], dict): + diffprint[item].pop(ditem) + if ditem.lower() in ignorekeys or ditem.isdigit(): + continue + else: + diffprint.update({"removed": ditem}) + diffprint.pop(item) + elif item == "$insert": + for ditem in diff[item]: + del diffprint[item][diffprint[item].index(ditem)] + diffprint.update({"changed index position": ditem[1]}) + diffprint.pop(item) + elif isinstance(diff[item], dict): + diffprint[item] = self.recursdict(diff[item], ignorekeys) + + elif isinstance(diff[item], list): + diffprint.update({item: {"Current": diff[item][0], "Pending": diff[item][1]}}) + else: + continue + + return diffprint + + def pendingvalidation(self, options): + """Pending method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/COMMANDS/REQUIREDCOMMANDS/ExitCommand.py b/ilorest/extensions/COMMANDS/REQUIREDCOMMANDS/ExitCommand.py new file mode 100644 index 0000000..8151260 --- /dev/null +++ b/ilorest/extensions/COMMANDS/REQUIREDCOMMANDS/ExitCommand.py @@ -0,0 +1,80 @@ +### +# Copyright 2016-2021 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 -*- +""" Exit Command for rdmc """ + +import sys + +try: + from rdmc_helper import InvalidCommandLineErrorOPTS, ReturnCodes +except ImportError: + from ilorest.rdmc_helper import InvalidCommandLineErrorOPTS, ReturnCodes + + +class ExitCommand: + """Exit class to handle exiting from interactive mode""" + + def __init__(self): + self.ident = { + "name": "exit", + "usage": None, + "description": "Run to exit from the interactive shell\n\texample: exit", + "summary": "Exits from the interactive shell.", + "aliases": ["quit"], + "auxcommands": ["LogoutCommand"], + } + # self.rdmc = rdmcObj + # self.logoutobj = rdmcObj.commands_dict["LogoutCommand"](rdmcObj) + + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """If an argument is present, print help else exit + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (_, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if not args or not line: + self.auxcommands["logout"].run("") + + # System exit + sys.exit(ReturnCodes.SUCCESS) + else: + self.rdmc.ui.error("Exit command does not take any parameters.\n") + raise InvalidCommandLineErrorOPTS("Invalid command line arguments.") + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return diff --git a/ilorest/extensions/COMMANDS/REQUIREDCOMMANDS/HelpCommand.py b/ilorest/extensions/COMMANDS/REQUIREDCOMMANDS/HelpCommand.py new file mode 100644 index 0000000..b941877 --- /dev/null +++ b/ilorest/extensions/COMMANDS/REQUIREDCOMMANDS/HelpCommand.py @@ -0,0 +1,113 @@ +### +# Copyright 2016-2021 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 -*- +""" Help Command for RDMC """ + +try: + from rdmc_base_classes import RdmcCommandBase, RdmcOptionParser +except ImportError: + from ilorest.rdmc_base_classes import RdmcCommandBase, RdmcOptionParser +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class HelpCommand(RdmcCommandBase): + """Constructor""" + + def __init__(self): + self.ident = { + "name": "help", + "usage": None, + "description": "help [COMMAND]\n\tFor more detailed command descriptions" + " use the help command feature\n\texample: help login\n", + "summary": "Displays command line syntax and help menus for individual commands." " Example: help login\n", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Wrapper function for help main function + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if not args or not line: + RdmcOptionParser().print_help() + if self.rdmc: + cmddict = self.rdmc.get_commands() + sorted_keys = sorted(list(cmddict.keys())) + + for key in sorted_keys: + if key[0] == "_": + continue + else: + self.rdmc.ui.printer("\n%s\n" % key) + for cmd in cmddict[key]: + self.rdmc.ui.printer( + "%-25s - %s\n" + % ( + self.rdmc.commands_dict[cmd].ident["name"], + self.rdmc.commands_dict[cmd].ident["summary"], + ) + ) + else: + if self.rdmc: + cmddict = self.rdmc.get_commands() + sorted_keys = list(cmddict.keys()) + + for key in sorted_keys: + for cmd in cmddict[key]: + cmd_s = cmd.split("Command") + cmd_s = cmd_s[0] + if args[0].lower() == cmd_s.lower(): + self.rdmc.ui.printer(self.rdmc.commands_dict[cmd].ident["description"] + "\n") + return ReturnCodes.SUCCESS + raise InvalidCommandLineError("Command '%s' not found." % args[0]) + # Return code + return ReturnCodes.SUCCESS + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return diff --git a/ilorest/extensions/COMMANDS/REQUIREDCOMMANDS/__init__.py b/ilorest/extensions/COMMANDS/REQUIREDCOMMANDS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/extensions/COMMANDS/ResultsCommand.py b/ilorest/extensions/COMMANDS/ResultsCommand.py new file mode 100644 index 0000000..1e61f0f --- /dev/null +++ b/ilorest/extensions/COMMANDS/ResultsCommand.py @@ -0,0 +1,156 @@ +### +# Copyright 2016-2021 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 -*- +""" Results Command for rdmc """ + +from redfish.ris.resp_handler import ResponseHandler +from redfish.ris.rmc_helper import EmptyRaiseForEAFP + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class ResultsCommand: + """Monolith class command""" + + def __init__(self): + self.ident = { + "name": "results", + "usage": None, + "description": "Run to show the results of the last" " changes after a server reboot.\n\texample: results", + "summary": "Show the results of changes which require a server reboot.", + "aliases": [], + "auxcommands": ["LoginCommand", "SelectCommand"], + } + + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Gather results of latest BIOS change + + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if args: + raise InvalidCommandLineError("Results command does not take any arguments.") + self.resultsvalidation(options) + results = {} + if self.rdmc.app.typepath.defs.biospath[-1] == "/": + iscsipath = self.rdmc.app.typepath.defs.biospath + "iScsi/" + bootpath = self.rdmc.app.typepath.defs.biospath + "Boot/" + else: + iscsipath = self.rdmc.app.typepath.defs.biospath + "/iScsi" + bootpath = self.rdmc.app.typepath.defs.biospath + "/Boot" + + try: + self.auxcommands["select"].selectfunction("SmartStorageConfig") + smartarray = self.rdmc.app.getprops() + sapaths = [path["@odata.id"].split("settings")[0] for path in smartarray] + except: + sapaths = None + + biosresults = self.rdmc.app.get_handler(self.rdmc.app.typepath.defs.biospath, service=True, silent=True) + iscsiresults = self.rdmc.app.get_handler(iscsipath, service=True, silent=True) + bootsresults = self.rdmc.app.get_handler(bootpath, service=True, silent=True) + saresults = [] + if sapaths: + saresults = [self.rdmc.app.get_handler(path, service=True, silent=True) for path in sapaths] + try: + results.update({"Bios:": biosresults.dict[self.rdmc.app.typepath.defs.biossettingsstring]["Messages"]}) + except Exception: + results.update({"Bios:": None}) + + try: + results.update({"Iscsi:": iscsiresults.dict[self.rdmc.app.typepath.defs.biossettingsstring]["Messages"]}) + except: + results.update({"Iscsi:": None}) + + try: + results.update({"Boot:": bootsresults.dict[self.rdmc.app.typepath.defs.biossettingsstring]["Messages"]}) + except: + results.update({"Boot:": None}) + try: + for result in saresults: + loc = "SmartArray" + if saresults.index(result) > 0: + loc += " %d:" % saresults.index(result) + else: + loc += ":" + results.update({loc: result.dict[self.rdmc.app.typepath.defs.biossettingsstring]["Messages"]}) + except: + results.update({"SmartArray:": None}) + + self.rdmc.ui.printer("Results of the previous reboot changes:\n\n") + + for result in results: + self.rdmc.ui.printer("%s\n" % result) + try: + for msg in results[result]: + _ = ResponseHandler( + self.rdmc.app.validationmanager, + self.rdmc.app.typepath.defs.messageregistrytype, + ).message_handler(response_data=msg, message_text="", verbosity=0, dl_reg=False) + pass + except EmptyRaiseForEAFP as exp: + raise EmptyRaiseForEAFP(exp) + except Exception: + self.rdmc.ui.error("No messages found for %s.\n" % result[:-1]) + + self.cmdbase.logout_routine(self, options) + return ReturnCodes.SUCCESS + + def resultsvalidation(self, options): + """Results method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/COMMANDS/SaveCommand.py b/ilorest/extensions/COMMANDS/SaveCommand.py new file mode 100644 index 0000000..16354e5 --- /dev/null +++ b/ilorest/extensions/COMMANDS/SaveCommand.py @@ -0,0 +1,347 @@ +### +# Copyright 2016-2021 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 -*- +""" Save Command for RDMC """ + +import json +from collections import OrderedDict + +import redfish.ris + +try: + from rdmc_helper import ( + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + ReturnCodes, + iLORisCorruptionError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + ReturnCodes, + iLORisCorruptionError, + ) + +# default file name +__filename__ = "ilorest.json" + + +class SaveCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "save", + "usage": None, + "description": "Run to save a selected type to a file" + "\n\texample: save --selector HpBios.\n\n\tChange the default " + "output filename\n\texample: save --selector HpBios. -f " + "output.json\n\n\tTo save multiple types in one file\n\texample: " + "save --multisave Bios.,ComputerSystem.", + "summary": "Saves the selected type's settings to a file.", + "aliases": [], + "auxcommands": ["SelectCommand"], + } + self.filename = __filename__ + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main save worker function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.savevalidation(options) + + # if args: + # raise InvalidCommandLineError("Save command takes no arguments.") + + self.rdmc.ui.printer("Saving configuration...\n") + if options.filter: + try: + if (str(options.filter)[0] == str(options.filter)[-1]) and str(options.filter).startswith(("'", '"')): + options.filter = options.filter[1:-1] + + (sel, val) = options.filter.split("=") + sel = sel.strip() + val = val.strip() + except: + raise InvalidCommandLineError("Invalid filter" " parameter format [filter_attribute]=[filter_value]") + + instances = self.rdmc.app.select( + selector=self.rdmc.app.selector, + fltrvals=(sel, val), + path_refresh=True, + ) + contents = self.saveworkerfunction(instances=instances) + else: + contents = self.saveworkerfunction() + + if options.multisave: + for select in options.multisave: + try: + self.auxcommands["select"].run(select) + except: + pass + contents += self.saveworkerfunction() + + if not contents: + raise redfish.ris.NothingSelectedError + else: + # TODO: Maybe add this to the command. Not sure we use it elsewhere in the lib + contents = self.add_save_file_header(contents) + + if options.encryption: + with open(self.filename, "wb") as outfile: + outfile.write( + Encryption().encrypt_file( + json.dumps(contents, indent=2, cls=redfish.ris.JSONEncoder), + options.encryption, + ) + ) + else: + with open(self.filename, "w") as outfile: + outfile.write(json.dumps(contents, indent=2, cls=redfish.ris.JSONEncoder, sort_keys=True)) + self.rdmc.ui.printer("Configuration saved to: %s\n" % self.filename) + + self.cmdbase.logout_routine(self, options) + + # Return code + return ReturnCodes.SUCCESS + + def saveworkerfunction(self, instances=None): + """Returns the currently selected type for saving + + :param instances: list of instances from select to save + :type instances: list. + """ + + content = self.rdmc.app.getprops(insts=instances) + try: + contents = [{val[self.rdmc.app.typepath.defs.hrefstring]: val} for val in content] + except KeyError: + try: + contents = [{val["links"]["self"][self.rdmc.app.typepath.defs.hrefstring]: val} for val in content] + except KeyError: + raise iLORisCorruptionError( + "iLO Database seems to be corrupted. Please check. Reboot the server to " "restore\n" + ) + type_string = self.rdmc.app.typepath.defs.typestring + + templist = list() + srnum_list = list() + + for content in contents: + typeselector = None + pathselector = None + + for path, values in content.items(): + # if "Managers/1/EthernetInterfaces/1" not in path: + for dictentry in list(values.keys()): + if dictentry == type_string: + typeselector = values[dictentry] + pathselector = path + del values[dictentry] + if dictentry in [ + "IPv4Addresses", + "IPv6Addresses", + "IPv6AddressPolicyTable", + "MACAddress", + "StaticNameServers", + # "AutoNeg", + # "FullDuplex", + "SpeedMbps", + ]: + del values[dictentry] + if dictentry in [ + "IPv6StaticAddresses", + "IPv6StaticDefaultGateways", + "IPv4StaticAddresses", + ]: + if values[dictentry]: + del values[dictentry] + if dictentry in ["IPv4", "IPv6", "DHCPv6", "DHCPv4"]: + if "DNSServers" in values[dictentry]: + del values[dictentry]["DNSServers"] + del values["Oem"]["Hpe"][dictentry]["DNSServers"] + if "UseDNSServers" in values[dictentry]: + del values[dictentry]["UseDNSServers"] + del values["Oem"]["Hpe"][dictentry]["UseDNSServers"] + + if values: + skip = False + if "SerialNumber" in values and values["SerialNumber"] is not None and values["SerialNumber"] != "": + if values["SerialNumber"] in srnum_list: + skip = True + else: + srnum_list.append(values["SerialNumber"]) + tempcontents = dict() + + if not skip: + if typeselector and pathselector: + tempcontents[typeselector] = {pathselector: values} + else: + raise InvalidFileFormattingError("Missing path or selector in input file.") + + templist.append(tempcontents) + + return templist + + def nested_sort(self, data): + """Helper function to sort all dictionary key:value pairs + + :param data: dictionary to sort + :type data: dict. + """ + + for key, value in data.items(): + if isinstance(value, dict): + data[key] = self.nested_sort(value) + + data = OrderedDict(sorted(list(data.items()), key=lambda x: x[0])) + + return data + + def savevalidation(self, options): + """Save method validation function + + :param options: command line options + :type options: list. + """ + + if options.multisave: + options.multisave = options.multisave.replace('"', "").replace("'", "") + options.multisave = options.multisave.replace(" ", "").split(",") + if not len(options.multisave) >= 1: + raise InvalidCommandLineError("Invalid number of types in multisave option.") + options.selector = options.multisave[0] + options.multisave = options.multisave[1:] + + self.cmdbase.login_select_validation(self, options) + + # filename validations and checks + self.filename = None + + if options.filename and len(options.filename) > 1: + raise InvalidCommandLineError("Save command doesn't support multiple filenames.") + elif options.filename: + self.filename = options.filename[0] + elif self.rdmc.config: + if self.rdmc.config.defaultsavefilename: + self.filename = self.rdmc.config.defaultsavefilename + + if not self.filename: + self.filename = __filename__ + + def add_save_file_header(self, contents): + """Helper function to retrieve the comments for save file + + :param contents: current save contents + :type contents: list. + """ + templist = list() + + headers = self.rdmc.app.create_save_header() + templist.append(headers) + + for content in contents: + templist.append(content) + + return templist + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + customparser.add_argument( + "-f", + "--filename", + dest="filename", + help="Use this flag if you wish to use a different filename than the default one. " + "The default filename is %s." % __filename__, + action="append", + default=None, + ) + + customparser.add_argument( + "--selector", + dest="selector", + help="Optionally include this flag to select a type to run the current command on. " + "Use this flag when you wish to select a type without entering another command, " + "or if you wish to work with a type that is different from the one currently " + "selected.", + default=None, + # required=True, + ) + customparser.add_argument( + "--multisave", + dest="multisave", + help="Optionally include this flag to save multiple types to a single file. " + "Overrides the currently selected type.\n\t Usage: --multisave type1.,type2.,type3.", + default="", + ) + customparser.add_argument( + "--filter", + dest="filter", + help="Optionally set a filter value for a filter attribute. This uses the provided " + "filter for the currently selected type. Note: Use this flag to narrow down your " + "results. For example, selecting a common type might return multiple objects that " + "are all of that type. If you want to modify the properties of only one of those " + "objects, use the filter flag to narrow down results based on properties." + "\n\t Usage: --filter [ATTRIBUTE]=[VALUE]", + default=None, + ) + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the displayed output to " + "JSON format. Preserving the JSON data structure makes the information easier to " + "parse.", + default=False, + ) + customparser.add_argument( + "--encryption", + dest="encryption", + help="Optionally include this flag to encrypt/decrypt a file using the key provided.", + default=None, + ) diff --git a/ilorest/extensions/COMMANDS/SelectCommand.py b/ilorest/extensions/COMMANDS/SelectCommand.py new file mode 100644 index 0000000..05c6dbb --- /dev/null +++ b/ilorest/extensions/COMMANDS/SelectCommand.py @@ -0,0 +1,134 @@ +### +# Copyright 2016-2021 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 -*- +""" Select Command for RDMC """ + +from redfish.ris import NothingSelectedError + +try: + from rdmc_helper import LOGGER, InvalidCommandLineErrorOPTS, ReturnCodes +except ImportError: + from ilorest.rdmc_helper import LOGGER, InvalidCommandLineErrorOPTS, ReturnCodes + + +class SelectCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "select", + "usage": None, + "description": "Selects the Redfish/HpRest type to be used.\nIn order to " + "remove the need of including the version while selecting you" + " can simply enter the type name until the first period\n\t" + "example: select HpBios.\n" + "Run without an argument to " + "display the currently selected type\n\texample: select", + "summary": "Selects the object type to be used.", + "aliases": ["sel"], + "auxcommands": ["LoginCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def selectfunction(self, line): + """Main select worker function + + :param line: command line input + :type line: string. + """ + + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.selectvalidation(options) + + if args: + if options.ref: + LOGGER.warning("Patches from current selection will be cleared.") + selector = args[0] + selections = self.rdmc.app.select(selector=selector, path_refresh=options.ref) + + if self.rdmc.opts.verbose and selections: + templist = list() + self.rdmc.ui.printer("Selected option(s): ") + + for item in selections: + if item.type not in templist: + templist.append(item.type) + + self.rdmc.ui.printer("%s\n" % ", ".join(map(str, templist))) + + else: + selector = self.rdmc.app.selector + + if selector: + sellist = [sel for sel in self.rdmc.app.monolith.typesadded if selector.lower() in sel.lower()] + self.rdmc.ui.printer("Current selection: ") + self.rdmc.ui.printer("%s\n" % ", ".join(map(str, sellist))) + else: + raise NothingSelectedError + + self.cmdbase.logout_routine(self, options) + + def selectvalidation(self, options): + """Select data validation function + + :param options: command line options + :type options: list. + """ + + self.cmdbase.login_select_validation(self, options) + + def run(self, line, help_disp=False): + """Wrapper function for main select function + + :param line: entered command line + :type line: list. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + self.selectfunction(line) + + # Return code + return ReturnCodes.SUCCESS + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--refresh", + dest="ref", + action="store_true", + help="Optionally reload the data of selected type and clear " "patches from current selection.", + default=False, + ) diff --git a/ilorest/extensions/COMMANDS/SetCommand.py b/ilorest/extensions/COMMANDS/SetCommand.py new file mode 100644 index 0000000..329673c --- /dev/null +++ b/ilorest/extensions/COMMANDS/SetCommand.py @@ -0,0 +1,422 @@ +### +# Copyright 2016-2023 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 -*- +""" Set Command for RDMC """ +import json +import os +import sys + +import redfish.ris + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidOrNothingChangedSettingsError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidOrNothingChangedSettingsError, + ) + +from redfish.ris.rmc_helper import NothingSelectedError + + +class SetCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "set", + "usage": None, + "description": "Setting a " + "single level property example:\n\tset property=value\n\n\t" + "Setting multiple single level properties example:\n\tset " + "property=value property=value property=value\n\n\t" + "Setting a multi level property example:\n\tset property/" + "subproperty=value", + "summary": "Changes the value of a property within the" " currently selected type.", + "aliases": [], + "auxcommands": [ + "CommitCommand", + "RebootCommand", + "RawPatchCommand", + "SelectCommand", + ], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def setfunction(self, line, skipprint=False): + """Main set worker function + + :param line: command line input + :type line: string. + :param skipprint: boolean to determine output + :type skipprint: boolean. + """ + + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if not self.rdmc.interactive and not self.rdmc.app.cache: + raise InvalidCommandLineError("The 'set' command is not useful in " "non-interactive and non-cache modes.") + + self.setvalidation(options) + _ = args[0].find("HighSecurity") + + fsel = None + fval = None + if args: + if options.filter: + try: + (fsel, fval) = str(options.filter).strip("'\" ").split("=") + (fsel, fval) = (fsel.strip(), fval.strip()) + except: + raise InvalidCommandLineError( + "Invalid filter" " parameter format [filter_attribute]=[filter_value]" + ) + + if any([s.lower().startswith("adminpassword=") for s in args]) and not any( + [s.lower().startswith("oldadminpassword=") for s in args] + ): + raise InvalidCommandLineError( + "'OldAdminPassword' must also " + "be set with the current password \nwhen " + "changing 'AdminPassword' for security reasons." + ) + count = 0 + for arg in args: + if arg: + if len(arg) > 2: + if ('"' in arg[0] and '"' in arg[-1]) or ("'" in arg[0] and "'" in arg[-1]): + args[count] = arg[1:-1] + elif len(arg) == 2: + if (arg[0] == '"' and arg[1] == '"') or (arg[0] == "'" and arg[1] == "'"): + args[count] = None + count += 1 + if not self.rdmc.app.selector: + raise NothingSelectedError + if "." not in self.rdmc.app.selector: + self.rdmc.app.selector = self.rdmc.app.selector + "." + if self.rdmc.app.selector: + if self.rdmc.app.selector.lower().startswith("bios."): + if "attributes" not in arg.lower(): + arg = "Attributes/" + arg + + try: + (sel, val) = arg.split("=") + sel = sel.strip().lower() + val = val.strip("\"'") + + if val.lower() == "true" or val.lower() == "false": + val = val.lower() in ("yes", "true", "t", "1") + except: + raise InvalidCommandLineError("Invalid set parameter format. [Key]=[Value]") + + newargs = list() + + if "/" in sel and "/" not in str(val): + newargs = sel.split("/") + elif "/" in sel: + items = arg.split("=", 1) + newargs = items[0].split("/") + if not isinstance(val, bool): + if val: + if val[0] == "[" and val[-1] == "]": + val = val[1:-1].split(",") + payload = {newargs[-1]: val} if newargs else {sel: val} + if newargs: + for key in newargs[:-1][::-1]: + payload = {key: payload} + try: + contents = self.rdmc.app.loadset( + seldict=payload, + latestschema=options.latestschema, + fltrvals=(fsel, fval), + uniqueoverride=options.uniqueoverride, + ) + if not contents: + if not sel.lower() == "oldadminpassword": + raise InvalidOrNothingChangedSettingsError( + "Nothing changed " + "for attribute '%s'.\nPlease check if the attribute is Oem/Hpe Attribute or Read-only " + "or System Unique property or the value trying to set is same or invalid" % sel + ) + elif contents == "No entries found": + raise InvalidOrNothingChangedSettingsError( + "No " "entries found in the current " "selection for the setting '%s'." % sel + ) + elif contents == "reverting": + self.rdmc.ui.error("Removing previous patch and returning to the " "original value.\n") + else: + for content in contents: + self.rdmc.ui.printer("Added the following patch:\n") + self.rdmc.ui.print_out_json(content) + + except redfish.ris.ValidationError as excp: + errs = excp.get_errors() + + for err in errs: + if err.sel and err.sel.lower() == "adminpassword": + types = self.rdmc.app.monolith.types + + for item in types: + for instance in types[item]["Instances"]: + if "hpbios." in instance.maj_type.lower(): + _ = [ + instance.patches.remove(patch) + for patch in instance.patches + if patch.patch[0]["path"] == "/OldAdminPassword" + ] + + if isinstance(err, redfish.ris.RegistryValidationError): + self.rdmc.ui.printer(err.message) + + raise redfish.ris.ValidationError(excp) + + if options.commit: + self.auxcommands["commit"].commitfunction(options) + + if options.reboot and not options.commit: + self.auxcommands["reboot"].run(options.reboot) + + # if options.logout: + # self.logoutobj.run("") + + else: + raise InvalidCommandLineError("Missing parameters for 'set' command.\n") + + def patchfunction(self, line): + """Main set worker function + :param line: command line input + :type line: string. + :param skipprint: boolean to determine output + :type skipprint: boolean. + """ + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if not self.rdmc.interactive and not self.rdmc.app.cache: + raise InvalidCommandLineError("The 'set' command is not useful in " "non-interactive and non-cache modes.") + + _ = self.rdmc.app.selector + self.setvalidation(options) + if args: + count = 0 + for arg in args: + if arg: + if len(arg) > 2: + if ('"' in arg[0] and '"' in arg[-1]) or ("'" in arg[0] and "'" in arg[-1]): + args[count] = arg[1:-1] + elif len(arg) == 2: + if (arg[0] == '"' and arg[1] == '"') or (arg[0] == "'" and arg[1] == "'"): + args[count] = None + count += 1 + if not self.rdmc.app.selector: + raise NothingSelectedError + if "." not in self.rdmc.app.selector: + self.rdmc.app.selector = self.rdmc.app.selector + "." + if self.rdmc.app.selector: + if self.rdmc.app.selector.lower().startswith("bios."): + if "attributes" not in arg.lower(): + arg = "Attributes/" + arg + + try: + (sel, val) = arg.split("=") + sel = sel.strip() + val = val.strip("\"'") + + if val.lower() == "true" or val.lower() == "false": + val = val.lower() in ("yes", "true", "t", "1") + except: + raise InvalidCommandLineError("Invalid set parameter format. [Key]=[Value]") + + newargs = list() + + if "/" in sel and "/" not in str(val): + newargs = sel.split("/") + elif "/" in sel: + items = arg.split("=", 1) + newargs = items[0].split("/") + + if not isinstance(val, bool): + if val: + if val[0] == "[" and val[-1] == "]": + val = val[1:-1].split(",") + if val.isdigit(): + val = int(val) + patch_data = dict() + payload = {newargs[-1]: val} if newargs else {sel: val} + if newargs: + for key in newargs[:-1][::-1]: + if key == "PowerControl": + payload = {key: [payload]} + else: + payload = {key: payload} + patch_data.update(payload) + if "Oem" in key: + if "managernetworkprotocol." or "thermal." in self.rdmc.app.selector: + import platform + import tempfile + tempdir = "/tmp" if platform.system() == "Darwin" else tempfile.gettempdir() + temp_file = os.path.join(tempdir, "temp_patch.json") + out_file = open(temp_file, "w") + if "managernetworkprotocol." in self.rdmc.app.selector: + patchpath = self.rdmc.app.typepath.defs.managerpath + "NetworkProtocol/" + elif "thermal." in self.rdmc.app.selector: + patchpath = "/redfish/v1/Chassis/1/Thermal/" + patch_payload = {patchpath: payload} + json.dump(patch_payload, out_file, indent=6) + out_file.close() + sys.stdout.write("payload %s \n" % patch_payload) + self.auxcommands["rawpatch"].run(temp_file + " --service") + self.auxcommands["select"].run(self.rdmc.app.selector + " --refresh") + os.remove(temp_file) + if "PowerControl" in payload.keys(): + import platform + import tempfile + tempdir = "/tmp" if platform.system() == "Darwin" else tempfile.gettempdir() + temp_file = os.path.join(tempdir, "temp_patch.json") + out_file = open(temp_file, "w") + patch_path = "/redfish/v1/Chassis/1/Power" + patch_payload = {patch_path: payload} + json.dump(patch_payload, out_file, indent=6) + out_file.close() + self.auxcommands["rawpatch"].run(temp_file + " --service") + os.remove(temp_file) + + def run(self, line, skipprint=False, help_disp=False): + """Main set function + + :param line: command line input + :type line: string. + :param skipprint: boolean to determine output + :type skipprint: boolean. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + if ( + ("Oem/Hpe/EnhancedDownloadPerformanceEnabled" in line[0]) + or ("Oem/Hpe/ThermalConfiguration" in line[0]) + or ("Oem/Hpe/FanPercentMinimum" in line[0]) + or ("Power" in line[0]) + ): + self.patchfunction(line) + else: + self.setfunction(line, skipprint=skipprint) + # Return code + return ReturnCodes.SUCCESS + + def setvalidation(self, options): + """Set data validation function""" + + if self.rdmc.opts.latestschema: + options.latestschema = True + if self.rdmc.config.commit.lower() == "true": + options.commit = True + try: + self.cmdbase.login_select_validation(self, options) + except redfish.ris.NothingSelectedError: + raise redfish.ris.NothingSelectedSetError("") + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--selector", + dest="selector", + help="Optionally include this flag to select a type to run" + " the current command on. Use this flag when you wish to" + " select a type without entering another command, or if you" + " wish to work with a type that is different from the one" + " you currently have selected.", + default=None, + ) + + customparser.add_argument( + "--filter", + dest="filter", + help="Optionally set a filter value for a filter attribute." + " This uses the provided filter for the currently selected" + " type. Note: Use this flag to narrow down your results. For" + " example, selecting a common type might return multiple" + " objects that are all of that type. If you want to modify" + " the properties of only one of those objects, use the filter" + " flag to narrow down results based on properties." + "\t\t\t\t\t Usage: --filter [ATTRIBUTE]=[VALUE]", + default=None, + ) + customparser.add_argument( + "--commit", + dest="commit", + action="store_true", + help="Use this flag when you are ready to commit all pending" + " changes. Note that some changes made in this way will be updated" + " instantly, while others will be reflected the next time the" + " server is started.", + default=None, + ) + customparser.add_argument( + "--reboot", + dest="reboot", + help="Use this flag to perform a reboot command function after" + " completion of operations. For help with parameters and" + " descriptions regarding the reboot flag, run help reboot.", + default=None, + ) + customparser.add_argument( + "--latestschema", + dest="latestschema", + action="store_true", + help="Optionally use the latest schema instead of the one " + "requested by the file. Note: May cause errors in some data " + "retrieval due to difference in schema versions.", + default=None, + ) + customparser.add_argument( + "--uniqueoverride", + dest="uniqueoverride", + action="store_true", + help="Override the measures stopping the tool from writing " "over items that are system unique.", + default=None, + ) + diff --git a/ilorest/extensions/COMMANDS/StatusCommand.py b/ilorest/extensions/COMMANDS/StatusCommand.py new file mode 100644 index 0000000..ae35023 --- /dev/null +++ b/ilorest/extensions/COMMANDS/StatusCommand.py @@ -0,0 +1,205 @@ +### +# Copyright 2016-2021 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 -*- +""" Status Command for RDMC """ + +from functools import reduce + +from redfish.ris.utils import merge_dict + +try: + from rdmc_helper import InvalidCommandLineErrorOPTS, ReturnCodes +except ImportError: + from ilorest.rdmc_helper import InvalidCommandLineErrorOPTS, ReturnCodes + + +class StatusCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "status", + "usage": None, + "description": "Run to display all pending changes within" + " the currently\n\tselected type that need to be" + " committed\n\texample: status", + "summary": "Displays all pending changes within a selected type" " that need to be committed.", + "aliases": [], + "auxcommands": ["SelectCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main status worker function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.statusvalidation(options) + contents = self.rdmc.app.status() + selector = self.rdmc.app.selector + + if contents and options.json: + self.jsonout(contents) + elif contents: + self.outputpatches(contents, selector) + else: + self.rdmc.ui.printer("No changes found\n") + + # Return code + return ReturnCodes.SUCCESS + + def jsonout(self, contents): + """Helper function to print json output of patches + + :param contents: contents for the selection + :type contents: string. + """ + self.rdmc.ui.printer("Current changes found:\n") + createdict = lambda y, x: {x: y} + totdict = {} + for item in contents: + for keypath, value in item.items(): + path = keypath.split("(")[1].strip("()") + cont = {} + totdict[path] = cont + for content in value: + val = ( + ["List Manipulation"] + if content["op"] == "move" + else [content["value"].strip("\"'")] + if len(content["value"]) + else [""] + ) + cont = reduce( + createdict, + reversed([path] + content["path"].strip("/").split("/") + val), + ) + merge_dict(totdict, cont) + self.rdmc.ui.print_out_json(totdict) + + def outputpatches(self, contents, selector): + """Helper function for status for use in patches + + :param contents: contents for the selection + :type contents: string. + :param selector: type selected + :type selector: string. + """ + self.rdmc.ui.printer("Current changes found:\n") + for item in contents: + moveoperation = "" + for key, value in item.items(): + if selector and key.lower().startswith(selector.lower()): + self.rdmc.ui.printer("%s (Currently selected)\n" % key) + else: + self.rdmc.ui.printer("%s\n" % key) + + for content in value: + try: + if content["op"] == "move": + moveoperation = "/".join(content["path"].split("/")[1:-1]) + continue + except: + if content[0]["op"] == "move": + moveoperation = "/".join(content[0]["path"].split("/")[1:-1]) + continue + try: + if isinstance(content[0]["value"], int): + self.rdmc.ui.printer("\t%s=%s" % (content[0]["path"][1:], content[0]["value"])) + elif not isinstance(content[0]["value"], bool) and not len(content[0]["value"]) == 0: + if content[0]["value"][0] == '"' and content[0]["value"][-1] == '"': + self.rdmc.ui.printer( + "\t%s=%s" + % ( + content[0]["path"][1:], + content[0]["value"][1:-1], + ) + ) + else: + self.rdmc.ui.printer("\t%s=%s" % (content[0]["path"][1:], content[0]["value"])) + else: + output = content[0]["value"] + + if not isinstance(output, bool): + if len(output) == 0: + output = '""' + + self.rdmc.ui.printer("\t%s=%s" % (content[0]["path"][1:], output)) + except: + if isinstance(content["value"], int): + self.rdmc.ui.printer("\t%s=%s" % (content["path"][1:], content["value"])) + elif ( + content["value"] + and not isinstance(content["value"], bool) + and not len(content["value"]) == 0 + ): + if content["value"][0] == '"' and content["value"][-1] == '"': + self.rdmc.ui.printer("\t%s=%s" % (content["path"][1:], content["value"])) + else: + self.rdmc.ui.printer("\t%s=%s" % (content["path"][1:], content["value"])) + else: + output = content["value"] + + if output and not isinstance(output, bool): + if len(output) == 0: + output = '""' + + self.rdmc.ui.printer("\t%s=%s" % (content["path"][1:], output)) + self.rdmc.ui.printer("\n") + if moveoperation: + self.rdmc.ui.printer("\t%s=List Manipulation\n" % moveoperation) + + def statusvalidation(self, options): + """Status method validation function""" + + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) diff --git a/ilorest/extensions/COMMANDS/TypesCommand.py b/ilorest/extensions/COMMANDS/TypesCommand.py new file mode 100644 index 0000000..6b8e22d --- /dev/null +++ b/ilorest/extensions/COMMANDS/TypesCommand.py @@ -0,0 +1,125 @@ +### +# Copyright 2016-2021 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 -*- +""" Types Command for RDMC """ + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class TypesCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "types", + "usage": None, + "description": "Run to display currently " "available selectable types\n\tExample: types", + "summary": "Displays all selectable types within the currently logged in server.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def typesfunction(self, line, returntypes=False): + """Main types worker function + + :param line: command line input + :type line: string. + :param returntypes: flag to determine if types should be printed + :type returntypes: boolean. + """ + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.typesvalidation(options) + + if not args: + typeslist = list() + typeslist = sorted(set(self.rdmc.app.types(options.fulltypes))) + + if not returntypes: + self.rdmc.ui.printer("Type options:\n") + + for item in typeslist: + self.rdmc.ui.printer("%s\n" % item) + else: + return typeslist + else: + raise InvalidCommandLineError("The 'types' command does not take any arguments.") + + self.cmdbase.logout_routine(self, options) + + def run(self, line, help_disp=False): + """Wrapper function for types main function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + self.typesfunction(line) + + # Return code + return ReturnCodes.SUCCESS + + def typesvalidation(self, options): + """types method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--fulltypes", + dest="fulltypes", + action="store_true", + help="Optionally include this flag if you would prefer to " + "return the full type name instead of the simplified versions" + " (Redfish only option).", + default=None, + ) diff --git a/ilorest/extensions/COMMANDS/__init__.py b/ilorest/extensions/COMMANDS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/AdvancedPmmConfigCommand.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/AdvancedPmmConfigCommand.py new file mode 100644 index 0000000..6922a6d --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/AdvancedPmmConfigCommand.py @@ -0,0 +1,453 @@ +### +# Copyright 2016-2021 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 -*- +"""Command to apply specified configuration to PMM""" +from __future__ import absolute_import # verify if python3 libs can handle + +from argparse import Action +from copy import deepcopy + +try: + from rdmc_helper import ( + LOGGER, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoChangesFoundOrMadeError, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + LOGGER, + NoChangesFoundOrMadeError, + NoContentsFoundForOperationError, + ) + +from .lib.RestHelpers import RestHelpers + + +class _ParseOptionsList(Action): + def __init__(self, option_strings, dest, nargs, **kwargs): + super(_ParseOptionsList, self).__init__(option_strings, dest, nargs, **kwargs) + + def __call__(self, parser, namespace, values, option_strings): + """ + Callback to parse a comma-separated list into an array. + Then store the array in the option's destination + """ + try: + setattr(namespace, self.dest, next(iter(values)).split(",")) + except: + raise InvalidCommandLineError("Values in a list must be separated by a comma.") + + +class AdvancedPmmConfigCommand: + """ + Command to apply specified configuration to PMM + """ + + def __init__(self): + self.ident = { + "name": "provisionpmm", + "usage": None, + "description": "Applies specified configuration to PMM.\n" "\texample: provisionpmm -m 50 -i On -pid 1,2", + "summary": "Applies specified configuration to PMM.", + "aliases": ["provisionpmm"], + "auxcommands": [ + "ShowPmemPendingConfigCommand", + "ClearPendingConfigCommand", + ], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + self._rest_helpers = RestHelpers(rdmcObject=self.rdmc) + + def run(self, line, help_disp=False): + """ + Wrapper function for new command main function + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + LOGGER.info("PMM: %s", self.ident["name"]) + + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineError("Failed to parse options") + + if args: + self.validate_args(args, options) + self.validate_options(options) + # Raise exception if server is in POST + if self._rest_helpers.in_post(): + raise NoContentsFoundForOperationError( + "Unable to retrieve resources - " "server might be in POST or powered off" + ) + self.configure_pmm(options) + + return ReturnCodes.SUCCESS + + @staticmethod + def validate_args(args, options): + """ + Produces relevant error messages when unwanted + extra arguments are specified with flags. + """ + error_message = ( + "Chosen flag is invalid. Use the '-m | --memory-mode'" " flag to specify the size in percentage." + ) + some_flag = options.memorymode or options.interleave or options.proc or options.force + + case_a = options.force and not options.memorymode and not options.interleave and not options.proc + case_b = options.interleave and not options.memorymode and not options.force and not options.proc + case_c = options.proc and not options.memorymode and not options.force and not options.interleave + case_d = options.memorymode and not options.proc and not options.interleave and not options.force + + if case_a or case_b or case_c: + raise InvalidCommandLineError(error_message) + if case_d or (some_flag not in args): + raise InvalidCommandLineError("Chosen flag is Invalid") + + def validate_options(self, options): + """ + Validate whether options specified by user are valid or not + :param options: options specified by the user with the 'provisionpmm' command + :type options: instance of OptionParser class + """ + some_flag = options.memorymode or options.interleave or options.proc or options.force + if not some_flag: + raise InvalidCommandLineError("No flag specified.\n\nUsage: " + self.ident["usage"]) + + # If the memory mode option value is not valid. + resp = self._rest_helpers.retrieve_model(self.rdmc) + model = resp["Model"] + + if "Gen10 Plus" in model: + if not (options.memorymode == 0 or options.memorymode == 100): + raise InvalidCommandLineError("Specify the correct value (0 or 100)" " to configure PMM") + else: + if options.memorymode < 0 or options.memorymode > 100: + raise InvalidCommandLineError("Specify the correct value (1-100)" " to configure PMM") + if options.interleave: + if options.interleave.lower() not in ["on", "off"]: + raise InvalidCommandLineError( + "Specify the correct value to set interleave" + " state of persistent memory regions\n\n" + self.ident["usage"] + ) + options.interleave = options.interleave.lower() + + if options.proc and not options.memorymode and not options.interleave: + raise InvalidCommandLineError( + "Use '-m|--memory-mode' flag to specify the size in percentage" + " or '-i|--pmem-interleave' flag to set interleave" + " state of persistent memory regions" + ) + + # If not 100% memorymode, -i flag is mandatory to interleave memroy regions. + if 0 <= options.memorymode < 100 and not options.interleave: + raise InvalidCommandLineError( + "Use '-i | --pmem-interleave' flag to" " specify the interleave state of persistent" " memory regions" + ) + if options.proc: + for proc_id in options.proc: + if not proc_id.isdigit(): + raise InvalidCommandLineError("Specify the correct processor id") + + def definearguments(self, customparser): + """ + Wrapper function for new command main function + :param customparser: command line input + :type customparser: parser + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-m", + "--memory-mode", + dest="memorymode", + type=int, + help="Percentage of the total capacity to configure all PMMs to MemoryMode." + " This flag is optional. If not specified," + " by default configuration will be applied with 0%% Volatile." + " To interleave persistent memory regions, use this flag in conjunction with" + " the --pmem-interleave flag. To configure all the PMMs on specific processor," + " use this flag in conjunction with the --proc flag.", + default=0, + ) + + customparser.add_argument( + "-i", + "--pmem-interleave", + dest="interleave", + help="Indicates whether the persistent memory regions should be interleaved or not.", + default=None, + ) + + customparser.add_argument( + "-pid", + "--proc", + nargs=1, + action=_ParseOptionsList, + dest="proc", + help="To apply selected configuration on specific processors, " + "supply a comma-separated list of Processor IDs (without spaces) where " + "p=processor id. This flag is optional. If not specified, " + "by default configuration will be applied to all the Processors.", + ) + + customparser.add_argument( + "-f", + "--force", + action="store_true", + dest="force", + help="Allow the user to force the configuration " "by automatically accepting any prompts.", + default=False, + ) + + @staticmethod + def warn_existing_chunks_and_tasks(self, memory_chunk_tasks, memory_chunks): + """ + Checks for existing Memory Chunks and Pending Configuration Task resources on + a server where a user is trying to selected configuration + :param memory_chunk_tasks: Pending Configuration Tasks + :type memory_chunk_tasks: list + :memory_chunks: Memory Chunks in the existing configuration + :type memory_chunks: list + :returns: None + """ + # If Memory Chunks exist, display Existing configuration warning + if memory_chunks: + self.rdmc.ui.warn( + "Existing configuration found. Proceeding with applying a new " + "configuration will result in overwriting the current configuration and " + "cause data loss.\n" + ) + # If Pending Configuration Tasks exist, display warning + if memory_chunk_tasks: + self.rdmc.ui.printer( + "Warning: Pending configuration tasks found. Proceeding with applying " + "a new configuration will result in overwriting the pending " + "configuration tasks.\n" + ) + # Raise a NoChangesFoundOrMade exception when either of the above conditions exist + if memory_chunks or memory_chunk_tasks: + # Line feed for proper formatting + raise NoChangesFoundOrMadeError( + "\nFound one or more of Existing Configuration or " + "Pending Configuration Tasks. Please use the " + "'--force | -f' flag with the same command to " + "approve these changes." + ) + return None + + def delete_existing_chunks_and_tasks(self, memory_chunk_tasks, memory_chunks): + """ + Delete existing Memory Chunks and Pending Configuration Tasks + :param memory_chunk_tasks: Pending Configuration Tasks + :type memory_chunk_tasks: list + :memory_chunks: Memory Chunks in the existing configuration + :type memory_chunks: list + :returns: None + """ + # Delete any pending configuration tasks + if memory_chunk_tasks: + self.auxcommands["clearpmmpendingconfig"].delete_tasks(memory_chunk_tasks) + # Delete any existing configuration + if memory_chunks: + for chunk in memory_chunks: + data_id = chunk.get("@odata.id") + resp = self._rest_helpers.delete_resource(data_id) + if not resp: + raise NoChangesFoundOrMadeError("Error occurred while deleting " "existing configuration") + return None + + @staticmethod + def get_valid_processor_list(input_proc_list, domain_members): + """ + Function to check processors specified by user are valid or not + :param domain_members: Memory Resources + :type input_proc_list: List + :type domain_members: Dict + :returns: List of valid processors in string format (ex: [PROC1MemoryDomain]) + """ + proc_list = [] + index = 0 + invalid_proc_list = [] + # Create list with valid processors based on 'Id' attribute. + for member in domain_members: + proc_list.append(member["Id"]) + + for index, proc_id in enumerate(input_proc_list): + temp_proc_id = "PROC" + str(proc_id) + "MemoryDomain" + if temp_proc_id not in proc_list: + invalid_proc_list.append(proc_id) + else: + input_proc_list[index] = temp_proc_id + + # Raise execption if given proc id is not exist. + if invalid_proc_list: + if len(invalid_proc_list) == 1: + raise NoChangesFoundOrMadeError( + "Specified processor number {proc_list} is invalid".format(proc_list=invalid_proc_list[0]) + ) + else: + raise NoChangesFoundOrMadeError( + "Specified processor numbers {proc_list} are invalid".format(proc_list=",".join(invalid_proc_list)) + ) + + return input_proc_list + + @staticmethod + def filter_memory_chunks(memory_chunks, domain_members, proc_list): + """ + Filter memory chunks based on specified processor list + :param memory_chunks: list of all memory chunks + :param domain_members: dict of memory resources + :param proc list: List of specified processors + :Retrun: List of memory chunks corresponding to specified processors + """ + all_chunks = list() + for member in domain_members: + proc_id = member["Id"] + if not proc_list or (proc_id in proc_list): + data_id = member.get("MemoryChunks").get("@odata.id") + all_chunks += [chunk for chunk in memory_chunks if data_id in chunk.get("@odata.id")] + + return all_chunks + + @staticmethod + def get_post_data(config_data, interleavable_memory_sets): + """ + Create post body based on config data + :param config_data: dict with user input + :type config_data: dict + :param interleavable_memory_sets: List of interleavable sets + :type interleavable_memory_sets: List + :returns: Returns post data + """ + + body = { + "AddressRangeType": "PMEM", + "InterleaveSets": [], + "Oem": {"Hpe": {"MemoryChunkSizePercentage": 100 - config_data["size"]}}, + } + # Get the list of interleave sets based on the configuration + if config_data["pmeminterleave"] == "on" or config_data["size"] == 100: + # If persistent memory regions are interleaved or if it's 100% MemoryMode, + # choose the list with maximum entries. + interleave_sets = [max(interleavable_memory_sets, key=lambda x: len(x["MemorySet"]))] + else: + # If persistent memory regions are not interleaved, + # choose all the lists with exactly one entry. + interleave_sets = [il_set for il_set in interleavable_memory_sets if len(il_set["MemorySet"]) == 1] + + # Using in-place update to change the interleave sets format. + # Replace 'MemorySet' with 'Memory' for each MemorySet in interleave_sets. + for index, interleavableset in enumerate(interleave_sets): + interleave_sets[index] = [ + {"Memory": {"@odata.id": str(memory_region["@odata.id"])}} + for memory_region in interleavableset["MemorySet"] + ] + + # Create post body for each interleave sets from post body template. + post_data = [] + for interleaveset in interleave_sets: + current_body = deepcopy(body) + current_body["InterleaveSets"] = interleaveset + post_data.append(current_body) + + return post_data + + def configure_pmm(self, options): + """ + Applies selected configuration to the PMMs + :param options: options specified by user + :returns: None + """ + # Retrieve Memory Chunks and Task Resources from server. + ( + task_members, + domain_members, + memory_chunks, + ) = self._rest_helpers.retrieve_task_members_and_mem_domains() + + # Filter Task Resources to include only Pending Configuration Tasks. + memory_chunk_tasks = self._rest_helpers.filter_task_members(task_members) + + if not domain_members: + raise NoContentsFoundForOperationError( + "Failed to retrieve Memory Domain Resources, please check if persistent memory is present/configured" + ) + + # Dict with input config data + config_data = { + "size": options.memorymode, + "pmeminterleave": options.interleave, + "proc": options.proc, + } + # Check for given processor id list is valid or not. + if options.proc: + config_data["proc"] = self.get_valid_processor_list(config_data["proc"], domain_members) + memory_chunks = self.filter_memory_chunks(memory_chunks, domain_members, config_data["proc"]) + + # In case of 100% Volatile, Interleaving memory regions is not applicable. + if ( + config_data["size"] == 100 + and config_data["pmeminterleave"] + and config_data["pmeminterleave"].lower() == "on" + ): + raise InvalidCommandLineError( + "Selected configuration is invalid. " "Interleaving not supported in 100% volatile." + ) + + if options.force: + self.delete_existing_chunks_and_tasks(memory_chunk_tasks, memory_chunks) + else: + self.warn_existing_chunks_and_tasks(self, memory_chunk_tasks, memory_chunks) + + for member in domain_members: + proc_id = member["Id"] + # If given proc list is not empty, applies configuration to selected processors + if not config_data["proc"] or (proc_id in config_data["proc"]): + path = member["MemoryChunks"].get("@odata.id") + data = self.get_post_data(config_data, member["InterleavableMemorySets"]) + for body in data: + resp = self._rest_helpers.post_resource(path, body) + if resp is None: + raise NoChangesFoundOrMadeError("Error occurred while applying configuration") + + # Display warning + self.rdmc.ui.printer("\nConfiguration changes require reboot to take effect.\n") + + # Display pending configuration + self.auxcommands["showpmmpendingconfig"].show_pending_config(type("MyOptions", (object,), dict(json=False))) + return None diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ApplyPmemConfigCommand.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ApplyPmemConfigCommand.py new file mode 100644 index 0000000..fd7ba0a --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ApplyPmemConfigCommand.py @@ -0,0 +1,357 @@ +### +# Copyright 2016-2021 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 -*- +"""Command to apply a pre-defined configuration to PMM""" +from __future__ import absolute_import + +from copy import deepcopy + +try: + from rdmc_helper import ( + LOGGER, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoChangesFoundOrMadeError, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + LOGGER, + NoChangesFoundOrMadeError, + NoContentsFoundForOperationError, + ) + +from .lib.DisplayHelpers import DisplayHelpers +from .lib.RestHelpers import RestHelpers + + +class ApplyPmemConfigCommand: + """ + Command to apply a pre-defined configuration to PMM + """ + + # Available configIDs + config_ids = [ + { + "name": "MemoryMode", + "description": "Configure all the PMMs to 100% Memory Mode.", + "totalPmemPercentage": 0, + "pmemIntereleaved": False, + }, + { + "name": "PmemInterleaved", + "description": "Configure all PMMs to 100% Persistent. " + "Interleave the Persistent memory regions within each processor.", + "totalPmemPercentage": 100, + "pmemIntereleaved": True, + }, + { + "name": "PmemNotInterleaved", + "description": "Configure all PMMs to 100% Persistent. " + "The Persistent memory regions are not interleaved.", + "totalPmemPercentage": 100, + "pmemIntereleaved": False, + }, + ] + + def __init__(self): + self.ident = { + "name": "applypmmconfig", + "usage": None, + "description": "Applies a pre-defined configuration to PMM\n" + "\texample: applypmmconfig --pmmconfig MemoryMode\n", + "summary": "Applies a pre-defined configuration to PMM.", + "aliases": [], + "auxcommands": [ + "ShowPmemPendingConfigCommand", + "ClearPendingConfigCommand", + ], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + self._display_helpers = DisplayHelpers() + + def definearguments(self, customparser): + """ + Wrapper function for new command main function + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-C", + "--pmmconfig", + action="store", + type=str, + dest="config", + help="Specify one of the pre-defined configIDs to apply" " to all Persistent Memory Modules.", + default=None, + ) + + customparser.add_argument( + "-L", + "--list", + action="store_true", + dest="list", + help="Display a list of available pre-defined configIDs" " along with a brief description.", + default=False, + ) + + customparser.add_argument( + "-f", + "--force", + action="store_true", + dest="force", + help="Allow the user to force the configuration " "by automatically accepting any prompts.", + default=False, + ) + + def run(self, line, help_disp=False): + """ + Wrapper function for new command main function + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + LOGGER.info("PMM Apply Pre-Defined Configuration: %s", self.ident["name"]) + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineError("Failed to parse options") + if args: + self.validate_args(options) + self.validate_options(options) + self.apply_pmm_config(options) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + @staticmethod + def validate_args(options): + """ + Produces relevant error messages when unwanted extra arguments are specified with flags. + """ + if options.config: + raise InvalidCommandLineError("'config | -C' expects a single argument") + else: + raise InvalidCommandLineError("Chosen flag doesn't expect additional arguments") + + def validate_options(self, options): + """ + Validate whether options specified by user are valid. + :param options: options specified by the user. + :type options: instance of OptionParser class. + """ + # Set of valid configIDs + valid_config_ids = {config_id.get("name").lower() for config_id in self.config_ids} + + if not options.config and not options.list and not options.force: + raise InvalidCommandLineError("No flag specified.\n\nUsage: " + self.ident["usage"]) + if not options.config and options.force: + raise InvalidCommandLineError("'--force | -f' flag mandatorily requires the " "'--pmmconfig | -C' flag.") + if options.config and options.list: + raise InvalidCommandLineError("Only one of '--pmmconfig | -C' or '--list | -L' " "may be specified") + # Check whether the user entered configID is valid + if options.config and not options.config.lower() in valid_config_ids: + raise InvalidCommandLineError( + "Invalid configID.\nTo view a list of pre-defined " "configIDs, use 'applypmmconfig --list'" + ) + + def apply_pmm_config(self, options): + """ + Calls relevant functions based on the flags specified by the user. + :param options: options specified by the user. + :type options: instance of OptionParser class. + """ + if options.list: + self.show_predefined_config_options() + elif options.config: + # Raise exception if server is in POST + if RestHelpers(rdmcObject=self.rdmc).in_post(): + raise NoContentsFoundForOperationError( + "Unable to retrieve resources - " "server might be in POST or powered off" + ) + self.apply_predefined_config(options) + + def show_predefined_config_options(self): + """ + Display the available pre-defined configIDs that the user can choose from to + apply to their Persistent memory Modules. + """ + self.rdmc.ui.printer("\nAvailable Configurations:\n\n") + for config_id in ApplyPmemConfigCommand.config_ids: + self.rdmc.ui.printer(config_id.get("name") + "\n") + self.rdmc.ui.printer("\t" + config_id.get("description") + "\n") + self.rdmc.ui.printer("\n") + + def warn_existing_chunks_and_tasks(self, memory_chunk_tasks, memory_chunks): + """ + Checks for existing Memory Chunks and Pending Configuration Task resources on + a server where a user is trying to apply a pre-defined configuration + :param memory_chunk_tasks: Pending Configuration Tasks + :type memory_chunk_tasks: list + :memory_chunks: Memory Chunks in the existing configuration + :type memory_chunks: list + :returns: None + """ + # If Memory Chunks exist, display Existing configuration warning + if memory_chunks: + self.rdmc.ui.printer( + "Warning: Existing configuration found. Proceeding with applying a new " + "configuration will result in overwriting the current configuration and " + "cause data loss.\n" + ) + # If Pending Configuration Tasks exist, display warning + if memory_chunk_tasks: + self.rdmc.ui.printer( + "Warning: Pending configuration tasks found. Proceeding with applying " + "a new configuration will result in overwriting the pending " + "configuration tasks.\n" + ) + # Raise a NoChangesFoundOrMade exception when either of the above conditions exist + if memory_chunks or memory_chunk_tasks: + # Line feed for proper formatting + raise NoChangesFoundOrMadeError( + "\nFound one or more of Existing Configuration or " + "Pending Configuration Tasks. Please use the " + "'--force | -f' flag with the same command to " + "approve these changes." + ) + return None + + def delete_existing_chunks_and_tasks(self, memory_chunk_tasks, memory_chunks): + """ + Delete existing Memory Chunks and Pending Configuration Tasks + :param memory_chunk_tasks: Pending Configuration Tasks + :type memory_chunk_tasks: list + :memory_chunks: Memory Chunks in the existing configuration + :type memory_chunks: list + :returns: None + """ + # Delete any pending configuration tasks + if memory_chunk_tasks: + self.auxcommands["clearpmmpendingconfig"].delete_tasks(memory_chunk_tasks) + # Delete any existing configuration + if memory_chunks: + for chunk in memory_chunks: + data_id = chunk.get("@odata.id") + resp = RestHelpers(rdmcObject=self.rdmc).delete_resource(data_id) + if not resp: + raise NoChangesFoundOrMadeError("Error occured while deleting " "existing configuration") + return None + + @staticmethod + def get_post_data(config_data, interleavable_memory_sets): + """ + Create post body based on config data + :param config_data: An entry of self.config_iD's list + :type config_data: dict + :param interleavable_memory_sets: List of interleavable sets + :type interleavable_memory_sets: List + :returns: Returns post data + """ + + body = { + "AddressRangeType": "PMEM", + "InterleaveSets": [], + "Oem": {"Hpe": {"MemoryChunkSizePercentage": config_data["totalPmemPercentage"]}}, + } + # Get the list of interleave sets based on the configuration + if config_data["pmemIntereleaved"] or config_data["totalPmemPercentage"] == 0: + # If pmem regions are interleaved or if it MemoryMode, choose the list with maximum entries. + interleave_sets = [max(interleavable_memory_sets, key=lambda x: len(x["MemorySet"]))] + else: + # If pmem regions are NOT interleaved, choose all the lists with exactly one entry. + interleave_sets = [il_set for il_set in interleavable_memory_sets if len(il_set["MemorySet"]) == 1] + + # Using in-place update to change the interleave sets format. + # Replace 'MemorySet' with 'Memory' for each MemorySet in interleave_sets. + # Get '@odata.id' for each memory region from original interleave_sets and add this to new structure. + for index, interleavableset in enumerate(interleave_sets): + interleave_sets[index] = [ + {"Memory": {"@odata.id": str(memory_region["@odata.id"])}} + for memory_region in interleavableset["MemorySet"] + ] + + # Create post body for each interleave sets from post body template. + post_data = [] + for interleaveset in interleave_sets: + current_body = deepcopy(body) + current_body["InterleaveSets"] = interleaveset + post_data.append(current_body) + + return post_data + + def apply_predefined_config(self, options): + """ + Apply the selected pre-defined configuration to Persistent Memory Modules. + :param options: option specified by the user. + :returns: None + """ + # Retrieve Memory Chunks and Task Resources from server + (task_members, domain_members, memory_chunks) = RestHelpers( + rdmcObject=self.rdmc + ).retrieve_task_members_and_mem_domains() + + # Filter Task Resources to include only Pending Configuration Tasks + memory_chunk_tasks = RestHelpers(rdmcObject=self.rdmc).filter_task_members(task_members) + + if options.force: + self.delete_existing_chunks_and_tasks(memory_chunk_tasks, memory_chunks) + else: + self.warn_existing_chunks_and_tasks(memory_chunk_tasks, memory_chunks) + + if not domain_members: + raise NoContentsFoundForOperationError("Failed to retrive Memory Domain Resources") + + # Get the user specified configID + config_data = next( + (config_id for config_id in self.config_ids if config_id.get("name").lower() == options.config.lower()), + None, + ) + + for proc in domain_members: + path = proc["MemoryChunks"].get("@odata.id") + data = self.get_post_data(config_data, proc["InterleavableMemorySets"]) + for body in data: + resp = RestHelpers(rdmcObject=self.rdmc).post_resource(path, body) + if resp is None: + raise NoChangesFoundOrMadeError("Error occured while applying configuration") + + # display warning + self.rdmc.ui.printer("Configuration changes require reboot to take effect.\n") + + # display pending configuration + self.auxcommands["showpmmpendingconfig"].show_pending_config(type("MyOptions", (object,), dict(json=False))) diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ClearPendingConfigCommand.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ClearPendingConfigCommand.py new file mode 100644 index 0000000..0635343 --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ClearPendingConfigCommand.py @@ -0,0 +1,134 @@ +### +# Copyright 2016-2021 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 -*- +"""Command to clear pending tasks""" +from __future__ import absolute_import + +try: + from rdmc_helper import ( + LOGGER, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoChangesFoundOrMadeError, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + NoChangesFoundOrMadeError, + LOGGER, + ) + +from .lib.RestHelpers import RestHelpers + + +class ClearPendingConfigCommand: + """Command to clear pending config tasks""" + + def __init__(self): + self.ident = { + "name": "clearpmmpendingconfig", + "usage": None, + "description": "Clear pmm pending config tasks\n" "\texample: clearpmmpendingconfig", + "summary": "Clear pending config tasks", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def get_memory_chunk_tasks(self): + """ + Function to retrieve Memory Chunk Tasks + :returns: Retrieved Memory Chunk Tasks + """ + # Retrieving tasks members + tasks = RestHelpers(rdmcObject=self.rdmc).retrieve_task_members() + if tasks: + # Filtering out Memory Chunk Tasks + memory_chunk_tasks = RestHelpers(rdmcObject=self.rdmc).filter_task_members(tasks) + if memory_chunk_tasks: + return memory_chunk_tasks + self.rdmc.ui.printer("No pending configuration tasks found.\n\n") + return [] + + def delete_tasks(self, memory_chunk_tasks, verbose=False): + """ + Function to delete pending configuration tasks + :param memory_chunk_tasks: Pending confguration tasks. + :type memory_chunk_tasks: list + :param verbose: Toggles verbose mode, which print task IDs as + individual tasks are deleted. + :type verbose: Boolean + :returns: None + """ + for task in memory_chunk_tasks: + data_id = task.get("@odata.id") + task_id = task.get("Id") + resp = RestHelpers(rdmcObject=self.rdmc).delete_resource(data_id) + if resp: + if verbose: + self.rdmc.ui.printer("Deleted Task #{}".format(task_id) + "\n") + else: + raise NoChangesFoundOrMadeError("Error occured while deleting " "task #{}".format(task_id)) + return None + + def run(self, line, help_disp=False): + """ + Wrapper function for new command main function + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + LOGGER.info("Clear Pending Configuration: %s", self.ident["name"]) + # pylint: disable=unused-variable + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineError("Failed to parse options") + if args: + raise InvalidCommandLineError("Chosen flag doesn't expect additional arguments") + # Raise exception if server is in POST + if RestHelpers(rdmcObject=self.rdmc).in_post(): + raise NoContentsFoundForOperationError( + "Unable to retrieve resources - " "server might be in POST or powered off" + ) + memory_chunk_tasks = self.get_memory_chunk_tasks() + self.delete_tasks(memory_chunk_tasks, verbose=True) + + return ReturnCodes.SUCCESS + + def definearguments(self, customparser): + """ + Wrapper function for new command main function + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/DisplaySecurityStateCommand.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/DisplaySecurityStateCommand.py new file mode 100644 index 0000000..e40e703 --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/DisplaySecurityStateCommand.py @@ -0,0 +1,123 @@ +### +# Copyright 2016-2021 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 -*- + +""" Command to Display Security State of Dimms """ + +# ---------Imports--------- +from __future__ import absolute_import + +from tabulate import tabulate + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ) + +from .lib.RestHelpers import RestHelpers + +# ---------End of imports--------- + + +class DisplaySecurityStateCommand: + """Command to Display Security State of Dimms""" + + def __init__(self): + self.ident = { + "name": "pmmsecuritystate", + "usage": None, + "description": "Displaying the Security state of dimms\n\texample: pmmsecuritystate", + "summary": "Displaying the Security state of dimms.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + self._rest_helpers = None + + def display_SecurityState(self): + resp = self._rest_helpers.retrieve_pmem_location() + + pmemLocation = [] + for item in resp["Members"]: + if item["MemoryType"] == "IntelOptane": + pmemLocation.append(item["Name"]) + + securityState = [] + headers = ["Location", "State"] + for i in pmemLocation: + path = "/redfish/v1/Systems/1/Memory/" + i + resp = self._rest_helpers.retrieve_security_state(path) + state = resp["SecurityState"] + securityState.append(state) + + merged_list = tuple(zip(pmemLocation, securityState)) + print(tabulate(merged_list, headers, tablefmt="psql")) + + def run(self, line, help_disp=False): + """ + Wrapper function for new command main function + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineError("Failed to parse options") + if args: + raise InvalidCommandLineError("Chosen flag doesn't expect additional arguments") + + # Raise exception if server is in POST + self._rest_helpers = RestHelpers(rdmcObject=self.rdmc) + if self._rest_helpers.in_post(): + raise NoContentsFoundForOperationError( + "Unable to retrieve resources - " "server might be in POST or powered off" + ) + + self.display_SecurityState() + + return ReturnCodes.SUCCESS + + def definearguments(self, customparser): + """Wrapper function for smbios command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ShowPmemCommand.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ShowPmemCommand.py new file mode 100644 index 0000000..e48cc83 --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ShowPmemCommand.py @@ -0,0 +1,455 @@ +### +# Copyright 2016-2021 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 -*- +"""Command to display information about Persistent Memory modules""" +from __future__ import absolute_import, division + +import re +from argparse import Action +from enum import Enum + +try: + from rdmc_helper import ( + LOGGER, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + LOGGER, + ) + +from .lib.DisplayHelpers import DisplayHelpers, OutputFormats +from .lib.Mapper import Mapper +from .lib.MapperRenderers import MappingTable +from .lib.PmemHelpers import PmemHelpers +from .lib.RestHelpers import RestHelpers + + +class _Parse_Options_List(Action): + def __init__(self, option_strings, dest, nargs, **kwargs): + super(_Parse_Options_List, self).__init__(option_strings, dest, nargs, **kwargs) + + def __call__(self, parser, namespace, values, option_strings): + """ + Callback to parse a comma-separated list into an array. + Then store the array in the option's destination + """ + try: + setattr(namespace, self.dest, next(iter(values)).split(",")) + except: + raise InvalidCommandLineError("Values in a list must be separated by a comma.") + + +class DefaultAttributes(Enum): + """ + Enum class containing default display attributes for various flags + """ + + device = ["Location", "Capacity", "Status", "DIMMStatus", "Life", "FWVersion"] + config = ["Location", "VolatileSize", "PmemSize", "PmemInterleaved"] + summary = ["TotalCapacity", "TotalVolatileSize", "TotalPmemSize"] + logical = ["PmemSize", "DimmIds"] + + +def get_default_attributes(flag): + """ + This method returns default attributes to be displayed + :param flag: name of flag + :type flag: string + :return: list of default attributes for the passed flag + """ + if flag == "device": + return list(DefaultAttributes.device.value) + if flag == "config": + return list(DefaultAttributes.config.value) + if flag == "logical": + return list(DefaultAttributes.logical.value) + if flag == "summary": + return list(DefaultAttributes.summary.value) + return [] + + +class ShowPmemCommand: + """Command to display information about Persistent Memory modules""" + + def __init__(self): + self.ident = { + "name": "showpmm", + "usage": None, + "description": "Display information about " "Persistent Memory modules \n\texample: showpmm --device", + "summary": "Display information about Persistent Memory modules.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + self._display_helpers = DisplayHelpers() + self._mapper = Mapper() + self._pmem_helpers = PmemHelpers() + + def show_pmem_modules(self, options): + """ + Command to display information about Persistent Memory modules + :param options: command options + :type options: options. + """ + # Retrieving memory collection resources + if options.logical or options.config: + memory, domain_members, all_chunks = RestHelpers(rdmcObject=self.rdmc).retrieve_mem_and_mem_domains() + if not domain_members: + raise NoContentsFoundForOperationError("Failed to retrieve Memory " "Domain resources") + else: + memory = RestHelpers(rdmcObject=self.rdmc).retrieve_memory_resources() + + if memory: + memory_members = memory.get("Members") + else: + raise NoContentsFoundForOperationError("Failed to retrieve Memory Resources") + + # Get dimm ids of all memory objects + member_dimm_ids = set() + for member in memory_members: + member_dimm_ids.add(member.get("DeviceLocator")) + + # Filtering Persistent Memory members + pmem_members, pmem_dimm_ids = self._pmem_helpers.get_pmem_members(memory_members) + if not pmem_members: + raise NoContentsFoundForOperationError("No Persistent Memory Modules found") + + parsed_dimm_ids = list() + if options.dimm: + parsed_dimm_ids = self._pmem_helpers.parse_dimm_id(options.dimm) + + for dimm_id in parsed_dimm_ids: + if dimm_id not in member_dimm_ids: + raise InvalidCommandLineError("One or more of the specified " "DIMM ID(s) are invalid") + elif dimm_id not in pmem_dimm_ids: + raise InvalidCommandLineError( + "One or more of the specified DIMM ID(s) " "are not Persistent Memory Modules" + ) + + # Creating a list of persistent memory members according to specified DIMM Ids + selected_pmem_members = list() + if parsed_dimm_ids: + for pmem_member in pmem_members: + if pmem_member.get("DeviceLocator") in parsed_dimm_ids: + selected_pmem_members.append(pmem_member) + else: + selected_pmem_members = pmem_members + + # Call 'show_pmem_module_device()' when either the user specifies '--device' flag + # or specifies no flag at all + if ( + not options.device and not options.config and not options.logical and not options.summary + ) or options.device: + self.show_pmem_module_device(selected_pmem_members, options) + + elif options.config: + self.show_pmem_module_configuration(selected_pmem_members, all_chunks, options) + + elif options.summary: + self.show_pmem_module_summary(selected_pmem_members, options) + + elif options.logical: + if not all_chunks: + self.rdmc.ui.printer("No Persistent Memory regions found\n\n") + return + self.show_persistent_interleave_sets(selected_pmem_members, all_chunks, options) + + def generate_display_output(self, members, options, flag, mapping_table, **resources): + """ + :param members: list of members returned as a result of GET request + :type members: list of dictionaries + :param options: command options + :type options: options + :param mapping_table: Mapping table to be used to extract attributes + :param flag: name of flag + :type flag: string + :return: filtered list of dictionaries to be sent to Display Helpers for output + """ + display_output_list = list() + # use default attributes for now + display_attributes = get_default_attributes(flag) + + for member in members: + temp_dict = self._mapper.get_multiple_attributes( + member, display_attributes, mapping_table, output_as_json=options.json, **resources + ) + display_output_list.append(temp_dict) + return display_output_list + + def show_pmem_module_device(self, selected_pmem_members, options): + """ + Command to display information about DIMMs when the + '--device' | '-D' flag is specified + :param selected_pmem_members: pmem members to be displayed + :type selected_pmem_members: list + :param options: command options + :type options: options + """ + # generating the data to be printed + display_output = self.generate_display_output( + selected_pmem_members, options, "device", MappingTable.device.value + ) + # Displaying output based on --json flag + if options.json: + self._display_helpers.display_data(display_output, OutputFormats.json) + else: + self._display_helpers.display_data(display_output, OutputFormats.table) + + def show_pmem_module_configuration(self, selected_pmem_members, all_chunks, options=None): + """ + Command to display information about DIMMs when the + '--pmmconfig' | '-C' flag is specified + :param selected_pmem_members: pmem members to be displayed + :type selected_pmem_members: list + :param all_chunks: list of memory chunks + :type all_chunks: list + :param options: command options + :type options: options + """ + # generating the data to be printed + display_output = self.generate_display_output( + selected_pmem_members, + options, + "config", + MappingTable.config.value, + chunks=all_chunks, + ) + # Displaying output based on --json flag + if options.json: + self._display_helpers.display_data(display_output, OutputFormats.json) + else: + self._display_helpers.display_data(display_output, OutputFormats.table) + + def show_persistent_interleave_sets(self, selected_pmem_members, all_chunks, options=None): + """ + Command to display information about the Persistent interleave + regions among the Persistent Memory Modules when the '--logical' | '-L' flag is + specified + :param selected_pmem_members: list of memory members retrieved via GET request + :type selected_pmem_members: list + :param all_chunks: list of memory chunks + :type all_chunks: list + :param options: command options + :type options: options + """ + # generating the data to be printed + display_output = self.generate_display_output( + all_chunks, + options, + "logical", + MappingTable.logical.value, + memory=selected_pmem_members, + ) + # Displaying output based on --json flag + if options.json: + self._display_helpers.display_data(display_output, OutputFormats.json) + else: + self.rdmc.ui.printer("\nInterleave Persistent Memory regions\n") + self._display_helpers.display_data(display_output, OutputFormats.table) + + def show_pmem_module_summary(self, selected_pmem_members, options=None): + """ + Command to display a summary of the current Persistent Memory + configuration when the '--summary' | '-M' flag is specified + :param selected_pmem_members: list of memory members retrieved via GET request + :type selected_pmem_members: list + :param options: command options + :type options: options + """ + # getting default attributes for --summary flag + attribute_list = get_default_attributes("summary") + # generating the data to be printed + display_output = self._mapper.get_multiple_attributes( + selected_pmem_members, + attribute_list, + MappingTable.summary.value, + options.json, + ) + # Displaying output based on --json flag + if options.json: + self._display_helpers.display_data(display_output, OutputFormats.json) + else: + self._display_helpers.print_properties([display_output]) + + def run(self, line, help_disp=False): + """ + Wrapper function for new command main function + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + LOGGER.info("PMM: %s", self.ident["name"]) + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineError("Failed to parse options") + + self.rdmc.login_select_validation(self, options) + + if args: + self.validate_args(options) + self.validate_show_pmem_options(options) + # Raise exception if server is in POST + if RestHelpers(rdmcObject=self.rdmc).in_post(): + raise NoContentsFoundForOperationError( + "Unable to retrieve resources - " "server might be in POST or powered off" + ) + self.show_pmem_modules(options) + + return ReturnCodes.SUCCESS + + @staticmethod + def validate_args(options): + """ + Produces relevant error messages when unwanted extra arguments are specified with flags + """ + some_flag = options.device or options.config or options.logical or options.summary + if options.logical or options.summary: + raise InvalidCommandLineError("Chosen flag doesn't expect additional arguments") + elif (options.device or options.config or not some_flag) and not options.dimm: + raise InvalidCommandLineError("Use the '--dimm | -I' flag to filter by DIMM IDs") + elif (options.device or options.config or not some_flag) and options.dimm: + raise InvalidCommandLineError("Values in a list must be comma-separated " "(no spaces)") + + @staticmethod + def validate_show_pmem_options(options): + """ + Validate whether options specified by user are valid + :param options: options specified by the user with the 'showpmm' command + :type options: instance of OptionParser class + """ + # Usage/Error strings + usage_multiple_flags = ( + "Only one of '--device | -D', '--pmmconfig | -C', " "'--logical | -L' or '--summary | -M' may be specified" + ) + # usage_all_display = "Only one of '--all | -a' or '--display | -d' may be specified\n" + usage_dimm_flag = ( + "'--dimm | -I' can only be specified with either the " + "'--device | -D' or '--pmmconfig | -C' flag\n" + " or without any flag" + ) + error_dimm_format = "DIMM IDs should be of the form 'ProcessorNumber@SlotNumber'" + error_dimm_range = "One or more of the specified DIMM ID(s) are invalid" + + views = [options.device, options.config, options.logical, options.summary] + + if (options.logical or options.summary) and options.dimm: + raise InvalidCommandLineError(usage_dimm_flag) + + if views.count(False) < 3: + raise InvalidCommandLineError(usage_multiple_flags) + + if options.dimm: + for dimm_id in options.dimm: + # Regex match test for expressions of type 'num1@num2' + if not re.match(r"^\d+\@\d+$", dimm_id): + raise InvalidCommandLineError(error_dimm_format) + # Accepts expressions from '1@1' to '9@19' + if not re.match(r"^[1-9]\@([1-9]|[1]\d)$", dimm_id): + raise InvalidCommandLineError(error_dimm_range) + + def definearguments(self, customparser): + """ + Wrapper function for new command main function + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-j", + "--json", + action="store_true", + dest="json", + help="Optionally include this flag to change the output to JSON format.", + default=False, + ) + + customparser.add_argument( + "-D", + "--device", + action="store_true", + default=False, + dest="device", + help="Show information about the physical persistent memory modules." + " Default view shows information about all PMMs." + " To filter DIMMs, use this flag in conjunction with" + " the --dimm flag.", + ) + + customparser.add_argument( + "-C", + "--pmmconfig", + action="store_true", + default=False, + dest="config", + help="Show the current configuration of the persistent memory modules." + " Default view shows information about all PMMs." + " To filter DIMMs, use this flag in conjunction with" + " the --dimm flag.", + ) + + customparser.add_argument( + "-L", + "--logical", + action="store_true", + default=False, + dest="logical", + help="Show the Persistent Memory Regions.", + ) + + customparser.add_argument( + "-M", + "--summary", + action="store_true", + default=False, + dest="summary", + help="Show the summary of the persistent memory resources.", + ) + + customparser.add_argument( + "-I", + "--dimm", + type=str, + action=_Parse_Options_List, + metavar="IDLIST", + nargs=1, + dest="dimm", + help=" To view specific devices, supply a comma-separated list" + " of DIMM IDs in the format P@S (without spaces)," + " where P=processor and S=slot. Example: '1@1,1@12'", + ) diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ShowPmemPendingConfigCommand.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ShowPmemPendingConfigCommand.py new file mode 100644 index 0000000..a56251a --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ShowPmemPendingConfigCommand.py @@ -0,0 +1,170 @@ +### +# Copyright 2016-2021 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 -*- +"""Command to show the pending configuration for PMM""" + +from __future__ import absolute_import + +try: + from rdmc_helper import ( + LOGGER, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + LOGGER, + NoContentsFoundForOperationError, + ) + +from .lib.DisplayHelpers import DisplayHelpers, OutputFormats +from .lib.Mapper import Mapper +from .lib.MapperRenderers import MappingTable +from .lib.PmemHelpers import PmemHelpers +from .lib.RestHelpers import RestHelpers + + +class ShowPmemPendingConfigCommand: + """ + Command to show the pending configuration for PMM + """ + + def __init__(self): + self.ident = { + "name": "showpmmpendingconfig", + "usage": None, + "description": "Shows the pending tasks for configuring PMM\n" "\texample: showpmmpendingconfig --json", + "summary": "Shows the pending configuration for PMM.", + "aliases": [], + "auxcommands": [], + } + self._mapper = Mapper() + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + self._display_helpers = DisplayHelpers() + self._pmem_helpers = PmemHelpers() + + def definearguments(self, customparser): + """ + Wrapper function for new command main function + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-j", + "--json", + action="store_true", + dest="json", + help="Optionally include this flag to change the output to JSON format.", + default=False, + ) + + def run(self, line, help_disp=False): + """ + Wrapper function for new command main function + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + LOGGER.info("PMM Pending Configuration: %s", self.ident["name"]) + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineError("Failed to parse options") + if args: + raise InvalidCommandLineError("Chosen command or flag doesn't expect additional arguments") + # Raise exception if server is in POST + if RestHelpers(rdmcObject=self.rdmc).in_post(): + raise NoContentsFoundForOperationError( + "Unable to retrieve resources - " "server might be in POST or powered off" + ) + self.show_pending_config(options) + + return ReturnCodes.SUCCESS + + def show_pending_config(self, options): + """ + Command to display information about pending Persistent Memory Configuration + :param options: command options + :type options: options. + """ + # retrieving task members + task_members = RestHelpers(rdmcObject=self.rdmc).retrieve_task_members() + + # filtering task members + filtered_task_members = RestHelpers(rdmcObject=self.rdmc).filter_task_members(task_members) + if not filtered_task_members: + self.rdmc.ui.printer("No pending configuration tasks found.\n\n") + return None + + # retrieving memory members + memory = RestHelpers(rdmcObject=self.rdmc).retrieve_memory_resources() + if memory: + memory_members = memory.get("Members") + else: + raise NoContentsFoundForOperationError("Failed to retrieve Memory Resources") + + attributes = ["Operation", "PmemSize", "VolatileSize", "DimmIds"] + display_output = list() + for task in filtered_task_members: + # finding operation of task + operation = self._mapper.get_single_attribute(task, "Operation", MappingTable.tasks.value, True) + # displaying existing configuration for DELETE operation + if operation.get("Operation", "") == "DELETE": + target_uri = task.get("Payload").get("TargetUri") + data = RestHelpers(rdmcObject=self.rdmc).get_resource(target_uri) + table = MappingTable.delete_task.value + else: + task_type = self._mapper.get_single_attribute(task, "Type", MappingTable.tasks.value, True) + task_type = task_type.get("Type", "") + if task_type != "PMEM": + self.rdmc.ui.warn("Unsupported interleave set type found: " + task_type) + continue + data = task + table = MappingTable.tasks.value + + task_output = self._mapper.get_multiple_attributes( + data, + attributes, + table, + output_as_json=options.json, + memory=memory_members, + ) + display_output.append(task_output) + + if options.json: + self._display_helpers.display_data(display_output, OutputFormats.json) + else: + self._display_helpers.display_data(display_output, OutputFormats.table) diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ShowRecommendedConfigCommand.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ShowRecommendedConfigCommand.py new file mode 100644 index 0000000..c634618 --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/ShowRecommendedConfigCommand.py @@ -0,0 +1,193 @@ +### +# Copyright 2016-2021 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 -*- +"""Command to show recommended configurations""" +from __future__ import absolute_import, division + +from collections import OrderedDict + +try: + from rdmc_helper import ( + LOGGER, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + LOGGER, + ) + +from .lib.DisplayHelpers import DisplayHelpers, OutputFormats +from .lib.Mapper import Mapper +from .lib.MapperRenderers import MappingTable +from .lib.PmemHelpers import PmemHelpers +from .lib.RestHelpers import RestHelpers + + +class ShowRecommendedConfigCommand: + """Show recommended configurations""" + + def __init__(self): + self.ident = { + "name": "showrecommendedpmmconfig", + "usage": None, + "description": "Show recommended configurations\n" "\texample: showrecommendedpmmconfig", + "summary": "Show Recommended Configuration", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + self._display_helpers = DisplayHelpers() + self._mapper = Mapper() + self._pmem_helpers = PmemHelpers() + + def definearguments(self, customparser): + """ + Wrapper function for new command main function + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-j", + "--json", + action="store_true", + dest="json", + help="Optionally include this flag to change the output to JSON format.", + default=False, + ) + + def run(self, line, help_disp=False): + """ + Wrapper function for new command main function + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + LOGGER.info("Show Recommended Configuration: %s", self.ident["name"]) + try: + (_, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineError("Failed to parse options") + if args: + raise InvalidCommandLineError("Chosen command doesn't expect additional arguments") + # Raise exception if server is in POST + if RestHelpers(rdmcObject=self.rdmc).in_post(): + raise NoContentsFoundForOperationError( + "Unable to retrieve resources - " "server might be in POST or powered off" + ) + self.show_recommended_config() + + return ReturnCodes.SUCCESS + + def show_recommended_config(self): + """ + Show recommended pmm configuration + """ + members = RestHelpers(rdmcObject=self.rdmc).retrieve_memory_resources().get("Members") + + if not members: + raise NoContentsFoundForOperationError("Failed to retrieve memory resources") + + # Retreving dimms + pmem_members = self._pmem_helpers.get_pmem_members(members)[0] + + if not pmem_members: + raise NoContentsFoundForOperationError("No Persistent Memory Modules found") + + # Retreving dram dimms + dram_members = self._pmem_helpers.get_non_aep_members(members)[0] + + if not dram_members: + raise NoContentsFoundForOperationError("No DRAM DIMMs found") + + # retrieving Total Capacity of PMEM dimms + attr = self._mapper.get_single_attribute(pmem_members, "TotalCapacity", MappingTable.summary.value, True) + pmem_size = attr.get("TotalCapacity", {}).get("Value", 0) + + # retrieving Total Capacity of DRAM dimms + dram_size = self._mapper.get_single_attribute(dram_members, "TotalCapacity", MappingTable.summary.value, True) + dram_size = dram_size.get("TotalCapacity", {}).get("Value", 0) + + display_output = list() + recommended_config = list() + + # Add AppDirect Mode + temp_dict = OrderedDict() + temp_dict["MemoryModeTotalSize"] = 0 + temp_dict["PmemTotalSize"] = pmem_size + temp_dict["CacheRatio"] = None + recommended_config.append(temp_dict) + + # Add Memory Mode + temp_dict = OrderedDict() + temp_dict["MemoryModeTotalSize"] = pmem_size + temp_dict["PmemTotalSize"] = 0 + temp_dict["CacheRatio"] = pmem_size / dram_size + recommended_config.append(temp_dict) + + # Add Mixed Mode (BPS doesn't support it) + if "Gen10 Plus" not in RestHelpers(rdmcObject=self.rdmc).retrieve_model(self.rdmc)["Model"]: + stepsize = 32 + appdirect_size = 0 + step = 1 + while appdirect_size < pmem_size: + appdirect_size = step * stepsize * len(pmem_members) + memorymode_size = pmem_size - appdirect_size + cache_ratio = memorymode_size / dram_size + if 2 <= cache_ratio <= 16: + temp_dict = OrderedDict() + temp_dict["MemoryModeTotalSize"] = memorymode_size + temp_dict["PmemTotalSize"] = appdirect_size + temp_dict["CacheRatio"] = cache_ratio + recommended_config.append(temp_dict) + step += 1 + + # Sorting based on MemoryModeTotalSize + recommended_config = sorted(recommended_config, key=lambda x: x["MemoryModeTotalSize"]) + + # Adding units and formating cache ratio + for output in recommended_config: + output["MemoryModeTotalSize"] = "%d GB" % output["MemoryModeTotalSize"] + output["PmemTotalSize"] = "%d GB" % output["PmemTotalSize"] + if output["CacheRatio"] is None: + output["CacheRatio"] = "N/A" + else: + output["CacheRatio"] = "1:%.1f" % output["CacheRatio"] + display_output.append(self._pmem_helpers.json_to_text(output)[0]) + + # Display output in table format + self._display_helpers.display_data(display_output, OutputFormats.table) diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/__init__.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/DisplayHelpers.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/DisplayHelpers.py new file mode 100644 index 0000000..8649a9c --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/DisplayHelpers.py @@ -0,0 +1,187 @@ +### +# Copyright 2016-2021 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 -*- +"""There are the helper functions for displaying data on the console""" +from __future__ import absolute_import + +from enum import Enum + +from tabulate import tabulate + +try: + from rdmc_helper import LOGGER, UI +except ImportError: + from ilorest.rdmc_helper import LOGGER, UI + + +class OutputFormats(Enum): + """Enum class representing output formats""" + + table = "table" + list = "list" + json = "json" + + +class DisplayHelpers(object): + """Helper functions for printing data to display on the console""" + + output_dict = dict() + + def __init__(self, width=20): + self.output_dict[OutputFormats.table] = self.print_table + self.output_dict[OutputFormats.list] = self.print_list + self.output_dict[OutputFormats.json] = self.output_to_json + self.max_width = width + self._ui = UI() + + def display_data(self, data_to_display, output_format, unique_id_property_name=None): + """ + Wrapper function for displaying data in the proper format: table, list, json, etc. + :param data_to_display: The data to display. Each object represents a row in a table + or an item in a list. + :type data_to_display: array of objects + :param output_format: Specifies which output format to use + :type output_format: enum + :param unique_id_property_name: Specifies the property which acts as the identifier + :type unique_id_property_name: string + """ + if data_to_display is None or data_to_display == []: + LOGGER.info("DCPMEM: Empty data") + return None + if output_format not in self.output_dict: + LOGGER.info("Incorrect output format") + return None + self.output_dict[output_format](data_to_display, unique_id_property_name) + return None + + def format_data(self, data, truncate=False): + """ + Function to identify header properties and convert Redfish property names to friendly names. + It also truncate strings whenever required + :param data: The data from which header has to be identified. + :type data: array of objects + :param truncate: Specifies whether truncation of strings is to be done or not + :type truncate: Boolean + :return:2 arrays. First array is an array of properties. + Second array is an array of objects. + Each object is an array containing value of properties at corresponding index. + """ + table = [] + for item in data: + row = [":".join(x.split(":")[1:]).strip() for x in item.split("\n") if len(x.split(":")) >= 2] + table.append(row) + headers = [x.split(":")[0].strip() for x in data[0].split("\n") if len(x.split(":")) == 2] + not_found = [x for x in data[0].split("\n") if len(x.split(":")) < 2] + self._ui.printer("\n".join(not_found)) + if not truncate: + return headers, table + truncated_headers = [self.truncate_lengthy(str(x), self.max_width) for x in headers] + truncated_data = [[self.truncate_lengthy(str(x), self.max_width) for x in row] for row in table] + return truncated_headers, truncated_data + + # pylint: disable=unused-argument + def print_table(self, table_data, property_id=None): + """ + This function prints data in table format + :param table_data: data to be printed in table format + :type array of objects + :param property_id: Specifies the property which acts as the identifier + :type string + """ + headers, data = self.format_data(table_data, True) + self._ui.printer("\n") + self._ui.printer(tabulate(data, headers, tablefmt="plain")) + self._ui.printer("\n\n") + return + + def print_list(self, list_data, property_id=None): + """ + This function prints data in list format + :param list_data: data to be printed in list format + :type array of objects + :param property_id: Specifies the property which acts as the identifier + :type string + """ + + headers, data = self.format_data(list_data) + flag = 0 + counter = 0 + if property_id is None or property_id not in headers: + flag = 1 + + if flag == 0: + property_id_index = headers.index(property_id) + del headers[property_id_index] + + for item in data: + if flag == 0: + self._ui.printer("--- " + property_id + ": " + str(item[property_id_index] + " ---")) + item.remove(item[property_id_index]) + else: + counter += 1 + self._ui.printer("--- " + str(counter) + " ---") + for prop in enumerate(headers): + self._ui.printer("\n" + headers[prop[0]] + ": " + str(item[prop[0]])) + self._ui.printer("\n\n") + return + + def print_properties(self, data): + """ + This function prints the data in list format without any header + :param data:data to be printed without header + :type array of string + """ + if not data: + self._ui.printer("\n") + return + headers, data = self.format_data(data) + for item in data: + for prop in enumerate(headers): + self._ui.printer("\n" + headers[prop[0]] + ": " + str(item[prop[0]])) + self._ui.printer("\n") + self._ui.printer("\n") + return + + # pylint: disable=unused-argument + def output_to_json(self, json_data, property_id=None): + """ + This function prints data in json format + :param json_data: data to be printed in json format + :type json_data: array of objects + :param property_id: Specifies the property which acts as the identifier + :type property_id: string + """ + self._ui.printer("\n") + self._ui.print_out_json(json_data) + self._ui.printer("\n") + return + + @staticmethod + def truncate_lengthy(stringin, max_length): + """Truncate lengthy strings to a maximum size + :param stringin: string to truncate + :type stringin: string + :param max_length: maximum allowed length of a string + :type max_length: int + :return: return the truncated string + :rtype: string + """ + if stringin: + if len(stringin) > max_length: + return stringin[: (max_length - 2)] + ".." + return stringin + return "" diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/Mapper.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/Mapper.py new file mode 100644 index 0000000..ddd3443 --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/Mapper.py @@ -0,0 +1,96 @@ +### +# Copyright 2016-2021 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 -*- +"""There are helper functions used to get required data using mapping table""" +from __future__ import absolute_import + +from jsonpointer import resolve_pointer + + +class Mapper(object): + """Helper functions for getting data""" + + def get_multiple_attributes(self, data, attributes_list, mapping_table, output_as_json=False, **resources): + """ + Method used to get data for multiple attributes + :param data: json from which data is to be extracted + :param attributes_list: list of attributes to be extracted + :param mapping_table: table used to extract data + :param output_as_json: specifies whether the output should be in json or not + :param resources: additional resources required to compute attributes + :return: Either string or json containing extracted information for all the attributes + """ + # loop over the list of attribute names provided + if output_as_json: + output = {} + for attribute in attributes_list: + output.update( + self.get_single_attribute( + data, + attribute, + mapping_table, + output_as_json=True, + resources=resources, + ) + ) + else: + output = "" + for attribute in attributes_list: + output += "\n" + self.get_single_attribute(data, attribute, mapping_table, resources=resources) + return output + + @staticmethod + def get_single_attribute(data, attribute_name, mapping_table, output_as_json=False, resources=None): + """ + Method used to get data for single attribute + :param data: json from which data is to be extracted + :param attribute_name: attribute to be extracted + :param mapping_table: table used to extract data + :param output_as_json: specifies whether the output should be in json or not + :param resources: additional resources required to compute attributes + :return: Either string or json containing extracted information for single attribute + """ + output = {} if output_as_json else "No match found for '{}'".format(attribute_name) + # finding the attribute in the mapping table + mapping = mapping_table.get(attribute_name, None) + if mapping: + # attribute exists in mapping table + resolved_data = resolve_pointer(data, mapping["path"], None) + + if resolved_data is not None: + # attribute_name is present in data + if "compute" in mapping: + resolved_data = mapping["compute"](data=resolved_data, resources=resources) + value = resolved_data + + if output_as_json: + if "renderJSON" in mapping: + # calling the rendering function to generate the JSON output + value = mapping["renderJSON"](value) + if isinstance(value, bytes): + value = value.decode("utf-8") + output = {attribute_name: value} + else: + if "renderText" in mapping: + # calling the rendering function to generate the TEXT output + value = mapping["renderText"](value) + if isinstance(value, bytes): + value = value.decode("utf-8") + output = "{}: {}".format(attribute_name, value) + + # attribute does not exist in mapping table or value is None + return output diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/MapperRenderers.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/MapperRenderers.py new file mode 100644 index 0000000..57208b1 --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/MapperRenderers.py @@ -0,0 +1,478 @@ +### +# Copyright 2016-2021 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 -*- +"""These are helper functions used to render different attributes""" +from __future__ import absolute_import, division + +from enum import Enum + +from .Mapper import Mapper +from .PmemHelpers import PmemHelpers + + +class MapperRenderers(object): + """Methods used to render different attributes""" + + @staticmethod + def format_num(num): + """ + Method to convert any number to GiB + Note: The result produced by the unit conversion is in GiB. + However, it is labeled as GB to be consistent with other products. + :param num: any number + :return: formatted number in string format + """ + return str(PmemHelpers.py3_round(num / 1024, 2)) + " GB" + + @staticmethod + def format_num_json(num): + """ + Method to convert any number to GiB in json format + Note: The result produced by the unit conversion is in GiB. + However, it is labeled as GB to be consistent with other products. + :param num: any number + :return: formatted number in json format + """ + return {"Units": "GB", "Value": PmemHelpers.py3_round(num / 1024, 2)} + + @staticmethod + def format_percentage(num): + """ + Method to append % symbol + :param num: any number + :return: formatted percentage in string format + """ + return "{}%".format(num) + + @staticmethod + def format_percentage_json(num): + """ + Method to get formatted percentage + :param num: any number + :return: formatted percentage in json format + """ + return {"Units": "%", "Value": num} + + @staticmethod + def find_pmem_interleaved(**kwargs): + """ + Method to find whether specified member is interleaved or not + :param kwargs: + data (dict): all the Memory REST data for the individual Memory to check whether + interleaved or not + resources (dict): a dict containing all required REST resources; the 'chunks' keyword + containing all MemoryChunk data is required for this method + :return: Yes if interleaved, No if not interleaved, N/A otherwise + """ + member = kwargs.get("data", {}) + chunks = kwargs.get("resources").get("chunks", []) + data_id = member.get("@odata.id", "") + for chunk in chunks: + interleave_sets = chunk.get("InterleaveSets") + if not interleave_sets: + continue + for mem_set in interleave_sets: + if PmemHelpers.compare_id(data_id, mem_set.get("Memory").get("@odata.id")): + if len(interleave_sets) > 1: + return "Yes" + return "No" + return "N/A" + + @staticmethod + def calculate_total_capacity(**kwargs): + """ + Method to calculate total capacity + :param kwargs: + data (list): all the memory members for which total capacity is to be calculated + :return: total capacity in MiB + """ + members = kwargs.get("data", []) + total = 0 + if members: + for member in members: + capacity = Mapper.get_single_attribute( + member, "Capacity", MappingTable.device.value, output_as_json=True + ) + total += capacity.get("Capacity", {}).get("Value", 0) + # returning value in MiB + return total * 1024 + + @staticmethod + def calculate_total_pmem(**kwargs): + """ + Method to calculate total pmem size + :param kwargs: + data (list): all the memory members for which total pmem size is to be calculated + :return: total pmem size in MiB + """ + members = kwargs.get("data", []) + total = 0 + if members: + for member in members: + capacity = Mapper.get_single_attribute( + member, "PmemSize", MappingTable.device.value, output_as_json=True + ) + total += capacity.get("PmemSize", {}).get("Value", 0) + # returning value in MiB + return total * 1024 + + @staticmethod + def calculate_total_volatile(**kwargs): + """ + Method to calculate total volatile size + :param kwargs: + data (list): all the members for which total volatile size is to be calculated + :return: total volatile size in MiB + """ + members = kwargs.get("data", []) + total = 0 + if members: + for member in members: + capacity = Mapper.get_single_attribute( + member, "VolatileSize", MappingTable.device.value, output_as_json=True + ) + total += capacity.get("VolatileSize", {}).get("Value", 0) + # returning value in MiB + return total * 1024 + + @staticmethod + def find_dimm_ids(**kwargs): + """ + Method to find DimmIds present in given chunk + :param kwargs: + data (dict): all the MemoryChunk REST data for the individual Memory chunk of which + dimmids is to be found + resources (dict): a dict containing all required REST resources; the 'memory' keyword + containing all memory resources is required for this method + :return: string containing dimmids + """ + member = kwargs.get("data", {}) + memory_members = kwargs.get("resources").get("memory", []) + interleave_sets = member.get("InterleaveSets", []) + locations = [] + for interleave_set in interleave_sets: + for pmem in memory_members: + if PmemHelpers.compare_id(interleave_set.get("Memory").get("@odata.id"), pmem.get("@odata.id")): + location = Mapper.get_single_attribute( + pmem, "Location", MappingTable.device.value, output_as_json=True + ) + locations.append(location.get("Location", "")) + return PmemHelpers.location_format_converter(locations)[0] + + @staticmethod + def map_operation(**kwargs): + """ + Method to map HTTP operations to user-friendly names + :param kwargs: + data (str): HTTP operation to be mapped to user-friendly name + :return: mapped operation + """ + operation = kwargs.get("data", "") + operation_mapper = {"POST": "CREATE"} + return operation_mapper.get(operation, operation) + + @staticmethod + def calculate_task_pmem_size(**kwargs): + """ + Method to calculate pmem size for a task + :param kwargs: + data (dict): all the Task REST data for the individual task of which pmem size + is to be calculated + resources (dict): a dict containing all required REST resources; the 'memory' keyword + containing all memory resources is required for this method + :return: pmem size of task in MiB + """ + size = 0 + task = kwargs.get("data", {}) + # finding memory chunk size + memory_chunk_size = Mapper.get_single_attribute( + task, "MemoryChunkSize", MappingTable.tasks.value, output_as_json=True + ) + memory_chunk_size = memory_chunk_size.get("MemoryChunkSize", {}).get("Value", None) + if memory_chunk_size is not None: + size = memory_chunk_size + else: + memory_members = kwargs.get("resources", {}).get("memory", []) + interleave_sets = task.get("Payload").get("JsonBody").get("InterleaveSets", []) + selected_members = [] + for interleave_set in interleave_sets: + for pmem in memory_members: + if PmemHelpers.compare_id( + interleave_set.get("Memory").get("@odata.id"), + pmem.get("@odata.id"), + ): + selected_members.append(pmem) + # finding total capacity + total_capacity = Mapper.get_single_attribute( + selected_members, + "TotalCapacity", + MappingTable.summary.value, + output_as_json=True, + ) + total_capacity = total_capacity.get("TotalCapacity", {}).get("Value", 0) + # finding memory chunk size percentage + memory_chunk_size_percentage = Mapper.get_single_attribute( + task, + "MemoryChunkSizePercentage", + MappingTable.tasks.value, + output_as_json=True, + ) + memory_chunk_size_percentage = memory_chunk_size_percentage.get("MemoryChunkSizePercentage", {}).get( + "Value", None + ) + if memory_chunk_size_percentage is not None: + size = total_capacity * memory_chunk_size_percentage / 100 + # returning value in MiB + return size * 1024 + + @staticmethod + def calculate_task_volatile_size(**kwargs): + """ + Method to calculate volatile size for a task + :param kwargs: + data (dict): all the Task REST data for the individual task of which volatile size + is to be calculated + resources (dict): a dict containing all required REST resources; the 'memory' keyword + containing all memory resources is required for this method + :return: volatile size of task in MiB + """ + task = kwargs.get("data", {}) + memory_members = kwargs.get("resources", {}).get("memory", []) + interleave_sets = task.get("Payload").get("JsonBody").get("InterleaveSets", []) + selected_members = [] + for interleave_set in interleave_sets: + for pmem in memory_members: + if PmemHelpers.compare_id(interleave_set.get("Memory").get("@odata.id"), pmem.get("@odata.id")): + selected_members.append(pmem) + # finding total capacity + total_capacity = Mapper.get_single_attribute( + selected_members, + "TotalCapacity", + MappingTable.summary.value, + output_as_json=True, + ) + total_capacity = total_capacity.get("TotalCapacity", {}).get("Value", 0) + volatile_size = total_capacity + # finding memory chunk size + memory_chunk_size = Mapper.get_single_attribute( + task, "MemoryChunkSize", MappingTable.tasks.value, output_as_json=True + ) + memory_chunk_size = memory_chunk_size.get("MemoryChunkSize", {}).get("Value", None) + if memory_chunk_size is not None: + size = memory_chunk_size + volatile_size = total_capacity - size + else: + # finding memory chunk size percentage + memory_chunk_size_percentage = Mapper.get_single_attribute( + task, + "MemoryChunkSizePercentage", + MappingTable.tasks.value, + output_as_json=True, + ) + memory_chunk_size_percentage = memory_chunk_size_percentage.get("MemoryChunkSizePercentage", {}).get( + "Value", None + ) + if memory_chunk_size_percentage is not None: + size = total_capacity * memory_chunk_size_percentage / 100 + volatile_size = total_capacity - size + # returning value in MiB + return volatile_size * 1024 + + @staticmethod + def calculate_chunk_volatile_size(**kwargs): + """ + Method to calculate volatile size for a chunk + :param kwargs: + data (dict): all the Task REST data for the individual chunk of which volatile size + is to be calculated + resources (dict): a dict containing all required REST resources; the 'memory' keyword + containing all memory resources is required for this method + :return: volatile size of chunk in MiB + """ + chunk = kwargs.get("data", {}) + memory_members = kwargs.get("resources", {}).get("memory", []) + interleave_sets = chunk.get("InterleaveSets", []) + selected_members = [] + for interleave_set in interleave_sets: + for pmem in memory_members: + if PmemHelpers.compare_id(interleave_set.get("Memory").get("@odata.id"), pmem.get("@odata.id")): + selected_members.append(pmem) + # finding total capacity + total_capacity = Mapper.get_single_attribute( + selected_members, + "TotalCapacity", + MappingTable.summary.value, + output_as_json=True, + ) + total_capacity = total_capacity.get("TotalCapacity", {}).get("Value", 0) + volatile_size = total_capacity + # finding memory chunk size + memory_chunk_size = Mapper.get_single_attribute( + chunk, "PmemSize", MappingTable.delete_task.value, output_as_json=True + ) + memory_chunk_size = memory_chunk_size.get("PmemSize", {}).get("Value", None) + if memory_chunk_size is not None: + volatile_size = total_capacity - memory_chunk_size + + # returning value in MiB + return volatile_size * 1024 + + +DEVICE_MAPPING_TABLE = { + "PmemSize": { + "path": "/PersistentRegionSizeLimitMiB", + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, + "Capacity": { + "path": "/CapacityMiB", + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, + "DIMMStatus": {"path": "/Oem/Hpe/DIMMStatus", "renderJSON": lambda data: data}, + "FWVersion": {"path": "/FirmwareRevision", "renderJSON": lambda data: data}, + "Life": { + "path": "/Oem/Hpe/PredictedMediaLifeLeftPercent", + "renderText": MapperRenderers.format_percentage, + "renderJSON": MapperRenderers.format_percentage_json, + }, + "Location": {"path": "/DeviceLocator", "renderJSON": lambda data: data}, + "Status": {"path": "/Status/Health", "renderJSON": lambda data: data}, + "VolatileSize": { + "path": "/VolatileRegionSizeLimitMiB", + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, +} + +CONFIG_MAPPING_TABLE = { + "PmemInterleaved": {"path": "", "compute": MapperRenderers.find_pmem_interleaved}, + "PmemSize": { + "path": "/PersistentRegionSizeLimitMiB", + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, + "Location": { + "path": "/DeviceLocator", + "renderJSON": lambda data: data.encode("ascii", "ignore"), + }, + "VolatileSize": { + "path": "/VolatileRegionSizeLimitMiB", + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, +} + +SUMMARY_MAPPING_TABLE = { + "TotalCapacity": { + "path": "", + "compute": MapperRenderers.calculate_total_capacity, + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, + "TotalPmemSize": { + "path": "", + "compute": MapperRenderers.calculate_total_pmem, + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, + "TotalVolatileSize": { + "path": "", + "compute": MapperRenderers.calculate_total_volatile, + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, +} + +LOGICAL_MAPPING_TABLE = { + "PmemSize": { + "path": "/MemoryChunkSizeMiB", + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, + "DimmIds": { + "path": "", + "compute": MapperRenderers.find_dimm_ids, + "renderJSON": lambda data: data, + }, +} + +TASK_MAPPING_TABLE = { + "DimmIds": { + "path": "/Payload/JsonBody", + "compute": MapperRenderers.find_dimm_ids, + "renderJSON": lambda data: data, + }, + "MemoryChunkSize": { + "path": "/Payload/JsonBody/MemoryChunkSizeMiB", + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, + "MemoryChunkSizePercentage": { + "path": "/Payload/JsonBody/Oem/Hpe/MemoryChunkSizePercentage", + "renderText": MapperRenderers.format_percentage, + "renderJSON": MapperRenderers.format_percentage_json, + }, + "PmemSize": { + "path": "", + "compute": MapperRenderers.calculate_task_pmem_size, + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, + "Operation": { + "path": "/Payload/HttpOperation", + "compute": MapperRenderers.map_operation, + }, + "Type": {"path": "/Payload/JsonBody/AddressRangeType"}, + "VolatileSize": { + "path": "", + "compute": MapperRenderers.calculate_task_volatile_size, + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, +} + +DELETE_TASK_MAPPING_TABLE = { + "DimmIds": { + "path": "", + "compute": MapperRenderers.find_dimm_ids, + "renderJSON": lambda data: data, + }, + "Operation": {"path": "", "compute": lambda **kwargs: "DELETE"}, + "PmemSize": { + "path": "/MemoryChunkSizeMiB", + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, + "VolatileSize": { + "path": "", + "compute": MapperRenderers.calculate_chunk_volatile_size, + "renderText": MapperRenderers.format_num, + "renderJSON": MapperRenderers.format_num_json, + }, +} + + +class MappingTable(Enum): + """Enum class representing mapping tables""" + + device = DEVICE_MAPPING_TABLE + summary = SUMMARY_MAPPING_TABLE + config = CONFIG_MAPPING_TABLE + logical = LOGICAL_MAPPING_TABLE + tasks = TASK_MAPPING_TABLE + delete_task = DELETE_TASK_MAPPING_TABLE diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/PmemHelpers.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/PmemHelpers.py new file mode 100644 index 0000000..0dee0cf --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/PmemHelpers.py @@ -0,0 +1,132 @@ +### +# Copyright 2016-2021 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 -*- +"""This module contains common helper functions used by several pmem commands""" + + +class PmemHelpers(object): + """ + Class containing common helper functions used by several pmem commands + """ + + @staticmethod + def py3_round(number, precision): + """ + Rounds numbers in accordance with the Python 3 round specification + :param number: number to be rounded + :type number: floating point number + :param precision: Number of decimal places the number should be rounded off to + :type precision: integer + :return: rounded off number + """ + if abs(round(number) - number) == 0.5: + return 2.0 * round(number / 2.0, precision) + return round(number, precision) + + @staticmethod + def parse_dimm_id(dimm_id_list): + """ + Converts DIMM IDs from the 'X@Y' format + to the 'PROC X DIMM Y' format + :param dimm_id_list: DIMM IDs in the 'X@Y' format + :type dimm_id_list: list + :return: list of DIMM IDs in the 'PROC X DIMM Y' format + """ + parsed_list = list() + for dimm_id in dimm_id_list: + temp = dimm_id.split("@") + parsed_list.append("PROC " + temp[0] + " DIMM " + temp[1]) + return parsed_list + + @staticmethod + def get_pmem_members(memory_members): + """ + Filters persistent memory members from memory resources + :param memory_members: members of memory collection resource + :type memory_members: list of members + :returns: list of persistent memory members if found else empty list + """ + base_module_type = "PMM" + pmem_members = list() + pmem_dimm_id = set() + for member in memory_members: + memory_type = member.get("Oem").get("Hpe").get("BaseModuleType") + if memory_type == base_module_type: + pmem_members.append(member) + pmem_dimm_id.add(member.get("DeviceLocator")) + return pmem_members, pmem_dimm_id + + @staticmethod + def get_non_aep_members(memory_members): + """ + Filters dram memory members from memory resources + :param memory_members: members of memory collection resource + :type memory_members: list of members + :returns: list of dram memory members if found else empty list + """ + base_module_type = "PMM" + dram_members = list() + dram_dimm_id = set() + for member in memory_members: + memory_type = member.get("Oem").get("Hpe").get("BaseModuleType") + if memory_type != base_module_type: + dram_members.append(member) + dram_dimm_id.add(member.get("DeviceLocator")) + return dram_members, dram_dimm_id + + @staticmethod + def json_to_text(dictionary): + """ + Converts json to string format + :param dictionary: json to be converted + :return: list containing the string + """ + output = "" + for key, value in dictionary.items(): + item = "\n" + key + ":" + str(value) + output += item + return [output] + + @staticmethod + def location_format_converter(location_list): + """ + Converts location format from 'PROC X DIMM Y' to 'X@Y' + :param location_list: list of locations of the format 'PROC X DIMM Y' + :type location_list: list of strings + :returns: string of locations in the 'X@Y' format (comma separated) + """ + converted_str = "" + for location in location_list: + temp = location.split(" ") + converted_str += temp[1] + "@" + temp[3] + if location is not location_list[-1]: + converted_str += ", " + return converted_str, ("PROC " + converted_str[0]) + + @staticmethod + def compare_id(id1, id2): + """ + Compares two ids + :param id1: first id to be compared + :param id2: second id to be compared + :return: True if ids are same else False + """ + if id1[-1] == "/": + id1 = id1[:-1] + if id2[-1] == "/": + id2 = id2[:-1] + return id1 == id2 diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/RestHelpers.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/RestHelpers.py new file mode 100644 index 0000000..66d1ca3 --- /dev/null +++ b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/RestHelpers.py @@ -0,0 +1,268 @@ +### +# Copyright 2016-2021 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 -*- +"""This is the helper class with functions that manipulate REST data""" +from __future__ import absolute_import # check if python3 supported + +import json +import multiprocessing +from multiprocessing.dummy import Pool as ThreadPool + + +class RestHelpers(object): + """This is the helper class with functions that manipulate REST data""" + + def __init__(self, rdmcObject): + self.rdmc = rdmcObject # relies on the updated reference to the RDMC class object + + def get_resource(self, url): + """ + Perform a GET request for the specified URL + :param url: the URL of the resource to fetch + :type: string + :returns: object containing the REST responsed + :rtype: RestResponse object + """ + accepted_status = [200, 202] + resp = self.rdmc.app.get_handler(url, service=True, silent=True) + if resp and resp.status in accepted_status and resp.dict: + return resp.dict + return None + + def retrieve_memory_resources(self): + """ + Retrieve the expanded Memory Collection resource + """ + return self.get_resource("/redfish/v1/systems/1/memory?$expand=.") + + def retrieve_mem_domain_resources(self, chunks_flag=True): + """ + Retrieve Memory Domain Resources and All Chunks + """ + mem_domain_resources = self.get_resource("/redfish/v1/systems/1/MemoryDomains?$expand=.") + domain_members = [] + all_chunks = [] + if mem_domain_resources: + domain_members = mem_domain_resources.get("Members") + if not chunks_flag: + return domain_members + chunk_id_list = list() + for member in domain_members: + chunk_id_list.append(member.get("MemoryChunks").get("@odata.id") + "?$expand=.") + if chunk_id_list: + chunks = self.concurrent_get(chunk_id_list) + else: + chunks = [] + # combining all chunks + for chunk in chunks: + chunk_members = chunk.get("Members") + if chunk_members: + temp_chunks = [member for member in chunk_members] + else: + temp_chunks = [] + all_chunks.extend(temp_chunks) + return domain_members, all_chunks + + @staticmethod + def filter_task_members(task_members): + """ + Filters new memory chunk task members + :param task_members: list of task members to be filtered + :return: list of filtered task members + """ + members = list() + for member in task_members: + # retrieving only new tasks + if member.get("TaskState") == "New": + target_uri = member.get("Payload").get("TargetUri") + # checking if type Memory Chunk collection + if "MemoryChunks" in target_uri: + json_body = member.get("Payload").get("JsonBody") + if json_body: + member["Payload"]["JsonBody"] = json.loads(json_body) + members.append(member) + return members + + def retrieve_task_members(self): + """ + Retrieve task members + :returns: list of task members + """ + # retrieving task resources + task_resources = self.get_resource("/redfish/v1/TaskService/Tasks?$expand=.") + # getting task members + if task_resources: + task_members = task_resources.get("Members") + if task_members: + return task_members + return [] + + def retrieve_mem_and_mem_domains(self): + """ + Helper Function to retrieve Memory Resources and Memory Domain Resources + concurrently + :returns: memory resources, memory domain members and a list of memory chunks + """ + # 'resource_list' is a list of functions that will be called to retrieve resources. + resource_list = [ + self.retrieve_memory_resources, + self.retrieve_mem_domain_resources, + ] + # 'response_list' contains the list of responses received on calling the functions + # that are a part of 'resource_list', in the same order. + response_list = self.concurrent_retrieve(resource_list) + # Segregating individual responses from 'response_list': + # The 'get()' call is responsible for retrieving the value that a function returns + # from the 'AsyncResult' object returned by 'apply_async' in 'concurrent_retrieve'. + memory = response_list[0].get() + # domain_members and all chunks are returned as a tuple in 'response_list' as per the + # 'retrieve_mem_domain_resources()' function definition. They are unpacked here. + (domain_members, all_chunks) = response_list[1] + # Returns the values of resources obtained above. + return memory, domain_members, all_chunks + + def retrieve_task_members_and_mem_domains(self): + """ + Helper Function to retrieve Task Resources and Memory Domain Resources + concurrently + :returns: task members, memory domain members and a list of memory chunks + """ + # 'resource_list' is a list of functions that will be called to retrieve resources. + resource_list = [self.retrieve_task_members, self.retrieve_mem_domain_resources] + # 'response_list' contains the list of responses received on calling the functions + # that are a part of 'resource_list', in the same order. + response_list = self.concurrent_retrieve(resource_list) + # Segregating individual responses from 'response_list': + # The 'get()' call is responsible for retrieving the value that a function returns + # from the 'AsyncResult' object returned by 'apply_async' in 'concurrent_retrieve'. + task_members = response_list[0].get() + # domain_members and all chunks are returned as a tuple in 'response_list' as per the + # 'retrieve_mem_domain_resources()' function definition. They are unpacked here. + (domain_members, all_chunks) = response_list[1] + # Returns the values of resources obtained above. + return task_members, domain_members, all_chunks + + @staticmethod + def concurrent_retrieve(resource_list): + """ + Concurrently retrieve Resources + :param resource_list: List of resources to retrieve concurrently + :type: List of functions + :returns: List of response objects obtained as a result of GET requests + :rtype: List of objects + """ + response_list = list() + # Spawn a pool of worker threads based on the CPU core count and + # the number of functions to call. + # 'len(resource_list) - 1' ensures that the last function call happens + # on the main thread and not on a worker thread. + pool = ThreadPool(min(len(resource_list) - 1, multiprocessing.cpu_count())) + # Asynchronously call funtions from 'resource_list' on worker threads and + # append responses to 'response_list'. These responses will be 'AsyncResult' + # objects and the actual return value will have to be retrieved by a 'get()' + # call to the returned response. + for resource in resource_list[:-1]: + response_list.append(pool.apply_async(resource)) + # Call the last function in 'resource_list' on the main thread so that + # the main thread is doing some useful work and not sitting idle waiting + # for all other threads to complete. This reduces the thread communication + # overhead as well. The response returned here will not be an 'AsyncResult' + # object and no 'get()' call is required to retrieve the actual return value. + temp_response = resource_list[-1]() + # Wait for all worker threads to complete execution. + pool.close() + pool.join() + # Once all threads have finished execution, append the response recieved + # on the main thread to the 'response_list'. + response_list.append(temp_response) + # Return list of responses obtained from the main and worker threads. + return response_list + + def concurrent_get(self, uri_list): + """ + Spawns a Threadpool and sends concurrent GET Requests + :param uri_list: List of URIs on which the GET Requests + are supposed to be made + :returns: List of responses from the GET Requests + """ + # Spawn a pool of worker threads based on the CPU core count and + # the number of GET requests to be sent + pool = ThreadPool(min(multiprocessing.cpu_count(), len(uri_list))) + response_list = pool.map(self.get_resource, uri_list) + # Wait for worker threads to complete + pool.close() + pool.join() + # Return list of responses obtained via GET requests from worker threads + return response_list + + def delete_resource(self, url): + """ + Perform a delete request for the specified URL + :param url: the URL of the resource to delete + :type: string + :returns: status code + """ + resp = self.rdmc.app.delete_handler(url, service=True, silent=True) + if resp and resp.status in [200, 202]: + return resp.status + return None + + def post_resource(self, path, body): + """ + Perform a post request for the specified path with body + :param path: the URL path + :type: string + :body: the body to be sent + :type: string + :returns: status code + """ + accepted_status = [200, 201, 202, 204] + resp = self.rdmc.app.post_handler(path, body, service=True, silent=True) + if resp and resp.status in accepted_status: + return resp.status + return None + + def in_post(self): + """ + Check whether the server is in POST + :return: True if server is in Post, False otherwise + :rtype: Boolean + """ + resp_body = self.get_resource("/redfish/v1/systems/1") + if resp_body: + if resp_body.get("Oem").get("Hpe").get("PostState") == "FinishedPost": + return False + return True + + def retrieve_security_state(self, path): + """ + Get the security state + """ + return self.get_resource(path) + + def retrieve_pmem_location(self): + """ + Retrieve the Pmem location + """ + return self.get_resource("/redfish/v1/Systems/1/Memory/?$expand=.#") + + def retrieve_model(self, rdmcObj): + """ + Retrieve the Model + """ + self.rdmc = rdmcObj + return self.get_resource("/redfish/v1/Chassis/1") diff --git a/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/__init__.py b/ilorest/extensions/PERSISTENT_MEMORY_COMMANDS/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/extensions/RAW_COMMANDS/RawDeleteCommand.py b/ilorest/extensions/RAW_COMMANDS/RawDeleteCommand.py new file mode 100644 index 0000000..471afb1 --- /dev/null +++ b/ilorest/extensions/RAW_COMMANDS/RawDeleteCommand.py @@ -0,0 +1,219 @@ +### +# Copyright 2016-2021 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 -*- +""" RawDelete Command for rdmc """ + +import sys + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class RawDeleteCommand: + """Raw form of the delete command""" + + def __init__(self): + self.ident = { + "name": "rawdelete", + "usage": None, + "description": "Run to to delete data from" + ' the passed in path.\n\texample: rawdelete "/redfish/v1/' + 'Sessions/(session ID)"\n', + "summary": "Raw form of the DELETE command.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main raw delete worker function + + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + headers = {} + + if hasattr(options, "sessionid") and options.sessionid: + self.sessionvalidation(options) + else: + self.deletevalidation(options) + + if options.path.startswith('"') and options.path.endswith('"'): + options.path = options.path[1:-1] + + if options.expand: + options.path = options.path + "?$expand=." + + try: + currentsess = self.rdmc.app.current_client.session_location + except: + currentsess = None + + if options.headers: + extraheaders = options.headers.split(",") + + for item in extraheaders: + header = item.split(":") + + try: + headers[header[0]] = header[1] + except: + InvalidCommandLineError("Invalid format for --headers option.") + + if currentsess and (options.path in currentsess): + self.rdmc.app.logout() + self.rdmc.ui.printer( + "Your session has been terminated (deleted).\nPlease log " "back in if you wish to continue.\n" + ) + else: + returnresponse = False + + if options.response or options.getheaders: + returnresponse = True + + results = self.rdmc.app.delete_handler( + options.path, + headers=headers, + silent=options.silent, + service=options.service, + ) + + if returnresponse and results: + if options.getheaders: + self.rdmc.ui.print_out_json(dict(results.getheaders())) + + if options.response: + sys.stdout.write(results.read) + self.rdmc.ui.printer("\n") + elif results.status == 404: + return ReturnCodes.NO_CONTENTS_FOUND_FOR_OPERATION + elif results.status != 200: + return ReturnCodes.UI_CLI_USAGE_EXCEPTION + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def deletevalidation(self, options): + """Raw delete validation function + + :param options: command line options + :type options: list. + """ + self.rdmc.login_select_validation(self, options, skipbuild=True) + + def sessionvalidation(self, options): + """Raw delete session validation function + + :param options: command line options + :type options: list. + """ + + url = None + if options.user or options.password or options.url: + if options.url: + url = options.url + else: + if self.rdmc.app.config.get_url(): + url = self.rdmc.app.config.get_url() + if url and "https://" not in url: + url = "https://" + url + + return url + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "path", + help="Uri on iLO to be deleted.", + ) + customparser.add_argument( + "--response", + dest="response", + action="store_true", + help="Use this flag to return the iLO response body.", + default=False, + ) + customparser.add_argument( + "--getheaders", + dest="getheaders", + action="store_true", + help="Use this flag to return the iLO response headers.", + default=False, + ) + customparser.add_argument( + "--headers", + dest="headers", + help="Use this flag to add extra headers to the request." + "\t\t\t\t\t Usage: --headers=HEADER:VALUE,HEADER:VALUE", + default=None, + ) + customparser.add_argument( + "--silent", + dest="silent", + action="store_true", + help="""Use this flag to silence responses""", + default=False, + ) + customparser.add_argument( + "--service", + dest="service", + action="store_true", + help="""Use this flag to enable service mode and increase the function speed""", + default=False, + ) + customparser.add_argument( + "--expand", + dest="expand", + action="store_true", + help="""Use this flag to expand the path specified using the """ """expand notation '?$expand=.'""", + default=False, + ) diff --git a/ilorest/extensions/RAW_COMMANDS/RawGetCommand.py b/ilorest/extensions/RAW_COMMANDS/RawGetCommand.py new file mode 100644 index 0000000..57f9946 --- /dev/null +++ b/ilorest/extensions/RAW_COMMANDS/RawGetCommand.py @@ -0,0 +1,284 @@ +### +# Copyright 2016-2021 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 -*- +""" RawGet Command for rdmc """ + +import json + +import redfish + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ResourceNotReadyError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ResourceNotReadyError, + ReturnCodes, + ) + + +class RawGetCommand: + """Raw form of the get command""" + + def __init__(self): + self.ident = { + "name": "rawget", + "usage": None, + "description": "Run to to retrieve data from " + 'the passed in path.\n\tExample: rawget "/redfish/v1/' + 'systems/(system ID)"', + "summary": "Raw form of the GET command.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main raw get worker function + + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + headers = {} + + if hasattr(options, "sessionid") and options.sessionid: + _ = self.sessionvalidation(options) + else: + self.getvalidation(options) + + if options.path.endswith("?=."): + path = options.path + strip = path[:-3] + options.path = strip + "?$expand=." + + if options.path.startswith('"') and options.path.endswith('"'): + options.path = options.path[1:-1] + + if options.expand: + options.path = options.path + "?$expand=." + + if options.headers: + extraheaders = options.headers.split(",") + for item in extraheaders: + header = item.split(":") + + try: + headers[header[0]] = header[1] + except: + InvalidCommandLineError("Invalid format for --headers " "option.") + + returnresponse = False + if options.response or options.getheaders: + returnresponse = True + + extra_path = None + if "#" in options.path: + path_list = options.path.split("#") + options.path = path_list[0] + extra_path = path_list[1] + if "/" in extra_path: + extra_path_list = extra_path.split("/") + extra_path_list = list(filter(None, extra_path_list)) + + results = self.rdmc.app.get_handler( + options.path, + sessionid=options.sessionid, + headers=headers, + silent=options.silent, + service=options.service, + username=options.user, + password=options.password, + base_url=options.url, + ) + result = None + if results.dict: + if extra_path: + result = results.dict + for p in extra_path_list: + if p.isdigit(): + p = int(p) + result = result[p] + else: + result = results.dict + + if results and results.status == 200 and options.binfile: + output = results.read + filehndl = open(options.binfile[0], "wb") + filehndl.write(output) + filehndl.close() + elif results and returnresponse: + if options.getheaders: + self.rdmc.ui.printer(json.dumps(dict(results.getheaders())) + "\n") + if options.response: + self.rdmc.ui.printer(results.read + "\n") + elif results and results.status == 200: + if results.dict: + if options.filename: + output = json.dumps( + result, + indent=2, + cls=redfish.ris.JSONEncoder, + sort_keys=True, + ) + + filehndl = open(options.filename[0], "w") + filehndl.write(output) + filehndl.close() + + self.rdmc.ui.printer("Results written out to '%s'.\n" % options.filename[0]) + else: + if not result: + result = results.dict + if options.service: + self.rdmc.ui.printer("%s\n" % result) + else: + self.rdmc.ui.print_out_json(result) + else: + json_payload = json.loads(results._http_response.data) + try: + message_id = json_payload["error"]["@Message.ExtendedInfo"][0]["MessageId"] + self.rdmc.ui.error("%s" % message_id) + if "ResourceNotReadyRetry" in message_id: + raise ResourceNotReadyError("Resources are not ready in iLO, Please wait for some time and retry") + except: + self.rdmc.ui.error("An invalid or incomplete response was received: %s\n" % json_payload) + return ReturnCodes.NO_CONTENTS_FOUND_FOR_OPERATION + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def getvalidation(self, options): + """Raw get validation function + + :param options: command line options + :type options: list. + """ + self.rdmc.login_select_validation(self, options, skipbuild=True) + + def sessionvalidation(self, options): + """Raw patch session validation function + + :param options: command line options + :type options: list. + """ + + url = None + if options.user or options.password or options.url: + if options.url: + url = options.url + else: + if self.rdmc.app.redfishinst and self.rdmc.app.redfishinst.base_url: + url = self.rdmc.app.redfishinst.base_url + if url and "blobstore://" not in url and "https://" not in url: + url = "https://" + url + + return url + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "path", + help="Uri on iLO", + ) + customparser.add_argument( + "--response", + dest="response", + action="store_true", + help="Use this flag to return the iLO response body.", + default=False, + ) + customparser.add_argument( + "--getheaders", + dest="getheaders", + action="store_true", + help="Use this flag to return the iLO response headers.", + default=False, + ) + customparser.add_argument( + "--headers", + dest="headers", + help="Use this flag to add extra headers to the request." " example: --headers=HEADER:VALUE,HEADER:VALUE", + default=None, + ) + customparser.add_argument( + "--silent", + dest="silent", + action="store_true", + help="""Use this flag to silence responses""", + default=False, + ) + customparser.add_argument( + "-f", + "--filename", + dest="filename", + help="""Write results to the specified file.""", + action="append", + default=None, + ) + customparser.add_argument( + "-b", + "--writebin", + dest="binfile", + help="""Write the results to the specified file in binary.""", + action="append", + default=None, + ) + customparser.add_argument( + "--service", + dest="service", + action="store_true", + help="""Use this flag to enable service mode and increase the function speed""", + default=False, + ) + customparser.add_argument( + "--expand", + dest="expand", + action="store_true", + help="""Use this flag to expand the path specified using the """ """expand notation '?$expand=.'""", + default=False, + ) diff --git a/ilorest/extensions/RAW_COMMANDS/RawHeadCommand.py b/ilorest/extensions/RAW_COMMANDS/RawHeadCommand.py new file mode 100644 index 0000000..c206930 --- /dev/null +++ b/ilorest/extensions/RAW_COMMANDS/RawHeadCommand.py @@ -0,0 +1,148 @@ +### +# Copyright 2016-2021 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 -*- +""" RawHead Command for rdmc """ + +import json + +import redfish + +try: + from rdmc_helper import InvalidCommandLineErrorOPTS, ReturnCodes +except ImportError: + from ilorest.rdmc_helper import InvalidCommandLineErrorOPTS, ReturnCodes + + +class RawHeadCommand: + """Raw form of the head command""" + + def __init__(self): + self.ident = { + "name": "rawhead", + "usage": None, + "description": "Run to to retrieve data from the " + 'passed in path\n\texample: rawhead "/redfish/v1/systems/' + '(system ID)"\n', + "summary": "Raw form of the HEAD command.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main raw head worker function + + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.headvalidation(options) + + if options.path.startswith('"') and options.path.endswith('"'): + options.path = options.path[1:-1] + + results = self.rdmc.app.head_handler(options.path, silent=options.silent, service=options.service) + + content = None + tempdict = dict() + + if results and results.status == 200: + if results._http_response: + content = results.getheaders() + else: + content = results._headers + + tempdict = dict(content) + + if options.filename: + output = json.dumps(tempdict, indent=2, cls=redfish.ris.JSONEncoder, sort_keys=True) + filehndl = open(options.filename[0], "w") + filehndl.write(output) + filehndl.close() + + self.rdmc.ui.printer("Results written out to '%s'.\n" % options.filename[0]) + else: + if options.service: + self.rdmc.ui.printer("%s\n" % tempdict) + else: + self.rdmc.ui.print_out_json(tempdict) + else: + return ReturnCodes.NO_CONTENTS_FOUND_FOR_OPERATION + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def headvalidation(self, options): + """Raw head validation function + + :param options: command line options + :type options: list. + """ + self.rdmc.login_select_validation(self, options, skipbuild=True) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "path", + help="Uri on iLO to query HEAD.", + ) + customparser.add_argument( + "--silent", + dest="silent", + action="store_true", + help="""Use this flag to silence responses""", + default=None, + ) + customparser.add_argument( + "-f", + "--filename", + dest="filename", + help="""Use the provided filename to perform operations.""", + action="append", + default=None, + ) + customparser.add_argument( + "--service", + dest="service", + action="store_true", + help="""Use this flag to enable service mode and increase the function speed""", + default=False, + ) diff --git a/ilorest/extensions/RAW_COMMANDS/RawPatchCommand.py b/ilorest/extensions/RAW_COMMANDS/RawPatchCommand.py new file mode 100644 index 0000000..7819d74 --- /dev/null +++ b/ilorest/extensions/RAW_COMMANDS/RawPatchCommand.py @@ -0,0 +1,234 @@ +### +# Copyright 2016-2021 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 -*- +""" RawPatch Command for rdmc """ + +import json +import re +from collections import OrderedDict + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + InvalidFileInputError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + InvalidFileInputError, + ReturnCodes, + ) + + +class RawPatchCommand: + """Raw form of the patch command""" + + def __init__(self): + self.ident = { + "name": "rawpatch", + "usage": None, + "description": "Run to send a patch from the data" + " in the input file.\n\tMultiple PATCHes can be performed in sequence by " + "\n\tadding more path/body key/value pairs.\n" + "\n\texample: rawpatch rawpatch.txt" + '\n\n\tExample input file:\n\t{\n\t "/redfish/' + 'v1/systems/(system ID)":\n\t {\n\t ' + '"AssetTag": "NewAssetTag"\n\t }\n\t}', + "summary": "Raw form of the PATCH command.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main raw patch worker function + + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + headers = {} + results = [] + + if hasattr(options, "sessionid") and options.sessionid: + _ = self.sessionvalidation(options) + else: + self.patchvalidation(options) + + contentsholder = None + try: + with open(options.path, "r") as _if: + contentsholder = json.loads(_if.read(), object_pairs_hook=OrderedDict) + except IOError: + raise InvalidFileInputError( + "File '%s' doesn't exist. " "Please create file by running 'save' command." % options.path + ) + except ValueError: + raise InvalidFileFormattingError("Input file '%s' was not " "formatted properly." % options.path) + + if options.headers: + extraheaders = options.headers.split(",") + + for item in extraheaders: + header = item.split(":") + + try: + headers[header[0]] = header[1] + except: + raise InvalidCommandLineError("Invalid format for --headers option.") + + if "path" in contentsholder and "body" in contentsholder: + results.append( + self.rdmc.app.patch_handler( + contentsholder["path"], + contentsholder["body"], + headers=headers, + silent=options.silent, + optionalpassword=options.biospassword, + service=options.service, + ) + ) + + elif all([re.match("^\/(\S+\/?)+$", key) for key in contentsholder]): + for path, body in contentsholder.items(): + results.append( + self.rdmc.app.patch_handler( + path, + body, + headers=headers, + silent=options.silent, + optionalpassword=options.biospassword, + service=options.service, + ) + ) + else: + raise InvalidFileFormattingError("Input file '%s' was not format properly." % options.path) + + returnresponse = False + + if options.response or options.getheaders: + returnresponse = True + + if results and returnresponse: + for result in results: + if options.getheaders: + self.rdmc.ui.print_out_json(dict(result.getheaders())) + + if options.response: + self.rdmc.ui.printer(result.read) + self.rdmc.ui.printer("\n") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def patchvalidation(self, options): + """Raw patch validation function + + :param options: command line options + :type options: list. + """ + self.rdmc.login_select_validation(self, options, skipbuild=True) + + def sessionvalidation(self, options): + """Raw patch session validation function + + :param options: command line options + :type options: list. + """ + + url = None + if options.user or options.password or options.url: + if options.url: + url = options.url + else: + if self.rdmc.app.redfishinst.base_url: + url = self.rdmc.app.redfishinst.base_url + if url and "https://" not in url: + url = "https://" + url + + return url + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "path", + help="Path to the JSON file containing the data to be patched.", + ) + customparser.add_argument( + "--silent", + dest="silent", + action="store_true", + help="""Use this flag to silence responses""", + default=False, + ) + customparser.add_argument( + "--response", + dest="response", + action="store_true", + help="Use this flag to return the iLO response body.", + default=False, + ) + customparser.add_argument( + "--getheaders", + dest="getheaders", + action="store_true", + help="Use this flag to return the iLO response headers.", + default=False, + ) + customparser.add_argument( + "--headers", + dest="headers", + help="Use this flag to add extra headers to the request." + "\t\t\t\t\t Usage: --headers=HEADER:VALUE,HEADER:VALUE", + default=None, + ) + customparser.add_argument( + "--service", + dest="service", + action="store_true", + help="""Use this flag to enable service mode and increase the function speed""", + default=False, + ) diff --git a/ilorest/extensions/RAW_COMMANDS/RawPostCommand.py b/ilorest/extensions/RAW_COMMANDS/RawPostCommand.py new file mode 100644 index 0000000..b0128ed --- /dev/null +++ b/ilorest/extensions/RAW_COMMANDS/RawPostCommand.py @@ -0,0 +1,245 @@ +### +# Copyright 2016-2021 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 -*- +""" RawPost Command for rdmc """ + +import json +import re +from collections import OrderedDict + +try: + from rdmc_helper import ( + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + InvalidFileInputError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + InvalidFileInputError, + ReturnCodes, + ) + + +class RawPostCommand: + """Raw form of the post command""" + + def __init__(self): + self.ident = { + "name": "rawpost", + "usage": None, + "description": "Run to send a post from " + "the data in the input file.\n\tMultiple POSTs can be performed in sequence by" + " \n\tadding more path/body key/value pairs.\n" + "\n\texample: rawpost rawpost." + 'txt\n\n\tExample input file:\n\t{\n\t "/' + "redfish/v1/systems/(system ID)/Actions/ComputerSystem." + 'Reset":\n\t {\n\t "ResetType": ' + '"ForceRestart"\n\t }\n\t}', + "summary": "Raw form of the POST command.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main raw patch worker function + + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + headers = {} + results = [] + + if hasattr(options, "sessionid") and options.sessionid: + self.sessionvalidation(options) + else: + self.postvalidation(options) + + contentsholder = None + + try: + with open(options.path, "r") as _if: + contentsholder = json.loads(_if.read(), object_pairs_hook=OrderedDict) + except IOError: + raise InvalidFileInputError( + "File '%s' doesn't exist. " "Please create file by running 'save' command." % options.path + ) + except ValueError: + raise InvalidFileFormattingError("Input file '%s' was not " "formatted properly." % options.path) + if options.encode: + if ( + "body" in contentsholder + and "UserName" in contentsholder["body"] + and "Password" in contentsholder["body"] + and len(list(contentsholder["body"].keys())) == 2 + ): + encobj = Encryption() + contentsholder["body"]["UserName"] = encobj.decode_credentials(contentsholder["body"]["UserName"]) + contentsholder["body"]["Password"] = encobj.decode_credentials(contentsholder["body"]["Password"]) + + if options.headers: + extraheaders = options.headers.split(",") + for item in extraheaders: + header = item.split(":") + + try: + headers[header[0]] = header[1] + except: + raise InvalidCommandLineError("Invalid format for --headers option.") + + if "path" in contentsholder and "body" in contentsholder: + results.append( + self.rdmc.app.post_handler( + contentsholder["path"], + contentsholder["body"], + headers=headers, + silent=options.silent, + service=options.service, + ) + ) + elif all([re.match("^\/(\S+\/?)+$", key) for key in contentsholder]): + for path, body in contentsholder.items(): + results.append( + self.rdmc.app.post_handler( + path, + body, + headers=headers, + silent=options.silent, + service=options.service, + ) + ) + else: + raise InvalidFileFormattingError("Input file '%s' was not " "formatted properly." % options.path) + returnresponse = False + + if options.response or options.getheaders: + returnresponse = True + + if results and returnresponse: + for result in results: + if options.getheaders: + self.rdmc.ui.print_out_json(dict(result.getheaders())) + + if options.response: + if isinstance(result.ori, bytes): + self.rdmc.ui.printer(result.ori.decode("utf-8") + "\n") + else: + self.rdmc.ui.printer(result.ori + "\n") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def postvalidation(self, options): + """Raw post validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options, skipbuild=True) + + def sessionvalidation(self, options): + """Raw post session validation function + + :param options: command line options + :type options: list. + """ + + url = None + if options.user or options.password or options.url: + if options.url: + url = options.url + else: + if self.rdmc.app.redfishinst.base_url: + url = self.rdmc.app.redfishinst.base_url + if url and "https://" not in url: + url = "https://" + url + + return url + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "path", + help="Path to the JSON file containing the data to be patched.", + ) + customparser.add_argument( + "--response", + dest="response", + action="store_true", + help="Use this flag to return the iLO response body.", + default=False, + ) + customparser.add_argument( + "--getheaders", + dest="getheaders", + action="store_true", + help="Use this flag to return the iLO response headers.", + default=False, + ) + customparser.add_argument( + "--headers", + dest="headers", + help="Use this flag to add extra headers to the request." + "\t\t\t\t\t Usage: --headers=HEADER:VALUE,HEADER:VALUE", + default=None, + ) + customparser.add_argument( + "--silent", + dest="silent", + action="store_true", + help="""Use this flag to silence responses""", + default=False, + ) + customparser.add_argument( + "--service", + dest="service", + action="store_true", + help="""Use this flag to enable service mode and increase the function speed""", + default=False, + ) diff --git a/ilorest/extensions/RAW_COMMANDS/RawPutCommand.py b/ilorest/extensions/RAW_COMMANDS/RawPutCommand.py new file mode 100644 index 0000000..388665a --- /dev/null +++ b/ilorest/extensions/RAW_COMMANDS/RawPutCommand.py @@ -0,0 +1,239 @@ +### +# Copyright 2016-2021 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 -*- +""" RawPut Command for rdmc """ + +import json +import re +import sys +from collections import OrderedDict + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + InvalidFileInputError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + InvalidFileInputError, + ReturnCodes, + ) + + +class RawPutCommand: + """Raw form of the put command""" + + def __init__(self): + self.ident = { + "name": "rawput", + "usage": None, + "description": "Run to send a post from " + "the data in the input file.\n\tMultiple PUTs can be performed in sequence by " + "\n\tadding more path/body key/value pairs.\n" + "\n\texample: rawput rawput." + 'txt\n\n\tExample input file:\n\t{\n\t "/redfish/' + 'v1/systems/(system ID)/bios/Settings/":\n\t {\n\t' + '\t"Attributes": {\n\t\t' + ' "BaseConfig": "default"\n\t\t}\n\t }\n\t}', + "summary": "Raw form of the PUT command.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main raw put worker function + + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + headers = {} + results = [] + + if hasattr(options, "sessionid") and options.sessionid: + self.sessionvalidation(options) + else: + self.putvalidation(options) + + contentsholder = None + + try: + with open(options.path, "r") as _if: + contentsholder = json.loads(_if.read(), object_pairs_hook=OrderedDict) + except IOError: + raise InvalidFileInputError( + "File '%s' doesn't exist. " "Please create file by running 'save' command." % options.path + ) + except ValueError: + raise InvalidFileFormattingError("Input file '%s' was not " "formatted properly." % options.path) + + if options.headers: + extraheaders = options.headers.split(",") + + for item in extraheaders: + header = item.split(":") + + try: + headers[header[0]] = header[1] + except: + raise InvalidCommandLineError("Invalid format for --headers option.") + + if "path" in contentsholder and "body" in contentsholder: + results.append( + self.rdmc.app.put_handler( + contentsholder["path"], + contentsholder["body"], + headers=headers, + silent=options.silent, + optionalpassword=options.biospassword, + service=options.service, + ) + ) + elif all([re.match("^\/(\S+\/?)+$", key) for key in contentsholder]): + for path, body in contentsholder.items(): + results.append( + self.rdmc.app.put_handler( + path, + body, + headers=headers, + silent=options.silent, + optionalpassword=options.biospassword, + service=options.service, + ) + ) + else: + raise InvalidFileFormattingError("Input file '%s' was not " "formatted properly." % options.path) + + returnresponse = False + + if options.response or options.getheaders: + returnresponse = True + + if results and returnresponse: + for result in results: + if options.getheaders: + sys.stdout.write(json.dumps(dict(result.getheaders())) + "\n") + + if options.response: + if isinstance(result.read, bytes): + sys.stdout.write(result.read.decode("utf-8")) + else: + sys.stdout.write(result.read) + sys.stdout.write("\n") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def putvalidation(self, options): + """Raw put validation function + + :param options: command line options + :type options: list. + """ + self.rdmc.login_select_validation(self, options, skipbuild=True) + + def sessionvalidation(self, options): + """Raw put session validation function + + :param options: command line options + :type options: list. + """ + + url = None + if options.user or options.password or options.url: + if options.url: + url = options.url + else: + if getattr(self.rdmc.app.redfishinst, "base_url", False): + url = self.rdmc.app.redfishinst.base_url + if url and "https://" not in url: + url = "https://" + url + + return url + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "path", + help="Path to the JSON file containing the data to be patched.", + ) + customparser.add_argument( + "--response", + dest="response", + action="store_true", + help="Use this flag to return the iLO response body.", + default=False, + ) + customparser.add_argument( + "--getheaders", + dest="getheaders", + action="store_true", + help="Use this flag to return the iLO response headers.", + default=False, + ) + customparser.add_argument( + "--headers", + dest="headers", + help="Use this flag to add extra headers to the request." + "\t\t\t\t\t Usage: --headers=HEADER:VALUE,HEADER:VALUE", + default=None, + ) + customparser.add_argument( + "--silent", + dest="silent", + action="store_true", + help="""Use this flag to silence responses""", + default=False, + ) + customparser.add_argument( + "--service", + dest="service", + action="store_true", + help="""Use this flag to enable service mode and increase the function speed""", + default=False, + ) diff --git a/ilorest/extensions/RAW_COMMANDS/__init__.py b/ilorest/extensions/RAW_COMMANDS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/extensions/SMART_ARRAY_COMMANDS/ClearControllerConfigCommand.py b/ilorest/extensions/SMART_ARRAY_COMMANDS/ClearControllerConfigCommand.py new file mode 100644 index 0000000..b983ce0 --- /dev/null +++ b/ilorest/extensions/SMART_ARRAY_COMMANDS/ClearControllerConfigCommand.py @@ -0,0 +1,166 @@ +### +# Copyright 2016-2021 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 -*- +""" Clear Controller Configuration Command for rdmc """ + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class ClearControllerConfigCommand: + """Drive erase/sanitize command""" + + def __init__(self): + self.ident = { + "name": "clearcontrollerconfig", + "usage": None, + "description": "To clear a controller" + " config.\n\tExample: clearcontrollerconfig --controller=1" + '\n\texample: clearcontrollerconfig --controller="Slot0"', + "summary": "Clears smart array controller configuration.", + "aliases": [], + "auxcommands": ["SelectCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main disk inventory worker function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.clearcontrollerconfigvalidation(options) + + ilo_ver = self.rdmc.app.getiloversion() + if ilo_ver >= 6.110: + self.rdmc.ui.printer( + "ClearController option is not supported for this device." + "\nUse factoryresetcontroller command to reset the controller," + "\tthe action does erase or sanitize data on the drives.\n" + ) + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + else: + self.auxcommands["select"].selectfunction("SmartStorageConfig.") + content = self.rdmc.app.getprops() + + if not options.controller: + raise InvalidCommandLineError("You must include a controller to select.") + if options.controller: + controllist = [] + contentsholder = { + "LogicalDrives": [], + "Actions": [{"Action": "ClearConfigurationMetadata"}], + "DataGuard": "Disabled", + } + try: + if options.controller.isdigit(): + slotlocation = self.get_location_from_id(options.controller) + if slotlocation: + slotcontrol = slotlocation.lower().strip('"').split("slot")[-1].lstrip() + for control in content: + if slotcontrol.lower() == control["Location"].lower().split("slot")[-1].lstrip(): + controllist.append(control) + elif "slot" in options.controller.lower(): + controllerid = options.controller.strip("Slot ") + slotlocation = self.get_location_from_id(controllerid) + if slotlocation: + slotcontrol = slotlocation.lower().strip('"').split("slot")[-1].lstrip() + for control in content: + if slotcontrol.lower() == control["Location"].lower().split("slot")[-1].lstrip(): + controllist.append(control) + if not controllist: + raise InvalidCommandLineError("") + except InvalidCommandLineError: + raise InvalidCommandLineError("Selected controller not found in the current " "inventory list.") + for controller in controllist: + # self.rdmc.ui.printer( + # "ClearController path and payload: %s, %s\n" + # % (controller["@odata.id"], contentsholder) + # ) + self.rdmc.app.put_handler(controller["@odata.id"], contentsholder) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def get_storage_location_from_id(self, storage_id): + for sel in self.rdmc.app.select("StorageController", path_refresh=True): + if "Collection" not in sel.maj_type: + controller_storage = sel.dict + if controller_storage["Id"] == str(storage_id): + return controller_storage["Location"]["PartLocation"]["ServiceLabel"] + return None + + def get_location_from_id(self, controller_id): + for sel in self.rdmc.app.select("SmartStorageArrayController", path_refresh=True): + if "Collection" not in sel.maj_type: + controller = sel.dict + if controller["Id"] == str(controller_id): + return controller["Location"] + return None + + def clearcontrollerconfigvalidation(self, options): + """clear controller config validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--controller", + dest="controller", + help="Use this flag to select the corresponding controller " "using either the slot number or index.", + default=None, + ) diff --git a/ilorest/extensions/SMART_ARRAY_COMMANDS/CreateVolumeCommand.py b/ilorest/extensions/SMART_ARRAY_COMMANDS/CreateVolumeCommand.py new file mode 100644 index 0000000..4076ca7 --- /dev/null +++ b/ilorest/extensions/SMART_ARRAY_COMMANDS/CreateVolumeCommand.py @@ -0,0 +1,1160 @@ +### +# Copyright 2016-2021 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 -*- +""" Create Volume Command for rdmc """ + +from argparse import RawDescriptionHelpFormatter + +from redfish.ris import IdTokenError +from redfish.ris.rmc_helper import IloResponseError, InstanceNotFoundError + +try: + from rdmc_helper import ( + IloLicenseError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidSmartArrayConfigurationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IloLicenseError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidSmartArrayConfigurationError, + ReturnCodes, + ) + + +class CreateVolumeCommand: + """Create volume command""" + + def __init__(self): + self.ident = { + "name": "createvolume", + "usage": None, + "description": "Creates volumes on compatible HPE SSA RAID controllers\nTo view " + "help on specific sub-commands run: createvolume -h\n\n" + "NOTE: Refer http://www.hpe.com/info/scmo for additional information on creating Volumes on Gen11 " + "servers.\n\t" + "Also, when you select multiple physicaldrives you can select by both\n\t" + "physical drive name and by the location at the same time.\n\t" + "You can also select controllers by slot number as well as index.\n\t" + "For iLO6, storage id need to be specified using --storageid=DE00E000 along with --controller=1", + "summary": "Creates a new volume on the selected controller.", + "aliases": ["createlogicaldrive"], + "auxcommands": ["SelectCommand", "StorageControllerCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main disk inventory worker function + + :param line: command line input + :type line: string. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.createvolumevalidation(options) + ilo_ver = self.rdmc.app.getiloversion() + if ilo_ver < 6.110: + try: + self.auxcommands["select"].selectfunction("SmartStorageConfig.") + except Exception: + if options.command == "quickdrive": + raise InvalidCommandLineError("This controller is not compatible with quickdrive option. " + "Please use customdrive or volume option\n") + ilo_ver = 6.110 + options.command = "volume" + + if ilo_ver >= 6.110: + if options.command == "customdrive" or options.command == "quickdrive": + raise InvalidCommandLineError("customdrive or quickdrive subcommand is not supported on iLO6(Gen11).\n") + if not options.storageid: + raise InvalidCommandLineError("--storageid option is mandatory for iLO6 or latest iLO5 onwards.\n") + else: + if options.command == "volume": + raise InvalidCommandLineError("volume subcommand is not supported on iLO5(Gen10).\n") + if options.controller: + if ilo_ver >= 6.110: + if options.storageid: + controllers = self.auxcommands["storagecontroller"].storagecontroller(options, single_use=True) + else: + raise InvalidCommandLineError("--storageid option is mandatory for iLO6 onwards.\n") + else: + try: + controllers = self.auxcommands["storagecontroller"].controllers(options, single_use=True) + if len(controllers) == 0: + if not options.storageid: + raise InvalidCommandLineError( + "--storageid option is mandatory. Please input storageid as well so that " + "controllers/volumes can be identified.\n" + ) + controllers = self.auxcommands["storagecontroller"].storagecontroller(options, single_use=True) + options.command = "volume" + ilo_ver = 6.110 + except InstanceNotFoundError: + if not options.storageid: + raise InvalidCommandLineError( + "--storageid option is mandatory. Please input storageid as well so that " + "controllers/volumes can be identified.\n" + ) + controllers = self.auxcommands["storagecontroller"].storagecontroller(options, single_use=True) + options.command = "volume" + ilo_ver = 6.110 + try: + logical_drives = self.rdmc.app.get_handler( + "/redfish/v1/Systems/1/Storage/" + options.storageid + "/Volumes/", + silent=True, + service=True, + ).dict["Members"] + for volume_list in logical_drives: + volume = self.rdmc.app.get_handler(volume_list["@odata.id"], silent=True, service=True).dict + if ( + ("Status" in volume) + and (volume["Status"]["State"] == "Enabled") + and ("RAIDType" in volume) + and (volume["RAIDType"] == "None") + ): + self.rdmc.app.delete_handler(volume["@odata.id"], {}, silent=True, service=True) + + controller = controllers[next(iter(controllers))] + (create_flag, newdrive) = self.createvolume(options, controller) + if create_flag: + if ilo_ver >= 6.110: + temp_odata = controller["@odata.id"] + volume_path = temp_odata.split("Controllers")[0] + "Volumes" + self.rdmc.ui.printer("CreateVolume path and payload: %s, %s\n" % (volume_path, newdrive)) + result = self.rdmc.app.post_handler(volume_path, newdrive) + + self.rdmc.ui.printer("Volume created successfully \n") + if options.sparedrives: + controller["physical_drives"] = self.auxcommands[ + "storagecontroller" + ].storagephysical_drives(options, controller, options.storageid, single_use=True) + array = options.disks.split(",") + set_spare = False + newdrive1 = {"Links": {"DedicatedSpareDrives": [{}]}} + volume_path1 = volume_path + result.session_location.split("Volumes")[1] + + sparedrives = options.sparedrives[0].split(",") + + if len(controller["physical_drives"]) > 0: + for p_id in controller["physical_drives"]: + p_loc = self.convertloc( + controller["physical_drives"][str(p_id)]["PhysicalLocation"]["PartLocation"][ + "ServiceLabel" + ] + ) + l_loc = p_loc.split(":")[1:] + p_loc = ":".join(l_loc) + for sdrive in sparedrives: + if sdrive == p_loc and sdrive not in array: + newdrive1["Links"]["DedicatedSpareDrives"] = [ + { + "@odata.id": controller["physical_drives"][str(p_id)]["@odata.id"], + } + ] + set_spare = True + if not set_spare: + raise InvalidCommandLineError( + "Invalid spare drive given, check whether given spare drive is different from " + "disks given or check whether spare drive belongs to same controller" + ) + + self.rdmc.app.patch_handler(volume_path1, newdrive1) + self.rdmc.ui.printer("Successfully added the %s sparedrive\n" % (newdrive1)) + + return ReturnCodes.SUCCESS + + else: + temp_odata = controller["@odata.id"] + payload_dict = dict() + payload_dict["DataGuard"] = "Disabled" + if "settings" not in temp_odata: + temp_odata = temp_odata + "settings/" + settings_controller = self.rdmc.app.get_handler(temp_odata, service=False, silent=True) + # Fix for multiple logical creation at single reboot + if self.rdmc.app.typepath.defs.isgen9: + payload_dict["logical_drives"] = dict() + payload_dict["logical_drives"]["new"] = newdrive + else: + payload_dict["LogicalDrives"] = settings_controller.dict["LogicalDrives"] + payload_dict["LogicalDrives"].append(newdrive) + self.rdmc.ui.printer("CreateVolume path and payload: %s, %s\n" % (temp_odata, payload_dict)) + self.rdmc.app.put_handler( + temp_odata, + payload_dict, + headers={"If-Match": self.getetag(temp_odata)}, + ) + self.rdmc.app.download_path([temp_odata], path_refresh=True, crawl=False) + self.rdmc.ui.printer( + "One or more properties were changed and will not take effect " "until system is reset \n" + ) + except IloLicenseError: + self.rdmc.ui.error("License Error Occured while creating volume\n") + return ReturnCodes.ILO_LICENSE_ERROR + except IdTokenError: + self.rdmc.ui.error("Insufficient Privilege to create volume\n") + return ReturnCodes.RIS_MISSING_ID_TOKEN + except IloResponseError: + self.rdmc.ui.error("iLO threw iLOResponseError\n") + return ReturnCodes.RIS_ILO_RESPONSE_ERROR + except StopIteration: + self.rdmc.ui.error("Drive or Controller not exist, Please check drive or controller\n") + except Exception as excp: + self.rdmc.ui.error(excp) + + else: + self.rdmc.ui.error("Provide the controller \n") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def convertloc(self, servicelabel): + loc = servicelabel.split(":") + temp_str = str( + loc[0].split("=")[1] + ":" + loc[1].split("=")[1] + ":" + loc[2].split("=")[1] + ":" + loc[3].split("=")[1] + ) + return temp_str + + def get_allowed_list(self, storage_id, attr): + url = "/redfish/v1/Systems/1/Storage/" + storage_id + "/Volumes/Capabilities" + attr_allowed_str = attr + "@Redfish.AllowableValues" + + results = self.rdmc.app.get_handler(url, service=True, silent=True).dict + return results[attr_allowed_str] + + def createvolume(self, options, controller): + """Create volume""" + global p_loc + ilo_ver = self.rdmc.app.getiloversion() + if options.command == "volume": + ilo_ver = 6.110 + if ilo_ver >= 6.110: + try: + raidlvllist = self.get_allowed_list(options.storageid, "RAIDType") + except: + raidlvllist = [ + "Raid0", + "Raid1", + "Raid1ADM", + "Raid10", + "Raid10ADM", + "Raid5", + "Raid50", + "Raid6", + "Raid60", + ] + + else: + raidlvllist = [ + "Raid0", + "Raid1", + "Raid1ADM", + "Raid10", + "Raid10ADM", + "Raid5", + "Raid50", + "Raid6", + "Raid60", + ] + interfacetypelist = ["SAS", "SATA", "NVMe"] + mediatypelist = ["SSD", "HDD"] + sparetypelist = ["Dedicated", "Roaming"] + acceltypelist = ["ControllerCache", "IOBypass", "None"] + locationtypelist = ["Internal", "External"] + legacylist = ["Primary", "Secondary", "All", "None"] + paritylist = ["Default", "Rapid"] + iOPerfModeEnabledlist = ["true", "false"] + if ilo_ver >= 6.110: + try: + readCachePolicylist = self.get_allowed_list(options.storageid, "ReadCachePolicy") + # writeCachePolicylist = self.get_allowed_list(options.storageid, "WriteCachePolicy") + writeCachePolicylist = [ + "Off", + "WriteThrough", + "ProtectedWriteBack", + "UnprotectedWriteBack", + ] + except: + readCachePolicylist = ["Off", "ReadAhead"] + writeCachePolicylist = [ + "Off", + "WriteThrough", + "ProtectedWriteBack", + "UnprotectedWriteBack", + ] + # InitMethodlist = self.get_allowed_list(options.storageid, "InitializeMethod") + WriteHoleProtectionPolicyList = ["Yes", "No"] + sparedrives = [] + changes = False + + if ilo_ver >= 6.110: + controller["physical_drives"] = self.auxcommands["storagecontroller"].storagephysical_drives( + options, controller, options.storageid, single_use=True + ) + else: + try: + controller["physical_drives"] = self.auxcommands["storagecontroller"].physical_drives( + options, controller, single_use=True + ) + except: + controller["physical_drives"] = self.auxcommands["storagecontroller"].storagephysical_drives( + options, controller, options.storageid, single_use=True + ) + if ilo_ver >= 6.110: + controller["logical_drives"] = self.auxcommands["storagecontroller"].storagelogical_drives( + options, controller, options.storageid, single_use=True + ) + else: + try: + controller["logical_drives"] = self.auxcommands["storagecontroller"].logical_drives( + options, controller, single_use=True + ) + except: + controller["logical_drives"] = self.auxcommands["storagecontroller"].storagelogical_drives( + options, controller, options.storageid, single_use=True + ) + if controller.get("Links"): + if ilo_ver >= 6.110: + newdrive = {"Links": {"Drives": {}}} + else: + newdrive = {"Links": {"DataDrives": {}}} + else: + if ilo_ver >= 6.110: + newdrive = {"Links": {"Drives": {}}} + else: + newdrive = {"links": {"DataDrives": {}}} + + changes = False + itemadded = False + + for item in raidlvllist: + if options.raid.lower() == item.lower(): + if options.command == "customdrive" or options.command == "volume": + drivecount = len(options.disks.replace(", ", ",").split(",")) + else: + try: + drivecount = int(options.disks) + except ValueError: + raise InvalidCommandLineError("Number of drives is not an integer.") + if self.raidvalidation(item.lower(), drivecount, options): + itemadded = True + if ilo_ver >= 6.110: + newdrive["RAIDType"] = options.raid.upper() + else: + newdrive["Raid"] = item + break + + if not itemadded: + raise InvalidCommandLineError("Invalid raid type or configuration.") + else: + itemadded = False + + if options.command == "customdrive": + if options.sparedrives: + sparedrives = options.sparedrives.replace(", ", ",").split(",") + newdrive["SpareDrives"] = [] + newdrive["SpareRebuildMode"] = "Dedicated" + + drives = options.disks.replace(", ", ",").split(",") + newdrive["DataDrives"] = [] + + if len(controller["physical_drives"]) > 0: + for id in controller["physical_drives"]: + for drive in drives: + drv_id = controller["physical_drives"][str(id)] + if "Location" in drv_id: + drv_loc = drv_id["Location"] + else: + location = drv_id["PhysicalLocation"]["PartLocation"]["ServiceLabel"] + loc = location.split(":") + del loc[0] + if len(loc) == 3: + temp_str = str( + loc[0].split("=")[1] + ":" + loc[1].split("=")[1] + ":" + loc[2].split("=")[1] + ) + drv_loc = temp_str + + if drive == drv_loc: + newdrive["DataDrives"].append(drive) + + for sdrive in sparedrives: + drv_id = controller["SpareDrives"][str(id)] + if "Location" in drv_id: + drv_loc = drv_id["Location"] + else: + location = drv_id["PhysicalLocation"]["PartLocation"]["ServiceLabel"] + loc = location.split(":") + del loc[0] + if len(loc) == 3: + temp_str = str( + loc[0].split("=")[1] + ":" + loc[1].split("=")[1] + ":" + loc[2].split("=")[1] + ) + drv_loc = temp_str + + if drive == drv_loc: + newdrive["DataDrives"].append(drive) + if sdrive == drv_id: + newdrive["SpareDrives"].append(sdrive) + else: + raise InvalidCommandLineError("No Physical Drives in this controller") + + if drivecount > len(newdrive["DataDrives"]): + raise InvalidCommandLineError( + "Not all of the selected drives could " "be found in the specified locations." + ) + + if options.sparetype: + itemadded = False + for item in sparetypelist: + if options.sparetype.lower() == item.lower(): + newdrive["SpareRebuildMode"] = item + itemadded = True + break + + if not itemadded: + raise InvalidCommandLineError("Invalid spare drive type.") + + if options.drivename: + newdrive["LogicalDriveName"] = options.drivename + + if options.capacitygib: + try: + capacitygib = int(options.capacitygib) + except ValueError: + raise InvalidCommandLineError("Capacity is not an integer.") + newdrive["CapacityGiB"] = capacitygib + + if options.acceleratortype: + itemadded = False + for item in acceltypelist: + if options.acceleratortype.lower() == item.lower(): + newdrive["Accelerator"] = item + itemadded = True + break + + if not itemadded: + raise InvalidCommandLineError("Invalid accelerator type.") + + if options.legacyboot: + itemadded = False + for item in legacylist: + if options.legacyboot.lower() in item.lower(): + newdrive["LegacyBootPriority"] = item + itemadded = True + break + + if not itemadded: + raise InvalidCommandLineError("Invalid legacy boot priority.") + + if options.capacityblocks: + try: + capacityblocks = int(options.capacityblocks) + except ValueError: + raise InvalidCommandLineError("Capacity is not an integer.") + + newdrive["CapacityBlocks"] = capacityblocks + + if options.paritygroup: + try: + paritygroup = int(options.paritygroup) + except ValueError: + raise InvalidCommandLineError("Parity group is not an integer.") + + newdrive["ParityGroupCount"] = paritygroup + + if options.paritytype: + itemadded = False + for item in paritylist: + if options.paritytype.lower() == item.lower(): + newdrive["ParityInitializationType"] = item + itemadded = True + break + + if not itemadded: + raise InvalidCommandLineError("Invalid parity type") + + if options.blocksize: + try: + blocksize = int(options.blocksize) + except ValueError: + raise InvalidCommandLineError("Block size is not an integer.") + + newdrive["BlockSizeBytes"] = blocksize + + if options.stripsize: + try: + stripsize = int(options.stripsize) + except ValueError: + raise InvalidCommandLineError("Strip size is not an integer.") + + newdrive["StripSizeBytes"] = stripsize + + if options.stripesize: + try: + stripesize = int(options.stripesize) + except ValueError: + raise InvalidCommandLineError("Stripe size is not an integer.") + + newdrive["StripeSizeBytes"] = stripesize + elif options.command == "quickdrive": + try: + numdrives = int(options.disks) + except ValueError: + raise InvalidCommandLineError("Number of drives is not an integer.") + + newdrive["DataDrives"] = { + "DataDriveCount": numdrives, + "DataDriveMinimumSizeGiB": 0, + } + for item in mediatypelist: + if options.drivetype.lower() == item.lower(): + newdrive["DataDrives"]["DataDriveMediaType"] = item + itemadded = True + break + if not itemadded: + raise InvalidCommandLineError("Invalid media type.") + else: + itemadded = False + for item in interfacetypelist: + if options.interfacetype.lower() == item.lower(): + newdrive["DataDrives"]["DataDriveInterfaceType"] = item + itemadded = True + break + if not itemadded: + raise InvalidCommandLineError("Invalid interface type.") + + if options.locationtype: + for item in locationtypelist: + if options.locationtype.lower() == item.lower(): + newdrive["DataDrives"]["DataDriveLocation"] = item + break + if options.minimumsize: + try: + minimumsize = int(options.minimumsize) + except ValueError: + raise InvalidCommandLineError("Minimum size is not an integer.") + newdrive["DataDrives"]["DataDriveMinimumSizeGiB"] = minimumsize + newdrive["CapacityGiB"] = minimumsize + elif options.command == "volume": + idval = [] + if len(controller["physical_drives"]) > 0: + array = options.disks.split(",") + DA = ["DA000000", "DA000001"] + for p_id in controller["physical_drives"]: + if p_id not in DA: + p_loc = self.convertloc( + controller["physical_drives"][str(p_id)]["PhysicalLocation"]["PartLocation"]["ServiceLabel"] + ) + + for ar in array: + if ar in p_loc: + idval.append(controller["physical_drives"][str(p_id)]["@odata.id"]) + newdrive["Links"]["Drives"] = [] + if idval is not None: + for id in idval: + newdrive["Links"]["Drives"].append( + { + "@odata.id": id, + } + ) + else: + raise InvalidCommandLineError( + "Disk location given is invalid , Kindly recheck and provide valid location" + ) + + if "capacitygib" in options and options.capacitygib: + options.capacitybytes = int(options.capacitygib) * 1024 * 1024 * 1024 + if "capacitybytes" in options and options.capacitybytes is not None: + if options.capacitybytes: + try: + if isinstance(options.capacitybytes, list): + capacitybytes = int(options.capacitybytes[0]) + else: + capacitybytes = int(options.capacitybytes) + except ValueError: + raise InvalidCommandLineError("Capacity is not an integer.") + + newdrive["CapacityBytes"] = capacitybytes + + if "iOPerfModeEnabled" in options and options.iOPerfModeEnabled: + for item in iOPerfModeEnabledlist: + if ( + options.iOPerfModeEnabled[0].lower() == item.lower() + and options.iOPerfModeEnabled[0].lower() == "false" + ): + newdrive["IOPerfModeEnabled"] = eval(options.iOPerfModeEnabled[0]) + itemadded = True + break + elif ( + options.iOPerfModeEnabled[0].lower() == item.lower() + and options.iOPerfModeEnabled[0].lower() == "true" + ): + if "SSD" in controller["SupportedDeviceProtocols"]: + newdrive["IOPerfModeEnabled"] = eval(options.iOPerfModeEnabled[0]) + itemadded = True + break + else: + raise InvalidCommandLineError( + " IOPerfModeEnabled can be true only when supported protocol is SSD" + ) + if not itemadded: + raise InvalidCommandLineError("Invalid IOPerfModeEnabled, Value should be either False or True") + else: + itemadded = False + + if "ReadCachePolicy" in options and options.ReadCachePolicy is not None: + for item in readCachePolicylist: + if options.ReadCachePolicy.lower() == item.lower(): + newdrive["ReadCachePolicy"] = item + itemadded = True + break + if not itemadded: + raise InvalidCommandLineError("Invalid ReadCachePolicy, Value should be 'Off' or 'ReadAhead'") + else: + itemadded = False + + if "WriteCachePolicy" in options and options.WriteCachePolicy is not None: + for item in writeCachePolicylist: + if options.WriteCachePolicy.lower() == item.lower(): + newdrive["WriteCachePolicy"] = item + itemadded = True + break + if not itemadded: + raise InvalidCommandLineError( + "Invalid WriteCachePolicy, Values should be 'Off', 'WriteThrough','ProtectedWriteBack'," + "'UnprotectedWriteBack'" + ) + else: + itemadded = False + + if "VROC" in controller["Model"]: + if options.WriteHoleProtectionPolicy is not None: + for item in WriteHoleProtectionPolicyList: + if ( + options.WriteHoleProtectionPolicy[0].lower() == item.lower() + and options.WriteHoleProtectionPolicy[0].lower() == "yes" + ): + newdrive["WriteHoleProtectionPolicy"] = "Journaling" + newdrive["Links"]["JournalingMedia"] = idval + itemadded = True + break + if not itemadded: + raise InvalidCommandLineError( + "Invalid WriteHoleProtectionPolicy, Values can be either Yes or No" + ) + else: + itemadded = False + if "drivename" in options and options.drivename: + options.DisplayName = options.drivename + if "DisplayName" in options and options.DisplayName is not None: + newdrive["DisplayName"] = options.DisplayName + + if newdrive: + if options.command == "quickdrive": + newdrive_count = newdrive["DataDrives"]["DataDriveCount"] + elif options.command == "customdrive": + newdrive_count = len(newdrive["DataDrives"]) + elif options.command == "volume": + newdrive_count = len(newdrive["Links"]["Drives"]) + + if len(controller["physical_drives"]) >= newdrive_count: + drives_avail = len(controller["physical_drives"]) + accepted_drives = 0 + for cnt, drive in enumerate(controller["physical_drives"]): + drivechecks = (False, False, False, False) + if drives_avail < newdrive_count: + raise InvalidSmartArrayConfigurationError( + "Unable to continue, requested " + "configuration not possible with current physical drive inventory.\n" + ) + else: + drivechecks = (True, False, False, False) + + if options.command == "quickdrive": + if ( + controller["physical_drives"][drive]["InterfaceType"] + == newdrive["DataDrives"]["DataDriveInterfaceType"] + ): + drivechecks = (True, True, False, False) + else: + drives_avail -= 1 + continue + if ( + controller["physical_drives"][drive]["MediaType"] + == newdrive["DataDrives"]["DataDriveMediaType"] + ): + drivechecks = (True, True, True, False) + else: + drives_avail -= 1 + continue + else: + drivechecks = (True, True, True, False) + in_use = False + if controller["logical_drives"] is not None: + for existing_logical_drives in controller["logical_drives"]: + _logical_drive = controller["logical_drives"][existing_logical_drives] + if _logical_drive.get("LogicalDrives"): + for _data_drive in _logical_drive["LogicalDrives"]["DataDrives"]: + if drive == _logical_drive["LogicalDrives"]["DataDrives"][_data_drive]: + in_use = True + elif _logical_drive.get("Links"): + if not ilo_ver >= 6.110: + for _data_drive in _logical_drive["Links"]["DataDrives"]: + if drive == _logical_drive["Links"]["DataDrives"][_data_drive]: + in_use = True + else: + for _data_drive in _logical_drive["Links"]["Drives"]: + if drive == _data_drive: + in_use = True + elif _logical_drive.get("links"): + for _data_drive in _logical_drive["links"]["DataDrives"]: + if drive == _logical_drive["links"]["DataDrives"][_data_drive]: + in_use = True + if in_use: + drives_avail -= 1 + continue + else: + drivechecks = (True, True, True, True) + if drivechecks[0] and drivechecks[1] and drivechecks[2]: + if controller.get("Links"): + newdrive["Links"]["DataDrives"][drive] = controller["physical_drives"][drive] + else: + if not ilo_ver >= 6.110: + newdrive["links"]["DataDrives"][drive] = controller["physical_drives"][drive] + accepted_drives += 1 + changes = True + if accepted_drives == newdrive_count: + break + else: + drives_avail -= 1 + + if changes: + if self.rdmc.app.typepath.defs.isgen9: + controller["logical_drives"]["new"] = newdrive + else: + if not ilo_ver >= 6.110: + try: + newdrive.pop("Links") + except KeyError: + newdrive.pop("links") + if not ilo_ver >= 6.110: + controller["LogicalDrives"].append(newdrive) + del controller["logical_drives"] + del controller["physical_drives"] + + return (changes, newdrive) + + def getetag(self, path): + """get etag from path""" + etag = None + instance = self.rdmc.app.monolith.path(path) + if instance: + etag = ( + instance.resp.getheader("etag") + if "etag" in instance.resp.getheaders() + else instance.resp.getheader("ETag") + ) + return etag + + def raidvalidation(self, raidtype, numdrives, options): + """vaidation function for raid levels + :param raidtype: raid type + :type options: string. + :param numdrives: number of drives + :type numdrives: int. + :param options: command line options + :type options: list. + """ + + valid = True + + if raidtype == "raid5": + if numdrives < 3: # or options.stripsize: + valid = False + elif raidtype == "raid6": + if numdrives < 4: # or options.stripsize: + valid = False + elif raidtype == "raid50": + if numdrives < 6: + valid = False + elif raidtype == "raid60": + if numdrives < 8: + valid = False + + return valid + + def createvolumevalidation(self, options): + """Create volume validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + @staticmethod + def options_argument_group(parser): + """Define optional arguments group + + :param parser: The parser to add the login option group to + :type parser: ArgumentParser/OptionParser + """ + group = parser.add_argument_group( + "GLOBAL OPTIONS", + "Options are available for all" "arguments within the scope of this command.", + ) + + group.add_argument( + "--controller", + dest="controller", + help="Use this flag to select the corresponding controller " + "using either the slot number or index.\nexample: --controller=Slot 0 OR " + "--controller=1", + default=None, + required=True, + ) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + # self.options_argument_group(customparser) + + subcommand_parser = customparser.add_subparsers(dest="command") + subcommand_parser.required = True + qd_help = ( + "Create a volume with a minimal number of arguments (utilizes default " + "values on the controller). This option is only for iLO5 or Gen10" + ) + # quickdrive sub-parser + qd_parser = subcommand_parser.add_parser( + "quickdrive", + help=qd_help, + description=qd_help + "\n\texample: createvolume quickdrive " + " " + "--locationtype=Internal --minimumsize=0 --controller=1", + formatter_class=RawDescriptionHelpFormatter, + ) + qd_parser.add_argument( + "raid", + help="Specify the RAID level for the volume to be created.", + metavar="Raid_Level", + ) + qd_parser.add_argument( + "disks", + help="For quick drive creation, specify number of disks.", + metavar="Drives", + ) + qd_parser.add_argument( + "drivetype", + help="Specify the drive media type of the physical disk(s) (i.e. HDD or SSD)", + metavar="Drive_Media_Type", + ) + qd_parser.add_argument( + "interfacetype", + help="Specify the interface type of the physical disk(s) (i.e. SATA or SAS or NVMe)", + metavar="Drive_Interface_Type", + ) + qd_parser.add_argument( + "--locationtype", + dest="locationtype", + help="Optionally specify the location of the physical disks(s) (i.e. Internal or External)", + default=None, + ) + qd_parser.add_argument( + "--minimumsize", + dest="minimumsize", + help="""Optionally include to set the minimum size of the drive """ + """in GiB. (usable in quick creation only, use -1 for max size)""", + default=None, + ) + qd_parser.add_argument( + "--controller", + dest="controller", + help="Use this flag to select the corresponding controller " + "using either the slot number or index.\nexample: --controller=Slot 0 OR " + "--controller=1", + default=None, + required=True, + ) + qd_parser.add_argument( + "--storageid", + dest="storageid", + help="Use this flag to select the corresponding storageid " + "using either the slot number or index.\nexample: --storageid=DE123234", + default=None, + required=False, + ) + self.cmdbase.add_login_arguments_group(qd_parser) + # self.options_argument_group(qd_parser) + + cd_help = ( + "Create a customised volume using all available properties (as optional " + "arguments) for creation. This option is only for iLO5 or Gen10" + ) + # customdrive sub-parser + cd_parser = subcommand_parser.add_parser( + "customdrive", + help=cd_help, + description=cd_help + "\n\texample: createvolume customdrive " + " --controller=1 " + "--name=drivename --spare-drives=1I:1:1,1I:1:3 --spare-type=Dedicated --capacitygib=10 " + "--accelerator-type=None\n\n\tOPTIONS:\n\traid-level:\t\t" + "Raid0, Raid1, Raid1ADM, Raid10, Raid10ADM, Raid5, Raid50, " + "Raid6, Raid60\n\tphysicaldrivelocation(s):\tLocation, Drive-name\n\t" + "media-type:\t\tSSD,HDD\n\tinterface-type:" + "\t\tSAS, SATA, NVMe\n\tdrive-location:\t\tInternal, External\n\t" + "--spare-type:\t\tDedicated, Roaming\n\t--accelerator-type:\t" + "ControllerCache, IOBypass, None\n\t--paritytype:\t\tDefault, Rapid" + "\n\t--capacitygib:\t\t-1 (for Max Size)\n\t--capacityblocks:\t" + "-1 (for Max Size)\n\n\t", + formatter_class=RawDescriptionHelpFormatter, + ) + cd_parser.add_argument( + "raid", + help="Specify the RAID level for the volume to be created.", + metavar="Raid_Level", + ) + cd_parser.add_argument( + "disks", + help="For custom drive, specify a comma separated physical disk locations.", + metavar="Drive_Indices", + ) + cd_parser.add_argument( + "-n", + "--name", + dest="drivename", + help="""Optionally include to set the drive name (usable in """ """custom creation only).""", + default=None, + ) + cd_parser.add_argument( + "--spare-drives", + dest="sparedrives", + help="""Optionally include to set the spare drives by the """ + """physical drive's location. (usable in custom creation only)""", + default=None, + ) + cd_parser.add_argument( + "--capacitygib", + dest="capacitygib", + help="""Optionally include to set the capacity of the drive in """ + """GiB. (usable in custom creation only, use -1 for max """ + """size)""", + default=None, + ) + cd_parser.add_argument( + "--accelerator-type", + dest="acceleratortype", + help="""Optionally include to choose the accelerator type.""", + default=None, + ) + cd_parser.add_argument( + "--spare-type", + dest="sparetype", + help="""Optionally include to choose the spare drive type. """ """(usable in custom creation only)""", + default=None, + ) + cd_parser.add_argument( + "--minimumsize", + dest="minimumsize", + help="""Optionally include to set the minimum size of the drive """ + """in GiB. (usable in quick creation only, use -1 for max size)""", + default=None, + ) + cd_parser.add_argument( + "--legacy-boot", + dest="legacyboot", + help="""Optionally include to choose the legacy boot priority. """ """(usable in custom creation only)""", + default=None, + ) + cd_parser.add_argument( + "--storageid", + dest="storageid", + help="Use this flag to select the corresponding storageid " + "using either the slot number or index.\nexample: --storageid=DE123234", + default=None, + required=False, + ) + cd_parser.add_argument( + "--capacityblocks", + dest="capacityblocks", + help="""Optionally include to choose the capacity in blocks. """ + """(use -1 for max size, usable in custom creation only)""", + default=None, + ) + cd_parser.add_argument( + "--paritygroupcount", + dest="paritygroup", + help="""Optionally include to include the number of parity """ + """groups to use. (only valid for certain RAID levels)""", + default=None, + ) + cd_parser.add_argument( + "--paritytype", + dest="paritytype", + help="""Optionally include to choose the parity initialization""" + """ type. (usable in custom creation only)""", + default=None, + ) + cd_parser.add_argument( + "--block-size-bytes", + dest="blocksize", + help="""Optionally include to choose the block size of the disk""" + """ drive. (usable in custom creation only)""", + default=None, + ) + cd_parser.add_argument( + "--strip-size-bytes", + dest="stripsize", + help="""Optionally include to choose the strip size in bytes. """ """(usable in custom creation only)""", + default=None, + ) + cd_parser.add_argument( + "--stripe-size-bytes", + dest="stripesize", + help="""Optionally include to choose the stripe size in bytes. """ """(usable in custom creation only)""", + default=None, + ) + cd_parser.add_argument( + "--controller", + dest="controller", + help="Use this flag to select the corresponding controller " + "using either the slot number or index.\nexample: --controller=Slot 0 OR " + "--controller=1", + default=None, + required=True, + ) + self.cmdbase.add_login_arguments_group(cd_parser) + # self.options_argument_group(cd_parser) + v_help = ( + "Create a volume using all available properties (as optional " + "arguments) for creation on gen 11 or higher " + ) + # volume sub-parser + v_parser = subcommand_parser.add_parser( + "volume", + help=v_help, + description=v_help + "\n\texample: createvolume volume " + " " + " --storageid=DE009000 --controller=0 " + " " + "\n\n\t", + formatter_class=RawDescriptionHelpFormatter, + ) + v_parser.add_argument( + "raid", + help="Specify the RAID level for the volume to be created.", + metavar="Raid_Level", + ) + v_parser.add_argument( + "disks", + help="For custom drive, specify a comma separated physical disk locations.", + metavar="Drive_Indices", + ) + v_parser.add_argument( + "--storageid", + dest="storageid", + help="Use this flag to select the corresponding storageid " + "using either the slot number or index.\nexample: --storageid=DE123234", + default=None, + required=True, + ) + v_parser.add_argument( + "--DisplayName", + dest="DisplayName", + help="""Optionally include to set the drive name """, + default=None, + ) + v_parser.add_argument( + "--iOPerfModeEnabled", + dest="iOPerfModeEnabled", + help="""Optionally include to choose the IOPerfModeEnabled . Allowed values are 'True', 'False'""", + default=None, + ) + v_parser.add_argument( + "--ReadCachePolicy", + dest="ReadCachePolicy", + help="""Optionally include to choose the ReadCachePolicy. """ """Allowed values are 'Off', 'ReadAhead'""", + default=None, + ) + v_parser.add_argument( + "--WriteCachePolicy", + dest="WriteCachePolicy", + help="Optionally include to set the WriteCachePolicy " + "Allowed values are 'Off', 'WriteThrough','ProtectedWriteBack','UnprotectedWriteBack'", + default=None, + ) + v_parser.add_argument( + "--WriteHoleProtectionPolicy", + dest="WriteHoleProtectionPolicy", + help="""Optionally include to choose the WriteHoleProtectionPolicy """ + """this is applicable only for VROC, You can send either Yes or No as values""", + default=None, + ) + v_parser.add_argument( + "--sparedrives", + dest="sparedrives", + help="""Optionally include to set the spare drives by the """ + """physical drive's location. (usable in custom creation only)""", + action="append", + default=None, + ) + v_parser.add_argument( + "--capacitybytes", + dest="capacitybytes", + help="""Optionally include to set the capacity of the drive in """ + """bytes. (usable in custom creation only, use -1 for max """ + """size)""", + action="append", + default=None, + ) + v_parser.add_argument( + "--controller", + dest="controller", + help="Use this flag to select the corresponding controller " + "using either the slot number or index.\nexample: --controller=Slot 0 OR " + "--controller=1", + default=None, + required=True, + ) + + self.cmdbase.add_login_arguments_group(v_parser) + # self.options_argument_group(v_parser) diff --git a/ilorest/extensions/SMART_ARRAY_COMMANDS/DeleteVolumeCommand.py b/ilorest/extensions/SMART_ARRAY_COMMANDS/DeleteVolumeCommand.py new file mode 100644 index 0000000..380e858 --- /dev/null +++ b/ilorest/extensions/SMART_ARRAY_COMMANDS/DeleteVolumeCommand.py @@ -0,0 +1,437 @@ +### +# Copyright 2016-2021 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 -*- +""" Delete Volume Command for rdmc """ + +from six.moves import input + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class DeleteVolumeCommand: + """Delete volume command""" + + def __init__(self): + self.ident = { + "name": "deletevolume", + "usage": None, + "description": "To delete a volume " + "to a controller by index.\n\texample: deletevolume " + "1 --controller=1\n\n\tTo delete multiple volumes by " + "index.\n\tExample: deletevolume 1,2 --controller=1" + "\n\n\tTo delete all volumes on a controller.\n\t" + "deletevolume --controller=1 --all\n\n\t" + 'deletevolume --controller="Slot1" --all\n\n\tNOTE: ' + "You can also delete volumes by " + '"VolumeUniqueIdentifier".\n\n\t' + "For iLO6, storage id need to be specified using --storageid=DE00E000 along with --controller=1\n\n\t" + "You can also delete volumes by VolumeName", + "summary": "Deletes volumes from the selected controller.", + "aliases": ["deletelogicaldrive"], + "auxcommands": ["SelectCommand", "StorageControllerCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main disk inventory worker function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.deletevolumevalidation(options) + ilo_ver = self.rdmc.app.getiloversion() + if ilo_ver < 6.110: + try: + self.auxcommands["select"].selectfunction("SmartStorageConfig.") + except Exception: + ilo_ver = 6.110 + if ilo_ver >= 6.110: + if not options.storageid: + raise InvalidCommandLineError( + "--storageid option is mandatory for iLO6 along with --controller option.\n" + ) + controller_logicaldrives = self.auxcommands["storagecontroller"].storagelogical_drives( + options, options.controller, options.storageid, single_use=True + ) + if len(controller_logicaldrives) == 0: + raise InvalidCommandLineError("No Logical drives found.\n") + get_contrller = [] + self.auxcommands["select"].selectfunction("StorageCollection.") + st_content = self.rdmc.app.getprops() + for st_controller in st_content: + path = st_controller["Members"] + for mem in path: + for val in mem.values(): + if "DE" in val and options.storageid in val: + getval = self.rdmc.app.get_handler(val, silent=True, service=True).dict + controller = getval["Controllers"] + for ctl in controller.values(): + ctl = ctl + "/0" + get_sel = self.rdmc.app.get_handler(ctl, silent=True, service=True) + get_contrller.append(get_sel) + else: + self.auxcommands["select"].selectfunction("SmartStorageConfig.") + content = self.rdmc.app.getprops() + + if not args and not options.all: + raise InvalidCommandLineError("You must include a logical drive to delete.") + elif not options.controller: + raise InvalidCommandLineError("You must include a controller to select.") + else: + if len(args) > 1: + logicaldrives = args + elif len(args) == 1: + logicaldrives = args[0].replace(", ", ",").split(",") + else: + logicaldrives = None + + controllist = [] + + + try: + if ilo_ver >= 6.110: + if options.controller.isdigit(): + slotlocation = self.storageget_location_from_id(options.controller, options.storageid) + if slotlocation: + slotcontrol = slotlocation.lower().strip('"').split("slot")[-1].lstrip().strip("=") + for control in get_contrller: + cont_temp = control.dict + temp = cont_temp["Location"]["PartLocation"]["ServiceLabel"] + if "Location" in cont_temp and slotcontrol.lower() == temp.lower().split("slot")[ + -1 + ].lstrip().strip("="): + controllist.append(cont_temp) + elif "slot" in options.controller.lower(): + slotlocation = options.controller + if slotlocation: + slotcontrol = slotlocation.lower().strip('"').split("slot")[-1].lstrip().strip("=") + for control in get_contrller: + cont_temp = control.dict + temp = cont_temp["Location"]["PartLocation"]["ServiceLabel"] + if "Location" in cont_temp and slotcontrol.lower() == temp.lower().split("slot")[ + -1 + ].lstrip().strip("="): + controllist.append(cont_temp) + if not controllist: + raise InvalidCommandLineError("") + else: + try: + controllers = self.auxcommands["storagecontroller"].storagecontroller(options, single_use=True) + options.command = "volume" + ilo_ver = 6.110 + except InstanceNotFoundError: + if not options.storageid: + raise InvalidCommandLineError( + "--storageid option is mandatory. Please input storageid as well so that " + "controllers/volumes can be identified.\n" + ) + controllers = self.auxcommands["storagecontroller"].storagecontroller(options, single_use=True) + options.command = "volume" + ilo_ver = 6.110 + if options.controller.isdigit(): + slotlocation = self.get_location_from_id(options.controller) + if slotlocation: + slotcontrol = slotlocation.lower().strip('"').split("slot")[-1].lstrip() + for control in content: + if slotcontrol.lower() == control["Location"].lower().split("slot")[-1].lstrip(): + controllist.append(control) + if not controllist: + raise InvalidCommandLineError("") + except InvalidCommandLineError: + raise InvalidCommandLineError("Selected controller not found in the current inventory " "list.") + if ilo_ver >= 6.110: + self.storagedeletevolume(controller_logicaldrives, logicaldrives, options.all, options.force) + else: + self.deletevolume(controllist, logicaldrives, options.all, options.force) + self.rdmc.ui.printer( + "One or more properties were changed and will not take effect " "until system is reset \n" + ) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def get_location_from_id(self, controller_id): + # self.rdmc.ui.printer("Controller ID: %s\n" % (controller_id)) + for sel in self.rdmc.app.select("SmartStorageArrayController", path_refresh=True): + if "Collection" not in sel.maj_type: + controller = sel.dict + self.rdmc.ui.printer("Controller ID: %s\n" % (controller["Id"])) + if controller["Id"] == str(controller_id): + self.rdmc.ui.printer("Controller Location: %s\n" % (controller["Location"])) + return controller["Location"] + return None + + def storageget_location_from_id(self, controller_id, storageid): + get_contrller = [] + # self.auxcommands["select"].selectfunction("StorageCollection.") + st_content = self.rdmc.app.getprops() + for st_controller in st_content: + path = st_controller["Members"] + for mem in path: + for val in mem.values(): + if "DE" in val and storageid in val: + getval = self.rdmc.app.get_handler(val, silent=True, service=True).dict + controller = getval["Controllers"] + for ctl in controller.values(): + ctl = ctl + "/0" + get_sel = self.rdmc.app.get_handler(ctl, silent=True, service=True) + get_contrller.append(get_sel) + for sel in get_contrller: + if "Collection" not in sel.path: + controller = sel.dict + if controller["Id"] == str(controller_id): + return controller["Location"]["PartLocation"]["ServiceLabel"] + return None + + def deletevolume(self, controllist, drivelist, allopt, force): + """Gets volumes ready for deletion + + :param controllist: list of controllers + :type controllist: list. + :param drivelist: volumes to delete + :type drivelist: list. + :param allopt: flag for deleting all volumes + :type allopt: bool. + """ + + for controller in controllist: + changes = False + + numdrives = len(controller["LogicalDrives"]) + deletecount = 0 + + if allopt: + controller["LogicalDrives"] = [] + controller["DataGuard"] = "Disabled" + if not force: + if not self.inputaccept(None): + return + self.lastlogicaldrive(controller) + changes = True + else: + for deldrive in drivelist: + found = False + + if deldrive.isdigit(): + deldrive = int(deldrive) + + for idx, ldrive in enumerate(controller["LogicalDrives"]): + if deldrive == ldrive["LogicalDriveNumber"]: + if not force: + if not self.inputaccept(ldrive["LogicalDriveName"]): + return + + controller["LogicalDrives"][idx]["Actions"] = [{"Action": "LogicalDriveDelete"}] + + controller["DataGuard"] = "Permissive" + deletecount += 1 + + changes = True + found = True + + break + + if not found: + raise NoContentsFoundForOperationError("Logical " "drive %s not found." % str(deldrive)) + + if deletecount == numdrives: + self.lastlogicaldrive(controller) + + if changes: + # self.rdmc.ui.printer( + # "DeleteVolume path and payload: %s, %s\n" + # % (controller["@odata.id"], controller) + # ) + put_path = controller["@odata.id"] + if "settings" not in put_path: + put_path = put_path + "settings/" + controller["@odata.id"] = put_path + self.rdmc.app.put_handler( + put_path, + controller, + headers={"If-Match": self.getetag(controller["@odata.id"])}, + ) + self.rdmc.app.download_path([controller["@odata.id"]], path_refresh=True, crawl=False) + + def lastlogicaldrive(self, controller): + """Special case that sets required properties after last drive deletion + + :param controller: controller change settings on + :type controller: dict. + """ + changelist = [ + "PredictiveSpareRebuild", + "SurfaceScanAnalysisPriority", + "FlexibleLatencySchedulerSetting", + "DegradedPerformanceOptimization", + "CurrentParallelSurfaceScanCount", + "SurfaceScanAnalysisDelaySeconds", + "MonitorAndPerformanceAnalysisDelaySeconds", + "InconsistencyRepairPolicy", + "DriveWriteCache", + "ExpandPriority", + "EncryptionEULA", + "NoBatteryWriteCache", + "ReadCachePercent", + "WriteCacheBypassThresholdKiB", + "RebuildPriority", + "QueueDepth", + "ElevatorSort", + ] + + for item in changelist: + if item in list(controller.keys()): + controller[item] = None + + def inputaccept(self, drivename): + while True: + if drivename is None: + ans = input("Are you sure you would" " like to continue deleting all volumes" "? (y/n)") + else: + ans = input("Are you sure you would" " like to continue deleting volume" " %s? (y/n)" % drivename) + + if ans.lower() == "y": + return 1 + elif ans.lower() == "n": + self.rdmc.ui.printer("Stopping command without deleting volume/volumes.\n") + return 0 + + def storagedeletevolume(self, logical_drives, drivelist, allopt, force): + if allopt: + sorted_dict = sorted(logical_drives, reverse=True) + if not force: + if not self.inputaccept(None): + return + for drive in sorted_dict: + self.rdmc.app.delete_handler(logical_drives[drive]["@odata.id"], {}) + else: + found = False + sorted_dict = sorted(logical_drives, reverse=True) + sorted_drivelist = sorted(drivelist, reverse=True) + for deldrive in sorted_drivelist: + found = False + changes = False + for drive in sorted_dict: + if logical_drives[drive]["Id"] == deldrive: + if not force: + if not self.inputaccept(logical_drives[drive]["Name"]): + return + self.rdmc.ui.printer("Setting volume %s " "for deletion\n" % logical_drives[drive]["Name"]) + changes = True + found = True + break + try: + if changes: + # self.rdmc.ui.printer( + # "DeleteVolume path and payload: %s, %s\n" + # % (logical_drives[drive]["@odata.id"], "{}") + # ) + self.rdmc.app.delete_handler(logical_drives[drive]["@odata.id"], {}) + except: + self.rdmc.ui.printer( + "This volume cannot" + "be deleted without deleting " + "the higher volumes in the same Physical drive first.\n" + ) + raise + if not found: + raise NoContentsFoundForOperationError("Logical " "drive not found.") + + def getetag(self, path): + """get etag from path""" + etag = None + instance = self.rdmc.app.monolith.path(path) + if instance: + etag = ( + instance.resp.getheader("etag") + if "etag" in instance.resp.getheaders() + else instance.resp.getheader("ETag") + ) + return etag + + def deletevolumevalidation(self, options): + """delete volume validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--controller", + dest="controller", + help="Use this flag to select the corresponding controller " "using either the slot number or index.", + default=None, + ) + customparser.add_argument( + "--storageid", + dest="storageid", + help="Use this flag to select the corresponding storageid in iLO6 only.", + default=None, + ) + customparser.add_argument( + "--all", + dest="all", + help="""Use this flag to delete all volumes on a controller.""", + action="store_true", + default=False, + ) + customparser.add_argument( + "--force", + dest="force", + help="""Use this flag to override the "are you sure?" text when """ """deleting a volume.""", + action="store_true", + default=False, + ) diff --git a/ilorest/extensions/SMART_ARRAY_COMMANDS/DriveSanitizeCommand.py b/ilorest/extensions/SMART_ARRAY_COMMANDS/DriveSanitizeCommand.py new file mode 100644 index 0000000..0c0cd74 --- /dev/null +++ b/ilorest/extensions/SMART_ARRAY_COMMANDS/DriveSanitizeCommand.py @@ -0,0 +1,537 @@ +### +# Copyright 2016-2021 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 -*- +""" Drive Erase/ Sanitize Command for rdmc """ + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class DriveSanitizeCommand: + """Drive erase/sanitize command""" + + def __init__(self): + self.ident = { + "name": "drivesanitize", + "usage": None, + "description": "To sanitize a physical drive " + 'by index.\n\texample: drivesanitize "1I:1:1" --controller=1\n\n\tTo' + " sanitize multiple drives by specifying location.\n\texample: " + "drivesanitize 1I:1:1,1I:1:2 --controller=1 --mediatype HDD/SSD\n\texample: drivesanitize " + "1I:1:1,1I:1:2 --controller=Slot 1 --mediatype=HDD " + "if incorrect mediatype is specified, error will generated " + "For iLO6, storage id need to be specified using --storageid=DE00E000 along with --controller=1\n" + "Once drivesanitize function is performed in iLO6, It may take a while to complete.\n " + "To check the status of Sanitization, perform to following command-\n" + "drivesanitize 1I:1:1 --controller=1 --storageid=DE00900 --status\n" + "Once the process in 100% complete, use the --drivereset tag to reset the drive. Example-\n" + "drivesanitize 1I:1:1 --controller=1 --storageid=DE00900 --drivereset\n", + "summary": "Erase/Sanitize physical drive(s)", + "aliases": ["DriveEraseCommand"], + "auxcommands": ["SelectCommand", "RebootCommand", "StorageControllerCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main disk inventory worker function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.drivesanitizevalidation(options) + + ilo_ver = self.rdmc.app.getiloversion() + + if ilo_ver < 6.110: + try: + controllers = self.auxcommands["storagecontroller"].controllers(options, single_use=True) + if len(controllers) == 0: + if not options.storageid: + raise InvalidCommandLineError( + "--storageid option is mandatory. Please input storageid as well so that " + "controllers/volumes can be identified.\n" + ) + ilo_ver = 6.110 + except: + if not options.storageid: + raise InvalidCommandLineError( + "--storageid option is mandatory. Please input storageid as well so that " + "controllers/volumes can be identified.\n" + ) + + if ilo_ver >= 6.110: + if not options.storageid: + raise InvalidCommandLineError( + "--storageid option is mandatory for iLO6" " along with --controller option.\n" + ) + self.auxcommands["select"].selectfunction("StorageController") + content = self.rdmc.app.getprops() + controllers = self.auxcommands["storagecontroller"].storagecontroller(options, single_use=True) + if controllers: + for controller in controllers: + if ( + getattr(options, "controller", False) == controller + or controllers[controller]["Location"]["PartLocation"]["ServiceLabel"][-1] + == getattr(options, "controller", False)[-1] + ): + controller_physicaldrives = self.auxcommands["storagecontroller"].storagephysical_drives( + options, + options.controller, + options.storageid, + single_use=True, + ) + else: + self.auxcommands["select"].selectfunction("SmartStorageConfig") + content = self.rdmc.app.getprops() + controllers = self.auxcommands["storagecontroller"].controllers(options, single_use=True) + if controllers: + for controller in controllers: + if int(controller) == int(options.controller): + controller_physicaldrives = self.auxcommands["storagecontroller"].physical_drives( + options, controllers[controller], single_use=True + ) + + if not args and not options.all: + raise InvalidCommandLineError("You must include a physical drive to sanitize.") + elif not options.controller: + raise InvalidCommandLineError("You must include a controller to select.") + else: + if len(args) > 1: + physicaldrives = args + elif len(args) == 1: + physicaldrives = args[0].replace(", ", ",").split(",") + else: + physicaldrives = None + + controllist = [] + + try: + if ilo_ver >= 6.110: + if options.controller.isdigit(): + slotlocation = self.storageget_location_from_id(options.controller, options.storageid) + if slotlocation: + slotcontrol = slotlocation.lower().strip('"').split("slot")[-1].lstrip().strip("=") + for control in controllers.values(): + if "Location" in control and slotcontrol.lower() == control["Location"]["PartLocation"][ + "ServiceLabel" + ].lower().split("slot")[-1].lstrip().strip("="): + controllist.append(control) + elif "slot" in options.controller.lower(): + slotlocation = options.controller + if slotlocation: + slotcontrol = slotlocation.lower().strip('"').split("slot")[-1].lstrip().strip("=") + for control in controllers.values(): + if "Location" in control and slotcontrol.lower() == control["Location"]["PartLocation"][ + "ServiceLabel" + ].lower().split("slot")[-1].lstrip().strip("="): + controllist.append(control) + if not controllist: + raise InvalidCommandLineError("") + else: + if options.controller.isdigit(): + slotlocation = self.get_location_from_id(options.controller) + if slotlocation: + slotcontrol = slotlocation.lower().strip('"').split("slot")[-1].lstrip() + for control in content: + if slotcontrol.lower() == control["Location"].lower().split("slot")[-1].lstrip(): + controllist.append(control) + if not controllist: + raise InvalidCommandLineError("") + except InvalidCommandLineError: + raise InvalidCommandLineError("Selected controller not found in the current inventory " "list.") + + if ilo_ver >= 6.110: + if options.status: + if options.all: + for drive in controller_physicaldrives.values(): + pdrive_inilo = self.convertloc(drive["PhysicalLocation"]["PartLocation"]["ServiceLabel"]) + if len(drive["Operations"]) != 0: + self.rdmc.ui.printer( + "The drive is in %s state, %s percent complete.\n" + % ( + drive["Operations"][0]["OperationName"], + drive["Operations"][0]["PercentageComplete"], + ) + ) + if int(drive["Operations"][0]["PercentageComplete"]) == 100: + self.rdmc.ui.printer("You can reset %s using --drivereset now.\n" % (pdrive_inilo)) + elif len(drive["Operations"]) == 0: + mr_ct = drive["Status"]["State"] + self.rdmc.ui.printer("Sanitization is completed and updated drive status = %s\n" % (mr_ct)) + else: + self.rdmc.ui.printer("Sanitization failed for drive %s.\n" % (pdrive_inilo)) + else: + for drive in controller_physicaldrives.values(): + pdrive_inilo = self.convertloc(drive["PhysicalLocation"]["PartLocation"]["ServiceLabel"]) + for userdrive in physicaldrives: + if userdrive == pdrive_inilo: + if len(drive["Operations"]) != 0: + self.rdmc.ui.printer( + "The drive is in %s state, %s percent complete.\n" + % ( + drive["Operations"][0]["OperationName"], + drive["Operations"][0]["PercentageComplete"], + ) + ) + if int(drive["Operations"][0]["PercentageComplete"]) == 100: + self.rdmc.ui.printer("You can reset %s using --drivereset now.\n" % (userdrive)) + elif len(drive["Operations"]) == 0: + mr_ct = drive["Status"]["State"] + self.rdmc.ui.printer( + "Sanitization is completed and updated drive status = %s\n" % (mr_ct) + ) + + else: + self.rdmc.ui.printer("Sanitization failed for drive %s.\n" % (userdrive)) + elif options.drivereset: + if options.all: + for drive in controller_physicaldrives.values(): + pdrive_inilo = self.convertloc(drive["PhysicalLocation"]["PartLocation"]["ServiceLabel"]) + if "Actions" in drive: + if "#Drive.Reset" in drive["Actions"]: + path = drive["@odata.id"] + "/Actions/Drive.Reset" + contentsholder = {"ResetType": "ForceOn"} + self.rdmc.ui.printer("DriveReset path and payload: %s, %s\n" % (path, contentsholder)) + self.rdmc.app.post_handler(path, contentsholder) + else: + if len(drive["Operations"]) != 0: + self.rdmc.ui.printer( + "Sanitization is in progress for drive %s. Use --status to check it's status.\n" + % (pdrive_inilo) + ) + else: + self.rdmc.ui.printer( + "Drive sanitization is not being performed on %s\n" % (pdrive_inilo) + ) + else: + self.rdmc.ui.printer("Drive sanitization is not being performed on %s\n" % (pdrive_inilo)) + else: + for drive in controller_physicaldrives.values(): + pdrive_inilo = self.convertloc(drive["PhysicalLocation"]["PartLocation"]["ServiceLabel"]) + for userdrive in physicaldrives: + if userdrive == pdrive_inilo: + if "Actions" in drive: + if "#Drive.Reset" in drive["Actions"]: + path = drive["@odata.id"] + "/Actions/Drive.Reset" + contentsholder = {"ResetType": "ForceOn"} + self.rdmc.ui.printer( + "DriveReset path and payload: %s, %s\n" % (path, contentsholder) + ) + self.rdmc.app.post_handler(path, contentsholder) + else: + if len(drive["Operations"]) != 0: + self.rdmc.ui.printer( + "Santization is in progress for drive %s. Use --status to check its " + "status.\n" % (pdrive_inilo) + ) + else: + self.rdmc.ui.printer( + "Drive sanitization is not being performed on %s\n" % (pdrive_inilo) + ) + else: + self.rdmc.ui.printer( + "Drive sanitization is not being performed on %s\n" % (pdrive_inilo) + ) + else: + if self.storagesanitizedrives(physicaldrives, controller_physicaldrives, options.all): + if options.reboot: + self.auxcommands["reboot"].run("ColdBoot") + self.rdmc.ui.printer("Preparing for sanitization...\n") + self.monitorsanitization() + + else: + if options.status is True or options.drivereset is True: + raise InvalidCommandLineError("--status and --drivereset options are not supported on iLO 5\n") + if self.sanitizedrives( + controllist, + physicaldrives, + controller_physicaldrives, + options.mediatype, + options.all, + ): + if options.reboot: + self.auxcommands["reboot"].run("ColdBoot") + self.rdmc.ui.printer("Preparing for sanitization...\n") + self.monitorsanitization() + else: + self.rdmc.ui.printer("Sanitization will occur on the next system reboot.\n") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def get_location_from_id(self, controller_id): + for sel in self.rdmc.app.select("SmartStorageArrayController", path_refresh=True): + if "Collection" not in sel.maj_type: + controller = sel.dict + if controller["Id"] == str(controller_id): + return controller["Location"] + return None + + def storageget_location_from_id(self, controller_id, storage_id): + for sel in self.rdmc.app.select("StorageController", path_refresh=True): + if "Collection" not in sel.maj_type and storage_id in sel.dict["@odata.id"]: + controller = sel.dict + if controller["Id"] == str(controller_id): + return controller["Location"]["PartLocation"]["ServiceLabel"] + return None + + def sanitizedrives(self, controllist, drivelist, controller_drives, mediatype, optall): + """Gets drives ready for sanitization + + :param controllist: list of controllers + :type controllist: list. + :param drivelist: physical drives to sanitize + :type drivelist: list. + :param optall: flag for sanitizing all drives + :type optall: bool. + """ + sanitizedrivelist = [] + logicaldrivelist = [] + changes = False + + for controller in controllist: + pdrivelist = [x["DataDrives"] for x in controller["LogicalDrives"]] + + for plist in pdrivelist: + for drive in plist: + logicaldrivelist.append(drive) + + if optall: + sanitizedrivelist = [x["Location"] for x in controller["PhysicalDrives"]] + else: + for erasedrive in drivelist: + try: + for idx, pdrive in enumerate(controller["PhysicalDrives"]): + if erasedrive == pdrive["Location"]: + if pdrive["Location"] in logicaldrivelist: + raise InvalidCommandLineError( + "Unable to" + " sanitize configured drive. Remove" + " any volume(s) associated " + "with drive %s and try again." % pdrive["Location"] + ) + + # Validate Media Type + if not (self.validate_mediatype(erasedrive, mediatype, controller_drives)): + raise InvalidCommandLineError( + "One or more of the drives given does not match the " + "mediatype %s which is specified" % mediatype + ) + self.rdmc.ui.printer( + "Setting physical drive %s " "for sanitization\n" % pdrive["Location"] + ) + sanitizedrivelist.append(pdrive["Location"]) + break + except KeyError as excp: + raise NoContentsFoundForOperationError( + "The property '%s' is missing " "or invalid." % str(excp) + ) + + if sanitizedrivelist: + changes = True + if mediatype == "SSD": + erase_pattern_string = "SanitizeRestrictedBlockErase" + else: + erase_pattern_string = "SanitizeRestrictedOverwrite" + + contentsholder = { + "Actions": [ + { + "Action": "PhysicalDriveErase", + "ErasePattern": erase_pattern_string, + "PhysicalDriveList": sanitizedrivelist, + } + ], + "DataGuard": "Disabled", + } + + # self.rdmc.ui.printer( + # "DriveSanitize path and payload: %s, %s\n" + # % (controller["@odata.id"], contentsholder) + # ) + patch_path = controller["@odata.id"] + if "settings" not in patch_path: + patch_path = patch_path + "settings/" + controller["@odata.id"] = patch_path + + self.rdmc.app.patch_handler(controller["@odata.id"], contentsholder) + + return changes + + def convertloc(self, servicelabel): + loc = servicelabel.split(":") + temp_str = str(loc[1].split("=")[1] + ":" + loc[2].split("=")[1] + ":" + loc[3].split("=")[1]) + return temp_str + + def storagesanitizedrives(self, drivelist, controller_drives, optall): + sanitizedrivelist = [] + changes = False + plocation = "" + if len(controller_drives) != 0: + for plist in controller_drives.values(): + logicaldrivelist = [] + logicaldrivelist.extend(plist["Links"]["Volumes"]) + plocation = self.convertloc(plist["PhysicalLocation"]["PartLocation"]["ServiceLabel"]) + + if optall: + if logicaldrivelist: + self.rdmc.ui.printer( + "Unable to" + " sanitize drive %s. Remove" + " any volume(s) associated " + "with this drive and try again.\n" % plocation + ) + break + sanitizedrivelist.append(plocation) + else: + for erasedrive in drivelist: + try: + if erasedrive == plocation: + if logicaldrivelist: + self.rdmc.ui.printer( + "Unable to" + " sanitize drive %s. Remove" + " any volume(s) associated " + "with this drive and try again.\n" % plocation + ) + self.rdmc.ui.printer("Setting physical drive %s " "for sanitization\n" % plocation) + sanitizedrivelist.append(plocation) + break + except KeyError as excp: + raise NoContentsFoundForOperationError( + "The property '%s' is missing " "or invalid.\n" % str(excp) + ) + + if sanitizedrivelist: + changes = True + path = plist["@odata.id"] + "/Actions/Drive.SecureErase" + contentsholder = {} + + self.rdmc.ui.printer("DriveSanitize path and payload: %s, %s\n" % (path, contentsholder)) + + self.rdmc.app.post_handler(path, contentsholder) + sanitizedrivelist = [] + self.rdmc.ui.printer("You can check the sanitization status of %s using --status\n" % (plocation)) + + return changes + + def validate_mediatype(self, erasedrive, mediatype, controller_drives): + """validates media type as HDD or SSD""" + for idx in list(controller_drives.keys()): + phy_drive = controller_drives[idx] + if phy_drive["Location"] == erasedrive and phy_drive["MediaType"] == mediatype: + return True + return False + + def monitorsanitization(self): + """monitors sanitization percentage""" + # TODO: Add code to give updates on sanitization + + def drivesanitizevalidation(self, options): + """drive sanitize validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--controller", + dest="controller", + help="Use this flag to select the corresponding controller " "using either the slot number or index.", + default=None, + ) + customparser.add_argument( + "--storageid", + dest="storageid", + help="Use this flag to select the corresponding storageid in iLO6 only.", + default=None, + ) + customparser.add_argument( + "--mediatype", + dest="mediatype", + help="""Use this flag to select the mediatype of the hard disk """, + default=None, + required=True, + ) + customparser.add_argument( + "--reboot", + dest="reboot", + help="Include this flag to perform a coldboot command " + "function after completion of operations and monitor " + "sanitization.", + action="store_true", + default=False, + ) + customparser.add_argument( + "--all", + dest="all", + help="""Use this flag to sanitize all physical drives on a """ """controller.""", + action="store_true", + default=False, + ) + customparser.add_argument( + "--drivereset", + dest="drivereset", + help="""Use this flag to reset physical drives on a """ """controller.""", + action="store_true", + default=False, + ) + customparser.add_argument( + "--status", + dest="status", + help="""Use this flag to check sanitization status of a """ """controller.""", + action="store_true", + default=False, + ) diff --git a/ilorest/extensions/SMART_ARRAY_COMMANDS/FactoryResetControllerCommand.py b/ilorest/extensions/SMART_ARRAY_COMMANDS/FactoryResetControllerCommand.py new file mode 100644 index 0000000..201676a --- /dev/null +++ b/ilorest/extensions/SMART_ARRAY_COMMANDS/FactoryResetControllerCommand.py @@ -0,0 +1,223 @@ +### +# Copyright 2016-2021 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 -*- +""" Factory Reset Controller Command for rdmc """ + + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class FactoryResetControllerCommand: + """Factory reset controller command""" + + def __init__(self): + self.ident = { + "name": "factoryresetcontroller", + "usage": None, + "description": "Run without " + "arguments for the current controller options.\n\texample: " + "factoryresetcontroller\n\n\tTo factory reset a controller " + "by index.\n\texample: factoryresetcontroller --controller=2" + '\n\texample: factoryresetcontroller --controller="Slot 1" \n\n' + "\tTo factory reset a controller on Gen11 server\n" + "\texample: factoryresetcontroller --reset_type resetall --storageid DE000100\n" + "\texample: factoryresetcontroller --reset_type preservevolumes --storageid DE000100\n", + "summary": "Factory resets a controller by index or location.", + "aliases": [], + "auxcommands": ["SelectCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main disk inventory worker function + + :param line: command line input + :type line: string. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.frcontrollervalidation(options) + + ilo_ver = self.rdmc.app.getiloversion() + if ilo_ver >= 6.110: + if not options.storageid: + raise InvalidCommandLineError( + "--storageid option is mandatory for iLO6 along with --controller option.\n" + ) + if ( + (options.controller is not None) + and (options.storageid is not None) + and (options.reset_type is not None) + ): + raise InvalidCommandLineError("--controller is not supported in iLO6.") + + elif (options.storageid is not None) and (options.reset_type is not None): + st_content = [] + self.auxcommands["select"].selectfunction("StorageCollection.") + get_content = self.rdmc.app.getprops() + for st_controller in get_content: + path = st_controller["Members"] + for mem in path: + for val in mem.values(): + if "DE" in val and options.storageid in val: + getval = self.rdmc.app.get_handler(val, silent=True, service=True).dict + if options.storageid: + if getval["Id"] == options.storageid: + st_content.append(getval) + else: + raise InvalidCommandLineError( + "Selected storage id not found in the current inventory " "list." + ) + else: + st_content.append(getval) + for st_controller in st_content: + path = st_controller["Actions"]["#Storage.ResetToDefaults"]["target"] + + if options.reset_type.lower() == "resetall": + actionitem = "ResetAll" + body = {"ResetType": actionitem} + elif options.reset_type.lower() == "preservevolumes": + actionitem = "PreserveVolumes" + body = {"ResetType": actionitem} + else: + raise InvalidCommandLineError("Invalid command.") + + # self.rdmc.ui.printer( + # "FactoryReset path and payload: %s, %s\n" + # % (path, body) + # ) + self.rdmc.app.post_handler(path, body) + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + elif (options.storageid is None) or (options.reset_type is None): + raise InvalidCommandLineError("Invalid command\n" "--reset_type and --storageid is required") + + else: + self.auxcommands["select"].selectfunction("SmartStorageConfig.") + content = self.rdmc.app.getprops() + if options.controller and options.reset_type is None and options.storageid is None: + controllist = [] + + try: + if options.controller and options.controller.isdigit(): + slotlocation = self.get_location_from_id(options.controller) + if slotlocation: + slotcontrol = slotlocation.lower().strip('"').split("slot")[-1].lstrip() + for control in content: + if slotcontrol.lower() == control["Location"].lower().split("slot")[-1].lstrip(): + controllist.append(control) + # else: + # self.parser.print_help() + # return ReturnCodes.SUCCESS + if not controllist: + raise InvalidCommandLineError("") + except InvalidCommandLineError: + raise InvalidCommandLineError("Selected controller not found in the current inventory " "list.") + for controller in controllist: + contentsholder = { + "Actions": [{"Action": "FactoryReset"}], + "DataGuard": "Disabled", + } + self.rdmc.ui.printer( + "FactoryReset path and payload: %s, %s\n" % (controller["@odata.id"], contentsholder) + ) + self.rdmc.app.patch_handler(controller["@odata.id"], contentsholder) + + elif options.controller and (options.reset_type is not None) or (options.storageid is not None): + raise InvalidCommandLineError( + "Invalid command\n" "--reset_type and --storageid is not supported in iLO5" + ) + + for idx, val in enumerate(content): + self.rdmc.ui.printer("[%d]: %s\n" % (idx, val["Location"])) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def get_location_from_id(self, controller_id): + for sel in self.rdmc.app.select("SmartStorageArrayController", path_refresh=True): + if "Collection" not in sel.maj_type: + controller = sel.dict + if controller["Id"] == str(controller_id): + return controller["Location"] + return None + + def frcontrollervalidation(self, options): + """Factory reset controller validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--controller", + dest="controller", + help="Use this flag to select the corresponding controller " "using either the slot number or index.", + default=None, + ) + + customparser.add_argument( + "--reset_type", + dest="reset_type", + help="Use this flag to pass the reset type for storage controller in iLO6 ", + default=None, + ) + + customparser.add_argument( + "--storageid", + dest="storageid", + help="Use this flag to select the corresponding storage id in iLO6", + default=None, + ) diff --git a/ilorest/extensions/SMART_ARRAY_COMMANDS/StorageControllerCommand.py b/ilorest/extensions/SMART_ARRAY_COMMANDS/StorageControllerCommand.py new file mode 100644 index 0000000..e42b1bc --- /dev/null +++ b/ilorest/extensions/SMART_ARRAY_COMMANDS/StorageControllerCommand.py @@ -0,0 +1,2128 @@ +### +# Copyright 2016-2021 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 -*- +""" Storage Controller Command for rdmc """ +import json +import time +import sys +from argparse import RawDescriptionHelpFormatter + +import redfish + +try: + from rdmc_helper import ( + UI, + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + UI, + InvalidFileInputError, + Encryption, + ) + +from redfish.ris.resp_handler import ResponseHandler + +# from redfish.ris.utils import iterateandclear + +__config_file__ = "storagecontroller_config.json" + +# from rdmc_base_classes import HARDCODEDLIST + +__subparsers__ = ["load", "save", "state"] + + +class StorageControllerCommand: + """Storage controller command""" + + def __init__(self): + self.ident = { + "name": "storagecontroller", + "usage": None, + "description": "\tTo Manage PLDM for RDE storage devices. For other storage devices (i.e NVMe)," + "use the `select type/get/set` paradigm or raw commands.\n\n" + "\tRun without arguments for the " + "current list of Storage controllers.\n\texample: " + "storagecontroller\n\n\tTo get more details on a specific controller " + "select it by index Note: On ILO6, --storageid is mandatory along with --controller.\n\t" + "example: storagecontroller --storageid=DE00E000 --controller=2" + "\n\n\tTo get more details on a specific controller select " + 'it by location.\n\texample: storagecontroller --storageid=DE00E000 --controller "Slot 0"' + "\n\n\tIn order to get a list of all physical drives for " + "each controller.\n\texample: storagecontroller --physicaldrives" + "\n\n\tTo obtain details about physical drives for a " + "specific controller.\n\texample: storagecontroller --storageid=DE00E000 --controller=3 " + "--physicaldrives\n\n\tTo obtain details about a specific " + "physical drive for a specific controller.\n\texample: storagecontroller " + "--storageid=DE00E000 --controller=3 --pdrive=1I:1:1\n\n\tIn order to get a list of " + "all volumes for the each controller.\n\texample: " + "storagecontroller --logicaldrives\n\n\tTo obtain details about " + "volumes for a specific controller.\n\texample: " + "storagecontroller --storageid=DE00E000 --controller=3 --logicaldrives\n\n\tTo obtain " + "details about a specific volume for a specific " + "controller.\n\texample: storagecontroller --storageid=DE00E000 --controller=3 --ldrive=1\n\n\tTo obtain " + "details about a specific Storage id for a specific " + "controllers.\n\texample: storagecontroller --storageid=DE00E000\n", + "summary": "Discovers all storage controllers installed in the " "server and managed by the SmartStorage.", + "aliases": ["smartarray"], + "auxcommands": ["SelectCommand"], + } + self.config_file = None + self.fdata = None + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def file_handler(self, filename, operation, options, data=None, sk=None): + """ + Wrapper function to read or write data to a respective file + + :param data: data to be written to output file + :type data: container (list of dictionaries, dictionary, etc.) + :param file: filename to be written + :type file: string (generally this should be self.clone_file or tmp_clone_file + :param operation: file operation to be performed + :type operation: string ('w+', 'a+', 'r+') + :param sk: sort keys flag + :type sk: boolean + :param options: command line options + :type options: attribute + :returns: json file data + """ + writeable_ops = ["w", "w+", "a", "a+"] + fdata = None + + try: + if operation in writeable_ops: + if getattr(options, "encryption", False): + with open(filename, operation + "b") as outfile: + outfile.write( + Encryption().encrypt_file( + json.dumps( + data, + indent=2, + cls=redfish.ris.JSONEncoder, + sort_keys=sk, + ), + getattr(options, "encryption", False), + ) + ) + else: + with open(filename, operation) as outfile: + outfile.write( + json.dumps( + data, + indent=2, + cls=redfish.ris.JSONEncoder, + sort_keys=sk, + ) + ) + else: + if getattr(options, "encryption", False): + with open(filename, operation + "b") as file_handle: + fdata = json.loads( + Encryption().decrypt_file( + file_handle.read(), + getattr(options, "encryption", False), + ) + ) + else: + with open(filename, operation) as file_handle: + fdata = json.loads(file_handle.read()) + return fdata + except Exception as excp: + raise InvalidFileInputError( + "Unable to open file: %s.\nVerify the file location " "and the file has a valid JSON format.\n" % excp + ) + + def controller_id(self, options): + """ + Get iLO types from server and save storageclone URL get Controller + :parm options: command line options + :type options: attribute + :returns: returns list + """ + self.auxcommands["select"].selectfunction("StorageControllerCollection.") + ctr_content = self.rdmc.app.getprops() + ctrl_data = [] + all_ctrl = dict() + for ct_controller in ctr_content: + path = ct_controller["Members"] + for i in path: + res = i["@odata.id"] + ctrl_data.append(res) + ctrl_id_url = res + "?$expand=." + get_ctr = self.rdmc.app.get_handler(ctrl_id_url, silent=True, service=True).dict + _ = get_ctr["@odata.id"].split("/") + get_ctr = self.rdmc.app.removereadonlyprops(get_ctr, False, True) + all_ctrl[get_ctr["Name"]] = get_ctr + return all_ctrl + + def get_volume(self, options): + """ + Get iLO types from server and save storageclone URL get volumes + :parm options: command line options + :type options: attribute + :returns: returns list + """ + self.auxcommands["select"].selectfunction("VolumeCollection.") + vol_content = self.rdmc.app.getprops() + vol_data = [] + all_vol = dict() + for st_volume in vol_content: + path = st_volume["Members"] + for i in path: + res = i["@odata.id"] + if self.rdmc.opts.verbose: + sys.stdout.write("Saving properties of type %s \t\n" % res) + vol_data.append(res) + vol_id_url = res + "?$expand=." + get_vol = self.rdmc.app.get_handler(vol_id_url, silent=True, service=True).dict + # print("Assigned drive", get_vol["Links"]["Drives"]) + get_vol = self.rdmc.app.removereadonlyprops(get_vol, False, True) + all_vol[get_vol["Name"]] = get_vol + return all_vol + + def run(self, line, help_disp=False): + """Main sstorage controller worker function + :param help_disp: command line input + :type help_disp: bool. + :param line: command line input + :type line: string. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + ident_subparser = False + for cmnd in __subparsers__: + if cmnd in line: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + ident_subparser = True + break + if not ident_subparser: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line, default=True) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.storagecontrollervalidation(options) + ilo_ver = self.rdmc.app.getiloversion() + + if options.command == "state": + if ilo_ver >= 6: + storage_ctlr = self.storagecontroller(options, print_ctrl=False, single_use=True) + for storage in storage_ctlr: + if storage_ctlr[storage].get("@Redfish.Settings"): + time = storage_ctlr[storage]["@Redfish.Settings"].get("Time", "Not Available") + sys.stdout.write("Last Configuration Attempt: %s\n" % str(time)) + for message in storage_ctlr[storage]["@Redfish.Settings"].get("Messages", []): + ResponseHandler( + self.rdmc.app.validationmanager, + self.rdmc.app.typepath.defs.messageregistrytype, + ).message_handler( + response_data=message, + message_text="", + verbosity=2, + dl_reg=False, + ) + else: + sys.stdout.write( + "Previous storage controller configuration status messages are " + "not available for controller '%s - %s - %s' (@Redfish.Settings not " + "available).\n" + % ( + storage, + storage_ctlr[storage].get("Location", "Unknown"), + storage_ctlr[storage].get("Model", "Unknown"), + ) + ) + else: + controllers = self.controllers(options, print_ctrl=False, single_use=True) + for controller in controllers: + if controllers[controller].get("@Redfish.Settings"): + time = controllers[controller]["@Redfish.Settings"].get("Time", "Not Available") + sys.stdout.write("Last Configuration Attempt: %s\n" % str(time)) + for message in controllers[controller]["@Redfish.Settings"].get("Messages", []): + ResponseHandler( + self.rdmc.app.validationmanager, + self.rdmc.app.typepath.defs.messageregistrytype, + ).message_handler( + response_data=message, + message_text="", + verbosity=2, + dl_reg=False, + ) + else: + sys.stdout.write( + "Previous storage controller configuration status messages are " + "not available for controller '%s - %s - %s' (@Redfish.Settings not " + "available).\n" + % ( + controller, + controllers[controller].get("Location", "Unknown"), + controllers[controller].get("Model", "Unknown"), + ) + ) + if options.command == "save": + if ilo_ver >= 6.110: + storage = {} + all_stgcntrl = {} + st_content = self.rdmc.app.get_handler("/redfish/v1/Systems/1/Storage/", silent=True, service=True).dict + sel = st_content["Members"] + for i in sel: + res = i["@odata.id"] + if "DE" in res: + break + else: + continue + storage_id_url = res + "?$expand=." + getval = self.rdmc.app.get_handler(storage_id_url, silent=True, service=True).dict + vol = self.get_volume(options) + ctr = self.controller_id(options) + getval = self.rdmc.app.removereadonlyprops(getval, False, True) + storage.update(getval) + getval["Controllers"]["Members"].append(ctr) + del getval["Controllers"]["Members"][0] + getval["Volumes"]["Members"].append(vol) + del getval["Volumes"]["Members"][0] + all_stgcntrl[getval["Id"]] = getval + self.file_handler(self.config_file, "w", options, all_stgcntrl, sk=True) + sys.stdout.write("Storage Controller configuration saved to '%s'.\n" % self.config_file) + else: + st_content = self.rdmc.app.get_handler("/redfish/v1/Systems/1/Storage/", silent=True, service=True).dict + sel = st_content["Members"] + for i in sel: + res = i["@odata.id"] + if "DE" in res: + break + else: + continue + storage = {} + all_stgcntrl = {} + storage_id_url = res + "?$expand=." + getval = self.rdmc.app.get_handler(storage_id_url, silent=True, service=True).dict + vol = self.get_volume(options) + ctr = self.controller_id(options) + getval = self.rdmc.app.removereadonlyprops(getval, False, True) + storage.update(getval) + getval["Controllers"]["Members"].append(ctr) + del getval["Controllers"]["Members"][0] + getval["Volumes"]["Members"].append(vol) + del getval["Volumes"]["Members"][0] + all_stgcntrl[getval["Id"]] = getval + self.file_handler(self.config_file, "w", options, all_stgcntrl, sk=True) + sys.stdout.write("Storage Controller configuration saved to '%s'.\n" % self.config_file) + controllers = self.controllers(options, print_ctrl=False, single_use=True) + for key, controller in controllers.items(): + physical_drives = self.physical_drives(options, controller, print_ctrl=False, single_use=True) + logical_drives = self.logical_drives(options, controller, print_ctrl=False, single_use=True) + if self.rdmc.app.typepath.defs.isgen10: + for drivec_idx, drivec in enumerate(controller.get("PhysicalDrives", [])): + for drive in physical_drives: + if drivec["Location"] == physical_drives[drive]["Location"]: + controller["PhysicalDrives"][drivec_idx].update(physical_drives[drive]) + break + for drivec_idx, drivec in enumerate(controller.get("LogicalDrives", [])): + for drive in logical_drives: + if drivec["LogicalDriveNumber"] == logical_drives[drive]["Id"]: + controller["LogicalDrives"][drivec_idx].update(logical_drives[drive]) + break + if controller.get("Links"): + controller["Links"]["LogicalDrives"] = logical_drives + controller["Links"]["PhysicalDrives"] = physical_drives + else: + controller["links"]["LogicalDrives"] = logical_drives + controller["links"]["PhysicalDrives"] = physical_drives + + self.file_handler(self.config_file, "w", options, controllers, sk=True) + sys.stdout.write("Storage Controller configuration saved to '%s'.\n" % self.config_file) + + if options.command == "load": + if ilo_ver >= 6.110: + storage_load = self.file_handler(self.config_file, operation="rb", options=options) + controller_id = storage_load["Controllers"] + logical_id = storage_load["Volumes"] + physical_id = storage_load["Drives"] + if controller_id: + for data in controller_id: + id_controller = controller_id[data].get("@odata.id") + controllers = self.rdmc.app.get_handler(id_controller, silent=True).dict + readonly_removed_controllers = self.storagecontrollerremovereadonly(controllers) + readonly_removed = storage_load["Controllers"] + for controller, val in readonly_removed.items(): + for i in readonly_removed_controllers: + try: + if (i in val) and (controllers[i] != val[i]): + body = {str(i): val[i]} + self.rdmc.app.patch_handler(id_controller, body) + except: + sys.stdout.write("Unable to update the property " "for: '%s'.\n" % i) + continue + if logical_id: + for logical_data in logical_id: + id_logical = logical_id[logical_data].get("@odata.id") + logicals = self.rdmc.app.get_handler(id_logical, silent=True).dict + readonly_removed_logical = self.storagecontrollerremovereadonly(logicals) + readonly_logical = storage_load["Volumes"] + for logical, val in readonly_logical.items(): + for lg in readonly_removed_logical: + try: + if (lg in val) and (logicals[lg] != val[lg]): + body = {str(lg): val[lg]} + self.rdmc.app.patch_handler(id_logical, body) + except: + sys.stdout.write("Unable to update the property " "for: '%s'.\n" % lg) + continue + if physical_id: + for physical_data in physical_id: + id_physical = physical_id[physical_data].get("@odata.id") + physicals = self.rdmc.app.get_handler(id_physical, silent=True).dict + readonly_removed_physical = self.storagecontrollerremovereadonly(physicals) + readonly_physical = storage_load["Drives"] + for logical, val in readonly_physical.items(): + for lg in readonly_removed_physical: + try: + if (lg in val) and (physicals[lg] != val[lg]): + body = {str(lg): val[lg]} + self.rdmc.app.patch_handler(id_physical, body) + except: + sys.stdout.write("Unable to update the property " "for: '%s'.\n" % lg) + continue + sys.stdout.write( + "Storage Controller configuration loaded. Reboot the server to " "finalize the configuration.\n" + ) + + else: + controllers = self.file_handler(self.config_file, operation="rb", options=options) + + for controller in controllers: + put_path = controllers[controller]["@Redfish.Settings"]["SettingsObject"]["@odata.id"] + if controllers[controller].get("DataGuard"): + controllers[controller]["DataGuard"] = "Disabled" + readonly_removed = self.rdmc.app.removereadonlyprops(controllers[controller]) + readonly_removed["LogicalDrives"] = controllers[controller]["LogicalDrives"] + readonly_removed["PhysicalDrives"] = controllers[controller]["PhysicalDrives"] + # self.rdmc.app.put_handler(controllers[controller]["@odata.id"], readonly_removed) + self.rdmc.app.put_handler(put_path, readonly_removed) + + sys.stdout.write( + "Storage Controller configuration loaded. Reboot the server to " + "finalize the configuration.\n" + ) + elif options.command == "default": + if ilo_ver >= 6: + if options.storageid and not options.controller: + self.storageid(options, print_ctrl=True, single_use=False) + elif options.storageid and options.controller: + self.storagecontroller(options, print_ctrl=True, single_use=False) + elif options.controller and not options.storageid: + raise InvalidCommandLineError( + "with --controller option, --storageid option is mandatory for iLO6.\n" + ) + elif ( + options.physicaldrives or options.pdrive or options.logicaldrives or options.ldrive + ) and not options.storageid: + raise InvalidCommandLineError("--storageid option is mandatory for iLO6.\n") + elif ( + not options.storageid + and not options.controller + and not options.physicaldrives + and not options.pdrive + and not options.logicaldrives + and not options.ldrive + ): + self.list_all_storages(options, print_ctrl=True, single_use=False) + else: + self.controllers(options, print_ctrl=True, single_use=False) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def list_all_storages(self, options, print_ctrl=False, single_use=False): + # self.auxcommands["select"].selectfunction("StorageCollection.") + st_url = "/redfish/v1/Systems/1/Storage/" + st_content = self.rdmc.app.get_handler(st_url, silent=True, service=True) + if not "error" in st_content.dict: + if st_content.dict["Members"]: + st_content = st_content.dict["Members"] + + # st_content = self.rdmc.app.getprops() + if self.rdmc.opts.verbose or not options.json: + sys.stdout.write("---------------------------------\n") + sys.stdout.write("List of RDE storage devices\n") + sys.stdout.write("---------------------------------\n") + if options.json: + outjson = dict() + for st_controller in st_content: + # path = st_controller["Members"] + # for mem in path: + for val in st_controller.values(): + if "DE" in val: + st = self.rdmc.app.get_handler(val, silent=True, service=True).dict + health = "" + if "Health" in st["Status"]: + health = st["Status"]["Health"] + elif "HealthRollup" in st["Status"]: + health = st["Status"]["HealthRollup"] + if options.json: + outjson[st["Id"]] = dict() + outjson[st["Id"]]["Name"] = st["Name"] + outjson[st["Id"]]["Health"] = health + outjson[st["Id"]]["State"] = st["Status"]["State"] + else: + sys.stdout.write( + "%s: %s: Health %s: %s\n" % (st["Id"], st["Name"], health, st["Status"]["State"]) + ) + + if options.json: + UI().print_out_json(outjson) + else: + raise InvalidCommandLineError( + "Storage controllers are not ready , Kindly re run the command after sometime\n") + else: + raise InvalidCommandLineError("Storage controllers are not ready , Kindly re run the command after sometime\n") + + def storagecontrollerremovereadonly(self, controller): + templist = [ + "Ports", + "CacheSummary", + "SupportedRAIDTypes", + "PCIeInterface", + "SupportedControllerProtocols", + "SupportedDeviceProtocols", + "SKU", + "SpeedGbps", + "SerialNumber", + "PartNumber", + "Model", + "@odata.etag", + "Status", + "Manufacturer", + "Location", + "Identifiers", + "FirmwareVersion", + "Name", + "Id", + "@odata.type", + "@odata.id", + "Links", + ] + + remove_data = self.rdmc.app.removereadonlyprops(controller, False, True, templist) + return remove_data + + def odataremovereadonly(self, tmp): + templist = [ + "@odata.etag", + "@odata.type", + "@odata.context", + ] + remove_data = self.rdmc.app.removereadonlyprops(tmp, False, True, templist) + return remove_data + + def controllers(self, options, print_ctrl=False, single_use=False): + """ + Identify/parse volumes (and child properties) of a parent array controller + + :param options: command line options (options.controller is a required attribute) + :type options: object attributes + :param print_ctrl: flag for console print enablement/disablement (default disabled) + :type print_ctrl: bool + :param single_use: singular usage - True or explore mode - False, + returns dictionary of results to calling function (default disabled) - see returns. + :type single_use: bool + :returns: None, dictionary of all controllers identified by 'Id' + """ + no_need_ilo5 = False + if getattr(options, "controller", False): + controller_ident = False + + if ( + not (getattr(options, "json", False)) + and not (getattr(options, "controller", False)) + and print_ctrl + and (options.physicaldrives is None) + and (options.pdrive is None) + and (options.logicaldrives is None) + and (options.ldrive is None) + ): + if not options.storageid and not options.controllers: + sys.stdout.write("---------------------------------\n") + sys.stdout.write("List of RDE storage devices\n") + sys.stdout.write("---------------------------------\n") + + st_flag = False + storage_data = {} + get_contrller = [] + list_storageid = self.rdmc.app.get_handler("/redfish/v1/Systems/1/Storage/", silent=True, service=True) + if not "error" in list_storageid.dict: + if len(list_storageid.dict["Members"]) > 0: + list_storageid = list_storageid.dict[ + "Members" + ] + for val in list_storageid: + storageid_fromurl = val["@odata.id"] + # storageid_fromurl = val.split("/")[-2] + try: + stdout = "DE" in storageid_fromurl and options.storageid in storageid_fromurl + except: + stdout = "DE" in storageid_fromurl + if "DE" in storageid_fromurl and stdout: + st_flag = True + getval = self.rdmc.app.get_handler(storageid_fromurl, silent=True, service=True).dict + try: + controller = getval["Controllers"] + volumes = getval["Volumes"] + list_controllers = self.rdmc.app.get_handler( + controller["@odata.id"], silent=True, service=True + ).dict + list_volumes = self.rdmc.app.get_handler(volumes["@odata.id"], silent=True, service=True).dict + except KeyError: + self.rdmc.ui.printer("Please check controller was Gen11.\n") + return ReturnCodes.NO_CONTENTS_FOUND_FOR_OPERATION + + for ctl in list_controllers["Members"]: + ctl = ctl["@odata.id"] + get_sel = self.rdmc.app.get_handler(ctl, silent=True, service=True) + get_contrller.append(get_sel) + else: + continue + if not st_flag: + sys.stdout.write( + "Storage ID {} not found or Storage ID is not redfish enabled and does not have DE\n".format( + options.storageid + ) + ) + return + no_need_ilo5 = True + outjson = dict() + for sel in get_contrller: + if "Collection" not in sel.path: + controller = sel.dict + storage_id = controller["@odata.id"].split("/") + storage_id = storage_id[6] + if getattr(options, "controller", False): + if ( + getattr(options, "controller", False) == controller["Id"] + or getattr(options, "controller", False)[-1] + == controller["Location"]["PartLocation"]["ServiceLabel"][-1] + ): + controller_ident = True + else: + continue + if ( + print_ctrl + and not getattr(options, "controller", False) + and not getattr(options, "physicaldrives", False) + and not getattr(options, "pdrive", False) + and not getattr(options, "ldrive", False) + and not getattr(options, "logicaldrives", False) + and options.storageid is None + and not options.json + and not getattr(options, "controllers", False) + ): + health = "" + if "Health" in controller["Status"]: + health = controller["Status"]["Health"] + elif "HealthRollup" in controller["Status"]: + health = controller["Status"]["HealthRollup"] + sys.stdout.write( + "%s: %s: Health %s: %s\n" + % (storage_id, controller["Model"], health, controller["Status"]["State"]) + ) + elif ( + getattr(options, "json", False) + and (options.controller is None) + and (options.physicaldrives is None) + and (options.pdrive is None) + and (options.ldrive is None) + and (options.logicaldrives is None) + and options.storageid is None + and options.json + and not options.controllers + ): + tmp = dict() + tmp["Name"] = controller["Name"] + tmp["State"] = controller["Status"]["Health"] + tmp["Health"] = controller["Status"]["State"] + outjson[storage_id] = tmp + + if ( + not getattr(options, "json", False) + and options.storageid + and not getattr(options, "controllers", False) + and not getattr(options, "physicaldrives", False) + and not getattr(options, "logicaldrives", False) + and not getattr(options, "ldrive", False) + and not getattr(options, "pdrive", False) + and not getattr(options, "controller", False) + ): + sys.stdout.write("-----------------------------------\n") + sys.stdout.write("Details of Storage %s\n" % options.storageid) + sys.stdout.write("-----------------------------------\n") + sys.stdout.write("\tId: %s\n\t" % getval["Id"]) + sys.stdout.write("Health: %s\n\t" % getval["Status"]["HealthRollup"]) + sys.stdout.write("Name: %s\n\t" % getval["Name"]) + sys.stdout.write("Number of Controllers: %s\n\t" % list_controllers["Members@odata.count"]) + sys.stdout.write("Number of Volumes: %s\n\t" % list_volumes["Members@odata.count"]) + sys.stdout.write("Number of Drives: %s\n\t\n" % getval["Drives@odata.count"]) + if ( + getattr(options, "json", False) + and options.storageid + and options.json + and not options.physicaldrives + and not options.logicaldrives + and not options.ldrive + and not options.pdrive + and not options.controller + and not options.controllers + ): + storage_data["Storage"] = dict() + + storage_data["Storage"].update({"Id": getval["Id"]}) + storage_data["Storage"].update({"Health": getval["Status"]["HealthRollup"]}) + storage_data["Storage"].update({"Name": getval["Name"]}) + storage_data["Storage"].update({"Number of Controllers": list_controllers["Members@odata.count"]}) + storage_data["Storage"].update({"Number of Volumes": list_volumes["Members@odata.count"]}) + storage_data["Storage"].update({"Number of Drives": getval["Drives@odata.count"]}) + UI().print_out_json(storage_data) + elif ( + # print_ctrl + getattr(options, "json", False) + and (options.controller is not None) + and ( + (options.logicaldrives is not None) + or (options.physicaldrives is not None) + or (options.ldrive is not None) + or (options.pdrive is not None) + ) + ): + pass + + elif ( + getattr(options, "json", False) + and (options.controller is not None) + and ( + (options.logicaldrives is not None) + or (options.physicaldrives is not None) + or (options.ldrive is not None) + or (options.pdrive is not None) + ) + ): + pass + # controllers code with json and without json + elif ( + getattr(options, "controllers", False) + and options.storageid + and options.controllers + and options.json is None + ): + sys.stdout.write("---------------------------------------------------\n") + sys.stdout.write("List of Controllers in this Storage ID %s\n" % options.storageid) + sys.stdout.write("---------------------------------------------------\n") + list_ctr = list_controllers["Members"] + for lst in list_ctr: + l_data = lst["@odata.id"] + get_ctr = self.rdmc.app.get_handler(l_data, silent=True, service=True).dict + sys.stdout.write("\tId: %s\n\t" % get_ctr["Id"]) + sys.stdout.write("Health: %s\n\t" % get_ctr["Status"]["Health"]) + sys.stdout.write("Name: %s\n\t" % get_ctr["Name"]) + sys.stdout.write("FirmwareVersion: %s\n\t" % get_ctr["FirmwareVersion"]) + sys.stdout.write("SerialNumber: %s\n\t\n" % get_ctr["SerialNumber"]) + elif ( + getattr(options, "json", False) + and options.storageid + and print_ctrl + and options.json + and options.controllers + ): + # sys.stdout.write("---------------------------------------------------\n") + # sys.stdout.write("List of Controllers in this Storage ID %s\n" % options.storageid) + # sys.stdout.write("---------------------------------------------------\n") + list_ctr = list_controllers["Members"] + for lst in list_ctr: + l_data = lst["@odata.id"] + get_ctr = self.rdmc.app.get_handler(l_data, silent=True, service=True).dict + ctr_data = dict() + ctr_data["Controllers"] = dict() + ctr_data["Controllers"].update({"Id": get_ctr["Id"]}) + ctr_data["Controllers"].update({"Health": get_ctr["Status"]["Health"]}) + ctr_data["Controllers"].update({"Name": get_ctr["Name"]}) + ctr_data["Controllers"].update({"FirmwareVersion": get_ctr["FirmwareVersion"]}) + ctr_data["Controllers"].update({"SerialNumber": get_ctr["SerialNumber"]}) + UI().print_out_json(ctr_data) + + elif ( + getattr(options, "controller", False) + and print_ctrl + and (options.controller is not None) + and options.json is None + and not options.physicaldrives + and not options.pdrive + and not options.logicaldrives + and not options.ldrive + ): + controller_info = "---------------------------------------------\n" + controller_info += "Controller {} Details on Storage Id {}\n".format(options.controller, storage_id) + controller_info += "---------------------------------------------\n" + controller_info += "Id: %s\n" % controller["Id"] + controller_info += "StorageId: %s\n" % storage_id + controller_info += "Name: %s\n" % controller["Name"] + controller_info += "FirmwareVersion: %s\n" % controller["FirmwareVersion"] + controller_info += "Manufacturer: %s\n" % controller["Manufacturer"] + controller_info += "Model: %s\n" % controller["Model"] + controller_info += "PartNumber: %s\n" % controller["PartNumber"] + controller_info += "SerialNumber: %s\n" % controller["SerialNumber"] + try: + controller_info += "SKU: %s\n" % controller["SKU"] + except KeyError: + pass + controller_info += "Status: %s\n" % controller["Status"] + try: + controller_info += "SupportedDeviceProtocols: %s\n" % controller["SupportedDeviceProtocols"] + except KeyError: + pass + try: + controller_info += ( + "SupportedControllerProtocols: %s\n" % controller["SupportedControllerProtocols"] + ) + except KeyError: + pass + self.rdmc.ui.printer(controller_info, verbose_override=True) + + elif getattr(options, "json", False) and (options.controller is not None): + outjson_data = dict() + outjson_data.update({"Id": controller["Id"]}) + outjson_data.update({"StorageId": storage_id}) + outjson_data.update({"Name": controller["Name"]}) + outjson_data.update({"FirmwareVersion": controller["FirmwareVersion"]}) + outjson_data.update({"Manufacturer": controller["Manufacturer"]}) + outjson_data.update({"Model": controller["Model"]}) + outjson_data.update({"PartNumber": controller["PartNumber"]}) + outjson_data.update({"SerialNumber": controller["SerialNumber"]}) + try: + outjson_data.update({"SKU": controller["SKU"]}) + except KeyError: + pass + outjson_data.update({"Status": controller["Status"]}) + try: + outjson_data.update({"SupportedDeviceProtocols": controller["SupportedDeviceProtocols"]}) + except KeyError: + pass + try: + outjson_data.update( + {"SupportedControllerProtocols": controller["SupportedControllerProtocols"]} + ) + except KeyError: + pass + UI().print_out_json(outjson_data) + + if getattr(options, "logicaldrives", False) or getattr(options, "ldrive", False) or single_use: + self.storagelogical_drives(options, controller, storage_id, print_ctrl) + + if getattr(options, "physicaldrives", False) or getattr(options, "pdrive", False) or single_use: + self.storagephysical_drives(options, controller, storage_id, print_ctrl) + + if single_use: + storage_data[controller["Id"]] = controller + if getattr(options, "controller", False) and not controller_ident and print_ctrl: + sys.stdout.write("Controller in position '%s' was not found\n" % (getattr(options, "controller", False))) + if single_use: + return storage_data + + else: + if not no_need_ilo5: + controller_data = {} + for sel in self.rdmc.app.select("SmartStorageArrayController", path_refresh=True): + if "Collection" not in sel.maj_type: + controller = sel.dict + if getattr(options, "controller", False): + if ( + getattr(options, "controller", False) == controller["Id"] + or getattr(options, "controller", False) == controller["Location"] + ): + controller_ident = True + else: + continue + + if self.rdmc.app.typepath.defs.isgen10: + for g10controller in self.rdmc.app.select("SmartStorageConfig.", path_refresh=True): + if g10controller.dict.get("Location") == controller.get("Location"): + id = controller.get("Id") + controller.update(g10controller.dict) + if id: # iLO Bug - overwrite the Id from collection + controller["Id"] = id + break + + if ( + print_ctrl + and (options.controller is None) + and (options.physicaldrives is None) + and (options.pdrive is None) + and (options.ldrive is None) + and (options.logicaldrives is None) + ): + sys.stdout.write( + "[%s]: %s - %s\n" + % ( + controller["Id"], + controller["Location"], + controller["Model"], + ) + ) + elif ( + getattr(options, "json", False) + and (options.controller is None) + and (options.physicaldrives is None) + and (options.pdrive is None) + and (options.ldrive is None) + and (options.logicaldrives is None) + ): + outjson = dict() + outjson["Controllers"] = dict() + outjson["Controllers"][controller["Id"]] = dict() + outjson["Controllers"][controller["Id"]]["Location"] = controller["Location"] + outjson["Controllers"][controller["Id"]]["Model"] = controller["Model"] + UI().print_out_json(outjson) + + elif ( + print_ctrl + and (options.controller is not None) + and ((options.ldrive is not None) or options.pdrive is not None) + ): + pass + + elif ( + getattr(options, "json", False) + and (options.controller is not None) + and ((options.ldrive is not None) or (options.pdrive is not None)) + ): + pass + + elif print_ctrl and (options.controller is not None) and options.json is None: + controller_info = "------------------------------------------------\n" + controller_info += "Controller Info \n" + controller_info += "------------------------------------------------\n" + controller_info += "Id: %s\n" % controller["Id"] + controller_info += "AdapterType: %s\n" % controller["AdapterType"] + controller_info += "DataGuard: %s\n" % controller["DataGuard"] + controller_info += "Description: %s\n" % controller["Description"] + controller_info += "FirmwareVersion: %s\n" % controller["FirmwareVersion"] + controller_info += "Name: %s\n" % controller["Name"] + controller_info += "Model: %s\n" % controller["Model"] + controller_info += "Location: %s\n" % controller["Location"] + # controller_info += "Links: %s\n" % controller["Links"] + controller_info += "SerialNumber: %s\n" % controller["SerialNumber"] + controller_info += "Status: %s\n" % controller["Status"] + self.rdmc.ui.printer(controller_info, verbose_override=True) + + elif getattr(options, "json", False) and (options.controller is not None): + outjson_data = dict() + outjson_data.update({"Id": controller["Id"]}) + outjson_data.update({"AdapterType": controller["AdapterType"]}) + outjson_data.update({"DataGuard": controller["DataGuard"]}) + outjson_data.update({"Description": controller["Description"]}) + outjson_data.update({"FirmwareVersion": controller["FirmwareVersion"]}) + outjson_data.update({"Name": controller["Name"]}) + outjson_data.update({"Model": controller["Model"]}) + outjson_data.update({"Location": controller["Location"]}) + # outjson_data.update({"Links": controller["Links"]}) + outjson_data.update({"SerialNumber": controller["SerialNumber"]}) + outjson_data.update({"Status": controller["Status"]}) + UI().print_out_json(outjson_data) + + if getattr(options, "logicaldrives", False) or getattr(options, "ldrive", False) or single_use: + self.logical_drives(options, controller, print_ctrl) + + if getattr(options, "physicaldrives", False) or getattr(options, "pdrive", False) or single_use: + self.physical_drives(options, controller, print_ctrl) + + if single_use: + controller_data[controller["Id"]] = controller + + if getattr(options, "controller", False) and not controller_ident and print_ctrl: + sys.stdout.write( + "Controller in position '%s' was not found\n" % (getattr(options, "controller", False)) + ) + if getattr(options, "json", False) and not options.storageid: + if self.rdmc.opts.verbose: + sys.stdout.write("---------------------------------\n") + sys.stdout.write("List of RDE storage devices\n") + sys.stdout.write("---------------------------------\n") + UI().print_out_json(outjson) + if single_use: + return controller_data + else: + raise InvalidCommandLineError( + "Storage cotroller is not ready, Kindly re run the command after sometime\n") + else: + raise InvalidCommandLineError("Storage cotroller is not ready, Kindly re run the command after sometime\n") + + def storagecontroller(self, options, print_ctrl=False, single_use=False): + if getattr(options, "controller", False): + controller_ident = False + + if ( + not (getattr(options, "json", False)) + and not (getattr(options, "controller", False)) + and print_ctrl + and (options.physicaldrives is None) + and (options.pdrive is None) + and (options.logicaldrives is None) + and (options.ldrive is None) + ): + sys.stdout.write("Controllers:\n") + + storage_data = {} + get_contrller = [] + + list_storageid = self.rdmc.app.get_handler("/redfish/v1/Systems/1/Storage/", silent=True, service=True).dict[ + "Members" + ] + for val in list_storageid: + storageid_fromurl = val["@odata.id"] + if "DE" in storageid_fromurl and options.storageid in storageid_fromurl: + getval = self.rdmc.app.get_handler(storageid_fromurl, silent=True, service=True).dict + try: + controller = getval["Controllers"] + list_controllers = self.rdmc.app.get_handler( + controller["@odata.id"], silent=True, service=True + ).dict + except KeyError: + self.rdmc.ui.printer("Please check controller was Gen11.\n") + return ReturnCodes.NO_CONTENTS_FOUND_FOR_OPERATION + + for ctl in list_controllers["Members"]: + ctl = ctl["@odata.id"] + get_sel = self.rdmc.app.get_handler(ctl, silent=True, service=True) + get_contrller.append(get_sel) + + for sel in get_contrller: + if "Collection" not in sel.path: + controller = sel.dict + storage_id = controller["@odata.id"].split("/") + storage_id = storage_id[6] + if getattr(options, "controller", False): + if ( + getattr(options, "controller", False) == controller["Id"] + or getattr(options, "controller", False)[-1] + == controller["Location"]["PartLocation"]["ServiceLabel"][-1] + ): + controller_ident = True + else: + continue + if ( + print_ctrl + and (options.controller is None) + and (options.physicaldrives is None) + and (options.pdrive is None) + and (options.ldrive is None) + and (options.logicaldrives is None) + ): + sys.stdout.write( + "StorageId:%s - ControllerId:%s - %s - %s\n" + % ( + storage_id, + controller["Id"], + controller["Location"]["PartLocation"]["ServiceLabel"], + controller["Model"], + ) + ) + + elif ( + getattr(options, "json", False) + and (options.controller is None) + and (options.physicaldrives is None) + and (options.pdrive is None) + and (options.ldrive is None) + and (options.logicaldrives is None) + ): + outjson = dict() + outjson["Controllers"] = dict() + outjson["Controllers"][storage_id] = dict() + outjson["Controllers"][storage_id]["controllerid"] = controller["Id"] + outjson["Controllers"][storage_id]["controllerid"]["Location"] = controller["Location"][ + "PartLocation" + ]["ServiceLabel"] + outjson["Controllers"][storage_id]["controllerid"]["Model"] = controller["Model"] + UI().print_out_json(outjson) + + elif ( + print_ctrl + and (options.controller is not None) + and ( + (options.logicaldrives is not None) + or (options.physicaldrives is not None) + or (options.ldrive is not None) + or (options.pdrive is not None) + ) + ): + pass + + elif ( + getattr(options, "json", False) + and (options.controller is not None) + and ( + (options.logicaldrives is not None) + or (options.physicaldrives is not None) + or (options.ldrive is not None) + or (options.pdrive is not None) + ) + ): + pass + + elif print_ctrl and (options.controller is not None) and options.json is None: + controller_info = "---------------------------------------------\n" + controller_info += "Controller {} Details on Storage Id {}\n".format(options.controller, storage_id) + controller_info += "---------------------------------------------\n" + controller_info += "Id: %s\n" % controller["Id"] + controller_info += "StorageId: %s\n" % storage_id + controller_info += "Name: %s\n" % controller["Name"] + controller_info += "FirmwareVersion: %s\n" % controller["FirmwareVersion"] + controller_info += "Manufacturer: %s\n" % controller["Manufacturer"] + controller_info += "Model: %s\n" % controller["Model"] + controller_info += "PartNumber: %s\n" % controller["PartNumber"] + controller_info += "SerialNumber: %s\n" % controller["SerialNumber"] + try: + controller_info += "SKU: %s\n" % controller["SKU"] + except KeyError: + pass + controller_info += "Status: %s\n" % controller["Status"] + try: + controller_info += "SupportedDeviceProtocols: %s\n" % controller["SupportedDeviceProtocols"] + except KeyError: + pass + try: + controller_info += ( + "SupportedControllerProtocols: %s\n" % controller["SupportedControllerProtocols"] + ) + except KeyError: + pass + self.rdmc.ui.printer(controller_info, verbose_override=True) + + elif getattr(options, "json", False) and (options.controller is not None): + outjson_data = dict() + outjson_data.update({"Id": controller["Id"]}) + outjson_data.update({"StorageId": storage_id}) + outjson_data.update({"Name": controller["Name"]}) + outjson_data.update({"FirmwareVersion": controller["FirmwareVersion"]}) + outjson_data.update({"Manufacturer": controller["Manufacturer"]}) + outjson_data.update({"Model": controller["Model"]}) + outjson_data.update({"PartNumber": controller["PartNumber"]}) + outjson_data.update({"SerialNumber": controller["SerialNumber"]}) + try: + outjson_data.update({"SKU": controller["SKU"]}) + except KeyError: + pass + outjson_data.update({"Status": controller["Status"]}) + try: + outjson_data.update({"SupportedDeviceProtocols": controller["SupportedDeviceProtocols"]}) + except KeyError: + pass + try: + outjson_data.update( + {"SupportedControllerProtocols": controller["SupportedControllerProtocols"]} + ) + except KeyError: + pass + UI().print_out_json(outjson_data) + + if getattr(options, "logicaldrives", False) or getattr(options, "ldrive", False) or single_use: + self.storagelogical_drives(options, controller, storage_id, print_ctrl) + + if getattr(options, "physicaldrives", False) or getattr(options, "pdrive", False) or single_use: + self.storagephysical_drives(options, controller, storage_id, print_ctrl) + + if single_use: + storage_data[controller["Id"]] = controller + if getattr(options, "controller", False) and not controller_ident and print_ctrl: + sys.stdout.write("Controller in position '%s' was not found\n" % (getattr(options, "controller", False))) + if single_use: + return storage_data + + def storageid(self, options, print_ctrl=False, single_use=False): + st_url = "/redfish/v1/Systems/1/Storage/" + st_content = self.rdmc.app.get_handler(st_url, silent=True, service=True).dict["Members"] + st_flag = False + for st_controller in st_content: + url = st_controller["@odata.id"] + if options.storageid in url and "DE" in url: + st_flag = True + break + else: + continue + if not st_flag: + sys.stdout.write( + "\nStorage ID {} not found or Storage ID is not redfish enabled and does not have DE\n".format( + options.storageid + ) + ) + return + + res_dict = dict() + res_dict["Storage"] = dict() + + storage_id_url = "/redfish/v1/Systems/1/Storage/" + options.storageid + "?$expand=." + + storage_resp = self.rdmc.app.get_handler(storage_id_url, silent=True, service=True).dict + if not storage_resp: + sys.stdout.write("\nStorage Id {} not found\n".format(options.storageid)) + return + controllers = storage_resp["Controllers"]["Members"] + volumes = storage_resp["Volumes"]["Members"] + drives = storage_resp["Drives"] + stg_data = [] + actual_no_drives = 0 + for d in storage_resp["Drives"]: + if d["Status"]["State"] == "Enabled": + actual_no_drives += 1 + if print_ctrl and storage_resp and options.json is None: + if ( + options.storageid + and not options.controllers + and not options.physicaldrives + and not options.logicaldrives + and not options.ldrive + and not options.pdrive + ): + sys.stdout.write("-----------------------------------\n") + sys.stdout.write("Details of Storage %s\n" % options.storageid) + sys.stdout.write("-----------------------------------\n") + sys.stdout.write("\tId: %s\n\t" % storage_resp["Id"]) + sys.stdout.write("Health: %s\n\t" % storage_resp["Status"]["HealthRollup"]) + sys.stdout.write("Name: %s\n\t" % storage_resp["Name"]) + sys.stdout.write("Number of Controllers: %s\n\t" % storage_resp["Controllers"]["Members@odata.count"]) + sys.stdout.write("Number of Volumes: %s\n\t" % storage_resp["Volumes"]["Members@odata.count"]) + sys.stdout.write("Number of Drives: %s\n\t\n" % actual_no_drives) + elif ( + options.storageid + and not options.controllers + and options.physicaldrives + and not options.logicaldrives + and not options.ldrive + and not options.pdrive + ): + self.storagephysical_drives(options, options.controller, options.storageid, print_ctrl) + elif ( + options.storageid + and not options.controllers + and not options.physicaldrives + and options.logicaldrives + and not options.ldrive + and not options.pdrive + ): + self.storagelogical_drives(options, options.controller, options.storageid, print_ctrl) + elif options.storageid and options.ldrive: + self.storagelogical_drives(options, options.controller, options.storageid, print_ctrl) + elif options.storageid and options.pdrive: + self.storagephysical_drives(options, options.controller, options.storageid, print_ctrl) + + elif options.json is not None: + if options.logicaldrives or options.ldrive: + self.storagelogical_drives(options, options.controller, options.storageid, print_ctrl) + if options.physicaldrives or options.pdrive: + self.storagephysical_drives(options, options.controller, options.storageid, print_ctrl) + + storage_data = dict() + storage_data.update({"Id": storage_resp["Id"]}) + storage_data.update({"Health": storage_resp["Status"]["HealthRollup"]}) + storage_data.update({"Name": storage_resp["Name"]}) + storage_data.update({"Number of Controllers": storage_resp["Controllers"]["Members@odata.count"]}) + storage_data.update({"Number of Volumes": storage_resp["Volumes"]["Members@odata.count"]}) + storage_data.update({"Number of Drives": actual_no_drives}) + stg_data.append(storage_data) + + _ = self.print_list_controllers(options, controllers, print_ctrl) + _ = self.print_list_volumes(options, volumes, print_ctrl) + _ = self.print_list_drives(options, drives, print_ctrl) + + # Append controller, volume and drive information to final dictionary + if ( + not options.controllers + and res_dict + and options.json + and not options.physicaldrives + and not options.logicaldrives + and not options.ldrive + and not options.pdrive + ): + if ( + options.storageid + and not options.controllers + and not options.physicaldrives + and not options.logicaldrives + and not options.ldrive + and not options.pdrive + ): + res_dict["Storage"].update(storage_data) + UI().print_out_json(res_dict) + + elif options.controllers and options.storageid and options.json is None: + sys.stdout.write("---------------------------------------------------\n") + sys.stdout.write("List of Controllers in this Storage ID %s\n" % options.storageid) + sys.stdout.write("---------------------------------------------------\n") + list_ctr = controllers + for lst in list_ctr: + l_data = lst["@odata.id"] + get_ctr = self.rdmc.app.get_handler(l_data, silent=True, service=True).dict + sys.stdout.write("\tId: %s\n\t" % get_ctr["Id"]) + sys.stdout.write("Health: %s\n\t" % get_ctr["Status"]["Health"]) + sys.stdout.write("Name: %s\n\t" % get_ctr["Name"]) + sys.stdout.write("FirmwareVersion: %s\n\t" % get_ctr["FirmwareVersion"]) + sys.stdout.write("SerialNumber: %s\n" % get_ctr["SerialNumber"]) + + elif options.controllers and print_ctrl and options.json: + sys.stdout.write("---------------------------------------------------\n") + sys.stdout.write("List of Controllers in this Storage ID %s\n" % options.storageid) + sys.stdout.write("---------------------------------------------------\n") + list_ctr = controllers + for lst in list_ctr: + l_data = lst["@odata.id"] + get_ctr = self.rdmc.app.get_handler(l_data, silent=True, service=True).dict + ctr_data = dict() + ctr_data["Controllers"] = dict() + ctr_data["Controllers"].update({"Id": get_ctr["Id"]}) + ctr_data["Controllers"].update({"Health": get_ctr["Status"]["Health"]}) + ctr_data["Controllers"].update({"Name": get_ctr["Name"]}) + ctr_data["Controllers"].update({"FirmwareVersion": get_ctr["FirmwareVersion"]}) + ctr_data["Controllers"].update({"SerialNumber": get_ctr["SerialNumber"]}) + UI().print_out_json(ctr_data) + + def print_list_controllers(self, options, controllers, print_ctrl): + c_data = [] + if not options.json: + sys.stdout.write("Controllers Details:\n") + if not controllers: + sys.stdout.write("\tNone:\n\n") + for c in controllers: + ctrlr = c["@odata.id"] + getctrl = self.rdmc.app.get_handler(ctrlr, silent=True, service=True).dict + if print_ctrl and getctrl and options.json is None: + sys.stdout.write("\tId: %s\n\t" % getctrl["Id"]) + sys.stdout.write("Health: %s\n\t" % getctrl["Status"]["Health"]) + sys.stdout.write("Location: %s\n\t" % getctrl["Location"]["PartLocation"]["ServiceLabel"]) + sys.stdout.write("Name: %s\n\t\n" % getctrl["Name"]) + elif options.json is not None: + ctrl_data = dict() + ctrl_data.update({"Id": getctrl["Id"]}) + ctrl_data.update({"Health": getctrl["Status"]["Health"]}) + ctrl_data.update({"Location": getctrl["Location"]["PartLocation"]["ServiceLabel"]}) + ctrl_data.update({"Name": getctrl["Name"]}) + c_data.append(ctrl_data) + return c_data + + def print_list_volumes(self, options, volumes, print_ctrl): + v_data = [] + if not options.json: + sys.stdout.write("\nVolume Details:\n") + if not volumes: + sys.stdout.write("\tNone\n\n") + for vol in volumes: + volume = vol["@odata.id"] + getvolumes = self.rdmc.app.get_handler(volume, silent=True, service=True).dict + if print_ctrl and getvolumes and options.json is None: + sys.stdout.write("\tId: %s\n\t" % getvolumes["Id"]) + sys.stdout.write("Name: %s\n\t" % getvolumes["Name"]) + sys.stdout.write("RAIDType: %s\n\t" % getvolumes["RAIDType"]) + sys.stdout.write("VolumeUniqueId: %s\n\t" % getvolumes["Identifiers"][0]["DurableName"]) + sys.stdout.write("Health: %s\n\n" % getvolumes["Status"]["Health"]) + + elif options.json is not None: + vol_data = dict() + vol_data.update({"Id": getvolumes["Id"]}) + vol_data.update({"Name": getvolumes["Name"]}) + vol_data.update({"Health": getvolumes["Status"]["Health"]}) + vol_data.update({"RAIDType": getvolumes["RAIDType"]}) + vol_data.update({"VolumeUniqueId": getvolumes["Identifiers"][0]["DurableName"]}) + v_data.append(vol_data) + return v_data + + def print_list_drives(self, options, drives, print_ctrl): + d_data = [] + if not options.json: + sys.stdout.write("Drive Details:\n\n") + if not drives: + sys.stdout.write("\tNone\n\n") + for drive in drives: + dve = drive["@odata.id"] + getdrives = self.rdmc.app.get_handler(dve, silent=True, service=True).dict + if print_ctrl and getdrives and options.json is None: + sys.stdout.write("\tId: %s\n\t" % getdrives["Id"]) + sys.stdout.write("Location: %s\n\t" % getdrives["PhysicalLocation"]["PartLocation"]["ServiceLabel"]) + sys.stdout.write("Health: %s\n\t" % getdrives["Status"].get("Health", "NA")) + sys.stdout.write("Name: %s\n\t" % getdrives["Name"]) + sys.stdout.write("Model: %s\n\t" % getdrives.get("Model", "NA")) + sys.stdout.write("Revision: %s\n\t" % getdrives.get("Revision", "NA")) + sys.stdout.write("Serial Number: %s\n\t" % getdrives.get("SerialNumber", "NA")) + sys.stdout.write("Protocol: %s\n\t" % getdrives.get("Protocol", "NA")) + sys.stdout.write("MediaType: %s\n\t" % getdrives.get("MediaType", "NA")) + sys.stdout.write("CapacityBytes: %s\n\t\n" % getdrives.get("CapacityBytes", "NA")) + + elif options.json is not None: + drives_data = dict() + drives_data.update({"Id": getdrives["Id"]}) + drives_data.update({"Model": getdrives.get("Model", "NA")}) + drives_data.update({"Revision": getdrives.get("Revision", "NA")}) + drives_data.update({"Protocol": getdrives.get("Protocol", "NA")}) + drives_data.update({"MediaType": getdrives.get("MediaType", "NA")}) + drives_data.update({"CapacityBytes": getdrives.get("CapacityBytes", "NA")}) + drives_data.update({"Name": getdrives["Name"]}) + drives_data.update({"Health": getdrives["Status"].get("Health", "NA")}) + drives_data.update({"Serial Number": getdrives.get("SerialNumber", "NA")}) + drives_data.update({"Location": getdrives["PhysicalLocation"]["PartLocation"]["ServiceLabel"]}) + d_data.append(drives_data) + return d_data + + def storagephysical_drives(self, options, controller, storage_id, print_ctrl=False, single_use=False): + found_entries = False + if not getattr(options, "json", False) and print_ctrl: + sys.stdout.write("--------------------------------------------------\n") + if not getattr(options, "pdrive", False): + if controller: + sys.stdout.write("Drives on Controller %s and Storage %s\n" % (options.controller, storage_id)) + else: + sys.stdout.write("Drives on Storage %s\n" % storage_id) + else: + if controller: + sys.stdout.write( + "Drive %s on Controller %s and Storage %s\n" % (options.pdrive, options.controller, storage_id) + ) + else: + sys.stdout.write("Drive %s on Storage %s\n" % (options.pdrive, storage_id)) + sys.stdout.write("--------------------------------------------------\n") + if getattr(options, "pdrive", False): + pdrive_ident = False + else: + pdrive_ident = True + if single_use: + physicaldrives = {} + if getattr(options, "json", False): + outjson = dict() + outjson["Drives"] = dict() + print_drives = [] + storage_url = self.rdmc.app.typepath.defs.systempath + "Storage/" + storage_id + dd_list = self.rdmc.app.get_handler(storage_url, silent=True).dict.get("Drives", {}) + if dd_list: + found_entries = True + for dd in dd_list: + drive_url = dd["@odata.id"] + drive_data = self.rdmc.app.get_handler(drive_url, silent=True).dict + location = drive_data["PhysicalLocation"]["PartLocation"]["ServiceLabel"] + loc = location.split(":") + del loc[0] + if len(loc) == 3: + temp_str = str(loc[0].split("=")[1] + ":" + loc[1].split("=")[1] + ":" + loc[2].split("=")[1]) + location = temp_str + else: + pass + + if getattr(options, "pdrive", False): + if options.pdrive != location: + continue + else: + pdrive_ident = True + if single_use: + physicaldrives.update({drive_data["Id"]: drive_data}) + if ( + print_ctrl + and pdrive_ident + and options.pdrive is None + and options.json is None + and (location not in print_drives) + ): + sys.stdout.write( + "\t[%s]: %s, Model %s, Location %s, Type %s, Serial %s - %s Bytes\n" + % ( + location, + drive_data.get("Name", "NA"), + drive_data.get("Model", "NA"), + location, + drive_data.get("MediaType","NA"), + drive_data.get("SerialNumber", "NA"), + drive_data.get("CapacityBytes", "NA"), + ) + ) + elif ( + print_ctrl + and pdrive_ident + and options.pdrive + and options.json is None + and (location not in print_drives) + ): + sys.stdout.write( + "\tId: %s\n\t%s\n\tModel: %s\n\tLocation: %s\n\tType: %s\n\tSerial: %s\n\tCapacity: %s Bytes\n" + % ( + drive_data["Id"], + drive_data["Name"], + drive_data.get("Model", "NA"), + location, + drive_data.get("MediaType","NA"), + drive_data.get("SerialNumber", "NA"), + drive_data.get("CapacityBytes", "NA"), + ) + ) + break + elif getattr(options, "json", False) and pdrive_ident and (location not in print_drives): + outjson["Drives"][location] = dict() + outjson["Drives"][location]["Id"] = drive_data["Id"] + outjson["Drives"][location]["Name"] = drive_data["Name"] + outjson["Drives"][location]["Model"] = drive_data.get("Model", "NA") + outjson["Drives"][location]["Location"] = location + outjson["Drives"][location]["MediaType"] = drive_data.get("MediaType","NA") + outjson["Drives"][location]["SerialNumber"] = drive_data.get("SerialNumber", "NA") + outjson["Drives"][location]["CapacityBytes"] = drive_data.get("CapacityBytes", "NA") + if options.pdrive: + break + + print_drives.append(location) + if getattr(options, "json", False) and pdrive_ident: + UI().print_out_json(outjson) + if getattr(options, "pdrive", False) and not pdrive_ident and print_ctrl: + sys.stdout.write("\tPhysical drive in position '%s' was not found\n" % options.pdrive) + elif not found_entries and print_ctrl: + sys.stdout.write("\tPhysical drives not found.\n") + + if single_use: + return physicaldrives + + def storagelogical_drives(self, options, controller, storage_id, print_ctrl=False, single_use=False): + found_entries = False + printed = False + if not getattr(options, "json", False) and print_ctrl: + sys.stdout.write("--------------------------------------------------\n") + if not getattr(options, "ldrive", False): + sys.stdout.write("Volumes on Controller %s and Storage %s\n" % (options.controller, storage_id)) + else: + if controller: + sys.stdout.write( + "Volume %s on Controller %s and Storage %s\n" % (options.ldrive, options.controller, storage_id) + ) + else: + sys.stdout.write("Volume %s on Storage %s\n" % (options.ldrive, storage_id)) + sys.stdout.write("--------------------------------------------------\n") + if getattr(options, "ldrive", False): + ldrive_ident = False + else: + ldrive_ident = True + if single_use: + logicaldrives = {} + if getattr(options, "json", False): + outjson = dict() + outjson["volumes"] = dict() + list_volumes = self.rdmc.app.get_handler( + "/redfish/v1/Systems/1/Storage/" + storage_id + "/Volumes/", + silent=True, + service=True, + ).dict["Members"] + for tmp in list_volumes: + tmp = tmp["@odata.id"] + tmp = self.rdmc.app.get_handler(tmp, silent=True, service=True).dict + try: + std_v = options.storageid in tmp["@odata.id"] + except: + std_v = tmp["@odata.id"] + if std_v: + if getattr(options, "ldrive", False): + ldrive_ident = False + else: + ldrive_ident = True + if getattr(options, "ldrive", False): + if options.ldrive != tmp["Id"]: + continue + else: + ldrive_ident = True + + if "Identifiers" in tmp: + durablename = tmp["Identifiers"] + if durablename is not None: + d_name = [name["DurableName"] for name in durablename] + found_entries = True + + drives = [] + d_data = tmp["Links"]["Drives"] + for dd in d_data: + drive = dd["@odata.id"] + drive_data = self.rdmc.app.get_handler(drive, silent=True).dict + location = drive_data["PhysicalLocation"]["PartLocation"]["ServiceLabel"] + loc = location.split(":") + del loc[0] + if len(loc) == 3: + temp_str = str(loc[0].split("=")[1] + ":" + loc[1].split("=")[1] + ":" + loc[2].split("=")[1]) + location = temp_str + drives.append(location) + volumes = ", ".join(str(item) for item in drives) + if print_ctrl and ldrive_ident and options.ldrive is None and not getattr(options, "json", False): + sys.stdout.write( + "\t[%s]: Name %s RAIDType %s VUID %s Capacity %s Bytes - Health %s\n" + % ( + tmp["Id"], + tmp["Name"], + tmp["RAIDType"], + d_name[0], + tmp["CapacityBytes"], + tmp["Status"]["Health"], + ) + ) + elif getattr(options, "json", False) and ldrive_ident: + outjson["volumes"][tmp["Id"]] = dict() + outjson["volumes"][tmp["Id"]]["VolumeName"] = tmp["Name"] + outjson["volumes"][tmp["Id"]]["RAIDType"] = tmp["RAIDType"] + outjson["volumes"][tmp["Id"]]["VolumeUniqueIdentifier"] = d_name[0] + #if options.ldrive: + # outjson["volumes"][tmp["Id"]]["Drive Location"] = volumes + outjson["volumes"][tmp["Id"]]["Capacity"] = tmp["CapacityBytes"] + outjson["volumes"][tmp["Id"]]["Health"] = tmp["Status"]["Health"] + printed = True + elif print_ctrl and ldrive_ident and options.ldrive is not None: + sys.stdout.write( + "\tId: %s\n\tName: %s\n\tRaidType: %s\n\tVolumeUniqueId: %s\n\tCapacity: %s Bytes\n\n" + % ( + tmp["Id"], + tmp["Name"], + tmp["RAIDType"], + d_name[0], + tmp["CapacityBytes"], + ) + ) + printed = True + if single_use: + logicaldrives.update({tmp["Id"]: tmp}) + if getattr(options, "json", False) and ldrive_ident: + UI().print_out_json(outjson) + printed = True + if getattr(options, "ldrive", False) and not ldrive_ident: + sys.stdout.write("\tVolume '%s' was not found.\n" % options.ldrive) + elif not found_entries and print_ctrl: + sys.stdout.write("\tVolumes not found.\n") + if not getattr(options, "logicaldrives", False) and not printed: + if getattr(options, "ldrive", False): + sys.stdout.write("\tVolume '%s' does not exists.\n" % getattr(options, "ldrive", False)) + + if single_use: + return logicaldrives + + def get_storage_data_drives(self, options, drives, print_ctrl=False, single_use=False): + if not (getattr(options, "json", False)) and print_ctrl: + sys.stdout.write("\tData Drives:\n\n") + if single_use: + subsetdrives = {drives["Id"]: drives} + found_entries = False + if print_ctrl: + self.tranverse_func(drives) + found_entries = True + + elif getattr(options, "json", False): + UI().print_out_json(drives) + + if not found_entries and print_ctrl: + sys.stdout.write("\t\tComponent drives not found.\n") + + if single_use: + return subsetdrives + + def tranverse_func(self, data, indent=0): + for key, value in data.items(): + sys.stdout.write("\t %s :" % str(key)) + if isinstance(value, dict): + self.tranverse_func(value, indent + 1) + else: + sys.stdout.write("%s \n" % str(value)) + + def physical_drives(self, options, content, print_ctrl=False, single_use=False): + """ + Identify/parse physical drives (and child properties) of a parent array controller + + :param options: command line options + :type options: object attributes + :param content: dictionary of physical drive href or @odata.id paths (expect @odata.type + "SmartStorageArrayController", @odata.id /Systems/1/SmartStorage/ArrayControllers/X/) + :type content: dict + :param print_ctrl: flag for console print enablement/disablement (default disabled) + :type print_ctrl: bool + :param single_use: singular usage - True or explore mode - False, + returns dictionary of results to calling function (default disabled) - see returns. + :type single_use: bool + :returns: None, dictionary of all physical drives identified by 'Id' + """ + dd = "" + confd = content.get("PhysicalDrives") + try: + dd = content["links"]["PhysicalDrives"][self.rdmc.app.typepath.defs.hrefstring] + except: + dd = content["Links"]["PhysicalDrives"][self.rdmc.app.typepath.defs.hrefstring] + finally: + if not getattr(options, "json", False) and print_ctrl: + sys.stdout.write("Physical Drives:\n") + if getattr(options, "pdrive", False): + pdrive_ident = False + else: + pdrive_ident = True + if single_use: + physicaldrives = {} + found_entries = False + for data in self.rdmc.app.get_handler(dd, silent=True).dict.get("Members", {}): + try: + tmp = data[self.rdmc.app.typepath.defs.hrefstring] + except: + tmp = data[next(iter(data))] + finally: + tmp = self.rdmc.app.get_handler(tmp, silent=True).dict + found_entries = True + if confd: + for confdd in confd: + if confdd.get("Location") == tmp.get("Location"): + tmp.update(confdd) + break + if getattr(options, "pdrive", False): + if options.pdrive != tmp["Location"]: + continue + else: + pdrive_ident = True + if single_use: + physicaldrives[tmp["Id"]] = tmp + if print_ctrl and pdrive_ident and options.pdrive is None: + sys.stdout.write( + "\t[%s]: Model %s, Location %s, Type %s, Serial %s - %s MiB\n" + % ( + tmp["Location"], + tmp.get("Model", "NA"), + tmp.get("Location", "NA"), + tmp.get("MediaType", "NA"), + tmp.get("SerialNumber", "NA"), + tmp.get("CapacityMiB", "NA"), + ) + ) + elif getattr(options, "json", False) and pdrive_ident and options.pdrive is None: + outjson = dict() + outjson["PhysicalDrives"] = dict() + outjson["PhysicalDrives"][tmp["Location"]] = dict() + outjson["PhysicalDrives"][tmp["Location"]]["Model"] = tmp.get("Model", "NA") + outjson["PhysicalDrives"][tmp["Location"]]["Location"] = tmp.get("Location", "NA") + outjson["PhysicalDrives"][tmp["Location"]]["MediaType"] = tmp.get("MediaType", "NA") + outjson["PhysicalDrives"][tmp["Location"]]["SerialNumber"] = tmp.get("SerialNumber", "NA") + outjson["PhysicalDrives"][tmp["Location"]]["CapacityMiB"] = tmp.get("CapacityMiB", "NA") + UI().print_out_json(outjson) + + elif print_ctrl and pdrive_ident and options.pdrive is not None: + ctl_logical = dict() + contoller_loc = content["Location"] + "-" + content["Model"] + ctl_logical.update({"Controller": contoller_loc}) + remove_odata = self.odataremovereadonly(tmp) + ctl_logical.update(remove_odata) + location = ctl_logical["Location"] + if print_ctrl and ctl_logical and options.ldrive is None and not getattr(options, "json", False): + sys.stdout.write( + "\tId:\t%s\n\tModel:\t%s\n\tLocation:\t%s\n\tType:\t%s\n\tSerial:\t%s\n\tCapacity:\t%s " + "Bytes\n" + % ( + ctl_logical["Id"], + ctl_logical["Model"], + ctl_logical["Location"], + ctl_logical["MediaType"], + ctl_logical["SerialNumber"], + ctl_logical["CapacityLogicalBlocks"], + ) + ) + elif getattr(options, "json", False) and ctl_logical: + outjson = dict() + outjson["Drives"] = dict() + outjson["Drives"][location] = dict() + outjson["Drives"][location]["Id"] = ctl_logical["Id"] + outjson["Drives"][location]["Model"] = ctl_logical["Model"] + outjson["Drives"][location]["Location"] = location + outjson["Drives"][location]["MediaType"] = ctl_logical["MediaType"] + outjson["Drives"][location]["SerialNumber"] = ctl_logical["SerialNumber"] + outjson["Drives"][location]["CapacityLogicalBlocks"] = ctl_logical["CapacityLogicalBlocks"] + UI().print_out_json(outjson) + + elif getattr(options, "json", False) and pdrive_ident and options.pdrive is not None: + ctl_logical = dict() + contoller_loc = content["Location"] + "-" + content["Model"] + ctl_logical.update({"Controller": contoller_loc}) + remove_odata = self.odataremovereadonly(tmp) + ctl_logical.update(remove_odata) + UI().print_out_json(ctl_logical) + + if getattr(options, "pdrive", False) and pdrive_ident: + break + if getattr(options, "pdrive", False) and not pdrive_ident and print_ctrl: + sys.stdout.write("\tPhysical drive in position '%s' was not found \n" % options.pdrive) + elif not found_entries and print_ctrl: + sys.stdout.write("\tPhysical drives not found.\n") + + if single_use: + return physicaldrives + + def logical_drives(self, options, storage_id, content, print_ctrl=False, single_use=False): + """ + Identify/parse volumes (and child properties) of an associated array controller + + :param options: command line options + :type options: object attributes + :param content: dictionary of volume href or @odata.id paths (expect @odata.type + "SmartStorageArrayController", @odata.id /Systems/1/SmartStorage/ArrayControllers/X/) + :type content: dict + :param print_ctrl: flag for console print enablement/disablement (default disabled) + :type print_ctrl: bool + :param single_use: singular usage - True or explore mode - False, + returns dictionary of results to calling function (default disabled) - see returns. + :type single_use: bool + :returns: None, dictionary of all volumes identified by 'Id' + """ + + confd = content.get("LogicalDrives") + try: + _ = content["links"]["LogicalDrives"][self.rdmc.app.typepath.defs.hrefstring] + except: + _ = content["Links"]["LogicalDrives"][self.rdmc.app.typepath.defs.hrefstring] + finally: + if not getattr(options, "json", False) and print_ctrl: + sys.stdout.write("Volumes:\n") + if getattr(options, "ldrive", False): + ldrive_ident = False + else: + ldrive_ident = True + if single_use: + logicaldrives = {} + found_entries = False + try: + list_volumes = self.rdmc.app.get_handler( + "/redfish/v1/Systems/1/Storage/" + options.storageid + "/Volumes/", + silent=True, + service=True, + ).dict["Members"] + except: + list_volumes = self.rdmc.app.get_handler( + "/redfish/v1/Systems/1/Storage/" + storage_id + "/Volumes/", + silent=True, + service=True, + ).dict["Members"] + for data in list_volumes: + try: + tmp = data[self.rdmc.app.typepath.defs.hrefstring] + except: + tmp = data[next(iter(data))] + finally: + tmp = self.rdmc.app.get_handler(tmp, silent=True).dict + found_entries = True + if confd: + for confdd in confd: + if confdd.get("LogicalDriveNumber") == tmp.get("LogicalDriveNumber"): + tmp.update(confdd) + break + if getattr(options, "ldrive", False): + if options.ldrive != tmp["Id"]: + continue + else: + ldrive_ident = True + if print_ctrl and ldrive_ident and options.ldrive is None: + sys.stdout.write( + "\t[%s]: Name %s Raid %s VUID %s - %s MiB\n" + % ( + tmp["Id"], + tmp["LogicalDriveName"], + tmp["Raid"], + tmp["VolumeUniqueIdentifier"], + tmp["CapacityMiB"], + ) + ) + elif getattr(options, "json", False) and ldrive_ident and options.ldrive is None: + outjson = dict() + outjson["LogicalDrives"] = dict() + outjson["LogicalDrives"][tmp["Id"]] = dict() + outjson["LogicalDrives"][tmp["Id"]]["LogicalDriveName"] = tmp["LogicalDriveName"] + outjson["LogicalDrives"][tmp["Id"]]["Raid"] = tmp["Raid"] + outjson["LogicalDrives"][tmp["Id"]]["VolumeUniqueIdentifier"] = tmp["VolumeUniqueIdentifier"] + outjson["LogicalDrives"][tmp["Id"]]["CapacityMiB"] = tmp["CapacityMiB"] + UI().print_out_json(outjson) + elif print_ctrl and ldrive_ident and options.ldrive is not None: + ctl_logical = dict() + contoller_loc = content["Location"] + "-" + content["Model"] + ctl_logical.update({"Controller": contoller_loc}) + remove_odata = self.odataremovereadonly(tmp) + ctl_logical.update(remove_odata) + id = ctl_logical["Id"] + if print_ctrl and ctl_logical and options.json is None: + sys.stdout.write("\tId: %s\n\t" % ctl_logical["Id"]) + sys.stdout.write("Name: %s\n\t" % ctl_logical["LogicalDriveName"]) + sys.stdout.write("RAIDType: %s\n\t" % ctl_logical["Raid"]) + sys.stdout.write("VolumeUniqueId: %s\n\t" % ctl_logical["VolumeUniqueIdentifier"]) + sys.stdout.write("Capacity: %s\n\t" % ctl_logical["CapacityBlocks"]) + sys.stdout.write("Health: %s\n\n" % ctl_logical["Status"]["Health"]) + # UI().print_out_json(ctl_logical) + elif getattr(options, "json", False) and ctl_logical: + outjson = dict() + outjson["Volumes"] = dict() + outjson["Volumes"][id] = dict() + # outjson["Volumes"][id]["Id"] = ctl_logical["Id"] + outjson["Volumes"][id]["VolumeName"] = ctl_logical["LogicalDriveName"] + outjson["Volumes"][id]["RAIDType"] = ctl_logical["Raid"] + outjson["Volumes"][id]["Health"] = ctl_logical["Status"]["Health"] + outjson["Volumes"][id]["Capacity"] = ctl_logical["CapacityBlocks"] + outjson["Volumes"][id]["VolumeUniqueIdentifier"] = ctl_logical["VolumeUniqueIdentifier"] + UI().print_out_json(outjson) + elif getattr(options, "json", False) and ldrive_ident and options.ldrive is not None: + ctl_logical = dict() + contoller_loc = content["Location"] + "-" + content["Model"] + ctl_logical.update({"Controller": contoller_loc}) + remove_odata = self.odataremovereadonly(tmp) + ctl_logical.update(remove_odata) + UI().print_out_json(ctl_logical) + + elif getattr(options, "json", False) and ldrive_ident: + UI().print_out_json(tmp) + if single_use: + logicaldrives[tmp["Id"]] = tmp + if getattr(options, "ldrive", False) and ldrive_ident: + break + if getattr(options, "ldrive", False) and not ldrive_ident: + sys.stdout.write("\tVolume '%s' was not found \n" % options.pdrive) + elif not found_entries and print_ctrl: + sys.stdout.write("\tVolumes not found.\n") + + if single_use: + return logicaldrives + + def get_data_drives(self, options, drives, print_ctrl=False, single_use=False): + """ + Identify/parse a physical component drive collection of a respective volume. The + physical disk properties as well as some logical RAID parameters within each respective + member. + + :param options: command line options + :type options: object attributes + :param content: collection of data drives href or @odata.id paths (as members) as attributed + to a respective volume. + :type content: dict + :param print_ctrl: flag for console print enablement/disablement (default disabled) + :type print_ctrl: bool + :param single_use: singular usage, returns dictionary of results to calling function + (default disabled) + :type single_use: bool + :returns: None, dictionary of all physical drives composing a parent volume, + each instance identified by 'Id' + """ + + if not (getattr(options, "json", False)) and print_ctrl: + sys.stdout.write("\tData Drives:\n\n") + if single_use: + subsetdrives = {} + found_entries = False + for member in drives.get("Members", {}): + try: + tmp = member[self.rdmc.app.typepath.defs.hrefstring] + except: + tmp = member[next(iter(member))] + finally: + tmp = self.rdmc.app.get_handler(tmp, silent=True).dict + found_entries = True + if single_use: + subsetdrives[tmp["Id"]] = tmp + if print_ctrl: + self.tranverse_func(tmp) + elif getattr(options, "json", False): + UI().print_out_json(tmp) + if not found_entries and print_ctrl: + sys.stdout.write("\t\tComponent drives not found.\n") + + if single_use: + return subsetdrives + + def storagecontrollervalidation(self, options): + """Storage controller validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + if getattr(options, "sa_conf_filename", False): + if options.command == "save": + open(options.sa_conf_filename, "w+b") + if options.command == "load": + open(options.sa_conf_filename, "r+b") + self.config_file = options.sa_conf_filename + else: + self.config_file = __config_file__ + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + subcommand_parser = customparser.add_subparsers(dest="command") + + default_parser = subcommand_parser.add_parser( + "default", + help="Running without any sub-command will return storage controller configuration data\n" + "on the currently logged in server. Additional optional arguments will narrow the\n" + "scope of returned data to individual controllers, physical or volumes", + ) + default_parser.add_argument( + "--controller", + dest="controller", + help="Use this flag to select the corresponding controller using either the slot " + "number or index. \n\tExamples:\n\t1. To get more details on a specific " + "controller, select it by index.\tstoragecontroller --controller=2" + "\n\t2. To get more details on a specific controller select " + "it by location.\tstoragecontroller --controller='Slot 0'", + default=None, + ) + default_parser.add_argument( + "--storage_id", + "--storageid", + dest="storageid", + help="Use this flag to select the corresponding controller using either the storageid " + "id. \n\tExamples:\n\t1. To get more details on a specific " + "controller, select it by storageid.\tstoragecontroller --storageid=DE00E000" + "\n\t2. To get more details on a specific controller select " + "it by location.\tstoragecontroller --storageid='DE00E000'", + default=None, + ) + default_parser.add_argument( + "--controllers", + dest="controllers", + action="store_true", + help="Use this flag to return the controllers for the storageid selected." + "\n\tExamples:\n\t1. storagecontroller --controllers\n", + default=None, + ) + default_parser.add_argument( + "--physicaldrives", + "--drives", + dest="physicaldrives", + action="store_true", + help="Use this flag to return the physical drives for the controller selected." + "\n\tExamples:\n\t1. storagecontroller --drives\n\t2. To obtain details about " + "physical drives for a specific controller.\tstoragecontroller --controller=3 " + "--physicaldrives", + default=None, + ) + default_parser.add_argument( + "--logicaldrives", + "--volumes", + dest="logicaldrives", + action="store_true", + help="Use this flag to return the volumes for the controller selected.\n\t " + "\n\tExamples:\n\t1. storagecontroller --logicaldrives\n\t2. To obtain details about " + "volumes for a specific controller.\tstoragecontroller --controller=3 " + "--logicaldrives", + default=None, + ) + default_parser.add_argument( + "--pdrive", + "--drive", + dest="pdrive", + help="Use this flag to select the corresponding physical disk.\n\tExamples:\n\t " + "1. To obtain details about a specific physical drive for a specific controller." + "\tstoragecontroller --controller=3 --pdrive=1I:1:1", + default=None, + ) + default_parser.add_argument( + "--ldrive", + "--volume", + dest="ldrive", + help="Use this flag to select the corresponding logical disk.\n\tExamples:\n\t " + "1. To obtain details about a specific physical drive for a specific controller." + "\tstoragecontroller --controller=3 --ldrive=1", + default=None, + ) + default_parser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="""Use this flag to output data in JSON format.""", + default=None, + ) + self.cmdbase.add_login_arguments_group(default_parser) + + state_help = "Print state/event information from @Redfish.Settings (if available)" + state_parser = subcommand_parser.add_parser( + "state", + help=state_help, + description=state_help + "\n\tExample: storagecontroller state", + formatter_class=RawDescriptionHelpFormatter, + ) + state_parser.add_argument( + "--controller", + dest="controller", + help="Use this flag to select the corresponding controller using either the slot " + "number or index. \n\tExamples:\n\t1. To get more details on a specific " + "controller, select it by index.\tstoragecontroller state --controller=2" + "\n\t2. To get more details on a specific controller select " + "it by location.\tstoragecontroller state --controller='Slot 0'", + default=None, + ) + self.cmdbase.add_login_arguments_group(state_parser) + json_save_help = ( + "Save a JSON file with all current configurations (all controllers, logical and \nphysical drives)." + ) + # json save sub-parser + json_save_parser = subcommand_parser.add_parser( + "save", + help=json_save_help, + description=json_save_help + "\n\tExample: storagecontroller save -f ", + formatter_class=RawDescriptionHelpFormatter, + ) + json_save_parser.add_argument( + "-f", + dest="sa_conf_filename", + help="Specify a filename for saving the storagecontroller configuration. (Default: " + "'storagecontroller_config.json')", + default=None, + ) + self.cmdbase.add_login_arguments_group(json_save_parser) + json_load_help = ( + "Load a JSON file with modified storagecontroller configurations (All read-only " + "properties\nare discarded)." + ) + # json load sub-parser + json_load_parser = subcommand_parser.add_parser( + "load", + help=json_load_help, + description=json_load_help + "\n\tExample: storagecontroller load -f ", + formatter_class=RawDescriptionHelpFormatter, + ) + json_load_parser.add_argument( + "-f", + dest="sa_conf_filename", + help="Specify a filename for loading a storagecontroller configuration. (Default: " + "'storagecontroller_config.json')", + default=None, + ) + self.cmdbase.add_login_arguments_group(json_load_parser) diff --git a/ilorest/extensions/SMART_ARRAY_COMMANDS/__init__.py b/ilorest/extensions/SMART_ARRAY_COMMANDS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/extensions/SMART_NIC_COMMANDS/SmartNicCommand.py b/ilorest/extensions/SMART_NIC_COMMANDS/SmartNicCommand.py new file mode 100644 index 0000000..d86daab --- /dev/null +++ b/ilorest/extensions/SMART_NIC_COMMANDS/SmartNicCommand.py @@ -0,0 +1,720 @@ +### +# Copyright 2016-2022 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 -*- +""" Smart Nic Command for rdmc """ +import os +import time + +try: + from rdmc_helper import ( + UI, + FirmwareUpdateError, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + UploadError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + UI, + FirmwareUpdateError, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + UploadError, + ) + + +class SmartNicCommand: + """Smart nic command""" + + def __init__(self): + self.ident = { + "name": "smartnic", + "usage": None, + "description": "\tRun without arguments for the " + "current list of smartnic including json format.\n\texample: " + "smartnic/smartnic -j\n\n" + "Show all available options\n\n\t" + "example: smartnic --id \n\t" + "smartnic --id -j\n\t" + "smartnic --id --logs\n\t" + "smartnic --id --bootprogress\n\t" + "smartnic --id --clearlog\n\t" + "smartnic --id --update_fw \n\t" + "smartnic --id --reset < GracefulShutdown / ForceRestart / Nmi / GracefulRestart >\n", + "summary": "Discovers all pensando nic installed in the " "server", + "aliases": [], + "auxcommands": ["SelectCommand"], + } + self.config_file = None + self.fdata = None + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Smartnic command""" + + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + if args: + raise InvalidCommandLineError("servernic command takes no " "arguments.") + + ilo_ver = self.rdmc.app.getiloversion() + if ilo_ver < 5.268: + raise IncompatibleiLOVersionError( + "Please upgrade to iLO 5 2.68 or " "greater to ensure correct flash of this firmware." + ) + + self.smartnicvalidation(options) + + path = self.rdmc.app.typepath.defs.systempath + + info = self.gatherinfo(path) + + if not info: + raise InvalidCommandLineError("No SmartNic available.") + + fw_version = self.get_fw_version() + options_system = True + + if options.json and options.id is not None: + string_id = options.id + to_list = list(string_id.split(",")) + for id in to_list: + if options.bootprogress: + if id == info["Id"]: + json_content = self.print_get_bootprogress(info) + UI().print_out_json(json_content) + elif id != info["Id"]: + self.rdmc.ui.printer("No bootprogress present for given smartnic id %s \n" % id) + + elif options.logs: + if id == info["Id"]: + json_content = self.print_get_logs(info) + UI().print_out_json(json_content) + elif id != info["Id"]: + self.rdmc.ui.printer("No logs present for given smartnic id %s \n" % id) + + elif options_system: + if id == info["Id"] and info["SystemType"] == "DPU": + json_content = self.build_json_out(info, fw_version) + UI().print_out_json(json_content) + elif id != info["Id"] or info["SystemType"] != "DPU": + self.rdmc.ui.printer("System %s:\n" % id) + self.rdmc.ui.printer("\tNo SmartNic present in given id %s \n" % id) + + elif options.json and options.id is None and options_system and not options.logs and not options.bootprogress: + if info["SystemType"] == "DPU": + json_content = self.build_json_out(info, fw_version) + UI().print_out_json(json_content) + if info["SystemType"] != "DPU": + self.rdmc.ui.printer("System %s:\n" % id) + self.rdmc.ui.printer("\tNo SmartNic present in given id %s \n" % id) + + elif (options.json and options.logs and options.id is None) or ( + options.logs and not options.json and options.id is None + ): + raise InvalidCommandLineError( + "No command --logs for smartnic\n" + "usage: smartnic --id --logs \n" + "\t smartnic --id --logs -j" + ) + + elif (options.bootprogress and options.json and options.id is None) or ( + options.bootprogress and not options.json and options.id is None + ): + raise InvalidCommandLineError( + "No command --bootprogress for smartnic\n" + "usage: smartnic --id --bootprogress\n" + "\t smartnic --id --bootprogress -j" + ) + + else: + if options.id is not None: + string_id = options.id + str_lst = list(string_id.split(",")) + if options.update_fw is None and options.reset is None: + for id in str_lst: + if options.bootprogress: + if id == info["Id"]: + self.get_bootprogress(info) + elif id != info["Id"]: + self.rdmc.ui.printer("No bootprogress present for given smartnic id %s\n" % id) + + elif options.logs: + if id == info["Id"]: + self.get_logs(info) + elif id != info["Id"]: + self.rdmc.ui.printer("No logs present for given smartnic id %s \n" % id) + + elif options_system and not options.clearlog and not options.logs: + if id == info["Id"] and info["SystemType"] == "DPU": + self.prettyprintinfo(info, fw_version) + elif id != info["Id"] or info["SystemType"] != "DPU": + self.rdmc.ui.printer("System %s:\n" % id) + self.rdmc.ui.printer("\tNo SmartNic present in given id %s \n" % id) + + elif options.clearlog and options_system: + try: + for id in str_lst: + if id == info["Id"]: + self.clearlog(options, info, id) + elif id != info["Id"]: + self.rdmc.ui.printer("Given smartnic id is not present to clear the logs.") + except Exception as excp: + raise excp + + elif options.update_fw is not None: + try: + self.upload_firmware(options, info) + except Exception as excp: + # if SessionExpired: + # time.sleep(320) # wait till reboot the server + # self.smartnicvalidation(options) + # self.cmdbase.login_select_validation(self, options) + # else: + raise excp + + elif options.reset is not None: + try: + for id in str_lst: + if id == info["Id"]: + self.get_resettype(options, info, id) + elif id != info["Id"]: + self.rdmc.ui.printer("No reset type present for given smartnic id %s \n" % id) + + except Exception as excp: + raise excp + + else: + if options.clearlog and options_system: + raise InvalidCommandLineError( + "No command --clearlog for smartnic\n" "usage: smartnic --id --clearlog" + ) + + if options_system and not options.clearlog: + if info["SystemType"] == "DPU": + self.prettyprintinfo(info, fw_version) + elif info["SystemType"] != "DPU": + self.rdmc.ui.printer("No SmartNic available.") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def get_fw_version(self): + # to get pensando firmware version + name = [] + f_version = "" + fw_path = "/redfish/v1/UpdateService/FirmwareInventory/?$expand=." + fw_result = self.rdmc.app.get_handler(fw_path, service=True, silent=True) + fw_result = fw_result.dict + members = fw_result["Members"] + for n in members: + name_version = n["Name"], n["Version"] + name.append(name_version) + + for fw_name in name: + if "Pensando" in fw_name[0]: + f_version += fw_name[1] + return f_version + + def clearlog(self, options, info, id): + try: + log_service = info["LogServices"] + for log_id in log_service.values(): + data = self.rdmc.app.get_handler(log_id, service=True, silent=True).dict + members = data["Members"] + for val in members: + for dpu in val.values(): + dpu_data = self.rdmc.app.get_handler(dpu, service=True, silent=True).dict + path = dpu_data["Actions"]["#LogService.ClearLog"]["target"] + action = path.split("/")[-2] + action = {"Action": action} + self.rdmc.app.post_handler(path, action) + + except Exception as excp: + raise excp + + def get_resettype(self, options, info, id): + self.printreboothelp(options.reset) + time.sleep(3) + + if id == info["Id"]: + put_path = info["@odata.id"] + + for item in info["Actions"]: + if "Reset" in item: + if self.rdmc.app.typepath.defs.isgen10: + # action = item.split("#")[-1] + put_path = info["Actions"][item]["target"] + break + + if options.reset.lower() == "gracefulshutdown": + body = {"ResetType": "GracefulShutdown"} + elif options.reset.lower() == "forcerestart": + body = {"ResetType": "ForceRestart"} + elif options.reset.lower() == "nmi": + body = {"ResetType": "Nmi"} + elif options.reset.lower() == "gracefulrestart": + body = {"ResetType": "GracefulRestart"} + + self.rdmc.app.post_handler(put_path, body) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def printreboothelp(self, flag): + if flag.upper() == "FORCERESTART": + self.rdmc.ui.warn( + "\nForcing a server restart. Note, the current session will be " + "terminated.\nPlease wait for the server to boot completely before logging in " + "again.\nRebooting the server in 3 seconds...\n" + ) + elif flag.upper() == "NMI": + self.rdmc.ui.warn( + "\nA non-maskable interrupt will be issued to this server. Note, the " + "current session will be terminated.\nIssuing interrupt in 3 seconds...\n" + ) + + elif flag.upper() == "GRACEFULSHUTDOWN": + self.rdmc.ui.warn( + "\nThe server will be graceful shutdown. Note, the current session will be " + "terminated.\nPlease wait for the server to boot completely before logging in " + "again.\nRebooting the server in 3 seconds...\n" + ) + + elif flag.upper() == "GRACEFULRESTART": + self.rdmc.ui.warn( + "\nGraceful server restart. Note, the current session will be " + "terminated.\nPlease wait for the server to boot completely before logging in " + "again.\nRebooting the server in 3 seconds...\n" + ) + + def upload_firmware(self, options, info): + try: + string_id = options.id + to_list = list(string_id.split(",")) + for id in to_list: + for info_id in info["Id"]: + if id == info_id: + target = info["@odata.id"] + fw_path = options.update_fw + results = self.rdmc.app.select(selector="UpdateService.", path_refresh=True)[0].dict + results = results["Actions"]["#UpdateService.SimpleUpdate"]["target"] + string_split = fw_path.split("/") + for tar in string_split: + if tar.endswith("tar"): + self.rdmc.ui.printer("Uploading firmware: %s\n" % os.path.basename(tar)) + + res = self.rdmc.app.post_handler(results, {"ImageURI": fw_path, "Targets": [target]}) + + # Taskmonitor to find the firmware status + task_id = res.dict["TaskMonitor"] + + if res.status == 202: + status = self.wait_for_state_change(task_id, options) + if status: + self.rdmc.ui.printer( + "Component " + tar + " uploaded successfully.\n" + "A reboot may be required for firmware changes to take effect.\n" + ) + + if not status: + # Failed to upload the component. + raise UploadError("Error while processing the component.") + + elif (res.status == 404) or (res.status == 402): + return ReturnCodes.FAILED_TO_UPLOAD_COMPONENT + + except (FirmwareUpdateError, UploadError) as excp: + raise excp + + def wait_for_state_change(self, taskid, options, wait_time=4800): + """Wait for the iLO UpdateService to a move to terminal state. + :param options: command line options + :type options: list. + :param wait_time: time to wait on upload + :type wait_time: int. + """ + total_time = 0 + spinner = ["|", "/", "-", "\\"] + state = "" + self.rdmc.ui.printer("Waiting for iLO UpdateService to finish flashing the firmware \n") + + while total_time < wait_time: + state, _ = self.get_update_service_state(taskid) + if state == "ERROR": + return False + elif ( + (state == "Running") + or (state == "New") + or (state == "Starting") + or (state == "Interrupted") + or (state == "Suspended") + ): + # Lets try again after 8 seconds + count = 0 + # fancy spinner + while count <= 32: + self.rdmc.ui.printer("Updating: %s\r" % spinner[count % 4]) + time.sleep(0.25) + count += 1 + total_time += 8 + elif state == "Completed": + break + + if total_time >= wait_time: + raise FirmwareUpdateError("UpdateService in " + state + " state for " + str(wait_time) + "s") + + return True + + def get_update_service_state(self, taskid): + results = self.rdmc.app.get_handler(taskid, service=True, silent=True) + if results.status == 202: + return results.dict["TaskState"], results.dict + + if results and results.status == 200 and results.dict: + output = results.dict + err = output["error"]["@Message.ExtendedInfo"] + for e in err: + msgsrg = e["MessageArgs"] + return msgsrg[0], results.dict + else: + return "UNKNOWN", {} + + def print_get_bootprogress(self, info): + try: + if info is not None: + for id in info["Id"]: + content = {"Id": id} + content.update({"LastState": info["BootProgress"]["LastState"]}) + content.update({"OemLastState": info["BootProgress"]["OemLastState"]}) + content_json = {"Boot Progress": content} + return content_json + except Exception as excp: + raise excp + + def get_bootprogress(self, info): + boot_output = "" + boot_output = "------------------------------------------------\n" + boot_output += "Boot Progress \n" + boot_output += "------------------------------------------------\n" + if info is not None: + for id in info["Id"]: + boot_output += "Id: %s\n" % id + boot_output += "LastState: %s\n" % info["BootProgress"]["LastState"] + boot_output += "OemLastState: %s\n" % info["BootProgress"]["OemLastState"] + self.rdmc.ui.printer(boot_output, verbose_override=True) + + def print_get_logs(self, info): + log_service = info["LogServices"] + for log_id in log_service.values(): + data = self.rdmc.app.get_handler(log_id, service=True, silent=True).dict + members = data["Members"] + for val in members: + for dpu in val.values(): + dpu_data = self.rdmc.app.get_handler(dpu, service=True, silent=True).dict + entry = dpu_data["Entries"] + for dpu_entry in entry.values(): + result = self.rdmc.app.get_handler(dpu_entry, service=True, silent=True).dict + result = result["Members"] + if not result: + content = {} + else: + for member in result: + get_res = member["@odata.id"] + get_result = self.rdmc.app.get_handler(get_res, service=True, silent=True).dict + content = {"Id": get_result["Id"]} + content.update({"Name": get_result["Name"]}) + content.update({"Created": get_result["Created"]}) + content.update({"EntryType": get_result["EntryType"]}) + content.update({"Severity": get_result["Severity"]}) + message = get_result["Message"] + message = message.split(":") + content.update({message[0]: message[1]}) + content_json = {"Logs View": content} + return content_json + + def get_logs(self, info): + log_output = "" + log_service = info["LogServices"] + for log_id in log_service.values(): + data = self.rdmc.app.get_handler(log_id, service=True, silent=True).dict + members = data["Members"] + log_output = "------------------------------------------------\n" + log_output += "Logs View \n" + log_output += "------------------------------------------------\n" + for val in members: + for dpu in val.values(): + dpu_data = self.rdmc.app.get_handler(dpu, service=True, silent=True).dict + entry = dpu_data["Entries"] + for dpu_entry in entry.values(): + result = self.rdmc.app.get_handler(dpu_entry, service=True, silent=True).dict + result = result["Members"] + for member in result: + get_res = member["@odata.id"] + get_result = self.rdmc.app.get_handler(get_res, service=True, silent=True).dict + log_output += "Id: %s\n" % get_result["Id"] + log_output += "Name: %s\n" % get_result["Name"] + log_output += "Created: %s\n" % get_result["Created"] + log_output += "EntryType: %s\n" % get_result["EntryType"] + log_output += "Severity: %s\n" % get_result["Severity"] + log_output += "%s\n" % get_result["Message"] + self.rdmc.ui.printer(log_output, verbose_override=True) + + def prettyprintinfo(self, info, fw_version): + output = "" + output = "------------------------------------------------\n" + output += "SmartNic Information\n" + output += "------------------------------------------------\n" + + if info is not None: + for id in info["Id"]: + output += "System %s:\n" % id + output += "\tModel: %s\n" % info["Model"] + output += "\tManufacturer: %s\n" % info["Manufacturer"] + output += "\tName: %s\n" % info["Name"] + output += "\tFirmware Version: %s\n" % fw_version + output += "\tPowerState: %s\n" % info["PowerState"] + output += "\tSerialNumber: %s\n" % info["SerialNumber"] + output += "\tSystemType: %s\n" % info["SystemType"] + try: + output += "\tHealth: %s\n" % info["Status"]["Health"] + except KeyError: + pass + try: + output += "\tState: %s\n" % info["Status"]["State"] + except KeyError: + pass + try: + output += ( + "\tOperating System: %s \n" + % info["Oem"][self.rdmc.app.typepath.defs.oemhp]["OperatingSystem"]["Kernel"]["Name"] + ) + except KeyError: + pass + try: + output += ( + "\tOS Version: %s \n" + % info["Oem"][self.rdmc.app.typepath.defs.oemhp]["OperatingSystem"]["Kernel"]["Version"] + ) + except KeyError: + pass + try: + avail_sys = info["Oem"][self.rdmc.app.typepath.defs.oemhp]["AvailableSystemCapabilities"] + avail_sys = str(avail_sys).strip("[]''") + output += "\tAvailable SystemCapabilities: %s \n" % avail_sys + except KeyError: + pass + try: + enable_sys = info["Oem"][self.rdmc.app.typepath.defs.oemhp]["EnabledSystemCapabilities"] + enable_sys = str(enable_sys).strip("[]''") + output += "\tEnable SystemCapabilities: %s \n" % enable_sys + except KeyError: + pass + try: + integration_con = info["Oem"][self.rdmc.app.typepath.defs.oemhp]["IntegrationConfig"] + integration_con = str(integration_con).strip("'{} ") + integration_con = integration_con.replace("'", "") + output += "\tIntegration Config: %s \n" % integration_con + except KeyError: + pass + + output += "\tUUID: %s\n" % info["UUID"] + + self.rdmc.ui.printer(output, verbose_override=True) + + def build_json_out(self, info, fw_version): + if info is not None: + for id in info["Id"]: + content = {"model": info["Model"]} + content.update({"Manufacturer": info["Manufacturer"]}) + content.update({"Name": info["Name"]}) + content.update({"Firmware Version": fw_version}) + content.update({"PowerState": info["PowerState"]}) + content.update({"SerialNumber": info["SerialNumber"]}) + content.update({"SystemType": info["SystemType"]}) + try: + content.update({"Health": info["Status"]["Health"]}) + except KeyError: + pass + try: + content.update({"State": info["Status"]["State"]}) + except KeyError: + pass + try: + content.update( + { + "Operating System": info["Oem"][self.rdmc.app.typepath.defs.oemhp]["OperatingSystem"][ + "Kernel" + ]["Name"] + } + ) + except KeyError: + pass + try: + content.update( + { + "OS Version": info["Oem"][self.rdmc.app.typepath.defs.oemhp]["OperatingSystem"]["Kernel"][ + "Version" + ] + } + ) + except KeyError: + pass + try: + content.update( + { + "Available SystemCapabilities": info["Oem"][self.rdmc.app.typepath.defs.oemhp][ + "AvailableSystemCapabilities" + ] + } + ) + except KeyError: + pass + try: + content.update( + { + "Enable SystemCapabilities": info["Oem"][self.rdmc.app.typepath.defs.oemhp][ + "EnabledSystemCapabilities" + ] + } + ) + except KeyError: + pass + try: + content.update( + {"Integration Config": info["Oem"][self.rdmc.app.typepath.defs.oemhp]["IntegrationConfig"]} + ) + except KeyError: + pass + content.update({"UUID": info["UUID"]}) + system = {"System %s" % id: content} + content_json = {"SmartNic Information": system} + return content_json + + def gatherinfo(self, path): + try: + # info = {} + results = "" + csysresults = self.rdmc.app.select(selector="ComputerSystemCollection.") + csysresults = csysresults[0].dict + members = csysresults["Members"] + for id in members: + for mem_id in id.values(): + if path != mem_id: + results = self.rdmc.app.get_handler(mem_id, service=True, silent=True).dict + return results + except: + pass + + def smartnicvalidation(self, options): + """smartnic validation function. + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + + customparser.add_argument( + "--id", + dest="id", + help="Optionally include this flag to retrive the selected smartnic .", + default=None, + ) + + customparser.add_argument( + "--logs", + dest="logs", + action="store_true", + help="Optionally include this flag to list all the logs .", + default=False, + ) + + customparser.add_argument( + "--bootprogress", + dest="bootprogress", + action="store_true", + help="Optionally include this flag to list the bootprogress information", + default=False, + ) + + customparser.add_argument( + "--update_fw", + dest="update_fw", + help="Include this flag to update firmware to pensando card", + default=None, + ) + # customparser.add_argument( + # "--update_os", + # dest="update_os", + # help="Include this flag to update OS to pensando card", + # default=None, + # ) + + customparser.add_argument( + "--reset", + dest="reset", + help="Add this flag to reset the server", + nargs="?", + type=str, + const="GracefulRestart", + ) + + customparser.add_argument( + "--clearlog", + dest="clearlog", + action="store_true", + help="Optionally include this flag to clear log", + default=False, + ) diff --git a/ilorest/extensions/SMART_NIC_COMMANDS/__init__.py b/ilorest/extensions/SMART_NIC_COMMANDS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/extensions/__init__.py b/ilorest/extensions/__init__.py new file mode 100644 index 0000000..cbde63d --- /dev/null +++ b/ilorest/extensions/__init__.py @@ -0,0 +1,31 @@ +"""find and add dynamic extensions""" +import os + +tl = [] +classNames = [] +Commands = {} + +extensionDir = os.path.dirname(__file__) + +if os.name != "nt": + replacement = "/" +else: + replacement = "\\" + +for cwd, dirs, filenames in os.walk(extensionDir): + dirs[:] = [d for d in dirs if not d[0] == "."] + tl.append((cwd, [files for files in filenames if not files[0] == "."])) + +for cwd, names in tl: + cn = cwd.split("extensions")[-1] + cn = cn.replace(replacement, ".") + comms = [] + for name in names: + if name.endswith(".pyc") and "__" not in name: + name = name.replace(".pyc", "") + classNames.append(cn + "." + name + "." + name) + elif name.endswith(".py") and "__" not in name: + name = name.replace(".py", "") + if name + ".pyc" in names: + continue + classNames.append(cn + "." + name + "." + name) diff --git a/ilorest/extensions/_hidden_commands/AHSdiagCommand.py b/ilorest/extensions/_hidden_commands/AHSdiagCommand.py new file mode 100644 index 0000000..85c50d2 --- /dev/null +++ b/ilorest/extensions/_hidden_commands/AHSdiagCommand.py @@ -0,0 +1,518 @@ +### +# Copyright 2017 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 -*- +""" AHS diag Command for rdmc """ + +import os +from ctypes import POINTER, c_char_p, c_int, c_ubyte, create_string_buffer + +import redfish.hpilo.risblobstore2 as risblobstore2 +try: + from rdmc_helper import ( + LOGGER, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + LOGGER, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +from redfish.hpilo.rishpilo import HpIlo + +if os.name != "nt": + from _ctypes import dlclose + + +class AHSdiagCommand: + """Add Sign/Marker posts into the AHS logs of the server""" + + def __init__(self): + self.ident = { + "name": "ahsdiag", + "usage": None, + "description": "Adding sign or Marker Post into AHS logs." + "\n\tahsdiag --WriteSignPost \n\tahsdiag --WriteMarkerPost" + ' --instance 1 --markervalue 3 --markertext "DIMM Test Start"', + "summary": "Adding sign or Marker Post into AHS logs.", + "aliases": ["ahsops"], + "auxcommands": [ + "LoginCommand", + "SelectCommand", + "LogoutCommand", + "ServerlogsCommand", + ], + } + + self.dynamicclass = -1 + self.lib = None + self.channel = None + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main ahsdiag function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.ahsdiagvalidation(options) + self.ahsdiagworkerfunction(options) + self.cmdbase.logout_routine(self, options) + return ReturnCodes.SUCCESS + + def ahsdiagworkerfunction(self, options): + """Main ahsdig worker function + + :param options: command line options + :type options: list. + """ + if not options: + raise InvalidCommandLineErrorOPTS("") + + if self.lib is None and self.channel is None: + self.resetilo() + + if options.signpost and options.markerpost: + raise InvalidCommandLineErrorOPTS("") + + if options.signpost: + if options.inst or options.mval or options.mtext: + raise InvalidCommandLineError("") + self.addsignpost(options) + elif options.markerpost: + if options.inst and options.mval and options.mtext: + self.addmarkerpost(options) + else: + raise InvalidCommandLineErrorOPTS("") + else: + self.rdmc.ui.printer("Choose an operation!\n") + raise InvalidCommandLineErrorOPTS("") + + def addsignpost(self, options): + """Function to add signpost + + :param options: command line options + :type options: list. + """ + excp = None + for _ in range(0, 3): # channel loop for iLO + self.rdmc.ui.printer("Start writing sign post ... \n") + try: + if not self.spregister(): + raise + self.rdmc.ui.printer("Successfully registered.\n") + fail = True + + for _ in range(3): + if self.signpostregister(): + self.rdmc.ui.printer("Signpost was successful!\n") + fail = False + break + else: + self.rdmc.ui.printer("Signpost attempt failed!\n") + + if options.postIML and fail: + self.addimlpost(options) + + self.unregister() + return + except Exception: + if self.rdmc.opts.verbose: + self.rdmc.ui.printer("Retrying with new channel...\n") + self.resetilo() + raise excp + + def spregister(self): + """Function that registers the signpost""" + self.bb_class_allocate() + + if self.dynamicclass == -1: + return False + + self.lib.bb_register_wrapper(self.dynamicclass) + size = self.lib.sizeofbbregister() + rcode = self.dowriteread(size) + + if rcode == 0: + self.lib.bb_descriptor_code_wrapper(self.dynamicclass) + size = self.lib.sizeofbbdescriptorcode() + rcode = self.dowriteread(size) + + # if rcode > 0: + # return True + # else: + # return False + return True + + def bb_class_allocate(self): + """Function to obtain a handle for further operations""" + self.lib.bb_class_allocate_wrapper() + self.lib.getreq.restype = POINTER(c_ubyte) + ptrreq = self.lib.getreq() + + # sizeofbbchifresp = self.lib.sizeofBBCHIFRESP() + size = self.lib.sizeofbbclassalloc() + data = bytearray(ptrreq[:size][:]) + + if self.rdmc.opts.debug: + LOGGER.info("Sending data to chif:{0}\n".format(bytes(data))) + + resp = self.channel.chif_packet_exchange(data) + + resp = create_string_buffer(bytes(resp)) + self.lib.setresp.argtypes = [c_char_p] + self.lib.setresp(resp) + + if len(resp) != 0: + self.dynamicclass = self.lib.respmsgrcode() + self.rdmc.ui.printer("Do Read Return error code:{0}\n".format(self.dynamicclass)) + else: + self.dynamicclass = 0 + + def unregister(self): + """Function to unregister the handle""" + self.lib.bb_unregister_wrapper() + size = self.lib.sizeinunregister() + rcode = self.dowriteread(size) + + if rcode == 0: + self.rdmc.ui.printer("Unregistration successful!\n") + else: + self.rdmc.ui.printer("Failed to unregister with code:{0}.\n".format(rcode)) + + def dowriteread(self, size): + """Function responsible for communication with the iLO + + :param size: size of the buffer to be read or written + :type size: int + """ + ptrreq = self.lib.getreq() + data = bytearray(ptrreq[:size][:]) + if self.rdmc.opts.debug: + LOGGER.info("Sending data to chif:{0}\n".format(data)) + try: + resp = self.channel.chif_packet_exchange(data) + except Exception: + self.resetilo() + return -1 + + self.lib.setresp.argtypes = [c_char_p] + resp = create_string_buffer(bytes(resp)) + self.lib.setresp(resp) + rcode = self.lib.respmsgrcode() + + if rcode == 0: + self.lib.updatehandle() + + return rcode + + def signpostregister(self): + """Worker function of signpost register""" + self.lib.bb_code_set_wrapper() + size = self.lib.sizeinbbcodeset() + returnval = self.dowriteread(size) + + if returnval != 0: + self.printsignpostregister(returnval) + return False + + self.lib.bb_log_wrapper() + size = self.lib.sizeinbblog() + returnval = self.dowriteread(size) + + if returnval != 0: + self.printsignpostregister(returnval) + return False + + self.lib.bb_log_static_wrapper() + size = self.lib.sizeinbblog() + returnval = self.dowriteread(size) + + if returnval != 0: + self.printsignpostregister(returnval) + return False + + return True + + def printsignpostregister(self, returnval): + """Commonly used output statement. + + :param returnval: return code for signpost + :type returnval: int + """ + self.rdmc.ui.printer("return signpost register failed={0}\n".format(returnval)) + + def addmarkerpost(self, options): + """Function to add marker post + + :param options: command line options + :type options: list. + """ + excp = None + for _ in range(0, 3): # channel loop for iLO + self.rdmc.ui.printer("Start writing marker post ... \n") + + try: + for i in range(3): + if self.mpregister(): + break + + self.rdmc.ui.printer("attempting to register Marker Post..." "failed {0} times\n".format(i + 1)) + + self.resetilo() + + self.rdmc.ui.printer("Successfully registered.\n") + fail = True + + for _ in range(3): + if self.markerpostregister(options): + self.rdmc.ui.printer("Marker Post was successful!\n") + fail = False + break + else: + self.rdmc.ui.printer("Marker Post attempt failed!\n") + + if options.postIML and fail: + self.addimlpost(options) + + self.unregister() + return + except Exception: + if self.rdmc.opts.verbose: + self.rdmc.ui.printer("Retrying with new channel...\n") + self.resetilo() + raise excp + + def mpregister(self): + """Function that registers the marker post""" + self.bb_class_allocate() + self.rdmc.ui.printer("return code(dynamic class)={0}.\n".format(self.dynamicclass)) + + if self.dynamicclass == -1: + return False + + self.lib.bb_register_mwrapper(self.dynamicclass) + size = self.lib.sizeofbbregister() + rcode = self.dowriteread(size) + + if rcode == 0: + self.lib.bb_descriptor_code_mwrapper(self.dynamicclass) + size = self.lib.sizeofbbdescriptorcode() + rcode = self.dowriteread(size) + + if rcode == -1: + return False + self.lib.bb_descriptor_field_mwrapper(self.dynamicclass) + size = self.lib.sizeofbbdescriptorcode() + rcode = self.dowriteread(size) + if rcode == -1: + return False + + return True + + def markerpostregister(self, options): + """Worker function of marker post register + + :param options: command line options + :type options: list. + """ + self.lib.bb_code_set_mwrapper() + size = self.lib.sizeinbbcodeset() + returnval = self.dowriteread(size) + + if returnval != 0: + return False + + mval = int(options.mval) + minst = int(options.inst) + mtext = create_string_buffer(bytes(options.mtext, "utf-8")) + + self.lib.markerpost_wrapper.argtypes = [c_int, c_int, c_char_p] + self.lib.markerpost_wrapper(mval, minst, mtext) + + size = self.lib.sizeinbblogm() + returnval = self.dowriteread(size) + + if returnval != 0: + return False + + return True + + def resetilo(self): + """Function to reset iLO""" + if self.channel: + self.channel.close() + self.loadlib() + self.channel = HpIlo(dll=self.lib) + self.lib.ChifDisableSecurity() + + def loadlib(self): + """Function to load the so library""" + self.closedll() + try: + if os.name == "nt": + self.rdmc.ui.printer("Operation can be performed only on Unix based systems!\n") + raise InvalidCommandLineErrorOPTS("") + else: + self.lib = risblobstore2.BlobStore2.gethprestchifhandle() + except Exception as excp: + raise InvalidCommandLineErrorOPTS(excp) + + def closedll(self): + """Deallocate dll handle.""" + try: + dlclose(self.lib) + except Exception: + pass + + def addimlpost(self, options): + """Adding maintenance post from serverlogs. + + :param options: command line options + :type options: list. + """ + if options.signpost: + imltext = "Signpost Writing Failed" + elif options.markerpost: + imltext = "Markerpost Writing Failed" + + options.service = "IML" + options.clearlog = None + options.mainmes = imltext + + path = self.serverlogsobj.returnimlpath() + self.serverlogsobj.addmaintenancelogentry(options, path=path) + + def ahsdiagvalidation(self, options): + """ahsdiag method validation function""" + client = None + + try: + self.cmdbase.login_select_validation(self, options) + client = self.rdmc.app.current_client + except Exception: + if client: + if not client.base_url == "blobstore://.": + raise InvalidCommandLineError("ahsdiag command " "available in local mode only.\n") + + if self.rdmc.config.url: + raise InvalidCommandLineError("ahsdiag command " "available in local mode only.\n") + + if not client: + self.lobobj.run("") + + def definearguments(self, customparser): + """Defines the required arguments for ahsdiag command. + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--selectlog", + dest="service", + help="""Read log from the given log service. Options: IML, """ """IEL or AHS.""", + default=None, + ) + customparser.add_argument( + "--clearlog", + "-c", + dest="clearlog", + action="store_true", + help="""Clears the logs for a the selected option.""", + default=None, + ) + customparser.add_argument( + "--maintenancemessage", + "-m", + dest="mainmes", + help="""Maintenance message to be inserted into the log. """ """(IML LOGS ONLY FEATURE)""", + default=None, + ) + customparser.add_argument( + "--customiseAHS", + dest="customiseAHS", + help="""Allows customised AHS log data to be downloaded.""", + default=None, + ) + customparser.add_argument( + "-s", + "--WriteSignPost", + dest="signpost", + action="store_true", + help="""Writes a sign post into the AHS log.""", + default=False, + ) + customparser.add_argument( + "-r", + "--WriteMarkerPost", + dest="markerpost", + action="store_true", + help="""Writes a marker post into the AHS log.""", + default=False, + ) + customparser.add_argument( + "-i", + "--instance", + dest="inst", + help="""Argument required by marker post.""", + default=None, + ) + customparser.add_argument( + "-l", + "--markervalue", + dest="mval", + help="""Argument required by marker post.""", + default=None, + ) + customparser.add_argument( + "-t", + "--markertext", + dest="mtext", + help="""Argument required by marker post.""", + default=None, + ) + customparser.add_argument( + "-w", + "--WriteIMLPost", + dest="postIML", + action="store_true", + help="""Writes an IML entry if failure occurs.""", + default=False, + ) diff --git a/ilorest/extensions/_hidden_commands/AutomaticTestingCommand.py b/ilorest/extensions/_hidden_commands/AutomaticTestingCommand.py new file mode 100644 index 0000000..e6dbd12 --- /dev/null +++ b/ilorest/extensions/_hidden_commands/AutomaticTestingCommand.py @@ -0,0 +1,2782 @@ +### +# Copyright 2016 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 -*- +""" Automatic Testing for rdmc """ + +import datetime +import json +import os +import platform +import random +import re +import string +import sys +import time +import traceback +from logging import FileHandler +from zipfile import ZipFile + +from six.moves.urllib.request import urlopen, urlretrieve + +try: + from rdmc_helper import ( + HARDCODEDLIST, + CommandNotEnabledError, + Encryption, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidOrNothingChangedSettingsError, + NoChangesFoundOrMadeError, + NoContentsFoundForOperationError, + PathUnavailableError, + ReturnCodes, + ) +except: + from ilorest.rdmc_helper import ( + HARDCODEDLIST, + CommandNotEnabledError, + Encryption, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidOrNothingChangedSettingsError, + NoChangesFoundOrMadeError, + NoContentsFoundForOperationError, + PathUnavailableError, + ReturnCodes, + ) + +from redfish.ris.rmc_helper import IloResponseError + +__error_logfile__ = "automatictesting_error_logfile.log" +__pass_fail__ = "passfail" +__last_line__ = "lastline" + + +def log_decor(func): + """decorator to gather and log traceback while continuing testing""" + + def func_wrap(*args, **kwargs): + """decorator wrapper function""" + try: + func(*args, **kwargs) + except Exception as exp: + if isinstance(exp, dict) or isinstance(exp, list): + # DOES NOT WORK YET. For errors which are captured and gathered before a raise, there + # is no point in showing the DEBUG log. + logging(command=func.__name__, error=exp, logfile_ignore=True) + elif args: + logging(command=func.__name__, error=exp, args=args[0]) + else: + logging(command=func.__name__, error=exp) + else: + logging(command=func.__name__, args=args[0]) + + return func_wrap + + +def logging(command, error=None, args=None, logfile_ignore=False): + """ + handler for logging errors as well as the pass/fail log. + + :param command: string representing the command/function which was tested + :param error: error string (if provided in the event of an exception) + :param args: arguments provided to the command/function which was tested. May need this + for various purposes (file handles, references to objects, references to object attributes) + + """ + debug_ret_err = False + debug_log_errs = [] + data2 = [] + data_passed_failed = {} + lockout = False + pass_flag = True + + error_strs = ["Code:400", "Code:401", "Exception", "EXCEPTION", "Warning", "WARNING"] + if not error: + try: + if args: + if args.rdmc.opts.debug: + for handle in args.rdmc.app.logger.parent.handlers: + if isinstance(handle, FileHandler): + # obtain last 25 lines of the debug log (if '-d' is used) + with open(handle.baseFilename, "rb") as dlfh: + debug_log_errs = tail_debug_log(dlfh, 25) + for line in debug_log_errs: + for err in error_strs: + if err in line: + with open(__last_line__, "r") as ll_file: + tmp_err = ll_file.readline() + if err not in tmp_err: + with open(__last_line__, "w+") as ll_file: + ll_file.write(line) + error = line + pass_flag = False + break + if error: + break + except: + debug_ret_err = True + + error_strs = ["Code:400", "Code:401", "Exception", "EXCEPTION", "Warning", "WARNING"] + if not error: + try: + if args: + if args.rdmc.opts.debug: + for handle in args.rdmc.app.logger.parent.handlers: + if isinstance(handle, FileHandler): + # obtain last 25 lines of the debug log (if '-d' is used) + with open(handle.baseFilename, "rb") as dlfh: + debug_log_errs = tail_debug_log(dlfh, 25) + for line in debug_log_errs: + for err in error_strs: + if err in line: + with open(__last_line__, "r") as ll_file: + tmp_err = ll_file.readline() + if err not in tmp_err: + with open(__last_line__, "w+") as ll_file: + ll_file.write(line) + error = line + pass_flag = False + break + if error: + break + except: + debug_ret_err = True + + try: + fhandle = open(__pass_fail__, "r") + data = fhandle.readlines() + fhandle.close() + for itr in data: + if "Total: " in itr: + data2.append("Total: " + str(int(itr.split(":")[-1].split("\n")[0]) + 1)) + continue + elif "Fail:" in itr and error: + data2.append("Fail: " + str(int(itr.split(":")[-1].split("\n")[0]) + 1)) + data_passed_failed[command] = "'{}' Command - Failed".format(command) + elif "Pass:" in itr and pass_flag: + data2.append("Pass: " + str(int(itr.split(":")[-1].split("\n")[0]) + 1)) + if command not in data_passed_failed: + data_passed_failed[command] = "'{}' Command - Passed".format(command) + elif "Command - " in itr: + resultdata = itr.split("'") + data_passed_failed[(resultdata[1])] = itr + elif not lockout: + lockout = True + data2.append(itr.split("\n")[0]) + except Exception: + sys.stderr.write("An error occurred with Pass/Fail log counter.\n") + data2 = None + pass + finally: + fhandle = open(__pass_fail__, "w+") + for element in data2: + fhandle.writelines(element + "\n") + fhandle.writelines("\n") + for k, v in list(data_passed_failed.items()): + if "\n" in v: + fhandle.writelines(v) + else: + fhandle.writelines(v + "\n") + fhandle.close() + + if error: + sys.stderr.write("An error occurred in: %s - %s\n" % (command, error)) + with open(__error_logfile__, "a+") as fhandle: + fhandle.write("\n" + command + " - Test#: ") + if data[0]: + fhandle.write(str(int(data[0].split(":")[-1].split("\n")[0]) + 1)) + else: + fhandle.write(" 1") + + fhandle.write("\nSimplified Error: " + str(error) + "\n") + # if args: + fhandle.write("\nTraceback:\n------------------------\n") + fhandle.write(str(traceback.format_exc()) + "\n") + if not debug_ret_err and not logfile_ignore: + fhandle.write("\nTail of Debug Logfile:") + fhandle.write("\n------------------------\n") + for item in debug_log_errs: + fhandle.write(item) + fhandle.write("\n------END OF DEBUG------\n") + + +def tail_debug_log(f, lines=1, _buffer=4098): + """Returns last n lines from the filename. + + :param f: filename for 'tail' retrieval + :type f: file handle + :param lines: number of lines to retrieve + :type lines: int + :param _buffer: buffer space for blocks + :type _buffer: bytes + """ + # place holder for the lines found + lines_found = [] + + # block counter will be multiplied by buffer + # to get the block size from the end + block_counter = -1 + + # loop until we find X lines + while len(lines_found) < lines: + try: + f.seek(block_counter * _buffer, os.SEEK_END) + except IOError: # either file is too small, or too many lines requested + f.seek(0) + lines_found = f.readlines() + break + + lines_found = f.readlines() + block_counter -= 1 + + return lines_found[-lines:] + + +class AutomaticTestingCommand: + """Automatic testing class command""" + + def __init__(self): + self.ident = { + "name": "automatictesting", + "usage": None, + "description": "Automatic testing command, not customer facing.", + "summary": "Automatic testing command, not customer facing.", + "aliases": [], + "auxcommands": [ + "LoginCommand", + "LogoutCommand", + "TypesCommand", + "SelectCommand", + "GetCommand", + "ListCommand", + "InfoCommand", + "SetCommand", + "StatusCommand", + "CommitCommand", + "SaveCommand", + "LoadCommand", + "RawDeleteCommand", + "RawHeadCommand", + "RawGetCommand", + "RawPatchCommand", + "RawPutCommand", + "RawPostCommand", + "IscsiConfigCommand", + "FirmwareUpdateCommand", + "IloResetCommand", + "ISToolCommand", + "ResultsCommand", + "BootOrderCommand", + "ServerStateCommand", + "VirtualMediaCommand", + "IloAccountsCommand", + "IloFederationCommand", + "ClearRestApiStateCommand", + "CertificateCommand", + "SigRecomputeCommand", + "ESKMCommand", + "SendTestCommand", + "SingleSignOnCommand", + "ServerlogsCommand", + "AHSdiagCommand", + "SMBiosCommand", + "IloLicenseCommand", + "DeleteComponentCommand", + "DownloadComponentCommand", + "InstallSetCommand", + "ListComponentCommand", + "UpdateTaskQueueCommand", + "UploadComponentCommand", + "HpGooeyCommand", + "PendingChangesCommand", + "ServerInfoCommand", + "MaintenanceWindowCommand", + "FwpkgCommand", + "RebootCommand", + "IPProfilesCommand", + "FirmwareIntegrityCheckCommand", + "SetPasswordCommand", + "BiosDefaultsCommand", + "FwpkgCommand", + "ServerCloneCommand", + "FactoryDefaultsCommand", + "DirectoryCommand", + "SmartArrayCommand", + ], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + self.ret_code = ReturnCodes.SUCCESS + + def run(self, line, help_disp=False): + """Main automatic testing worker function + + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + if sys.platform == "darwin": + raise CommandNotEnabledError("'%s' command is not supported on MacOS" % str(self.name)) + elif "VMkernel" in platform.uname(): + raise CommandNotEnabledError("'%s' command is not supported on VMWare" % str(self.name)) + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.getautomatictestingvalidation(options) + with open(__last_line__, "w+") as ll_file: + ll_file.write("") + with open(__pass_fail__, "w+") as pf_file: + pf_file.write("Total: 0\nPass: 0\nFail: 0\n") + with open(__error_logfile__, "w+") as log_file: + log_file.write("") + + self.rdmc.ui.printer("***************************************************" "******************************\n") + self.rdmc.ui.printer("****************************AUTOMATIC TESTING " "STARTING***************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.options = options + if options.sumtest: + self.testsumcode() + return ReturnCodes.SUCCESS + if options.ahsdiagtest: + self.ahsdiagtesting(options.local) + + # erase iLORest Debug Log (if debugging enabled) + if self.rdmc.opts.debug: + for handle in self.rdmc.app.logger.parent.handlers: + if isinstance(handle, FileHandler): + with open(handle.baseFilename, "w+") as dlfh: + dlfh.write("") + if self.rdmc.config: + if self.rdmc.config._ac__savefile: + self.logfile = self.rdmc.config._ac__savefile + + self.helpmenusautomatictesting() + self.loginautomatictesting() + self.typesandselectautomatictesting(options.local, options.latestschema) + self.getandsetautomatictesting(options.latestschema) + self.saveandloadautomatictesting(options.local, options.latestschema) + self.iscsiautomatictesting() + self.istoolautomatictesting() + self.resultsautomatictesting() + self.bootorderautomatictesting() + self.serverstateautomatictesting() + self.virtualmediaautomatictesting() + self.accountautomatictesting() + self.federationautomatictesting() + self.certificateautomatictesting() + self.eskmautomatictesting() + self.sendtestautomatictesting() + self.ssoautomatictesting() + self.sigrecomputeautomatictesting() + self.serverlogstesting() + self.ilolicensetesting() + self.setbiospasswordautomatictesting(options.local) + self.smbiostesting() + self.cloningautomatictesting() + self.pendingautomatictesting() + self.serverinfoautomatictesting() + self.directoryautomatictesting() + if options.complete: + self.serverclonecommandautomatictesting(options.local) + self.fwpkgcommandautomatictesting() + self.ipprofilesautomatictesting() + self.fwintegritycheckautomatictesting() + self.uploadcomptesting() + self.listcomptesting() + self.updatetaskqueuetesting() + self.installsettesting() + self.maintenancewindowautomatictesting() + self.listanddownloadcomptesting() + self.deletecomptesting() + if options.complete: + self.setbiosdefaultsautomatictesting() # Sets bios default + self.rawautomatictesting(options.local) + if options.complete: + self.smartarrayautomatictesting() + self.firmwareautomatictesting() + + self.rdmc.ui.printer("***************************************************" "******************************\n") + self.rdmc.ui.printer("******************************AUTOMATIC TESTING " "DONE*****************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + # Show PASS/FAIL + with open(__pass_fail__, "r") as fhandle: + data = fhandle.readlines() + self.rdmc.ui.printer("Results are:\n") + for item in data: + if item == "\n": + break + else: + self.rdmc.ui.printer(item) + + self.rdmc.ui.printer("Review logfiles: '%s', '%s',\n" % (__pass_fail__, __error_logfile__)) + + if options.archive: + self.archive_handler(options.archive[0]) + + self.cmdbase.logout_routine(self, options) + # Return code + self.rdmc.ui.printer("\n") + return self.ret_code + + def getautomatictestingvalidation(self, options): + """get inventory validation function + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + @log_decor + def helpmenusautomatictesting(self): + """handler for help menu auto test""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("**********************STARTING HELP MENU AUTOMATIC " "TESTING***********************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + # Argparse will attempt a force exit when using '-h'? Maybe we need to re-add the help + # command again... + # try: + # self.rdmc.run(['--h']) + # except Exception as exp: + # pass + + for command in self.auxcommands: + self.auxcommands[command].run("-h") + self.rdmc.ui.printer( + "\n*********************************************" "************************************\n" + ) + self.rdmc.ui.printer( + "***********************************************" "**********************************\n" + ) + self.rdmc.ui.printer( + "***********************************************" "**********************************\n\n" + ) + + @log_decor + def loginautomatictesting(self): + """handler for login auto test""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("************************STARTING LOGIN AUTOMATIC " "TESTING*************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + loopcount = 10 + + for xint in range(loopcount): + self.rdmc.ui.printer("Login operation counter: %d\n" % xint) + self.auxcommands["login"].loginfunction("") + + self.auxcommands["logout"].logoutfunction("") + self.rdmc.ui.printer("\n") + + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + @log_decor + def typesandselectautomatictesting(self, local, latestschema): + """handler for types and select auto test + + :param local: flag to enable local login + :type local: boolean. + :param latestschema: flag to determine if we should use smart schema. + :type latestschema: boolean. + """ + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*******************STARTING TYPES AND SELECT " "AUTOMATIC TESTING*******************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + if local: + self.auxcommands["login"].loginfunction("") + + typeslist = self.auxcommands["types"].typesfunction("", returntypes=True) + + for item in typeslist: + if item and not item.startswith("Session."): + self.rdmc.ui.printer("Selected option: '%s'\n" % item) + self.auxcommands["select"].selectfunction(item) + self.auxcommands["select"].selectfunction("") + + self.getandinfoautomatictesting(latestschema=latestschema) + + self.rdmc.ui.printer( + "\n*****************************************" "***************************************\n" + ) + self.rdmc.ui.printer( + "*******************************************" "*************************************\n" + ) + self.rdmc.ui.printer( + "*******************************************" "*************************************\n\n" + ) + + self.auxcommands["logout"].logoutfunction("") + + @log_decor + def getandsetautomatictesting(self, latestschema): + """handler for get and set auto test + + :param latestschema: flag to determine if we should use smart schema. + :type latestschema: boolean. + """ + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************STARTING GET AND SET AUTOMATIC" " TESTING**********************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].loginfunction("") + self.rdmc.ui.printer("\n") + + if self.rdmc.app.typepath.flagiften: + biosinputlist = [ + "Attributes/BootMode", + "Attributes/MissingItem", + "Attributes/AdminName", + "Attributes/AdminEmail", + "Attributes/AdminPhone", + "Attributes/CustomPostMessage", + "Attributes/ServerAssetTag", + "Attributes/ServerName", + "Attributes/ServerOtherInfo", + "Attributes/ServerPrimaryOs", + "Attributes/ServiceEmail", + "Attributes/ServiceName", + "Attributes/ServiceOtherInfo", + "Attributes/ServicePhone", + ] + else: + biosinputlist = [ + "BootMode", + "MissingItem", + "AdminName", + "AdminEmail", + "AdminPhone", + "CustomPostMessage", + "ServerAssetTag", + "ServerName", + "ServerOtherInfo", + "ServerPrimaryOs", + "ServiceEmail", + "ServiceName", + "ServiceOtherInfo", + "ServicePhone", + ] + + self.setautomatichelper(biosinputlist, "HpBios.", latestschema) + + self.rdmc.ui.printer("\n*************************************************" "*******************************\n") + self.rdmc.ui.printer("***************************************************" "*****************************\n") + self.rdmc.ui.printer("***************************************************" "*****************************\n\n") + + biosinputlist = ["Boot/BootSourceOverrideTarget", "AssetTag"] + self.setautomatichelper(biosinputlist, "ComputerSystem.", latestschema) + + self.rdmc.ui.printer("\n*************************************************" "*******************************\n") + self.rdmc.ui.printer("***************************************************" "*****************************\n") + self.rdmc.ui.printer("***************************************************" "*****************************\n\n") + self.auxcommands["status"].run("") + + self.rdmc.ui.printer("\n*************************************************" "*******************************\n") + self.rdmc.ui.printer("***************************************************" "*****************************\n") + self.rdmc.ui.printer("***************************************************" "*****************************\n\n") + self.auxcommands["commit"].run("") + + @log_decor + def setautomatichelper(self, inputlist, selector, latestschema): + """handler for set auto test + + :param inputlist: list of items to be set. + :type inputlist: list. + :param selector: type to be select for items that will be set. + :type selector: str. + :param latestschema: flag to determine if we should use smart schema. + :type latestschema: boolean. + """ + self.rdmc.ui.printer("STARTING TEST FOR SETTINGS ON '%s'.\n" % selector) + self.auxcommands["select"].selectfunction([selector]) + + for item in inputlist: + listresults = self.auxcommands["get"].getworkerfunction(item, self.options, results=True) + for getresults in listresults: + if getresults and isinstance(getresults[next(iter(getresults))], dict): + for key in getresults: + getresults = getresults[key] + + for key, value in list(getresults.items()): + if key == "BootSourceOverrideTarget": + if value == "None": + randstring = "Cd" + else: + randstring = "None" + else: + randstring = "".join( + random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10) + ) + + if value == randstring: + value = value[::-1] + + self.rdmc.ui.printer("Setting: '%s'\t\tBefore:'%s'\tAfter:'%s" "'\n" % (key, value, randstring)) + + inputline = item + "=" + randstring + + if latestschema: + inputline += " --latestschema" + + try: + self.auxcommands["set"].run(inputline, skipprint=True) + except: + pass + elif getresults: + for key, value in list(getresults.items()): + randstring = "".join( + random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10) + ) + + if value == randstring: + value = value[::-1] + + self.rdmc.ui.printer("Setting: '%s'\t\tBefore:'%s'\tAfter:'%s" "'\n" % (key, value, randstring)) + + inputline = key + "=" + randstring + + if latestschema: + inputline += " --latestschema" + + try: + self.auxcommands["set"].run(inputline, skipprint=True) + except: + pass + else: + InvalidCommandLineError("Variable '%s' was not found in the " "current selection.\n" % item) + + @log_decor + def getandinfoautomatictesting(self, item="", latestschema=False, getval=None, already_checked=[]): + """handler for get and info auto test + + :param item: list of items to be set. + :type item: list. + :param latestschema: flag to determine if we should use smart schema. + :type latestschema: boolean. + :param selectitems: list of selected items. + :type selectitems: list. + :param origin: location for current item. + :type origin: str. + :param already_checked: List of keys already checked. + :type already_checked: list. + """ + listdata = self.auxcommands["get"].getworkerfunction(item, self.options, results=True) + if not listdata: + self.rdmc.ui.warn("(GET) No list contents found for section: '%s'\n" % item) + + getdata = self.auxcommands["get"].getworkerfunction(item, self.options, uselist=True, results=True) + if getdata and item and self.rdmc.opts.verbose: + self.rdmc.ui.printer("Contents found for section: '%s'\n" % item) + elif not getdata: + self.rdmc.ui.warn("(LIST) No contents found for section: '%s'\n" % item) + + cline = item + " --latestschema" if latestschema else item + try: + if not self.auxcommands["info"].run(cline, autotest=True): + logging("info", cline, add_traceback=False) + raise KeyError + except: + self.rdmc.ui.warn("(LIST) No info contents found for section: '%s'\n" % cline) + + if not getdata: + return + + getdata = getval if getval is not None or item else getdata + if isinstance(getdata, dict): + for key, val in list(getdata.items()): + if key.lower() in HARDCODEDLIST or key.lower() in already_checked: + continue + key = (item + "/" + key) if item else key + already_checked.append(key) + self.getandinfoautomatictesting( + item=key, + latestschema=latestschema, + getval=val, + already_checked=already_checked, + ) + elif isinstance(getdata, (list, tuple)): + for kii in getdata: + if not isinstance(kii, dict): + continue + for key, val in list(kii.items()): + if key.lower() in HARDCODEDLIST: + continue + key = (item + "/" + key) if item else key + if key in already_checked: + continue + else: + already_checked.append(key) + self.getandinfoautomatictesting( + item=key, + latestschema=latestschema, + getval=val, + already_checked=already_checked, + ) + + @log_decor + def saveandloadautomatictesting(self, local, latestschema): + """handler for save and load auto test + + :param local: flag to enable local login + :type local: boolean. + :param latestschema: flag to determine if we should use smart schema. + :type latestschema: boolean. + """ + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("********************STARTING SAVE AND LOAD " "AUTOMATIC TESTING*********************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + biosinputlist = [ + "AdminName", + "AdminEmail", + "AdminPhone", + "CustomPostMessage", + "ServerAssetTag", + "ServerName", + "ServerOtherInfo", + "ServerPrimaryOs", + "ServiceEmail", + "ServiceName", + "ServiceOtherInfo", + "ServicePhone", + ] + # TODO: Verify the data in file matches server after load + # TODO: Add multi select save/load checking + if local: + self.auxcommands["login"].loginfunction("") + + self.auxcommands["save"].run("--selector=HpBios. -f ilorest.json") + + fileh = open("ilorest.json", "r") + tempholder = fileh.read() + + for item in biosinputlist: + restring = '"' + item + '": ".*?"' + randstring = "".join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10)) + + replacestring = '"' + item + '": "' + randstring + '"' + tempholder = re.sub(restring, replacestring, tempholder, flags=re.DOTALL) + + outfile = open("ilorest.json", "w") + outfile.write(tempholder) + outfile.close() + + self.rdmc.ui.printer("\n") + + if latestschema: + self.auxcommands["load"].run("--latestschema") + else: + self.auxcommands["load"].run("-f ilorest.json") + + self.rdmc.ui.printer("\n") + self.auxcommands["logout"].run("") + + @log_decor + def iscsiautomatictesting(self): + """iscsi automatic testing""" + self.rdmc.ui.printer("***************************************************" "******************************\n") + self.rdmc.ui.printer("*****************STARTING ISCSI CONFIGURATION " "AUTOMATIC TESTING******************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + found = 0 + testfile = None + self.auxcommands["iscsiconfig"].run("") + + try: + self.auxcommands["iscsiconfig"].run("--delete 1") + except: + pass + + self.auxcommands["iscsiconfig"].run("--add [1]") + self.auxcommands["iscsiconfig"].run("--list") + self.auxcommands["iscsiconfig"].run("--list -f TESTFILE") + + try: + testfile = open("TESTFILE") + for line in testfile: + if '"Attempt 1"' in line: + found = 1 + self.rdmc.ui.printer("Output Validated.\n") + break + + if found == 0: + raise NoContentsFoundForOperationError("Change not found.") + except Exception as excp: + raise excp + + try: + testfile.seek(0) + found = 1 + self.auxcommands["iscsiconfig"].run("--delete 1") + self.auxcommands["iscsiconfig"].run("--list -f TESTFILE") + + for line in testfile: + if '"Attempt 1"' in line: + raise NoChangesFoundOrMadeError("Not deleted") + + if found == 0: + self.rdmc.ui.printer("Attempt successfully deleted.\n") + except Exception as excp: + raise excp + + self.auxcommands["logout"].run("") + + @log_decor + def rawautomatictesting(self, local): + """raw commands automatic testing + + :param local: flag to enable local login + :type local: boolean. + """ + if local: + self.auxcommands["login"].loginfunction("") + + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************STARTING RAW_COMMANDS " "AUTOMATIC TESTING*********************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["rawhead"].run('"/rest/v1/systems/1" -f TESTFILE') + + try: + testfile = open("TESTFILE") + json.loads(testfile.read()) + except Exception as excp: + raise excp + finally: + testfile.close() + + self.rdmc.ui.printer("json return validated.\n") + self.auxcommands["rawget"].run('"/rest/v1/systems/1/bios/Settings" -f TESTFILE') + + try: + testfile = open("TESTFILE") + json.loads(testfile.read()) + except Exception as excp: + raise excp + finally: + testfile.close() + + self.rdmc.ui.printer("json return validated.\n") + + try: + writefile = open("WRITEFILE", "w") + writefile.write( + '{\n\t"path": "/rest/v1/systems/1/bios/Settings",' '\n\t"body": {\n\t\t"AdminName": "RexySmith"\n\t}\n}' + ) + writefile.close() + + self.auxcommands["rawpatch"].run("WRITEFILE") + self.auxcommands["rawget"].run('"/rest/v1/systems/1/bios/Settings" -f TESTFILE') + testfile = open("TESTFILE") + found = 0 + + for line in testfile: + if '"AdminName"' in line: + if '"RexySmith"' in line: + found = 1 + self.rdmc.ui.printer("json return validated.\n") + break + + if found == 0: + raise NoContentsFoundForOperationError("Change not found.") + + except Exception as excp: + raise excp + + finally: + testfile.close() + + # ******************RAW PUT************************* + try: + writefile = open("WRITEFILE", "w") + if self.rdmc.app.typepath.flagiften: + writefile.write( + '{\n\t"path":"/rest/v1/systems/1/bios/Settings",\n' + '\t"body":{\n\t\t"Attributes":{\n\t\t\t"BaseConfig":' + ' "default"\n\t\t}\n\t}\n}' + ) + else: + writefile.write( + '{\n\t"path": "/rest/v1/systems/1/bios/Settings",\n' + '\t"body":{\n\t\t"BaseConfig": "default"' + "\n\t}\n}" + ) + writefile.close() + + self.auxcommands["rawput"].run("WRITEFILE") + self.auxcommands["rawget"].run('"/rest/v1/systems/1/bios/Settings" -f TESTFILE') + testfile = open("TESTFILE") + found = 0 + + for line in testfile: + if '"BaseConfig"' in line: + if '"default"' in line: + found = 1 + self.rdmc.ui.printer("json return validated\n") + break + + if found == 0: + raise NoContentsFoundForOperationError("Change not found.") + except Exception as excp: + raise excp + finally: + testfile.close() + + # ******************RAW POST************************ + if not local: + try: + writefile = open("WRITEFILE", "w") + + if self.rdmc.app.typepath.flagiften: + writefile.write( + '{\n\t"path": "/redfish/v1/Systems/1/' + 'Actions/ComputerSystem.Reset/",\n\t"body": ' + '{\n\t\t"ResetType": "ForceRestart"\n\t}\n}' + ) + else: + writefile.write( + '{\n\t"path": "/rest/v1/Systems/1",\n' + '\t"body": {\n\t\t"Action": "Reset",' + '\n\t\t"ResetType": "ForceRestart"\n\t}\n}' + ) + + writefile.close() + + self.auxcommands["rawpost"].run("WRITEFILE") + self.rdmc.ui.printer("Waiting for BIOS to reset. See you in 7 mins.\n") + time.sleep(60) + self.rdmc.ui.printer("6 minutes remaining...\n") + time.sleep(60) + self.rdmc.ui.printer("5 minutes remaining...\n") + time.sleep(60) + self.rdmc.ui.printer("4 minutes remaining...\n") + time.sleep(60) + self.rdmc.ui.printer("3 minutes remaining...\n") + time.sleep(60) + self.rdmc.ui.printer("2 minutes remaining...\n") + time.sleep(60) + self.rdmc.ui.printer("1 minute remaining...\n") + time.sleep(60) + + self.auxcommands["rawget"].run('"/rest/v1/systems/1/bios/Settings" -f TESTFILE') + testfile = open("TESTFILE") + found = 0 + + # may error if bios don't update upon restart + for line in testfile: + if '"AdminEmail"' in line: + if '""' in line: + found = 1 + self.rdmc.ui.printer("reset validated\n") + break + + if found == 0: + raise NoContentsFoundForOperationError("Change not found.") + except Exception as excp: + raise excp + finally: + testfile.close() + + # ******************RAW DELETE********************** + self.auxcommands["rawget"].run('"/rest/v1/Sessions" -f TESTFILE') + + try: + testfile = open("TESTFILE") + next_item = False + + for line in testfile: + if next_item: + session = line + break + + if '"MySession":' in line and '"MySession": false' not in line and '"MySession": true' not in line: + next_item = True + + session = session.split('"') + + for item in session: + if "/Sessions/" in item: + session = '"%s"' % item + break + except Exception as excp: + raise excp + finally: + testfile.close() + + try: + self.auxcommands["rawdelete"].run(session) + self.rdmc.ui.printer("Session Deleted\n") + except Exception as excp: + raise excp + + ''' Rework to configure firmware update test. + @log_decor + def firmwareautomatictesting(self): + """ firmware update automatic testing """ + + self.rdmc.ui.printer("\n*************************************************" \ + "********************************\n") + self.rdmc.ui.printer("*********************SKIPING FIRMWARE " \ + "AUTOMATIC TESTING***************************\n") + self.rdmc.ui.printer("***************************************************" \ + "******************************\n\n") + + fw_url = "http://infinitymaster.us.rdlabs.hpecorp.net:1051/automatic_testing/components"\ + "/FIRMWARE/blah" + tmp = urlopen(fw_url) + rom_url = fw_url + "/ROM/" + ilo_url = fw_url + "/iLO/" + +# rom_fw_dict = [] +# ilo_fw_dict = [] + + rom_fw_arr = self.automatictesting_helper_fb(rom_url) +# ilo_fw_arr = self.automatictesting_helper_fb(ilo_url) + + try: + for i in rom_fw_arr: + #tmparr = path.split('/') + #urlretrieve(fw_url + path, tmparr[-1]) + (fmw, _) = urlretrieve(rom_url + i, i) + self.auxcommands['flashfwpkg'].run(fmw + ' --forceupload --ignorechecks') + + except Exception as excp: + self.rdmc.ui.printer("A General Error Occured: %s" %excp) + block = True + + #self.auxcommands['login'].run("") + +# if local: +# return + +# self.auxcommands['login'].run("") +# tests = {'rom': [], 'firmware': firmware} +# (romfamily, _) = self.rdmc.app.getbiosfamilyandversion() + +# for version in rom: +# if romfamily.lower() in version.lower(): +# tests['rom'].append(version) + +# for test in tests: +# for link in tests[test]: +# counter = 0 +# keep_going = True + +# self.auxcommands['logout'].run("") +# self.auxcommands['login'].run("") +# self.auxcommands['firmwareupdate'].run(link) + +# if test == 'rom': +# self.auxcommands['login'].run("") +# self.auxcommands['iloreset'].run("") + +# self.rdmc.ui.printer("Waiting for iLO...\n") +# time.sleep(60) +# self.rdmc.ui.printer("1 minute remaining...\n") +# time.sleep(60) + +# while keep_going: +# try: +# if counter > 50: +# raise + +# counter += 1 +# keep_going = False +# self.auxcommands['login'].run("") +# except Exception as excp: +# if counter > 50: +# raise excp +# else: +# keep_going = True + + ''' + + @log_decor + def istoolautomatictesting(self): + """istool automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************STARTING ISTOOL " "AUTOMATIC TESTING***************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].run("") + self.auxcommands["istool"].run("") + self.auxcommands["logout"].run("") + + @log_decor + def resultsautomatictesting(self): + """results automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************STARTING RESULTS " "AUTOMATIC TESTING**************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].run("") + self.auxcommands["results"].run("") + self.auxcommands["logout"].run("") + + @log_decor + def bootorderautomatictesting(self): + """boot order automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************STARTING BOOT ORDER " "AUTOMATIC TESTING***********************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + try: + self.auxcommands["bootorder"].run("--disablebootflag --commit") + except: + pass + + self.auxcommands["bootorder"].run("[5,4,3,2,1]") + self.auxcommands["logout"].run("") + + self.auxcommands["bootorder"].run("--onetimeboot=Cd") + self.auxcommands["commit"].run("") + btype, target = self.bootorderhelper() + + if not btype == "Once" or not target == "Cd": + raise InvalidOrNothingChangedSettingsError + + self.auxcommands["bootorder"].run("--onetimeboot=Hdd") + self.auxcommands["bootorder"].run("--continuousboot=Hdd --commit") + btype, target = self.bootorderhelper() + + if not btype == "Continuous" or not target == "Hdd": + raise InvalidOrNothingChangedSettingsError + + # self.auxcommands['bootorder'].run("--onetimeboot=Cd") + # self.auxcommands['bootorder'].run("--continuousboot=Hdd") + # self.auxcommands['bootorder'].run("--disablebootflag --commit") + # btype, target = self.bootorderhelper() + # + # if not btype == 'Disabled' or not target == 'None': + # raise InvalidOrNothingChangedSettingsError + + self.auxcommands["bootorder"].run("--disablebootflag") + # try: + # self.auxcommands['bootorder'].run("--disablebootflag") + # except: + # self.rdmc.ui.printer("Entry is the current boot setting.\n") + + self.auxcommands["logout"].run("") + + self.auxcommands["login"].run("") + self.auxcommands["bootorder"].run("--securebootkeys=deletepk") + self.auxcommands["logout"].run("") + + self.auxcommands["login"].run("") + self.auxcommands["bootorder"].run("--securebootkeys=defaultkeys") + self.auxcommands["logout"].run("") + + self.auxcommands["login"].run("") + self.auxcommands["bootorder"].run("--securebootkeys=deletekeys") + self.auxcommands["logout"].run("") + + @log_decor + def bootorderhelper(self): + """boot order helper for automatic testing""" + self.auxcommands["login"].run("") + results = self.rdmc.app.get_handler(self.rdmc.app.typepath.defs.systempath, service=True, silent=True) + + btype = results.dict["Boot"]["BootSourceOverrideEnabled"] + target = results.dict["Boot"]["BootSourceOverrideTarget"] + + return btype, target + + @log_decor + def serverstateautomatictesting(self): + """server state automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************SERVER STATE " "AUTOMATIC TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].run("") + self.auxcommands["serverstate"].run("") + self.auxcommands["logout"].run("") + + @log_decor + def virtualmediaautomatictesting(self): + """virtual media automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************VIRTUAL MEDIA " "AUTOMATIC TESTING*****************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + return + + self.auxcommands["login"].run("") + self.auxcommands["virtualmedia"].run("") + + try: + self.auxcommands["virtualmedia"].run("2 --remove") + except: + pass + + # Needs to be updated + self.auxcommands["virtualmedia"].run("2 http://10.0.0.1/vm.iso --bootnextreset") + results = self.rdmc.app.get_handler("/rest/v1/Managers/1/VirtualMedia/2", service=True, silent=True).dict + + if not results["Inserted"] or not results["Oem"][self.rdmc.app.typepath.defs.oemhp]["BootOnNextServerReset"]: + raise InvalidOrNothingChangedSettingsError("VM not found.") + + self.auxcommands["virtualmedia"].run("2 --remove") + + results = self.rdmc.app.get_handler("/rest/v1/Managers/1/VirtualMedia/2", service=True, silent=True).dict + + if results["Inserted"] or results["Oem"][self.rdmc.app.typepath.defs.oemhp]["BootOnNextServerReset"]: + raise InvalidOrNothingChangedSettingsError("VM not removed.") + + self.auxcommands["logout"].run("") + + @log_decor + def accountautomatictesting(self): + """iLO manager account automatic testing""" + """ - add, modify privs, changepass, delete""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("**************************ACCOUNT " "AUTOMATIC TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].run("") + self.auxcommands["iloaccounts"].run("") + errors = [] + priv_dict = { + "LoginPriv": 1, + "RemoteConsolePriv": 2, + "UserConfigPriv": 3, + "iLOConfigPriv": 4, + "VirtualMediaPriv": 4, + "VirtualPowerAndResetPriv": 5, + "HostNICConfigPriv": 6, + "HostBIOSConfigPriv": 7, + "HostStorageConfigPriv": 9, + "SystemRecoveryConfigPriv": 7, + } + for i in range(5): + randname = "".join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(6)) + accid = None + rand_privs = random.sample(priv_dict, 3) + rand_priv_str = "--addprivs " + for rp in enumerate(rand_privs): + if rp[1] == "SystemRecoveryConfigPriv": + continue + elif rp[0] == (len(rand_privs) - 1): + rand_priv_str += str(priv_dict[rp[1]]) + else: + rand_priv_str += str(priv_dict[rp[1]]) + "," + acct_str = "add " + randname + " " + randname + " testpassword " + rand_priv_str + try: + self.auxcommands["iloaccounts"].run(acct_str) + except Exception as excp: + if "rand_priv" in locals(): + errors.append("Unable to create account '{}', " "'{}': {}".format(randname, acct_str, str(excp))) + + results = self.rdmc.app.get_handler( + self.rdmc.app.typepath.defs.accountspath, service=True, silent=True + ).dict[self.rdmc.app.typepath.defs.collectionstring] + + if "Id" not in list(results[0].keys()): + newresults = [] + + for acct in results: + acct = self.rdmc.app.get_handler( + acct[self.rdmc.app.typepath.defs.hrefstring], + service=True, + silent=True, + ).dict + newresults.append(acct) + + results = newresults + + for acct in results: + if acct["Oem"][self.rdmc.app.typepath.defs.oemhp]["LoginName"] != randname: + continue + else: + self.rdmc.ui.printer("Created account found.\n") + accid = acct["Id"] + privs = acct["Oem"][self.rdmc.app.typepath.defs.oemhp]["Privileges"] + try: + rand_priv = next(iter(random.sample(privs, 1))) + privstr = "" + if privs[rand_priv]: + privstr = "--removeprivs=" + str(priv_dict[rand_priv]) + else: + privstr = "--addprivs=" + str(priv_dict[rand_priv]) + self.auxcommands["iloaccounts"].run("modify " + randname + " " + privstr) + except Exception as excp: + if "rand_priv" in locals(): + errors.append( + "Unable to modify privilege '{}' for account " + "'{}': {}".format(rand_priv, randname, str(excp)) + ) + else: + errors.append( + "Unable to retrieve privileges for account '{}' " ": {}".format(randname, excp) + ) + break + self.auxcommands["iloaccounts"].run("changepass " + randname + " newpassword") + self.auxcommands["iloaccounts"].run("delete " + accid) + + if not accid: + raise NoChangesFoundOrMadeError("Created account not found.") + + self.auxcommands["logout"].run("") + if errors: + raise Exception("The following errors occurred while testing accounts: %s" % errors) + + @log_decor + def federationautomatictesting(self): + """federation automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************ADDFEDERATION " "AUTOMATIC TESTING*****************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].run("") + self.auxcommands["ilofederation"].run("") + + redfish = self.rdmc.app.monolith.is_redfish + randname = "".join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(6)) + foundfed = False + + self.auxcommands["ilofederation"].run("add " + randname + " password") + self.auxcommands["ilofederation"].run("") + + path = self.rdmc.app.typepath.defs.federationpath + results = self.rdmc.app.get_handler(path, service=True, silent=True).dict + + if redfish: + results = results["Members"] + else: + results = results["links"]["Member"] + + newresults = [] + for fed in results: + fed = self.rdmc.app.get_handler(fed[self.rdmc.app.typepath.defs.hrefstring], service=True, silent=True).dict + + newresults.append(fed) + results = newresults + + for fed in results: + if fed["Id"] == randname: + self.rdmc.ui.printer("Created federation found.\n") + foundfed = True + break + + if not foundfed: + raise NoChangesFoundOrMadeError("Created federation not found.") + + self.auxcommands["ilofederation"].run("changekey " + randname + " newpassword") + self.auxcommands["ilofederation"].run("delete " + randname) + self.auxcommands["logout"].run("") + + @log_decor + def serverlogstesting(self): + """server logs automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************STARTING SERVERLOGS " "AUTOMATIC TESTING***********************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].loginfunction("") + self.auxcommands["serverlogs"].run("--selectlog=IEL -f IELlog.txt") + self.auxcommands["serverlogs"].run("--selectlog=IML -f IMLlog.txt") + self.auxcommands["serverlogs"].run("--selectlog=AHS --downloadallahs") + self.auxcommands["serverlogs"].run("--selectlog=AHS --clearlog") + self.auxcommands["serverlogs"].run("--selectlog=IEL --clearlog") + self.auxcommands["serverlogs"].run("--selectlog=IML --clearlog") + + self.auxcommands["logout"].run("") + + @log_decor + def ahsdiagtesting(self, local): + """ahs diags automatic testing""" + if local and os.name != "nt": + self.rdmc.ui.printer( + "\n*********************************************" "************************************\n" + ) + self.rdmc.ui.printer( + "*********************STARTING AHSDIAG " "AUTOMATIC TESTING**************************\n" + ) + self.rdmc.ui.printer( + "***********************************************" "**********************************\n\n" + ) + pass + + self.auxcommands["login"].loginfunction("") + self.auxcommands["ahsdiag"].run("--WriteSignPost") + self.auxcommands["ahsdiag"].run( + "--WriteMarkerPost --instance 1 --markervalue" ' 3 --markertext "Automatictesting"' + ) + + self.auxcommands["logout"].run("") + + @log_decor + def crapistesting(self): + """clear rest api automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************CLEARRESTAPI " "AUTOMATIC TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["clearrestapistate"].run("") + + if self.rdmc.app.monolith.is_redfish: + path = "/redfish/v1/registries/" + else: + path = "/rest/v1/registries" + + results = self.rdmc.app.get_handler(path, service=True, silent=True).dict + + for item in results[self.rdmc.app.typepath.dict.collectionstring]: + if "attributereg" in item[self.rdmc.app.typepath.defs.hrefstring]: + raise NoChangesFoundOrMadeError("No changes found.") + + self.auxcommands["logout"].run("") + + @log_decor + def certificateautomatictesting(self): + """generate csr automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*******************SSO AND TLS CERTIFICATE " "AUTOMATIC TESTING*********************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + error_dict = dict() + delete_items = list() + + url_path = "http://infinitymaster.us.rdlabs.hpecorp.net:1051/automatic_testing/x509_certs/" + + orgname = " HPE" + orgunit = " _iLOrest_Team" + commonname = " JustSomeGuys" + country = " USA" + state = " Tejas" + city = " Houston" + + self.auxcommands["login"].run("") + self.auxcommands["certificate"].run("csr" + orgname + orgunit + commonname + country + state + city) + + for item in self.automatictesting_helper_fb(url_path): + fld = urlopen(url_path + item) + data = fld.read() + with open(item, "w+b") as certfile: + certfile.write(data) + delete_items.append(item) + try: + self.auxcommands["certificate"].run("tls " + item) + except Exception: + error_dict[file] = "tls " + item + self.rdmc.ui.printer( + "iLO flagged an error while uploading the" " TLS certificate file to iLO: %s\n" % item + ) + + try: + self.auxcommands["singlesignon"].run("importcert " + item) + except Exception: + error_dict[file] = "tls " + item + self.rdmc.ui.printer( + "iLO flagged an error while uploading the" " SSO certificate file to iLO: %s\n" % item + ) + + self.automatictesting_helper_fd(delete_items) + self.auxcommands["logout"].run("") + + if error_dict: + try: + raise ValueError + except ValueError as err: + if not err.args: + err.args = ("iLO flagged an error with components: ",) + for item in error_dict: + err.args = err.args + ("Component: %s, Full String: %s" % (item, error_dict[item]),) + raise + + @log_decor + def eskmautomatictesting(self): + """eskm automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*************************ESKM " "AUTOMATIC TESTING**********************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + pass + + self.auxcommands["login"].run("") + self.auxcommands["eskm"].run("testconnections") + self.auxcommands["eskm"].run("clearlog") + self.auxcommands["logout"].run("") + + @log_decor + def sigrecomputeautomatictesting(self): + """sigrecompute automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************SIGRECOMPUTE " "AUTOMATIC TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].run("") + + try: + self.auxcommands["sigrecompute"].run("") + except IncompatibleiLOVersionError: + self.rdmc.ui.printer("Server is redfish. Skipping sigrecompute.\n") + + self.auxcommands["logout"].run("") + + @log_decor + def sendtestautomatictesting(self): + """results automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*************************SENDTEST " "AUTOMATIC TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + self.auxcommands["login"].run("") + try: + self.auxcommands["select"].run(self.rdmc.app.typepath.defs.snmpservice) + self.auxcommands["select"].run(self.rdmc.app.typepath.defs.managernetworkservicetype) + except: + self.rdmc.ui.printer("Skipping sendtest testing, resource not available.\n") + self.auxcommands["logout"].run("") + return + + try: + # setup for snmpalert + # set SNMPAlertProtocol=SNMPv1Trap + self.auxcommands["select"].run(self.rdmc.app.typepath.defs.snmpservice) + self.auxcommands["set"].run("AlertsEnabled=true") + self.auxcommands["set"].run("AlertDestinations=[testdns.newdnststr] --commit") + except: + pass + + try: + # setup for alertmail + # {"Oem": { "Hpe":{"AlertMailEnabled": true, "AlertMailEmail": "bob@bob.bob", + # "AlertMailSenderDomain": "domain", "AlertMailSMTPServer": "1.35.35.35"}}} + self.auxcommands["select"].run(self.rdmc.app.typepath.defs.managernetworkservicetype) + + if self.rdmc.app.typepath.flagiften: + oem = "Oem/Hpe" + else: + oem = "Oem/Hp" + + self.auxcommands["set"].run( + oem + + "/AlertMailEmail=test@test.test " + + oem + + "/AlertMailEnabled=True " + + oem + + "/AlertMailSMTPServer=testserver.test.test " + + oem + + "/AlertMailSenderDomain=testdomain.test.test --commit" + ) + except: + pass + + try: + # setup for syslog + self.auxcommands["select"].run(self.rdmc.app.typepath.defs.managernetworkservicetype) + self.auxcommands["set"].run( + oem + "/RemoteSyslogServer=testserver.test.svrtest " + oem + "/RemoteSyslogEnabled=True --commit" + ) + except: + pass + + self.auxcommands["login"].run("") + + self.auxcommands["sendtest"].run("syslog") + self.auxcommands["sendtest"].run("alertmail") + self.auxcommands["sendtest"].run("snmpalert") + + self.auxcommands["logout"].run("") + + @log_decor + def ssoautomatictesting(self): + """sso automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*********************SINGLESIGNON " "AUTOMATIC TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].run("") + self.auxcommands["singlesignon"].run("importdns newdnsname.dnstest") + self.auxcommands["singlesignon"].run("deleterecord 1") + self.auxcommands["singlesignon"].run("importdns newdnsname.dnstest") + self.auxcommands["singlesignon"].run("deleterecord all") + + self.auxcommands["logout"].run("") + + @log_decor + def ilolicensetesting(self): + """iLO license automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("***********************ILOLICENSE " "AUTOMATIC TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].run("") + + try: + self.auxcommands["ilolicense"].run("xx-xx-xx-xx") + except IloResponseError: + pass + + self.auxcommands["logout"].run("") + + @log_decor + def smbiostesting(self): + """smbios automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("***************************SMBIOS " "AUTOMATIC TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + if self.rdmc.app.typepath.flagiften: + self.auxcommands["login"].run("") + self.auxcommands["smbios"].run("smbios") + self.auxcommands["logout"].run("") + else: + self.rdmc.ui.printer("Skipping smbios testing, server not gen10.\n") + + @log_decor + def directoryautomatictesting(self): + """Directory command automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*************************DIRECTORY " "AUTOMATIC TESTING*****************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.rdmc.ui.printer("Testing settings output.\n") + self.auxcommands["directory"].run("kerberos") + self.auxcommands["directory"].run("ldap") + self.auxcommands["directory"].run("kerberos -j") + self.auxcommands["directory"].run("ldap -j") + + self.rdmc.ui.printer("Testing setting properties.\n") + self.auxcommands["directory"].run("kerberos --serviceaddress test.account --port 1337 --realm " "testrealm") + self.auxcommands["directory"].run("ldap testusername testpassword --enable") + self.auxcommands["directory"].run( + "ldap --serviceaddress test2.account --addsearch autotestsearch," "autotestsearch2" + ) + + self.rdmc.ui.printer("Testing adding roles.\n") + self.auxcommands["directory"].run( + 'kerberos --addrole "Administrator:a test,' 'ReadOnly:another test" --disable' + ) + + self.rdmc.ui.printer("Validating changes...\n") + + results = self.rdmc.app.select(selector="AccountService.", path_refresh=True)[0].dict + + if results["LDAP"]["Authentication"]["Username"] == "testusername": + self.rdmc.ui.printer("Validated Username.\n") + else: + sys.stderr.write("Username not changed.\n") + if ( + results["ActiveDirectory"]["ServiceAddresses"][0] == "test.account:1337" + and results["LDAP"]["ServiceAddresses"][0] == "test2.account" + ): + self.rdmc.ui.printer("Validated Service addresses.\n") + else: + sys.stderr.write("Service addresses not changed.\n") + raise IloResponseError("") + if ( + results["Oem"]["Hpe"]["DirectorySettings"]["LdapServerPort"] == 55 + and results["Oem"]["Hpe"]["KerberosSettings"]["KDCServerPort"] == 1337 + ): + self.rdmc.ui.printer("Validated Ports.\n") + else: + sys.stderr.write("Ports not changed.\n") + raise IloResponseError("") + if results["Oem"]["Hpe"]["KerberosSettings"]["KerberosRealm"] == "testrealm": + self.rdmc.ui.printer("Validated Realm.\n") + else: + sys.stderr.write("Realm not changed.\n") + raise IloResponseError("") + if ( + "autotestsearch" in results["LDAP"]["LDAPService"]["SearchSettings"]["BaseDistinguishedNames"] + and "autotestsearch2" in results["LDAP"]["LDAPService"]["SearchSettings"]["BaseDistinguishedNames"] + ): + self.rdmc.ui.printer("Validated SearchSettings.\n") + else: + sys.stderr.write("SearchSettings not changed.\n") + if results["LDAP"]["ServiceEnabled"] and not results["ActiveDirectory"]["ServiceEnabled"]: + self.rdmc.ui.printer("Validated ServiceEnabled.\n") + else: + sys.stderr.write("Service not enabled/disabled.\n") + raise IloResponseError("") + rolecount = 0 + for role in results["LDAP"]["RemoteRoleMapping"]: + if role["RemoteGroup"] == "a test" or role["RemoteGroup"] == "another test": + rolecount += 1 + if rolecount == 2: + self.rdmc.ui.printer("Validated Role mappings.\n") + else: + sys.stderr.write("Remote roles not changed.\n") + raise IloResponseError("") + + self.rdmc.ui.printer("Removing changes...\n") + self.auxcommands["directory"].run('kerberos --serviceaddress "" --realm ""') + self.auxcommands["directory"].run( + 'ldap --serviceaddress "" --disable --removesearch ' "autotestsearch,autotestsearch2" + ) + self.auxcommands["directory"].run('ldap --removerole "dirgroupa test,dirgroupanother test"') + + self.rdmc.ui.printer("Validating removal.\n") + + results = self.rdmc.app.select(selector="AccountService.", path_refresh=True)[0].dict + + if not results["LDAP"]["ServiceEnabled"]: + self.rdmc.ui.printer("Validated Service disabled.\n") + else: + sys.stderr.write("Service not disabled.\n") + raise IloResponseError("") + if not results["ActiveDirectory"]["ServiceAddresses"][0] and not results["LDAP"]["ServiceAddresses"][0]: + self.rdmc.ui.printer("Validated Service addresses.\n") + else: + sys.stderr.write("Service addresses not removed.\n") + raise IloResponseError("") + if not results["Oem"]["Hpe"]["KerberosSettings"]["KerberosRealm"]: + self.rdmc.ui.printer("Validated Realm.\n") + else: + sys.stderr.write("Realm not removed.\n") + raise IloResponseError("") + if ( + not "autotestsearch" in results["LDAP"]["LDAPService"]["SearchSettings"]["BaseDistinguishedNames"] + and not "autotestsearch2" in results["LDAP"]["LDAPService"]["SearchSettings"]["BaseDistinguishedNames"] + ): + self.rdmc.ui.printer("Validated SearchSettings.\n") + else: + sys.stderr.write("SearchSettings not removed.\n") + raise IloResponseError("") + rolecount = 0 + for role in results["LDAP"]["RemoteRoleMapping"]: + if role["RemoteGroup"] == "a test" or role["RemoteGroup"] == "another test": + rolecount += 1 + if rolecount == 0: + self.rdmc.ui.printer("Validated Role mappings.\n") + else: + sys.stderr.write("Remote roles not removed.\n") + raise IloResponseError("") + + @log_decor + def serverclonecommandautomatictesting(self, local): + """copy command automatic testing""" + + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("**************************SERVER CLONE " "AUTOMATIC TESTING*************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + ue_clone_file_name = "ilorest_clone_ue.json" + enc_clone_file_name = "ilorest_clone_enc.json" + delete_list = [ue_clone_file_name, enc_clone_file_name] + error = {} + + self.auxcommands["login"].loginfunction("") + try: + self.rdmc.ui.printer("Testing 'save' operation (unencrypted)...\n") + line_str = "save --nobios --silent -f " + ue_clone_file_name + self.auxcommands["serverclone"].run(line_str) + + self.rdmc.ui.printer("Testing 'load' operation (unencrypted)...\n") + line_str = "load --silent -f " + tmp_file = self.serverclone_helper(ue_clone_file_name, line_str) + delete_list.append(tmp_file) + self.auxcommands["serverclone"].run(line_str + tmp_file) + self.rdmc.ui.printer("Unencrypted Clone Test Complete\n") + + except Exception as excp: + self.rdmc.ui.printer("Unencrypted Clone Test Failed\n") + with open("clone_error_logfile.log", "r") as err_logfile: + error_log = err_logfile.read() + with open("changelog.log", "r") as chng_logfile: + try: + chng_log = json.loads(chng_logfile.read()) + except ValueError: + chng_log = chng_logfile.read() + error["unencrypted_clone"] = { + "base_error": "An error occurred working with serverclone unencrypted save/load: {}".format(excp), + "command": line_str + tmp_file, + "traceback": traceback.format_exc(), + "clone_error_logfile": error_log, + "change_log": chng_log, + } + + try: + self.rdmc.ui.printer("Testing 'save' operation (encrypted)...\n") + line_str = "save --encryption HPESecretAESKey1 --silent --nobios -f " + enc_clone_file_name + self.auxcommands["serverclone"].run(line_str) + + self.rdmc.ui.printer("Testing 'load' operation (encrypted)...\n") + line_str = "load --silent --encryption HPESecretAESKey1 -f " + tmp_file = self.serverclone_helper(enc_clone_file_name, line_str) + delete_list.append(tmp_file) + self.auxcommands["serverclone"].run(line_str) + self.rdmc.ui.printer("Encrypted Clone Test Complete\n") + + except Exception as excp: + self.rdmc.ui.printer("Encrypted Clone Test Failed\n") + with open("clone_error_logfile.log", "r") as err_logfile: + error_log = err_logfile.read() + with open("changelog.log", "r") as chng_logfile: + try: + chng_log = json.loads(chng_logfile.read()) + except ValueError: + chng_log = chng_logfile.read() + error["encrypted_clone"] = { + "base_error": "An error occurred working with serverclone unencrypted save/load: {}".format(excp), + "command": line_str + tmp_file, + "traceback": traceback.format_exc(), + "clone_error_logfile": error_log, + "change_log": chng_log, + } + + """ #DO NOT OPEN UNTIL CHRISTMAS (Ok maybe earlier) + self.rdmc.ui.printer("Attempting load of clone files from NFS server...\n") + for item in self.automatictesting_helper_fb(url_path): + f_name = "" + clone_file = urlopen(url_path + item) + data = clone_file.read() + with open(item, 'w+b') as target: + target.write(data) + if isinstance(item, six.string_types): + f_name += item + delete_list.append(f_name) + self.auxcommands['serverclone'].run("load --silent -f " + f_name) + + self.rdmc.ui.printer("Test Complete...cleaning up.\n") + """ + + try: + err_list, err_str = self.automatictesting_helper_fd(delete_list) + if err_list: + raise Exception(err_str) + except Exception as excp: + error["delete_cfs"] = "An error occurred deleting clone files: '%s'" % str(excp) + + if error: + raise Exception("The following exceptions occured in ServerClone:\n {}".format(error)) + + @log_decor + def cloningautomatictesting(self): + """cloning automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("**************************CLONING " "AUTOMATIC TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + if not self.rdmc.app.typepath.flagiften: + self.rdmc.ui.printer("Skipping iloclone command, server is not gen10.\n") + return ReturnCodes.SUCCESS + + self.auxcommands["login"].run("") + self.cloneobj.run("save -f CLONETEST.json", testing=True) + self.auxcommands["logout"].run("") + self.auxcommands["login"].run("") + self.cloneobj.run("load -f CLONETEST.json", testing=True) + self.auxcommands["logout"].run("") + + @log_decor + def pendingautomatictesting(self): + """pending automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("**************************PENDING " "AUTOMATIC TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].run("") + self.auxcommands["pending"].run("") + self.auxcommands["logout"].run("") + + @log_decor + def deletecomtesting(self): + """Delete component command testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*************************DELETE " "COMPONENT TESTING******************************\n") + self.rdmc.ui.printer( + "***************************************************" "********************************\n\n" + ) + + self.auxcommands["login"].run("") + + if not self.rdmc.app.typepath.flagiften: + self.rdmc.ui.printer("Skipping delete component command, server is not gen10.\n") + return ReturnCodes.SUCCESS + + self.auxcommands["deletecomp"].run("-a") + self.auxcommands["logout"].run("") + + @log_decor + def uploadcomptesting(self): + """Upload component command testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("***************************UPLOAD " "COMPONENT TESTING******************************\n") + self.rdmc.ui.printer("*************************************************" "********************************\n\n") + + error_dict = dict() + self.auxcommands["login"].run("") + url_path = "http://infinitymaster.us.rdlabs.hpecorp.net:1051/automatic_testing/components" "/COMPONENTS/" + + if not self.rdmc.app.typepath.flagiften: + self.rdmc.ui.printer("Skipping upload component command, server is not gen10.\n") + return ReturnCodes.SUCCESS + + # skip = False + # compname = "cp029917.exe" + # compsigname_1 = "cp029917_part1.compsig" + # compsigname_2 = "cp029917_part2.compsig" + # compnamepath = os.path.join(os.getcwd(), compname) + # compsigname_1path = os.path.join(os.getcwd(), compsigname_1) + # compsigname_2path = os.path.join(os.getcwd(), compsigname_2) + + components_dict = self.automatictesting_helper_fb(url_path, True) + delete_list = [] + for _, val in list(components_dict.items()): + compy = None + try: + uploadstr = "" + for file in val: + if len(file.split(".")) <= 2: + tmparr = file.split(".") + fld = urlopen(url_path + file) + data = fld.read() + with open(file, "w+b") as target: + target.write(data) + delete_list.append(file) + if "compsig" in tmparr[-1]: + uploadstr += " --compsig=" + else: + uploadstr += " --component=" + compy = file + uploadstr += file + + self.auxcommands["uploadcomp"].run(uploadstr + " --forceupload") + + except Exception: + error_dict[compy] = uploadstr + self.rdmc.ui.printer("iLO flagged an error while uploading the" " previous files: %s\n" % uploadstr) + self.rdmc.ui.printer("Check for correct file type and compsig.\n") + continue + + # cleanup routine + self.automatictesting_helper_fd(delete_list) + + if error_dict: + try: + raise ValueError + except ValueError as err: + if not err.args: + err.args = ("iLO flagged an error with components: ",) + for item in error_dict: + err.args = err.args + ("Component: %s, Full String: %s" % (item, error_dict[item]),) + raise + + # try: + # if not os.path.isfile(compnamepath): + # (compname, _) = urlretrieve("http://infinitymaster:81"\ + # "/automatic_testing/" \ + # + compname, compname) + # + # if not os.path.isfile(compsigname_1path): + # (compsigname_1, _) = urlretrieve(\ + # "http://16.83.62.70/jack/" + compsigname_1, compsigname_1) + # + # if not os.path.isfile(compsigname_2path): + # (compsigname_2, _) = urlretrieve(\ + # "http://16.83.62.70/jack/" + compsigname_2, compsigname_2) + # except: + # skip = True + # + # if not skip: + # self.auxcommands['uploadcomp'].run("--component={0} --compsig={1} " \ + # "--forceupload".format(compname, compsigname_1)) + # #self.auxcommands['uploadcomp'].run("--component=firmware-nic-qlogic-nx2-2." \ + # # "19.6-1.1.x86_64.rpm --compsig=firmware-" \ + # # "nic-qlogic-nx2-2.19.6-1.1.x86_64.compsig") + # + # self.auxcommands['logout'].run("") + # else: + # self.rdmc.ui.printer("Could not complete test due to missing test " \ + # "files.\n") + + @log_decor + def listanddownloadcomptesting(self): + """List and Download component command testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*************************DOWNLOAD " "COMPONENT TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + delete_list = list() + if not self.rdmc.app.typepath.flagiften: + self.rdmc.ui.printer("Skipping download component command, server is not gen10.\n") + return ReturnCodes.SUCCESS + + self.auxcommands["login"].run("") + + self.rdmc.ui.printer("Components found in iLO Repository:\n\n") + self.auxcommands["listcompt"].run("") + comps = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/ComponentRepository/") + + for item in comps: + uri_str = "" + uri_str += "/fwrepo/" + item["Filename"] + self.rdmc.ui.printer("Downloading component: '%s'.\n" % item["Filename"]) + self.auxcommands["downloadcomp"].run(uri_str) + self.rdmc.ui.printer("Successfully downloaded '%s' from iLO " "Repository.\n" % item["Filename"]) + delete_list.append(item["Filename"]) + + self.rdmc.ui.printer("Test Complete...cleaning up.\n") + self.automatictesting_helper_fd(delete_list) + self.auxcommands["logout"].run("") + return ReturnCodes.SUCCESS + + @log_decor + def deletecomptesting(self): + """Delete from component repository testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("***************************DELETE " "COMPONENT TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + return + + if not self.rdmc.app.typepath.flagiften: + self.rdmc.ui.printer("Skipping download component command, server is not gen10.\n") + return ReturnCodes.SUCCESS + + self.auxcommands["login"].run("") + self.rdmc.ui.printer("Components found in iLO Repository:\n\n") + self.auxcommands["listcompt"].run("") + + comps = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/ComponentRepository/") + + for item in comps: + # str = "" + # str += "/fwrepo/" + item['Filename'] + self.rdmc.ui.printer("Deleting component: '%s'.\n" % item["Filename"]) + self.auxcommands["deletecomp"].run(item["Filename"]) + self.rdmc.ui.printer("Successfully downloaded '%s' from iLO " "Repository.\n" % item["Filename"]) + + self.rdmc.ui.printer("Test Complete...\n") + self.auxcommands["logout"].run("") + return ReturnCodes.SUCCESS + + @log_decor + def installsettesting(self): + """Install set command testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*************************INSTALL " "SET COMMAND TESTING*****************************\n") + self.rdmc.ui.printer("**************************************************" "*******************************\n\n") + + self.auxcommands["login"].run("") + + if not self.rdmc.app.typepath.flagiften: + self.rdmc.ui.printer("Skipping install set command, server is not gen10.\n") + return ReturnCodes.SUCCESS + + installsetlist = [] + url = "http://infinitymaster.us.rdlabs.hpecorp.net:1051/automatic_testing/install_sets/" + + # skip MakeInstallSet + installsetlist = self.automatictesting_helper_fb(url) + i = 0 + + self.auxcommands["login"].loginfunction("") + + for installset in installsetlist: + try: + (installsetfile, _) = urlretrieve(url + installset, installset) + i = i + 1 + self.rdmc.ui.printer("Uploading Installset: %s\n" % installsetfile) + self.auxcommands["installset"].run("add " + installset) + self.rdmc.ui.printer("Invoking Installset: %s\n" % installsetfile) + self.auxcommands["installset"].run("invoke --name=TestSet" + str(i) + " --cleartaskqueue") + self.rdmc.ui.printer("Removing Installset: %s\n" % installsetfile) + self.auxcommands["installset"].run("delete --name=TestSet" + str(i)) + + except Exception as excp: + self.rdmc.ui.printer( + "A general error occured while attempting to " + "use the file: %s. The following error was " + "logged: %s\n" % (installsetfile, excp) + ) + self.rdmc.ui.printer("Check for missing test files\n") + continue + + self.rdmc.ui.printer("Removing any remaining installsets\n") + self.auxcommands["installset"].run("--removeall") + self.auxcommands["logout"].logoutfunction("") + + for installset in installsetlist: + try: + if os.path.exists(installsetfile): + self.rdmc.ui.printer("Removing local file: %s\n" % installsetfile) + os.remove(installsetfile) + except Exception as excp: + self.rdmc.ui.printer( + "An error occured attempting to remove the " "file: %s, logged: %s\n" % (installsetfile, excp) + ) + continue + + return ReturnCodes.SUCCESS + + @log_decor + def updatetaskqueuetesting(self): + """Update task queue command testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("**************************UPDATE " "TASK QUEUE TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].run("") + + if not self.rdmc.app.typepath.flagiften: + self.rdmc.ui.printer("Skipping taskqueue command, server is not gen10.\n") + return ReturnCodes.SUCCESS + + self.auxcommands["taskqueue"].run("") + self.auxcommands["taskqueue"].run("create 30") + self.auxcommands["taskqueue"].run("") + + self.auxcommands["taskqueue"].run("create reboot") + self.auxcommands["taskqueue"].run("") + self.auxcommands["taskqueue"].run("-r") + self.auxcommands["taskqueue"].run("") + + self.auxcommands["logout"].run("") + + @log_decor + def listcomptesting(self): + """List component command testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*****************************LIST " "COMPONENT TESTING******************************\n") + self.rdmc.ui.printer("***************************************************" "******************************\n\n") + + self.auxcommands["login"].run("") + + if not self.rdmc.app.typepath.flagiften: + self.rdmc.ui.printer("Skipping list component command, server is not gen10.\n") + return ReturnCodes.SUCCESS + + self.auxcommands["listcompt"].run("") + self.auxcommands["logout"].run("") + + @log_decor + def testsumcode(self): + """SUM commands batch testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*************************SUM " "COMMANDS TESTING******************************\n") + self.rdmc.ui.printer( + "***************************************************" "********************************\n\n" + ) + + self.auxcommands["login"].run("") + + if not self.rdmc.app.typepath.flagiften: + self.rdmc.ui.printer("Skipping list component command, server is not gen10.") + return ReturnCodes.SUCCESS + + # TODO:verification of cache to be done manually for now + if not self.rdmc.app.typepath.flagiften: + self.auxcommands["rawget"].run("/rest/v1/Chassis/1/Power") + self.auxcommands["rawget"].run("/redfish/v1/Chassis/1/Power/") + self.auxcommands["rawget"].run("/rest/v1/Systems/1") + self.auxcommands["rawget"].run("/redfish/v1/Systems/1/") + + self.auxcommands["hpgooey"].run(" --list --namespace perm") + # self.auxcommands['hpgooey'].run(" --read --key ipmanager --namespace perm -f " \ + # "c:\ipman.json") + self.auxcommands["logout"].run("") + self.auxcommands["login"].run("") + + # Update the password in session_payload.json file. + # self.auxcommands['rawpost'].run("session_payload.json --getheaders --service") + + session_key = "" + for client in self.rdmc.app._rmc_clients: + session_key = client.get_authorization_key() + session_key = " --sessionid={0}".format(session_key) + break + self.auxcommands["rawpost"].run( + "sut_provider_registration_payload.json " + "--getheaders --response --service --providerid" + "=SUT-PROVIDER" + session_key + ) + self.auxcommands["rawput"].run( + "sut_registry_payload.json --getheaders --response" " --service --providerid=SUT-PROVIDER" + session_key + ) + self.auxcommands["rawput"].run( + "sut_schema.json --getheaders --response --service " "--providerid=SUT-PROVIDER" + session_key + ) + self.auxcommands["rawput"].run( + "sut_settings_schema.json --getheaders --response " "--service --providerid=SUT-PROVIDER" + session_key + ) + self.auxcommands["rawput"].run( + "sut_tasksettings_schema.json --getheaders " + "--response --service --providerid=SUT-PROVIDER " + session_key + ) + self.auxcommands["rawput"].run( + "sut_systeminventory_schema.json --getheaders " + "--response --service --providerid=SUT-PROVIDER " + session_key + ) + self.auxcommands["rawput"].run("HPSUT_rest.json --providerid=HPSUT-PROVIDER") + self.auxcommands["rawput"].run("HPSUTSettings_rest.json --providerid=HPSUT-PROVIDER") + self.auxcommands["rawpatch"].run("HPSUTSettings_rest.json --providerid=HPSUT-PROVIDER") + self.auxcommands["logout"].run("") + + if self.rdmc.app.typepath.flagiften: + self.auxcommands["rawget"].run("/redfish/v1/Managers/1/") + self.auxcommands["rawget"].run("/redfish/v1/Chassis/1/Power/") + self.auxcommands["rawget"].run("/redfish/v1/Systems/1/") + self.auxcommands["rawget"].run("/redfish/v1/Managers/1/UpdateService") + self.auxcommands["rawget"].run("/redfish/v1/Chassis/") + + self.auxcommands["hpgooey"].run(" --list --namespace perm") + # self.auxcommands['hpgooey'].run(" --read --key ipmanager --namespace perm -f " \ + # "c:\ipman.json") + + self.auxcommands["rawget"].run("/redfish/v1/Managers/1/SecurityService/") + self.auxcommands["rawget"].run("/redfish/v1/UpdateService/FirmwareInventory/ --expand") + self.auxcommands["rawget"].run("/redfish/v1/UpdateService/SoftwareInventory/ --expand") + self.auxcommands["rawget"].run("/redfish/v1/updateService/installsets/ --expand") + + # Check if the uploadcomp commands are necessary for the continuation of the code + # compname = "xxx" + # compsigname = "yyy" + compname = "cp029917.exe" + compsigname_1 = "cp029917_part1.compsig" + self.auxcommands["uploadcomp"].run( + "--component={0} --compsig={1} " "--forceupload".format(compname, compsigname_1) + ) + + self.auxcommands["rawget"].run("/redfish/v1/updateService/ComponentRepository/ --expand") + self.auxcommands["rawget"].run("/redfish/v1/Managers/1/EthernetInterfaces/ --expand") + self.auxcommands["logout"].run("") + + self.auxcommands["login"].run("") + # TODO:update the password in session_payload_gen10.json file. + self.auxcommands["rawpost"].run("session_payload_gen10.json --getheaders --service") + session_key = "" + for client in self.rdmc.app._rmc_clients: + session_key = client.get_authorization_key() + session_key = " --sessionid={0}".format(session_key) + break + self.auxcommands["rawdelete"].run( + " " + "--getheaders --response --service --providerid=" + "SUT-PROVIDER" + session_key + ) + self.auxcommands["rawpost"].run( + "sut_provider_registration_payload_gen10.json " + "--getheaders --response --service --providerid=" + "SUT-PROVIDER" + session_key + ) + self.auxcommands["rawput"].run( + "sut_registry_payload_gen10.json --getheaders " + "--response --service --providerid=SUT-PROVIDER " + session_key + ) + self.auxcommands["rawput"].run( + "sut_schema_gen10.json --getheaders --response " "--service --providerid=SUT-PROVIDER" + session_key + ) + self.auxcommands["rawput"].run( + "sut_settings_schema_gen10.json --getheaders " + "--response --service --providerid=SUT-PROVIDER " + session_key + ) + self.auxcommands["rawput"].run( + "sut_tasksettings_schema_gen10.json --getheaders " + "--response --service --providerid=SUT-PROVIDER " + session_key + ) + self.auxcommands["rawput"].run( + "sut_systeminventory_schema_gen10.json --getheaders " + "--response --service --providerid=SUT-PROVIDER " + session_key + ) + + self.auxcommands["rawput"].run("HPSUT_redfish.json --providerid=HPSUT-PROVIDER") + self.auxcommands["rawput"].run("HPSUTSettings_redfish.json --providerid=HPSUT-PROVIDER") + self.auxcommands["rawpatch"].run("HPSUTSettings_redfish.json --providerid=HPSUT-PROVIDER") + self.auxcommands["logout"].run("") + + self.auxcommands["login"].run("") + self.auxcommands["rawget"].run("/redfish/v1/Chassis/1/Power/") + self.auxcommands["rawget"].run("/redfish/v1/Systems/1/") + self.auxcommands["rawget"].run("/redfish/v1/updateService/installsets/") + self.auxcommands["rawget"].run("/redfish/v1/updateService/updatetaskqueue/") + self.auxcommands["rawget"].run("/redfish/v1/UpdateService/FirmwareInventory/") + self.auxcommands["rawget"].run("/redfish/v1/UpdateService/SoftwareInventory/") + self.auxcommands["rawget"].run("/redfish/v1/Managers/1/SecurityService/CertificateAuthentication/") + self.auxcommands["rawget"].run("/redfish/v1/Managers/1/DateTime/") + self.auxcommands["rawget"].run("/redfish/v1/UpdateService/ComponentRepository/") + self.auxcommands["rawget"].run("/redfish/v1/UpdateService/FirmwareInventory/") + self.auxcommands["rawget"].run("/redfish/v1/UpdateService/UpdateTaskQueue/ --expand") + + # compname = "xxx" + # compsigname = "yyy" + compname = "cp029917.exe" + compsigname_1 = "cp029917_part1.compsig" + self.auxcommands["uploadcomp"].run( + "--component={0} --compsig={1} " "--forceupload".format(compname, compsigname_1) + ) + self.auxcommands["rawget"].run("/redfish/v1/updateService/ComponentRepository/") + # Component uri returned by the rawget above + # self.auxcommands['downloadcomp'].run("Component uri returned by the rawget above") + self.auxcommands["logout"].run("") + + @log_decor + def maintenancewindowautomatictesting(self): + """Maintenance window command automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("******************MAINTENANCE WINDOW " "AUTOMATIC TESTING***************************\n") + self.rdmc.ui.printer("***********************************************" "**********************************\n\n") + + if not self.rdmc.app.typepath.flagiften: + self.rdmc.ui.printer("Skipping... Not available on Gen 9.") + return + self.auxcommands["login"].loginfunction("") + + for entry_ in range(0, 10): + startoffset = random.randint(1, 10) + endoffset = random.randint(1, 10) + startoffset + + startdatetime = datetime.datetime.now() + datetime.timedelta(days=startoffset) + enddatetime = datetime.datetime.now() + datetime.timedelta(days=endoffset) + + maintenancewindow_start_time = startdatetime.strftime("%Y-%m-%dT%H:%M:%S") + maintenancewindow_descr = '"This is a test for planned maintenance"' + maintenancewindow_end_time = enddatetime.strftime("%Y-%m-%dT%H:%M:%S") + maintenancewindow_str = ( + "add " + + maintenancewindow_start_time + + " --expire=" + + maintenancewindow_end_time + + " --name=TestMaintenanceEntry" + + str(entry_) + + " --description " + + maintenancewindow_descr + ) + + self.rdmc.ui.printer("Adding Maintenance Window: " + maintenancewindow_str + "\n") + self.auxcommands["maintenancewindow"].run(maintenancewindow_str) + + self.auxcommands["maintenancewindow"].run("") + + maintenancewindow_str = "delete TestMaintenanceEntry" + str(entry_) + + self.rdmc.ui.printer("Removing Maintenance Window: " + maintenancewindow_str + "\n") + self.auxcommands["maintenancewindow"].run(maintenancewindow_str) + + self.auxcommands["logout"].logoutfunction("") + + @log_decor + def serverinfoautomatictesting(self): + """Serverinfo command automatic testing""" + info_list = { + "--fans", + "--processor", + "--memory", + "--thermals", + "--power", + "--system", + } + info_string = "--fans --processor --memory --thermals --power --system --showabsent" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*************************SERVER INFO AUTOMATIC " "TESTING***************************\n") + self.rdmc.ui.printer("***********************************************" "**********************************\n\n") + + self.auxcommands["login"].loginfunction("") + + for item in info_list: + self.auxcommands["serverinfo"].run(item) + + self.auxcommands["serverinfo"].run(info_string) + + self.auxcommands["logout"].logoutfunction("") + + @log_decor + def fwpkgcommandautomatictesting(self): + """Fwpkg command automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("************************FIRMWARE PACKAGE AUTOMATIC " "TESTING***********************\n") + self.rdmc.ui.printer("*************************************************" "********************************\n\n") + + self.rdmc.ui.printer("Skipping FW Package testing\n") + # self.fwpackage.run("") + + @log_decor + def ipprofilesautomatictesting(self): + """ipprofiles command automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("*****************************IP PROFILES AUTOMATIC " "TESTING***********************\n") + self.rdmc.ui.printer("*************************************************" "********************************\n\n") + + self.auxcommands["login"].loginfunction("") + try: + self.auxcommands["ipprofiles"].run("") # expect a non-empty string or buffer + # self.rdmc.ui.printer("Update with JSON:\n") + # self.rdmc.ui.printer(repr(self.auxcommands['ipprofiles']obj.run("ipprofiles" + \ + # ipprofile.json))) + # self.rdmc.ui.printer(repr(self.auxcommands['ipprofiles']obj.run("ipprofiles"))) + except PathUnavailableError: + self.rdmc.ui.printer("Skipping IP Profiles test, IP provider is unavailable.\n") + + self.auxcommands["logout"].logoutfunction("") + + @log_decor + def fwintegritycheckautomatictesting(self): + """fwintegrity command automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("******************FIRMWARE INTEGRITY CHECK AUTOMATIC " "TESTING*********************\n") + self.rdmc.ui.printer("*************************************************" "********************************\n\n") + + if not self.rdmc.app.typepath.flagiften: + self.rdmc.ui.printer("Skipping firmware integrity check, only available on Gen 10.") + else: + self.auxcommands["login"].loginfunction("") + self.auxcommands["fwintegritycheck"].run("--results") + self.auxcommands["fwintegritycheck"].run("") + self.auxcommands["logout"].logoutfunction("") + + @log_decor + def setbiospasswordautomatictesting(self, local): + """biospassword command automatic testing""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("**********************SET BIOS PASSWORD AUTOMATIC " "TESTING************************\n") + self.rdmc.ui.printer("***********************************************" "**********************************\n\n") + + if not local: + self.auxcommands["login"].loginfunction("") + self.auxcommands["set"].run("testpassword " + '""') + self.auxcommands["set"].run('""' + " testpassword") + self.auxcommands["logout"].logoutfunction("") + else: + self.rdmc.ui.printer("Skipping setting bios password testing in local.\n") + + @log_decor + def setbiosdefaultsautomatictesting(self): + """biosdefault command automatictesting""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("************************SET BIOS DEFAULTS AUTOMATIC " "TESTING**********************\n") + self.rdmc.ui.printer("***********************************************" "**********************************\n\n") + + self.auxcommands["login"].loginfunction("") + try: + self.rdmc.ui.printer("Setting BIOS Defaults:\n") + self.rdmc.ui.printer(repr(self.auxcommands["biosdefaults"].run("--manufacturingdefaults"))) + except: + self.rdmc.ui.printer("Unable to set BIOS Defaults\n") + self.auxcommands["logout"].logoutfunction("") + + @log_decor + def smartarrayautomatictesting(self): + """smart array command automatictesting""" + self.rdmc.ui.printer("\n*************************************************" "********************************\n") + self.rdmc.ui.printer("************************SMART ARRAY AUTOMATIC " "TESTING*******************\n") + self.rdmc.ui.printer("***********************************************" "********************************\n\n") + + self.auxcommands["select"].selectfunction("SmartStorageConfig.") + content = self.rdmc.app.getprops() + + if content: + self.auxcommands["smartarray"].selection_output(dict(controller=None), content) + else: + raise Exception("This system does not have a valid Smart Storage Controller") + + # helper functions + def automatictesting_helper_fd(self, data): + """ + Helper function to delete files pulled from remote to local machine. + + :param data: dictionary or list of files to be removed + :type data: dictonary or list + """ + errors = [] + + if isinstance(data, dict): + for _, d_file in list(data.items()): + try: + if os.path.exists(d_file): + os.remove(d_file) + except Exception as excp: + errors.append( + "An error occured attempting to remove the file: %s, logged: %s\n" + % (filename.split(".")[0], excp) + ) + + elif isinstance(data, list): + for d_file in data: + try: + if os.path.exists(d_file): + os.remove(d_file) + except Exception as excp: + errors.append( + "An error occured attempting to remove the file: %s, logged: %s\n" + % (filename.split(".")[0], excp) + ) + + err_str = "Errors occurred deleting: " + for blah in errors: + err_str += "{},".format(blah) + return errors + + def serverclone_helper(self, filename=None, line=None): + """ + Helper function to modify and remove properties from a serverclone file + + :param filename: filename of the clone file + :type filename: str + """ + errors = [] + delete_dict = [] + data = {} + try: + with open(filename, "r") as cf: + if "--encryption" in line: + entries_list = [(pos.start(), pos.end()) for pos in list(re.finditer(ending, path))] + encryption_key = None + data = json.loads(Encryption().decrypt_file(cf.read(), encryption_key)) + else: + data = json.loads(cf.read()) + except Exception as excp: + errors.append("An error occurred opening the clone file '%s': %s" % (filename, excp)) + + try: + for type in data: + if "AccountService" in type: + for path in list(data[type].keys()): + if "MinPasswordLength" in data[type][path]: + data[type][path]["MinPasswordLength"] = random.randint(8, 16) + if "AuthFailureDelayTimeSeconds" in data[type][path]: + data[type][path]["AuthFailureDelayTimeSeconds"] = random.randint(12, 40) + if "ComputerSystem." in type: + for path in list(data[type].keys()): + if "IndicatorLED" in data[type][path]: + data[type][path]["IndicatorLED"] = "Blinking" + if "AssetTag" in data[type][path]: + data[type][path]["AssetTag"] = "AutomaticTestAsset".join( + random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(6) + ) + if "EthernetInterface." in type: + for path in list(data[type].keys()): + if "SpeedMbps" in data[type][path]: + if data[type][path]["FullDuplex"]: + data[type][path]["FullDuplex"] = False + else: + data[type][path]["FullDuplex"] = True + if "AutoNeg" in data[type][path]: + if data[type][path]["AutoNeg"]: + data[type][path]["AutoNeg"] = False + else: + data[type][path]["AutoNeg"] = True + if "SpeedMbps" in data[type][path]: + if data[type][path]["SpeedMbps"] == 1000: + data[type][path]["SpeedMbps"] = 100 + else: + data[type][path]["SpeedMbps"] = 1000 + if "ManagerAccount." in type: + for path in list(data[type].keys()): + if "Password" in data[type][path]: + data[type][path]["Password"] = "password" + if "iLOLicense" in type: + delete_dict.append(type) + if "SecureBoot" in type: + for path in list(data[type].keys()): + if "SecureBootEnable" in data[type][path]: + data[type][path]["SecureBootEnable"] = False + except (KeyError, ValueError): + pass + + for item in delete_dict: + try: + del data[item] + except KeyError: + pass + + try: + filename2 = filename.split(".")[0] + "_edited" + ".json" + with open(filename2, "wb") as cf: + if "--encryption" in line: + [(pos.start(), pos.end()) for pos in list(re.finditer(ending, path))] + encryption_key = None + cf.write(Encryption().decrypt_file(json.dumps(data, indent=2), encryption_key)) + else: + cf.write(json.dumps(data, indent=2)) + except Exception as excp: + errors.append("An error occurred writing the clone file '%s': %s" % (filename2, excp)) + + if errors: + raise + else: + return filename2 + + def automatictesting_helper_fb(self, url, components=False): + """ + Helper function for browsing and creating file dictionary from a + file server index. Designed around the output display of Node.js + v10.10.0 which places files into an html table. + + :param url: path to the file server. + :type url: string + :param components: flag if file components will be needed + (multiple associated files) + :type components: boolean + """ + + # list of ignored files and paths + ignore_list = ["..", ".", "README"] + + table_row_list = [] + prev_tr = 7 + block = False + try: + source_url = urlopen(url) + read_url = source_url.read() + read_url = read_url[read_url.find("") : read_url.find("
") + read_url.find("
")] + while read_url.find("") > 0: + current_str = read_url[prev_tr : prev_tr + read_url.find("")] + current_str = current_str[ + current_str.find("") + ] + current_str = current_str[ + current_str.find('="') + 2 : current_str.find('="') + 2 + current_str.find('">') + ] + current_str = current_str[: current_str.find(">") - 1] + if current_str.split("/")[-1] in ignore_list or current_str.split("/")[-2] in ignore_list: + self.rdmc.ui.printer("Ignoring url: %s\n" % current_str) + else: + # data[current_str.split('/')[-1]] = [table_row_list.append(current_str) + table_row_list.append(current_str.split("/")[-1]) + + read_url = read_url[read_url.find("") + 1 :] + prev_tr = 0 + + source_url.close() + + if components: + components_dict = {} + + while len(table_row_list) > 0: + for url in table_row_list: + split_url = re.split("/", url) + last_element = split_url[-1] + if last_element.count(".") > 2: + sys.stderr.write("Invalid Filetype...skipping\n") + table_row_list.remove(url) + break + else: + (compname, ext) = last_element.split(".") + if "compsig" in last_element: + if components_dict.get(compname) is not None and compname in last_element: + components_dict[compname].append(url) + table_row_list.remove(url) + continue + if "part" in last_element and "_" in last_element: + (split_compname, _) = compname.split("_") + if split_compname in components_dict and url not in components_dict[split_compname]: + components_dict[split_compname].append(url) + table_row_list.remove(url) + else: + components_dict.setdefault(compname.split("_")[0], []) + continue + else: + components_dict.setdefault(compname, []).append(url) + table_row_list.remove(url) + + return components_dict + + else: + return table_row_list + + except ValueError or TypeError: + self.rdmc.ui.printer("The web server may not be available") + block = True + except Exception as excp: + self.rdmc.ui.printer("A General Error Occured: %s" % excp) + block = True + + def archive_handler(self, archive_file): + """ + Handles archiving of data for bug tracking and reporting + """ + + packlist = [__error_logfile__, __pass_fail__] + + if self.rdmc.opts.debug: + for handle in self.rdmc.app.logger.parent.handlers: + if isinstance(handle, FileHandler): + ilorestlog = handle.baseFilename + + if ilorestlog: + packlist.append(ilorestlog) + + with ZipFile(archive_file, "w") as zip_arch: + for _file in packlist: + try: + zip_arch.write(_file) + os.remove(_file) + except: + pass + + self.rdmc.ui.printer("Logifles archived in '%s'.\n" % archive_file) + zip_arch.printdir() + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--latestschema", + dest="latestschema", + action="store_true", + help="Optionally use the latest schema instead of the one " + "requested by the file. Note: May cause errors in some data " + "retrieval due to difference in schema versions.", + default=None, + ) + customparser.add_argument( + "--complete", + dest="complete", + action="store_true", + help="Run all available tests.", + default=False, + ) + customparser.add_argument( + "--sumtest", + dest="sumtest", + action="store_true", + help="Optionally use run only the sum command tests.", + default=False, + ) + customparser.add_argument( + "--local", + dest="local", + action="store_true", + help="""Use to perform a local login for every operation.""", + default=None, + ) + customparser.add_argument( + "--ahsdiagtest", + dest="ahsdiagtest", + action="store_true", + help="Optionally use run only the sum command tests.", + default=False, + ) + customparser.add_argument( + "--archive", + dest="archive", + help="Optionally archive the logfiles", + action="append", + default=None, + ) diff --git a/ilorest/extensions/_hidden_commands/GetInventoryCommand.py b/ilorest/extensions/_hidden_commands/GetInventoryCommand.py new file mode 100644 index 0000000..50b64fc --- /dev/null +++ b/ilorest/extensions/_hidden_commands/GetInventoryCommand.py @@ -0,0 +1,304 @@ +# ## +# Copyright 2016 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. +# BMN +# ## + +# -*- coding: utf-8 -*- +""" Get Inventory Command for rdmc """ + + +try: + from rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + +from redfish.ris.resp_handler import ResponseHandler + + +class GetInventoryCommand: + """GetInventory command class""" + + def __init__(self): + self.ident = { + "name": "getinventory", + "usage": None, + "description": "Get complete inventory" + "data from the iLO, including iLO Repo and install set." + "\n\texample: getinventory all", + "summary": "Get complete inventory data from the iLO.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + self.response_handler = None + + def run(self, line, help_disp=False): + """Main GetInventory worker function + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.getinventoryvalidation(options) + self.response_handler = ResponseHandler( + self.rdmc.app.validationmanager, + self.rdmc.app.typepath.defs.messageregistrytype, + ) + if self.rdmc.app.typepath.defs.isgen9: + raise IncompatibleiLOVersionError("GetInventory command is " "available only on iLO 5 node.") + + alldata = {} + results = {} + + # If both are not set, then we will get both inventory and repo data + if not options.inventory and not options.repo_data and not options.sut: + options.inventory = True + options.repo_data = True + options.sut = True + + if options.inventory: + try: + collectiondata = self.rdmc.app.get_handler("/redfish/v1/updateservice/firmwareinventory", service=False, + silent=True).dict + members = self.rdmc.app.getcollectionmembers(collectiondata.get("@odata.id")) + collectiondata.update({"Members": members}) + alldata.update({"firmwareInventory": collectiondata}) + except: + alldata.update({"firmwareInventory": {}}) + try: + collectiondata = self.rdmc.app.get_handler("/redfish/v1/updateservice/softwareinventory", service=False, + silent=True).dict + members = self.rdmc.app.getcollectionmembers(collectiondata.get("@odata.id")) + collectiondata.update({"Members": members}) + alldata.update({"softwareInventory": collectiondata}) + except: + alldata.update({"softwareInventory": {}}) + + try: + members = self.rdmc.app.get_handler("/redfish/v1/systems/", service=False, silent=True).dict["Members"] + systemdata = {} + rootsystem = {} + for id in members: + for mem_id in id.values(): + results = self.rdmc.app.get_handler(mem_id, service=True, silent=True).dict + id = results["Id"] + if id != "1": + rootsystem.update({"systems%s" % id: results}) + systemdata.update({"systems": rootsystem}) + alldata.update(systemdata) + if id == "1": + alldata.update({"systems1": results}) + except: + alldata.update({"systems": {}}) + + try: + sutlocation = alldata.get("systems1").get("Oem").get("Hpe").get("Links") + except: + if results: + sutlocation = next(iter(results)).dict.get("Links") + for key, value in list(sutlocation.items()): + if key == "SUT": + result2 = self.rdmc.app.get_handler(value.get("@odata.id"), service=False, silent=True) + + if result2.status == 200: + alldata["SUT"] = result2.dict + else: + return self.printlastfailedresult(result2) + break + try: + collectiondata = self.rdmc.app.get_handler("/redfish/v1/Managers/1/EthernetInterfaces/", service=False, + silent=True).dict + members = self.rdmc.app.getcollectionmembers(collectiondata.get("@odata.id")) + collectiondata.update({"Members": members}) + alldata.update({"EthernetInterfaces": collectiondata}) + except: + alldata.update({"EthernetInterfaces": {}}) + + try: + results = self.rdmc.app.select(selector="HpeiLODateTime.", path_refresh=True) + alldata["DateTime"] = next(iter(results)).resp.dict + except: + alldata.update({"DateTime": {}}) + + try: + collectiondata = self.rdmc.app.get_handler("/redfish/v1/UpdateService/", + service=False, silent=True).dict + alldata.update({"DowngradePolicy": collectiondata}) + except: + alldata.update({"DowngradePolicy": {}}) + + if options.sut: + if "system1" not in alldata: + try: + results = self.rdmc.app.get_handler("/redfish/v1/systems/", service=False, silent=True).dict + members = results["Members"] + for id in members: + for mem_id in id.values(): + results = self.rdmc.app.get_handler(mem_id, service=True, silent=True).dict + id = results["Id"] + if id == "1": + alldata.update({"systems1": results}) + except: + alldata.update({"systems1": {}}) + else: + _ = alldata["systems1"] + + if alldata.get("systems1"): + try: + sutlocation = alldata.get("systems1").get("Oem").get("Hpe").get("Links") + except: + if results: + sutlocation = next(iter(results)).dict.get("Links") + for key, value in list(sutlocation.items()): + if key == "SUT": + result2 = self.rdmc.app.get_handler(value.get("@odata.id"), service=False, silent=True) + + if result2.status == 200: + alldata["SUT"] = result2.dict + else: + return self.printlastfailedresult(result2) + break + + if options.repo_data: + try: + collectiondata = self.rdmc.app.get_handler("/redfish/v1/UpdateService/installsets", + service=False, silent=True).dict + members = self.rdmc.app.getcollectionmembers(collectiondata.get("@odata.id")) + if len(members) == 0: + members = [] + collectiondata.update({"Members": members}) + alldata.update({"installsets": collectiondata}) + except: + alldata.update({"installsets": []}) + + try: + collectiondata = self.rdmc.app.get_handler("/redfish/v1/UpdateService/updatetaskqueue", + service=False, silent=True).dict + members = self.rdmc.app.getcollectionmembers(collectiondata.get("@odata.id")) + if len(members) == 0: + members = [] + collectiondata.update({"Members": members}) + alldata.update({"updatetaskqueue": collectiondata}) + except: + alldata.update({"updatetaskqueue": []}) + + if self.rdmc.app.getiloversion(skipschemas=True) >= 5.130: + try: + collectiondata = self.rdmc.app.get_handler("/redfish/v1/UpdateService/maintenancewindows", + service=False, silent=True).dict + members = self.rdmc.app.getcollectionmembers(collectiondata.get("@odata.id")) + if len(members) == 0: + members = [] + collectiondata.update({"Members": members}) + alldata.update({"maintenancewindows": collectiondata}) + except: + alldata.update({"maintenancewindows": []}) + + comp_repo_url = "/redfish/v1/UpdateService/ComponentRepository/" + "?$expand=." + try: + members = self.rdmc.app.get_handler(comp_repo_url, silent=True, service=True).dict + alldata.update({"ComponentRepository": members}) + except: + alldata.update({"ComponentRepository": {}}) + + self.rdmc.ui.print_out_json(alldata) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def combineinstances(self, data, alldata, name, coll=None): + """combine the data into one json dictionary""" + if coll: + coll = coll.resp.dict + finaldata = [] + if "Members" in coll: + for member in data: + finaldata.append(member.resp.dict) + coll["Members"] = finaldata + alldata[name] = coll + elif len(data) > 1: + alldata.update({name: {"Members": []}}) + for item in data: + alldata[name]["Members"].append(item.resp.dict) + else: + alldata.update({name: {"Members": next(iter(data)).resp.dict}}) + + def printlastfailedresult(self, results): + """print last failed result function""" + self.response_handler.output_resp(results) + + return ReturnCodes.NO_CONTENTS_FOUND_FOR_OPERATION + + def getinventoryvalidation(self, options): + """get inventory validation function + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-i", + "--inventory", + dest="inventory", + action="store_true", + help="""Use this option to get only the inventory data.""", + default=False, + ) + customparser.add_argument( + "-s", + "--sut", + dest="sut", + action="store_true", + help="""Use this option to get only SUT data from iLO.""", + default=False, + ) + customparser.add_argument( + "-r", + "--repo_data", + dest="repo_data", + action="store_true", + help="""Use this option to get only iLO repository, install set """ """and Task queue details.""", + default=False, + ) diff --git a/ilorest/extensions/_hidden_commands/HpGooeyCommand.py b/ilorest/extensions/_hidden_commands/HpGooeyCommand.py new file mode 100644 index 0000000..6285ad3 --- /dev/null +++ b/ilorest/extensions/_hidden_commands/HpGooeyCommand.py @@ -0,0 +1,917 @@ +### +# Copyright 2016-2021 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 -*- +""" Hp Gooey Command for rdmc """ + +import ctypes +import gzip +import itertools +import os +import platform +import string +import struct +import subprocess +import sys +import tempfile +import time +import xml.etree.ElementTree as et + +from six import BytesIO, StringIO + +import redfish.hpilo.risblobstore2 as risblobstore2 + +try: + from rdmc_helper import ( + BirthcertParseError, + CommandNotEnabledError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + PartitionMoutingError, + ReturnCodes, + StandardBlobErrorHandler, + ) +except ImportError: + from ilorest.rdmc_helper import ( + BirthcertParseError, + CommandNotEnabledError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + PartitionMoutingError, + ReturnCodes, + StandardBlobErrorHandler, + ) + + +if os.name == "nt": + import win32api +elif sys.platform != "darwin" and "VMkernel" not in platform.uname(): + import pyudev + + +class HpGooeyCommand: + """Hp Gooey class command""" + + def __init__(self): + self.ident = { + "name": "hpgooey", + "usage": None, + "description": "Directly writes/reads from blobstore" + "\n\tBlobstore read example:\n\thpgooey --read " + "--key keyexample --namespace perm -f " + "\n\n\tBlobstore write example:\n\thpgooey --write" + " --key keyexample --namespace perm -f \n\n\tBlobstore delete example:\n\thpgooey " + "--delete --key keyexample --namespace perm\n\n\t" + "Blobstore list example:\n\thpgooey --list " + "--namespace perm\n\n\tNAMESPACES:\n\tperm, " + "tmp, dropbox, sfw, ris, volatile", + "summary": "directly writes/reads from blobstore", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + # TODO: Hack for high security cred issue (We need to keep a dll handle open) + try: + self.lib = risblobstore2.BlobStore2.gethprestchifhandle() + except: + self.lib = None + + def run(self, line, help_disp=False): + """Access blobstore directly and perform desired function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + if sys.platform == "darwin": + raise CommandNotEnabledError("'%s' command is not supported on MacOS" % str(self.name)) + elif "VMkernel" in platform.uname(): + raise CommandNotEnabledError("'%s' command is not supported on VMWare" % str(self.name)) + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.hpgooeyvalidation(options) + + if self.rdmc.app.current_client.base_url.startswith("blobstore"): + self.local_run(options) + else: + self.remote_run(options) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def remote_run(self, options): + path = "/blob" + if options.namespace: + path += "/%s" % options.namespace + if options.key: + path += "/%s" % options.key + + if options.write: + if not (options.key and options.namespace): + raise InvalidCommandLineError("Key and namespace are required" " for hpblob operations.") + if not options.filename or not os.path.isfile(options.filename[0]): + raise InvalidFileInputError("Given file doesn't exist, please " "provide a file with input data.") + + blobfiledata = None + if options.binfile: + _read_mode = "rb" + else: + _read_mode = "r" + + with open(options.filename[0], _read_mode) as bfh: + blobfiledata = bfh.read() + if options.key == "birthcert": + try: + bcert = self.remote_read(path) + if isinstance(bcert, bytes): + bcert = bcert.decode("utf-8") + except StandardBlobErrorHandler: + bcert = "" + blobdata = bytearray(bcert, encoding="utf-8") + blobfiledata = self.writebirthcert(blobfiledata=blobfiledata, blobdata=blobdata) + self.remote_write(path, blobfiledata) + + elif options.read: + if not (options.key and options.namespace): + raise InvalidCommandLineError("Key and namespace are required" " for hpblob operations.") + + filedata = BytesIO() + + filedatastr = bytes(self.remote_read(path)) + + if options.key == "birthcert": + filedatastr = self.readbirthcert(filedatastr) + + # if isinstance(filedatastr, bytes): + # filedatastr = filedatastr.decode('utf-8','ignore') + filedata.write(filedatastr) + if options.filename: + self.rdmc.ui.printer("Writing data to %s..." % options.filename[0]) + + with open(options.filename[0], "wb") as outfile: + outfile.write(filedata.getvalue()) + + self.rdmc.ui.printer("Done\n") + else: + self.rdmc.ui.printer("%s\n" % filedata.getvalue().decode("utf-8")) + + elif options.delete: + if not (options.key and options.namespace): + raise InvalidCommandLineError("Key and namespace are required" " for hpblob operations.") + self.remote_delete(path) + + elif options.list: + if not options.namespace: + raise InvalidCommandLineError("Namespace is required for hpblob operations.") + bs2 = risblobstore2.BlobStore2(log_dir=self.rdmc.log_dir) + recvpacket = bs2.list(options.namespace) + errorcode = struct.unpack(" 3: + found, path = self.manualmount(label) + if found: + return (True, path) + + count = count + 1 + time.sleep(1) + + raise PartitionMoutingError("Partition with label %s not found on the NAND, so not able to mount" % label) + + def check_flat_path(self): + """Check flat path directory.""" + context = pyudev.Context() + count = 0 + + while count < 20: + for dev in context.list_devices(subsystem="block"): + if str(dev.get("ID_SERIAL")).startswith("HP_iLO_LUN"): + path = dev.get("DEVNAME") + return (True, path) + + count = count + 1 + time.sleep(1) + + raise PartitionMoutingError("iLO not responding to request for mounting partition") + + def manualmount(self, label): + """Manually mount after fixed time.""" + context = pyudev.Context() + + for device in context.list_devices(subsystem="block"): + if device.get("ID_FS_LABEL") == label: + dirpath = os.path.join(tempfile.gettempdir(), label) + + if not os.path.exists(dirpath): + try: + os.makedirs(dirpath) + except Exception as excp: + raise excp + + pmount = subprocess.Popen( + ["mount", device.device_node, dirpath], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + _, _ = pmount.communicate() + return (True, dirpath) + + return (False, None) + + def get_available_drives(self): + """Obtain all drives""" + if "Windows" not in platform.system(): + return [] + + drive_bitmask = ctypes.cdll.kernel32.GetLogicalDrives() + return list( + itertools.compress( + string.ascii_uppercase, + [ord(drive) - ord("0") for drive in bin(drive_bitmask)[:1:-1]], + ) + ) + + def detecttype(self, readdata): + """Function to detect a packets encryption + + :param readdata: data read from the call + :type readdata: str. + """ + magic_dict = { + "\x1f\x8b\x08": "gz", + "\x42\x5a\x68": "bz2", + "\x50\x4b\x03\x04": "zip", + } + max_len = max(len(x) for x in magic_dict) + file_start = readdata[:max_len] + + for magic, filetype in list(magic_dict.items()): + if file_start.startswith(magic): + return filetype + + return "no match" + + def osunmount(self, labels=None): + """Function to unmount media using labels + + :param labels: list of labels to unmount + :type labels: list. + """ + if labels: + for label in labels: + try: + (_, path) = self.check_mount_path(label) + except PartitionMoutingError: + if self.rdmc.opts.verbose: + self.rdmc.ui.printer("Unable to find {0} partition.".format(label)) + continue + pumount = subprocess.Popen(["umount", path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + _, _ = pumount.communicate() + + def readbirthcert(self, blobdata): + """Function to read the birth certificate + + :param blobdata: data read from birth certificate call + :type blobdata: str. + """ + if "blobstore" in self.rdmc.app.redfishinst.base_url: + blobio = BytesIO(blobdata) + filehand = gzip.GzipFile(mode="rb", fileobj=blobio) + + data = filehand.read() + filehand.close() + else: + data = blobdata + return data + + def writebirthcert(self, blobdata, blobfiledata): + """Function to read the birth certificate + + :param blobdata: data to be written to birth certificate call + :type blobdata: str. + :param blobfiledata: data read from birth certificate call + :type blobfiledata: str. + """ + filetype = self.detecttype(blobfiledata) + if filetype != "no match": + raise StandardBlobErrorHandler + + if "blobstore" in self.rdmc.app.redfishinst.base_url: + blobdataunpacked = self.readbirthcert(blobdata) + else: + blobdataunpacked = blobdata.decode("utf-8") + + totdata = self.parsebirthcert(blobdataunpacked, blobfiledata) + databuf = BytesIO() + + filehand = gzip.GzipFile(mode="wb", fileobj=databuf) + filehand.write(totdata) + filehand.close() + + compresseddata = databuf.getvalue() + return compresseddata + + def parsebirthcert(self, blobdataunpacked, blobfiledata): + """Parse birth certificate function.""" + filedata = StringIO(blobfiledata) + if blobdataunpacked: + if isinstance(blobdataunpacked, bytes): + blobdataunpacked = blobdataunpacked.decode("utf-8") + readdata = StringIO(blobdataunpacked) + + try: + readtree = et.parse(readdata) + readroot = readtree.getroot() + readstr = b"" + + if readroot.tag == "BC": + for child in readroot: + readstr += et.tostring(child) + + if isinstance(readstr, bytes): + readstr = readstr.decode("utf-8") + totstr = readstr + blobfiledata + totstrdata = StringIO(totstr) + iterdata = itertools.chain("", totstrdata, "") + readroot = et.fromstringlist(iterdata) + totdata = et.tostring(readroot) + else: + raise + except Exception as excp: + self.rdmc.ui.error("Error while parsing birthcert.\n", excp) + raise BirthcertParseError(excp) + else: + iterdata = itertools.chain("", filedata, "") + newroot = et.fromstringlist(iterdata) + totdata = et.tostring(newroot) + + return totdata + + def birthcertdelete(self, options=None, compdata=None): + """Delete birth certificate function.""" + totdata = "" + databuf = StringIO() + filehand = gzip.GzipFile(mode="wb", fileobj=databuf) + + filehand.write(totdata) + filehand.close() + compresseddata = databuf.getvalue() + + if compdata: + compresseddata = compdata + + bs2 = risblobstore2.BlobStore2(log_dir=self.rdmc.log_dir) + risblobstore2.BlobStore2.initializecreds(options.user, options.password) + + errorcode = bs2.write(options.key, options.namespace, compresseddata) + + if not ( + errorcode == risblobstore2.BlobReturnCodes.SUCCESS or errorcode == risblobstore2.BlobReturnCodes.NOTMODIFIED + ): + raise StandardBlobErrorHandler(errorcode) + + return errorcode + + def hpgooeyvalidation(self, options): + """Download command method validation function + + :param options: command options + :type options: options. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-f", + "--filename", + dest="filename", + help="""Use the provided filename to perform operations.""", + action="append", + default=None, + ) + customparser.add_argument( + "-r", + "--read", + dest="read", + action="store_true", + help="""read data into the provided filename""", + default=None, + ) + customparser.add_argument( + "-w", + "--write", + dest="write", + action="store_true", + help="""use the provided filename to output data""", + default=None, + ) + customparser.add_argument( + "-d", + "--delete", + dest="delete", + action="store_true", + help="""delete the file from the provided namespace""", + default=None, + ) + customparser.add_argument( + "-l", + "--list", + dest="list", + action="store_true", + help="""list the files from the provided namespace""", + default=None, + ) + customparser.add_argument( + "-k", + "--key", + dest="key", + help="""blobstore key name to use for opetations with no """ """spaces and 32 character limit""", + default=None, + ) + customparser.add_argument( + "-n", + "--namespace", + dest="namespace", + help="""namespace where operation is to be performed""", + default=None, + ) + customparser.add_argument( + "--mountabsr", + dest="mountabsr", + action="store_true", + help="""use this flag to mount absaroka repo""", + default=None, + ) + customparser.add_argument( + "--mountgaius", + dest="mountgaius", + action="store_true", + help="""use this flag to mount gaius""", + default=None, + ) + customparser.add_argument( + "--mountvid", + dest="mountvid", + action="store_true", + help="""use this flag to mount vid""", + default=None, + ) + customparser.add_argument( + "--mountflat", + dest="mountflat", + action="store_true", + help="""use this flag to mount flat mode""", + default=None, + ) + customparser.add_argument( + "--unmountabsr", + dest="unmountabsr", + action="store_true", + help="""use this flag to unmount absaroka media""", + default=None, + ) + customparser.add_argument( + "--unmountvid", + dest="unmountvid", + action="store_true", + help="""use this flag to vid media""", + default=None, + ) + customparser.add_argument( + "--unmountgaius", + dest="unmountgaius", + action="store_true", + help="""use this flag to unmount gaius media""", + default=None, + ) + customparser.add_argument( + "--unmountmedia", + dest="unmountmedia", + action="store_true", + help="""use this flag to unmount all NAND partitions""", + default=None, + ) + customparser.add_argument( + "--binfile", + dest="binfile", + action="store_true", + help="""use this flag to write and read binary files""", + default=None, + ) diff --git a/ilorest/extensions/_hidden_commands/ISToolCommand.py b/ilorest/extensions/_hidden_commands/ISToolCommand.py new file mode 100644 index 0000000..c364d5a --- /dev/null +++ b/ilorest/extensions/_hidden_commands/ISToolCommand.py @@ -0,0 +1,186 @@ +### +# Copyright 2016 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 -*- +""" ISTool Command for rmdc """ + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class ISToolCommand: + """ISTool class command""" + + def __init__(self): + self.ident = { + "name": "istool", + "usage": None, + "description": "Displays ilo data useful in debugging", + "summary": "displays ilo data useful in debugging", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Debug helper for iLO + + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + ilodata = "" + reglist = [] + foundreg = False + + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if args: + raise InvalidCommandLineError("Istool command does not take any arguments.") + + self.istoolvalidation(options) + + if self.rdmc.app.monolith.is_redfish: + regstr = "/redfish/v1/Registries/" + ilostr = "/redfish/v1/Managers/1/" + else: + regstr = "/rest/v1/Registries" + ilostr = "/rest/v1/Managers/1" + + try: + biosresults = self.rdmc.app.get_handler(self.rdmc.app.typepath.defs.biospath, service=True, silent=True) + regresults = self.rdmc.app.get_handler(regstr, service=True, silent=True) + romresults = self.rdmc.app.get_handler(self.rdmc.app.typepath.defs.systempath, service=True, silent=True) + iloresults = self.rdmc.app.get_handler(ilostr, silent=True, service=True) + except: + raise + + if biosresults.dict: + try: + biosreg = biosresults.dict["AttributeRegistry"] + self.rdmc.ui.printer("System has attribute registry: %s\n" % biosreg) + except Exception as excp: + biosreg = None + self.rdmc.ui.error("Attribute registry not found in BIOS.\n", excp) + + if regresults.dict: + items = regresults.dict[self.rdmc.app.typepath.defs.collectionstring] + + for item in items: + try: + schema = item["Schema"] + except: + item = self.rdmc.app.get_handler( + item[self.rdmc.app.typepath.defs.hrefstring], + service=True, + silent=True, + ) + + item = item.dict + if "Schema" in item: + schema = item["Schema"] + else: + schema = item["Registry"] + + if schema: + available = True + + locationdict = ( + self.rdmc.app.validationmanager.geturidict(item["Location"][0]) + if self.rdmc.app.validationmanager + else None + ) + extref = ( + self.rdmc.app.get_handler(locationdict, service=True, silent=True) if locationdict else None + ) + + if not extref: + available = False + + if available: + val = schema + " : Available" + reglist.append(val) + else: + val = schema + " : Not Available" + reglist.append(val) + + if schema == biosreg: + foundreg = True + + self.rdmc.ui.printer( + "The following attribute registries are in the registry \ + repository:\n" + ) + for item in reglist: + self.rdmc.ui.printer("%s\n" % item) + + if foundreg: + self.rdmc.ui.printer("The system attribute registry was found in the " "registry repository.\n") + else: + self.rdmc.ui.error("The system attribute registry was not found in " "the registry repository.\n") + + if iloresults.dict: + try: + ilodata = iloresults.dict["FirmwareVersion"] + self.rdmc.ui.printer("iLO Version: %s\n" % ilodata) + except Exception as excp: + self.rdmc.ui.error("Unable to find iLO firmware data.\n", excp) + + if romresults.dict: + try: + biosversion = romresults.dict["BiosVersion"] + self.rdmc.ui.printer("BIOS Version: %s\n" % biosversion) + except Exception as excp: + self.rdmc.ui.error("Unable to find ROM data.\n", excp) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def istoolvalidation(self, options): + """ISTool validation function""" + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/_hidden_commands/MonolithCommand.py b/ilorest/extensions/_hidden_commands/MonolithCommand.py new file mode 100644 index 0000000..d84e03d --- /dev/null +++ b/ilorest/extensions/_hidden_commands/MonolithCommand.py @@ -0,0 +1,120 @@ +### +# Copyright 2016 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 -*- +""" Monolith Command for rdmc """ + +import json + +try: + from rdmc_helper import UI, InvalidCommandLineErrorOPTS, ReturnCodes +except ImportError: + from ilorest.rdmc_helper import UI, InvalidCommandLineErrorOPTS, ReturnCodes +from redfish.rest.containers import JSONEncoder +from redfish.ris import UndefinedClientError + + +class MonolithCommand: + """Monolith class command""" + + def __init__(self): + self.ident = { + "name": "monolith", + "usage": None, + "description": "Displays entire cached data structure available", + "summary": "displays entire cached data structure available", + "aliases": ["mono"], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main monolith worker function + + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if hasattr(self.rdmc.app, "config") and self.rdmc.app.config._ac__format.lower() == "json": + options.json = True + + if self.rdmc.app.current_client: + results = self.rdmc.app.monolith.capture(redmono=options.redmono) + else: + raise UndefinedClientError() + + if options.filename: + with open(options.filename[0], "w") as monolith: + if options.json: + json.dump(results, monolith, indent=2, cls=JSONEncoder) + else: + monolith.write(str(results)) + + elif options.json: + UI().print_out_json(results) # .reduce()) + else: + UI().print_out_human_readable(results) # .reduce()) + + # Return code + return ReturnCodes.SUCCESS + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + customparser.add_argument( + "-f", + "--filename", + dest="filename", + help="Use this flag if you wish to save the monolith" " into a file with the given filename.", + action="append", + default=None, + ) + customparser.add_argument( + "-r", + "--reduced", + dest="redmono", + action="store_true", + help="Use this flag if you wish to save the reduced monolith" " into a file with the given filename.", + default=False, + ) diff --git a/ilorest/extensions/_hidden_commands/SMBiosCommand.py b/ilorest/extensions/_hidden_commands/SMBiosCommand.py new file mode 100644 index 0000000..8557551 --- /dev/null +++ b/ilorest/extensions/_hidden_commands/SMBiosCommand.py @@ -0,0 +1,220 @@ +### +# Copyright 2016 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 -*- +""" New Command for RDMC """ + +import json +import struct + +import redfish.ris +try: + from rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + +__filename__ = "smbios.json" + + +class SMBiosCommand: + """Main smbios command class""" + + def __init__(self): + self.ident = { + "name": "smbios", + "usage": None, + "description": "Run to get the smbios for the system." "\n\texample write smbios data to file: smbios", + "summary": "Gets the smbios for the currently logged in server and " + "write results to a file in json format.", + "aliases": [], + "auxcommands": [], + } + self.rdmc = None + self.filename = None + + def smbiosfunction(self, options): + """Main smbios command worker function + + :param options: command options + :type options: options. + """ + + sysresp = self.rdmc.app.get_handler(self.rdmc.app.typepath.defs.systempath, silent=True, service=True) + + try: + path = sysresp.dict["Oem"][self.rdmc.app.typepath.defs.oemhp]["SMBIOS"]["extref"] + except: + raise NoContentsFoundForOperationError("Unable to find ComputerSystems") + + resp = self.rdmc.app.get_handler(path, silent=True, service=True) + + if resp and resp.status == 200: + data = self.unpackdata(resp.ori) + data = {"smbios data": data} + + outfile = open(self.filename, "w") + outfile.write(json.dumps(data, indent=2, cls=redfish.ris.JSONEncoder, sort_keys=True)) + outfile.close() + + self.rdmc.ui.printer("Smbios saved to: %s\n" % self.filename) + else: + raise NoContentsFoundForOperationError("Unable to find smbios.") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def unpackdata(self, data): + """Unpacks and returns json formatted data + + :param data: data to unpack + :type data: string. + """ + + dataptr = 0 + fulldata = [] + if not isinstance(data, (bytes, bytearray)): + data = data.encode() + + try: + while True: + record_header = struct.unpack_from("BBH", data[dataptr : dataptr + 4]) + + record_type = record_header[0] + record_length = record_header[1] + record_handle = record_header[2] + + # skip to the strings + dataptr += record_length + + # read strings from end of record + record_strings = [] + + while True: + tempstr = "" + + while data[dataptr] != 0: + tempstr += chr(data[dataptr]) + dataptr += 1 + + dataptr += 1 + + if len(tempstr) == 0: + break + + record_strings.append(str(tempstr)) + + while data[dataptr] == 0: + dataptr += 1 + + fulldata.append( + { + "Type": str(record_type), + "Length": str(record_length), + "Handle": str(record_handle), + } + ) + + if len(record_strings): + fulldata[-1]["Strings"] = str(record_strings) + except IndexError: + pass + return fulldata + + def run(self, line, help_disp=False): + """Wrapper function for smbios command main function + + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if self.rdmc.app.typepath.url: + if "http" not in self.rdmc.app.typepath.url: + options.logout = True + self.cmdbase.logout_routine(self, options) + else: + options.logout = True + self.cmdbase.logout_routine(self, options) + self.smbiosvalidation(options) + + if not self.rdmc.app.typepath.flagiften: + raise IncompatibleiLOVersionError("smbios command is RedFish only.") + + self.smbiosfunction(options) + # Return code + return ReturnCodes.SUCCESS + + def smbiosvalidation(self, options): + """smbios command method validation function + + :param options: command options + :type options: options. + """ + self.cmdbase.login_select_validation(self, options) + + # filename validations and checks + self.filename = None + + if options.filename: + self.filename = options.filename[0] + + if not self.filename: + self.filename = __filename__ + + def definearguments(self, customparser): + """Wrapper function for smbios command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-f", + "--filename", + dest="filename", + help="Use this flag if you wish to use a different" + " filename than the default one. The default filename is" + " %s." % __filename__, + action="append", + default=None, + ) diff --git a/ilorest/extensions/_hidden_commands/SecurityStatusCommand.py b/ilorest/extensions/_hidden_commands/SecurityStatusCommand.py new file mode 100644 index 0000000..e2991b3 --- /dev/null +++ b/ilorest/extensions/_hidden_commands/SecurityStatusCommand.py @@ -0,0 +1,179 @@ +### +# Copyright 2016 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 -*- +""" Security Status Command for rdmc """ + +import struct +from argparse import SUPPRESS +from ctypes import POINTER, c_char_p, c_ubyte, create_string_buffer + +try: + from rdmc_base_classes import RdmcCommandBase + from rdmc_helper import Encryption, InvalidCommandLineErrorOPTS, ReturnCodes +except ImportError: + from ilorest.rdmc_base_classes import RdmcCommandBase + from ilorest.rdmc_helper import Encryption, InvalidCommandLineErrorOPTS, ReturnCodes +from redfish.hpilo.risblobstore2 import BlobStore2 +from redfish.hpilo.rishpilo import BlobReturnCodes, HpIloChifAccessDeniedError + + +class SecurityStatusCommand(RdmcCommandBase): + """Security Status class command""" + + def __init__(self): + self.ident = { + "name": "securitystatus", + "usage": None, + "description": "Security status example:\n\tsecuritystatus", + "summary": "command to retrieve the current system security status and validate " "credentials via chif.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Access blobstore directly and perform desired function + + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if bool(options.user) ^ bool(options.password): + self.rdmc.ui.error("Credentials: Missing\n") + + elif options.user and options.password: + if options.encode: + options.user = Encryption.decode_credentials(options.user) + if isinstance(options.user, bytes): + options.user = options.user.decode("utf-8") + options.password = Encryption.decode_credentials(options.password) + if isinstance(options.password, bytes): + options.password = options.password.decode("utf-8") + + result = self.validate_creds(options.user, options.password) + # self.rdmc.ui.printer("Validate Creds is {}...\n".format(result)) + if result: + secstate = BlobStore2(log_dir=self.rdmc.log_dir).get_security_state() + if isinstance(secstate, bytes): + if secstate == b"\x00": + state = struct.unpack("B", secstate) + state = str(state).strip(",( )") + self.rdmc.ui.printer("Security State is {}\n".format(state)) + else: + secstate = secstate.decode("utf-8") + self.rdmc.ui.printer("Security State is {}\n".format(secstate)) + else: + self.rdmc.ui.printer("Security State is {}\n".format(secstate)) + self.rdmc.ui.printer("Credentials: Valid\n") + else: + self.rdmc.ui.error("Credentials: Invalid\n") + + else: + secstate = BlobStore2(log_dir=self.rdmc.log_dir).get_security_state() + if isinstance(secstate, bytes): + if secstate == b"\x00": + state = struct.unpack("B", secstate) + state = str(state).strip(",( )") + self.rdmc.ui.printer("Security State is {}\n".format(state)) + else: + secstate = secstate.decode("utf-8") + self.rdmc.ui.printer("Security State is {}\n".format(secstate)) + else: + self.rdmc.ui.printer("Security State is {}\n".format(secstate)) + + return ReturnCodes.SUCCESS + + def validate_creds(self, user, passwrd): + """Validates credentials via CHIF + + :param user: username to validate + :type user: str. + :param passwrd: password to validate + :type passwrd: str. + """ + + valid = False + dll = BlobStore2.gethprestchifhandle() + dll.ChifInitialize(None) + sec_support = dll.ChifGetSecuritySupport() + if sec_support <= 1: + dll.ChifEnableSecurity() + dll.initiate_credentials.argtypes = [c_char_p, c_char_p] + dll.initiate_credentials.restype = POINTER(c_ubyte) + + usernew = create_string_buffer(user.encode("utf-8")) + passnew = create_string_buffer(passwrd.encode("utf-8")) + + dll.initiate_credentials(usernew, passnew) + credreturn = dll.ChifVerifyCredentials() + if credreturn == 0: + valid = True + else: + valid = False + if not credreturn == BlobReturnCodes.CHIFERR_AccessDenied: + raise HpIloChifAccessDeniedError( + "Error %s - Chif Access Denied occurred while trying " "to open a channel to iLO." % credreturn + ) + + BlobStore2.unloadchifhandle(dll) + return valid + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + customparser.add_argument( + "-u", + "--user", + dest="user", + help="If you are not logged in yet, including this flag along" + " with the password and URL flags can be used to login to a" + " server in the same command." + "", + default=None, + ) + customparser.add_argument( + "-p", + "--password", + dest="password", + help="""Use the provided iLO password to log in.""", + default=None, + ) + customparser.add_argument( + "-e", + "--enc", + dest="encode", + action="store_true", + help=SUPPRESS, + default=False, + ) diff --git a/ilorest/extensions/_hidden_commands/__init__.py b/ilorest/extensions/_hidden_commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/extensions/iLO_COMMANDS/CertificateCommand.py b/ilorest/extensions/iLO_COMMANDS/CertificateCommand.py new file mode 100644 index 0000000..0ed2d09 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/CertificateCommand.py @@ -0,0 +1,1482 @@ +### +# Copyright 2016-2021 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 -*- +""" Certificates Command for rdmc """ + +import time +from argparse import RawDescriptionHelpFormatter + +try: + from rdmc_helper import ( + IloLicenseError, + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + NoContentsFoundForOperationError, + ReturnCodes, + ScepenabledError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IloLicenseError, + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + NoContentsFoundForOperationError, + ReturnCodes, + ScepenabledError, + ) + +__filename__ = "certificate.txt" + +from redfish.ris import IdTokenError + + +class CertificateCommand: + """Commands Certificates actions to the server""" + + def __init__(self): + self.ident = { + "name": "certificate", + "usage": None, + "description": "Generate a certificate signing request (CSR) or import an X509 formatted" + " TLS or CA certificate.\nImport Scep Certificate.\nInvoke Auto Enroll of certificate generation\n\n" + "NOTE: Use quotes to include parameters which contain whitespace when " + 'generating a CSR.\nexample: certificate gen_csr "Hewlett Packard Enterprise"' + '"iLORest Group" "CName"\n"United States" "Texas" "Houston" "False or True"\n\n' + "NOTE: unifiedcertificate command which was augmenting certificate command is merged" + " into single command certificate", + "summary": "Command for importing both iLO and login authorization " + "certificates as well as generating iLO certificate signing requests (CSR)\n", + "aliases": ["unifiedcertificate"], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + self.view = None + self.importcert = None + self.delete = None + self.gencsr = None + self.autoenroll = None + + def run(self, line, help_disp=False): + """Main Certificates Command function + + :param options: list of options + :type options: list. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.certificatesvalidation(options) + + returnCode = None + + if options.command == "csr": + self.rdmc.ui.printer( + "This command has been deprecated. Please use the following command: " + 'certificate gen_csr "Hewlet Packard Enterprice" "ILORestGroup" ...\n' + "It performs the same function.\n" + "Check --help for more information.\n" + ) + self.cmdbase.logout_routine(self, options) + # Return code + return returnCode + elif options.command == "ca": + self.rdmc.ui.printer( + "This command has been deprecated. Please use the following " + "command: certificate import --ca_cert \n" + "It performs the same function.\n" + "Check --help for more information.\n" + ) + self.cmdbase.logout_routine(self, options) + # Return code + return returnCode + elif options.command == "getcsr": + returnCode = self.get_csr_helper(options) + elif options.command == "crl": + self.rdmc.ui.printer( + "This command has been deprecated. Please use the following command: " + "certificate import --crl_cert \n" + "It performs the same function.\n" + "Check --help for more information.\n" + ) + self.cmdbase.logout_routine(self, options) + # Return code + return returnCode + elif options.command == "tls": + self.rdmc.ui.printer( + "This command has been deprecated. Please use the following command: " + "certificate import --tls_cert \n" + "It performs the same function.\n" + "Check --help for more information.\n" + ) + self.cmdbase.logout_routine(self, options) + # Return code + return returnCode + if "view" in options.command.lower(): + self.view = True + self.importcert = False + self.delete = False + self.gencsr = False + self.autoenroll = False + self.exportcert = False + elif "import" in options.command.lower(): + self.view = False + self.importcert = True + self.delete = False + self.gencsr = False + self.autoenroll = False + self.exportcert = False + elif "export" in options.command.lower(): + self.view = False + self.importcert = False + self.delete = False + self.gencsr = False + self.autoenroll = False + self.exportcert = True + elif "delete" in options.command.lower(): + self.view = False + self.importcert = False + self.delete = True + self.gencsr = False + self.autoenroll = False + self.exportcert = False + elif "gen_csr" in options.command.lower(): + self.view = False + self.importcert = False + self.delete = False + self.gencsr = True + self.autoenroll = False + self.exportcert = False + elif "auto_enroll" in options.command.lower(): + self.view = False + self.importcert = False + self.delete = False + self.gencsr = False + self.autoenroll = True + self.exportcert = False + + if self.view: + returnCode = self.viewfunction(options) + elif self.importcert: + returnCode = self.importfunction(options) + elif self.exportcert: + returnCode = self.exportfunction(options) + elif self.delete: + returnCode = self.deletefunction(options) + elif self.gencsr: + returnCode = self.gencsrfunction(options) + elif self.autoenroll: + if self.rdmc.app.typepath.defs.isgen10: + returnCode = self.autoenrollfunction(options) + else: + self.rdmc.ui.printer("Gen 9 doesnt support this feature\n") + returnCode = ReturnCodes.SUCCESS + + self.cmdbase.logout_routine(self, options) + # Return code + return returnCode + + def autoenrollfunction(self, options): + """Automatic Scep cert enrollement process + + :param options: list of options + :type options: list. + """ + select = self.rdmc.app.typepath.defs.securityservice + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + bodydict = results.resp.dict + + try: + for item in bodydict["Links"]: + if "AutomaticCertificateEnrollment" in item: + path = bodydict["Links"]["AutomaticCertificateEnrollment"]["@odata.id"] + break + except: + path = path + "AutomaticCertificateEnrollment" + + body = { + "AutomaticCertificateEnrollmentSettings": { + "ServiceEnabled": eval(options.autoenroll_ScepService.strip('"')), + "ServerUrl": options.autoenroll_scep_enrollAddress.strip('"'), + "ChallengePassword": options.autoenroll_challengepassword.strip('"'), + }, + "HttpsCertCSRSubjectValue": { + "OrgName": options.autoenroll_orgname.strip('"'), + "OrgUnit": options.autoenroll_orgunit.strip('"'), + "CommonName": options.autoenroll_commonname.strip('"'), + "Country": options.autoenroll_country.strip('"'), + "State": options.autoenroll_state.strip('"'), + "City": options.autoenroll_city.strip('"'), + "IncludeIP": eval(options.autoenroll_includeIP.strip('"')), + }, + } + + try: + results = self.rdmc.app.patch_handler(path, body, silent=True) + + i = 0 + results1 = self.rdmc.app.get_handler(path, silent=True) + while i < 9 and ( + results1.dict["AutomaticCertificateEnrollmentSettings"]["CertificateEnrollmentStatus"] == "InProgress" + ): + results1 = self.rdmc.app.get_handler(path, silent=True) + time.sleep(1) + i = i + 1 + + if results.status == 200 and ( + not (results1.dict["AutomaticCertificateEnrollmentSettings"]["CertificateEnrollmentStatus"] == "Failed") + ): + self.rdmc.ui.printer("Auto enrolment Enable/Disable operation successful \n") + return ReturnCodes.SUCCESS + elif results.status == 400: + self.rdmc.ui.error( + "There was a problem with auto enroll, Please check whether Scep CA cert is imported \t\n" + ) + + return ReturnCodes.SCEP_ENABLED_ERROR + else: + self.rdmc.ui.error( + "There was a problem with auto enroll, Plese Check the Url/Password whether it is correct\n" + ) + return ReturnCodes.SCEP_ENABLED_ERROR + except IloLicenseError: + self.rdmc.ui.error("License Error Occured while auto enroll\n") + return ReturnCodes.ILO_LICENSE_ERROR + except IdTokenError: + self.rdmc.ui.printer("Insufficient Privilege to auto enroll scep certificate process\n") + return ReturnCodes.RIS_MISSING_ID_TOKEN + + def gencsrfunction(self, options): + """Main Certificates Command function + + :param options: list of options + :type options: list. + """ + try: + select = self.rdmc.app.typepath.defs.hphttpscerttype + results = self.rdmc.app.select(selector=select) + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + bodydict = results.resp.dict + + try: + for item in bodydict["Actions"]: + if "GenerateCSR" in item: + if self.rdmc.app.typepath.defs.isgen10: + action = item.split("#")[-1] + else: + action = "GenerateCSR" + + path = bodydict["Actions"][item]["target"] + break + except: + action = "GenerateCSR" + except: + path = "redfish/v1/Managers/1/SecurityService/HttpsCert" + action = "GenerateCSR" + + body = { + "Action": action, + "OrgName": options.gencsr_orgname.strip('"'), + "OrgUnit": options.gencsr_orgunit.strip('"'), + "CommonName": options.gencsr_commonname.strip('"'), + "Country": options.gencsr_country.strip('"'), + "State": options.gencsr_state.strip('"'), + "City": options.gencsr_city.strip('"'), + "IncludeIP": eval(options.gencsr_parser_includeIP.strip('"')), + } + + try: + results = self.rdmc.app.post_handler(path, body) + if results.status == 200: + return ReturnCodes.SUCCESS + except ScepenabledError: + self.rdmc.ui.printer("SCEP is enabled , CSR cant be generated \n") + return ReturnCodes.SCEP_ENABLED_ERROR + except IloLicenseError: + self.rdmc.ui.error("License Error Occured while generating CSR") + return ReturnCodes.ILO_LICENSE_ERROR + except IdTokenError: + self.rdmc.ui.printer("Insufficient Privilege to generate CSR\n") + return ReturnCodes.RIS_MISSING_ID_TOKEN + + def deletefunction(self, options): + """Certificate Delete Function + + :param options: list of options + :type options: list. + """ + + select = self.rdmc.app.typepath.defs.hphttpscerttype + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + try: + results = self.rdmc.app.delete_handler(path, silent=True) + if results.status == 200: + self.rdmc.ui.printer("Deleted the https certifcate successfully\n") + return ReturnCodes.SUCCESS + if results.status == 403: + self.rdmc.ui.error("Insufficient Privilege to delete certificate\n") + return ReturnCodes.RIS_MISSING_ID_TOKEN + if results.status == 400: + self.rdmc.ui.error("SCEP is enabled , Cant delete the certificate\n") + return ReturnCodes.SCEP_ENABLED_ERROR + except IloLicenseError: + self.rdmc.ui.error("License Error Occured while delete") + return ReturnCodes.ILO_LICENSE_ERROR + except IncompatibleiLOVersionError: + self.rdmc.ui.error("iLO FW version on this server doesnt support this operation") + return ReturnCodes.INCOMPATIBLE_ILO_VERSION_ERROR + + def viewfunction(self, options): + """View scep certifcates or https certificates + + :param options: list of options + :type options: list. + """ + + if options.scep_cert: + if self.rdmc.app.typepath.defs.isgen10: + self.view_scepcertificate() # View scep certificates + else: + self.rdmc.ui.printer("Feature not supported on Gen 9\n") + return ReturnCodes.SUCCESS + if options.https_cert: + self.view_httpscertificate() # View https certificates + + def view_scepcertificate(self): + """ + View Scep certificate + """ + select = self.rdmc.app.typepath.defs.securityservice + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + bodydict = results.resp.dict + + try: + for item in bodydict["Links"]: + if "AutomaticCertificateEnrollment" in item: + path = bodydict["Links"]["AutomaticCertificateEnrollment"]["@odata.id"] + break + except: + path = path + "AutomaticCertificateEnrollment" + + try: + results = self.rdmc.app.get_handler(path, silent=True) + if results.status == 200: + self.rdmc.ui.printer("Scep Certificate details ...\n") + results = results.dict + self.print_cert_info(results) + return ReturnCodes.SUCCESS + + except IloLicenseError: + self.rdmc.ui.error("Error Occured while Uninstall") + return ReturnCodes.ILO_LICENSE_ERROR + + def print_cert_info(self, results): + """ + Prints the cert info + """ + for key, value in results.items(): + if "@odata" not in key: + if type(value) is dict: + self.print_cert_info(value) + else: + self.rdmc.ui.printer(key + ":" + str(value) + "\n") + + def view_httpscertificate(self): + """ + View Https certificate + """ + try: + select = self.rdmc.app.typepath.defs.hphttpscerttype + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + except: + path = "redfish/v1/Managers/1/SecurityService/HttpsCert" + + try: + results = self.rdmc.app.get_handler(path, silent=True) + if results.status == 200: + self.rdmc.ui.printer("Https Certificate details ...\n") + results = results.dict + self.print_cert_info(results) + return ReturnCodes.SUCESS + except IloLicenseError: + self.rdmc.ui.error("Error Occured while Uninstall") + return ReturnCodes.ILO_LICENSE_ERROR + + def exportfunction(self, options): + result = self.exportplatformhelper(options) + + if result: + if options.filename: + self.file_handler(next(iter(options.filename)), result, options, "wb") + self.rdmc.ui.printer("The certificate was saved to: %s\n" % next(iter(options.filename))) + return ReturnCodes.SUCCESS + else: + self.rdmc.ui.printer("The certificate retrieved is as follows:\n%s\n" % result) + return ReturnCodes.SUCCESS + else: + raise NoContentsFoundForOperationError("An error occurred retrieving the requested certificate.\n") + + def exportplatformhelper(self, options): + """Helper function for exporting a platform certificate + :param options: list of options + :type options: list. + """ + + str_type = "PlatformCert" + ss_instance = next(iter(self.rdmc.app.select("SecurityService." + ".", path_refresh=True))) + if options.ldevid_cert: + str_type = "iLO lDevID" + elif options.idevid_cert: + str_type = "iLO iDevID" + elif options.systemiak_cert: + str_type = "System IAK" + elif options.systemidevid_cert: + str_type = "System iDevID" + instance_path_uri = ( + (ss_instance.dict[str_type]["Certificates"][self.rdmc.app.typepath.defs.hrefstring]) + if ss_instance.dict.get("SystemIAK") + else None + ) + instance_data = self.rdmc.app.get_handler(instance_path_uri, silent=True) + cert = None + if instance_data.dict.get("Members"): + cert = self.rdmc.app.get_handler( + instance_data.dict["Members"][getattr(options, "id", 0) - 1].get( + self.rdmc.app.typepath.defs.hrefstring + ), + silent=True, + ).dict + return cert.get("CertificateString") + else: + raise NoContentsFoundForOperationError( + "Unable to find specified certificate at " "position %s." % getattr(options, "id", 0) + ) + + def file_handler(self, filename, data, options, operation="rb"): + """ + Wrapper function to read or write data to a respective file + :param data: data to be written to output file + :type data: container (list of dictionaries, dictionary, etc.) + :param file: filename to be written + :type file: string (generally this should be self.clone_file or tmp_clone_file + :param operation: file operation to be performed + :type operation: string ('w+', 'a+', 'r+') + :param options: command line options + :type options: attribute + :returns: json file data + """ + writeable_ops = ["wb", "w", "w+", "a", "a+"] + + if operation in writeable_ops: + with open(filename, operation) as fh: + try: + data = data.encode("UTF-8") + except IOError: + raise InvalidFileInputError("Unable to write to file '%s'" % filename) + except UnicodeEncodeError: + pass + finally: + fh.write(data) + else: + with open(filename, operation) as fh: + fdata = fh.read() + try: + return fdata.decode("UTF-8") + except UnicodeDecodeError: + return fdata + except IOError: + raise InvalidFileInputError("Unable to read from file '%s'" % filename) + + def importfunction(self, options): + if self.rdmc.app.typepath.defs.isgen10: + return self.importfunctionhelper(options) + else: + self.rdmc.ui.printer("Gen 9 doesnt support this feature\n") + return ReturnCodes.SUCCESS + + def importscephelper(self, options): + """ + Import Scep certificate + """ + select = self.rdmc.app.typepath.defs.securityservice + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + bodydict = results.resp.dict + + try: + for item in bodydict["Links"]: + if "AutomaticCertificateEnrollment" in item: + path = bodydict["Links"]["AutomaticCertificateEnrollment"]["@odata.id"] + break + except: + path = path + "AutomaticCertificateEnrollment" + + path = path + "Actions/HpeAutomaticCertEnrollment.ImportCACertificate" + + action = "HpeAutomaticCertEnrollment.ImportCACertificate" + + certdata = None + scep_CACert = options.certfile + + try: + with open(scep_CACert) as certfile: + certdata = certfile.read() + certfile.close() + except: + pass + + body = {"Action": action, "Certificate": certdata} + + try: + result = self.rdmc.app.post_handler(path, body) + if result.status == 200: + self.rdmc.ui.printer("Imported the scep certificate successfully\n") + return ReturnCodes.SUCCESS + except IdTokenError: + self.rdmc.ui.printer("Insufficient Privilege to import scep CA certificate\n") + return ReturnCodes.RIS_MISSING_ID_TOKEN + except IloLicenseError: + self.rdmc.ui.error("Error Occured while importing scep certificate") + return ReturnCodes.ILO_LICENSE_ERROR + except IncompatibleiLOVersionError: + self.rdmc.ui.error("iLO FW version on this server doesnt support this operation") + return ReturnCodes.INCOMPATIBLE_ILO_VERSION_ERROR + + def importfunctionhelper(self, options): + result = None + if getattr(options, "scep_cert"): + result = self.importscephelper(options) + elif getattr(options, "ca_cert"): + result = self.importcahelper(options) + elif getattr(options, "crl_cert"): + result = self.importcrlhelper(options) + elif getattr(options, "tls_cert"): + result = self.importtlshelper(options) + elif ( + getattr(options, "ldevid_cert") + or getattr(options, "idevid_cert") + or getattr(options, "systemiak_cert") + or getattr(options, "systemidevid_cert") + or getattr(options, "platform_cert") + ): + result = self.importplatformhelper(options) + + return result + + def generatecerthelper(self, options): + """Main Certificates Command function + + :param options: list of options + :type options: list. + """ + self.rdmc.ui.printer( + "Warning:Command CSR has been replaced with Gen_csr comamnd and " + "CSR command will be deprecated in next release of our tool. Please plan to use gen_csr command instead \n" + ) + select = self.rdmc.app.typepath.defs.hphttpscerttype + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + bodydict = results.resp.dict + + try: + for item in bodydict["Actions"]: + if "GenerateCSR" in item: + if self.rdmc.app.typepath.defs.isgen10: + action = item.split("#")[-1] + else: + action = "GenerateCSR" + + path = bodydict["Actions"][item]["target"] + break + except: + action = "GenerateCSR" + + body = { + "Action": action, + "OrgName": options.csr_orgname.strip('"'), + "OrgUnit": options.csr_orgunit.strip('"'), + "CommonName": options.csr_commonname.strip('"'), + "Country": options.csr_country.strip('"'), + "State": options.csr_state.strip('"'), + "City": options.csr_city.strip('"'), + } + + self.rdmc.ui.printer( + "iLO is creating a new certificate signing request. " "This process can take up to 10 minutes.\n" + ) + + try: + self.rdmc.app.post_handler(path, body) + return ReturnCodes.SUCCESS + except ScepenabledError: + self.rdmc.ui.printer("SCEP is enabled , operation not allowed \n") + return ReturnCodes.SCEP_ENABLED_ERROR + + def getcerthelper(self, options): + """Helper function for importing CRL certificate + + :param options: list of options + :type options: list. + """ + + select = self.rdmc.app.typepath.defs.hphttpscerttype + results = self.rdmc.app.select(selector=select, path_refresh=True) + + try: + results = results[0] + except: + pass + + if results: + try: + csr = results.resp.dict["CertificateSigningRequest"] + if not csr: + raise ValueError + except (KeyError, ValueError): + raise NoContentsFoundForOperationError( + "Unable to find a valid certificate. If " + "you just generated a new certificate " + "signing request the process may take " + "up to 10 minutes." + ) + + if not options.filename: + filename = __filename__ + else: + filename = options.filename[0] + + outfile = open(filename, "w") + outfile.write(csr) + outfile.close() + + self.rdmc.ui.printer("Certificate saved to: %s\n" % filename) + return ReturnCodes.SUCCESS + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + def get_csr_helper(self, options): + result = None + if options.TLSCERT: + instance = next(iter(self.rdmc.app.select("HttpsCert.", path_refresh=True))) + result = instance.dict.get("CertificateSigningRequest") + elif options.PLATFORM: + tmp = self.gen_csr_helper(options) + if tmp: + result = tmp.dict.get("CSRString") + + if result: + if not options.filename: + filename = __filename__ + else: + filename = options.filename[0] + + outfile = open(filename, "w") + outfile.write(result) + outfile.close() + self.rdmc.ui.printer("Certificate saved to: %s\n" % filename) + return ReturnCodes.SUCCESS + else: + self.rdmc.ui.error( + "An error occurred retrieving a CSR. Check whether CSR is requested , " + "if not kindly run gen_csr command and generate CSR" + ) + + def gen_csr_helper(self, options): + """ + :param options: list of options + :type options: attributes. + """ + body = None + path = None + action = "GenerateCSR" + if options.TLSCERT: + instance = next(iter(self.rdmc.app.select("HttpsCert.", path_refresh=True))) + body = { + "Action": action, + "OrgName": options.csr_orgname.strip('"'), + "OrgUnit": options.csr_orgunit.strip('"'), + "CommonName": options.csr_commonname.strip('"'), + "Country": options.csr_country.strip('"'), + "State": options.csr_state.strip('"'), + "City": options.csr_city.strip('"'), + } + + elif options.PLATFORM: + # There is seemingly no way to update the Certificate subject data which + # appears problematic. + _ = next(iter(self.rdmc.app.select("CertificateService.", path_refresh=True))) + ss_instance = next(iter(self.rdmc.app.select("SecurityService.", path_refresh=True))) + cert_obtain_path = ss_instance.dict.get("iLOLDevID")[next(iter(ss_instance.dict.get("iLOLDevID")))].get( + self.rdmc.app.typepath.defs.hrefstring + ) + if not cert_obtain_path: + raise NoContentsFoundForOperationError("Unable to find specified certificate path" " for CSR request") + instance = next(iter(self.rdmc.app.select("CertificateService.", path_refresh=True))) + body = {"CertificateCollection": {self.rdmc.app.typepath.defs.hrefstring: cert_obtain_path}} + + try: + for act in instance.dict.get("Actions"): + if "GenerateCSR" in act: + if self.rdmc.app.typepath.defs.isgen10: + action = act.split("#")[-1] + else: + action = "GenerateCSR" + path = instance.dict["Actions"][act]["target"] + break + except: + raise NoContentsFoundForOperationError("Unable to find specified certificate action" "path for CSR request") + + self.rdmc.ui.printer( + "iLO is creating a new certificate signing request. " "This request may take up to 10 minutes.\n" + ) + return self.rdmc.app.post_handler(path, body) + + def importtlshelper(self, options): + """Helper function for importing TLS certificate + + :param options: list of options + :type options: list. + """ + tlsfile = options.certfile + + try: + with open(tlsfile) as certfile: + certdata = certfile.read() + certfile.close() + except: + raise InvalidFileInputError("Error loading the specified file.") + + select = self.rdmc.app.typepath.defs.hphttpscerttype + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + bodydict = results.resp.dict + try: + for item in bodydict["Actions"]: + if "ImportCertificate" in item: + if self.rdmc.app.typepath.defs.isgen10: + action = item.split("#")[-1] + else: + action = "ImportCertificate" + path = bodydict["Actions"][item]["target"] + break + except: + action = "ImportCertificate" + + body = {"Action": action, "Certificate": certdata} + + try: + result = self.rdmc.app.post_handler(path, body) + if result.status == 200: + self.rdmc.ui.printer("Imported the TLS certificate successfully\n") + return ReturnCodes.SUCCESS + except IdTokenError: + self.rdmc.ui.printer("Insufficient Privilege to import TLS certificate\n") + return ReturnCodes.RIS_MISSING_ID_TOKEN + except ScepenabledError: + self.rdmc.ui.printer("SCEP is enabled , operation not allowed \n") + return ReturnCodes.SCEP_ENABLED_ERROR + except IloLicenseError: + self.rdmc.ui.error("Error occurred while importing TLS certificate") + return ReturnCodes.ILO_LICENSE_ERROR + except IncompatibleiLOVersionError: + self.rdmc.ui.error("iLO FW version on this server doesnt support this operation") + return ReturnCodes.INCOMPATIBLE_ILO_VERSION_ERROR + + def importcrlhelper(self, options): + """Helper function for importing CRL certificate + + :param options: list of options + :type options: list. + """ + if not self.rdmc.app.typepath.flagiften: + raise IncompatibleiLOVersionError("This certificate is not available on this system.") + + select = "HpeCertAuth." + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + bodydict = results.resp.dict + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + for item in bodydict["Actions"]: + if "ImportCRL" in item: + action = item.split("#")[-1] + path = bodydict["Actions"][item]["target"] + break + + body = {"Action": action, "ImportUri": options.certfile} + # self.rdmc.app.post_handler(path, body) + try: + result = self.rdmc.app.post_handler(path, body) + if result.status == 200: + self.rdmc.ui.printer("Imported the CRL certificate successfully\n") + return ReturnCodes.SUCCESS + except IdTokenError: + self.rdmc.ui.printer("Insufficient Privilege to import CRL certificate\n") + return ReturnCodes.RIS_MISSING_ID_TOKEN + except IloLicenseError: + self.rdmc.ui.error("Error Occured while importing CRL certificate") + return ReturnCodes.ILO_LICENSE_ERROR + except IncompatibleiLOVersionError: + self.rdmc.ui.error("iLO FW version on this server doesnt support this operation") + return ReturnCodes.INCOMPATIBLE_ILO_VERSION_ERROR + + def importcahelper(self, options): + """Helper function for importing CA certificate + + :param options: list of options + :type options: list. + """ + if not self.rdmc.app.typepath.flagiften: + raise IncompatibleiLOVersionError("This certificate is not available on this system.") + + tlsfile = options.certfile + + try: + with open(tlsfile) as certfile: + certdata = certfile.read() + certfile.close() + except: + raise InvalidFileInputError("Error loading the specified file.") + + select = "HpeCertAuth." + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + bodydict = results.resp.dict + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + for item in bodydict["Actions"]: + if "ImportCACertificate" in item: + action = item.split("#")[-1] + path = bodydict["Actions"][item]["target"] + break + + body = {"Action": action, "Certificate": certdata} + + try: + result = self.rdmc.app.post_handler(path, body) + if result.status == 200: + self.rdmc.ui.printer("Imported the CA certificate successfully\n") + return ReturnCodes.SUCCESS + except IdTokenError: + self.rdmc.ui.printer("Insufficient Privilege to import CA certificate\n") + return ReturnCodes.RIS_MISSING_ID_TOKEN + except IloLicenseError: + self.rdmc.ui.error("Error occurred while importing CA certificate") + return ReturnCodes.ILO_LICENSE_ERROR + except IncompatibleiLOVersionError: + self.rdmc.ui.error("iLO FW version on this server doesnt support this operation") + return ReturnCodes.INCOMPATIBLE_ILO_VERSION_ERROR + + def importplatformhelper(self, options): + """Helper function for importing a platform certificate + :param options: options attributes + :type options: attributes + """ + + # ss_instance = next( + # iter(self.rdmc.app.select("SecurityService.", path_refresh=True)) + # ) + if getattr(options, "ldevid_cert"): + instance_path_uri = "/redfish/v1/Managers/1/Diagnostics/Actions/HpeiLODiagnostics.ImportiLOLDevID" + else: + ss_instance = self.rdmc.app.get_handler("/redfish/v1/Managers/1/Diagnostics/", service=True, silent=True) + instance_path_uri = None + + if getattr(options, "ldevid_cert"): + type = "iLOLDevID" + elif getattr(options, "idevid_cert"): + type = "iLOIDevID" + elif getattr(options, "systemiak_cert"): + type = "SystemIAK" + elif getattr(options, "systemidevid_cert"): + type = "SystemIDevID" + elif getattr(options, "platform_cert"): + type = "PlatformCert" + else: + raise InvalidCommandLineErrorOPTS( + "An invalid set of options were selected...verify " " options were selected correctly and re-try." + ) + type_totarget = "#HpeiLODiagnostics.Import" + type + + if not instance_path_uri: + instance_path_uri = ss_instance.dict["Actions"][type_totarget]["target"] + + # instance_path_uri = ( + # ( + # ss_instance.dict[type]["Certificates"][ + # self.rdmc.app.typepath.defs.hrefstring + # ] + # ) + # if ss_instance.dict.get("SystemIAK") + # else None + # ) + if instance_path_uri: + certdata = None + try: + with open(options.certfile) as cf: + certdata = cf.read() + except: + raise InvalidFileInputError("Error loading the specified file.") + + payload = {"Certificate": certdata} + + if certdata: + try: + result = self.rdmc.app.post_handler(instance_path_uri, payload) + if result.status == 200: + self.rdmc.ui.printer("Imported the %s certificate successfully\n" % type) + return ReturnCodes.SUCCESS + except IdTokenError: + self.rdmc.ui.printer("Insufficient Privilege to import %s certificate\n" % type) + return ReturnCodes.RIS_MISSING_ID_TOKEN + except IloLicenseError: + self.rdmc.ui.error("Error Occured while importing %s certificate\n" % type) + return ReturnCodes.ILO_LICENSE_ERROR + except IncompatibleiLOVersionError: + self.rdmc.ui.error("iLO FW version on this server doesnt support this operation") + return ReturnCodes.INCOMPATIBLE_ILO_VERSION_ERROR + + def certificatesvalidation(self, options): + """certificates validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for certificates command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + subcommand_parser = customparser.add_subparsers(dest="command") + + # gen csr sub-parser + gen_csr_help = ( + "Please use the below example command to execute the same function." + ) + gen_csr_parser = subcommand_parser.add_parser( + "csr", + help=gen_csr_help, + description=gen_csr_help + "\nexample: certificate gen_csr [ORG_NAME] [ORG_UNIT]" + " [COMMON_NAME] [COUNTRY] [STATE] [CITY]\n\nNOTE: please make " + "certain the order of arguments is correct.", + formatter_class=RawDescriptionHelpFormatter, + ) + gen_csr_parser.add_argument( + "csr_orgname", + help="Organization name. i.e. Hewlett Packard Enterprise.", + metavar="ORGNAME", + ) + gen_csr_parser.add_argument( + "csr_orgunit", + help="Organization unit. i.e. Intelligent Provisioning.", + metavar="ORGUNIT", + ) + gen_csr_parser.add_argument( + "csr_commonname", + help="Organization common name. i.e. Common Organization Name.", + metavar="ORGCNAME", + ) + gen_csr_parser.add_argument( + "csr_country", + help="Organization country. i.e. United States.", + metavar="ORGCOUNTRY", + ) + gen_csr_parser.add_argument("csr_state", help="Organization state. i.e. Texas.", metavar="ORGSTATE") + gen_csr_parser.add_argument("csr_city", help="Organization city. i.e. Houston.", metavar="ORGCITY") + self.cmdbase.add_login_arguments_group(gen_csr_parser) + + # get csr + get_csr_help = ( + "Retrieve the generated certificate signing request (CSR) printed to the " "console or to a json file." + ) + get_csr_parser = subcommand_parser.add_parser( + "getcsr", + help=get_csr_help, + description=get_csr_help + + "\nexample: certificate getcsr\nexample: certificate getcsr --TLS_CERT/--PLATFORM_CERT" + "-f mycsrfile.json", + formatter_class=RawDescriptionHelpFormatter, + ) + get_csr_parser.add_argument( + "--TLS_CERT", + dest="TLSCERT", + help="specify to retrieve a TLS/SSL certificate signing request.", + action="store_true", + default=None, + ) + get_csr_parser.add_argument( + "--PLATFORM_CERT", + dest="PLATFORM", + help="specify to retrieve a platform certificate signing request.", + action="store_true", + default=None, + ) + get_csr_parser.add_argument( + "-f", + "--filename", + dest="filename", + help="Use this flag if you wish to use a different" + " filename for the certificate signing request. The default" + " filename is %s." % __filename__, + action="append", + default=None, + ) + self.cmdbase.add_login_arguments_group(get_csr_parser) + + # ca certificate + ca_help = "This command has been deprecated. Please use the below example command to execute the same function." + ca_parser = subcommand_parser.add_parser( + "ca", + help=ca_help, + description=ca_help + "\nexample: certificate import --ca_cert mycertfile.txt\nNote: The " + "certificate must be in X.509 format", + formatter_class=RawDescriptionHelpFormatter, + ) + ca_parser.add_argument("certfile", help="X.509 formatted CA certificate", metavar="CACERTFILE") + self.cmdbase.add_login_arguments_group(ca_parser) + + # crl certificate + crl_help = ( + "This command has been deprecated. Please use the below example command to execute the same function." + ) + crl_parser = subcommand_parser.add_parser( + "crl", + help=crl_help, + description=crl_help + "\nexample: certificate import --crl_cert https://mycertfileurl/mycertfile.txt" + "\nNote: The certificate must be in X.509 format", + formatter_class=RawDescriptionHelpFormatter, + ) + crl_parser.add_argument( + "certfile_url", + help="URL pointing to the location of the X.509 CA certificate", + metavar="CERTFILEURL", + ) + self.cmdbase.add_login_arguments_group(crl_parser) + + # tls certificate + tls_help = ( + "This command has been deprecated. Please use the below example command to execute the same function." + ) + tls_parser = subcommand_parser.add_parser( + "tls", + help=tls_help, + description=tls_help + "\nexample: certificate import --tls_cert mycertfile.txt\nNote: The " + "certificate must be in TLS X.509 format", + formatter_class=RawDescriptionHelpFormatter, + ) + tls_parser.add_argument("certfile", help="X.509 formatted TLS certificate", metavar="TLSCERTFILE") + self.cmdbase.add_login_arguments_group(tls_parser) + + # view certificate + view_help = "View Certificates (https or scep)" + view_parser = subcommand_parser.add_parser( + "view", + help=view_help, + description=view_help + + "\nexample: certificate view --https_cert \n or \n certificate view --scep_cert \n" + + "Webserver certificate whether self-signed or manually imported or issued by SCEP server can be viewed", + formatter_class=RawDescriptionHelpFormatter, + ) + view_parser.add_argument( + "--scep_cert", + dest="scep_cert", + help="Gets the information of SCEP settings for iLO such as SCEP enable status, URL of the SCEP server," + + " ChallengePassword, SCEP CA certificate name, webserver CSR subject contents, SCEP enrollment status", + action="store_true", + ) + view_parser.add_argument( + "--https_cert", + dest="https_cert", + help="Gets the https certificate whether self-signed or manually imported or issued by SCEP server", + action="store_true", + ) + self.cmdbase.add_login_arguments_group(view_parser) + + # import certificate + import_help = "Imports the Certificates." + import_parser = subcommand_parser.add_parser( + "import", + help=import_help, + description=import_help + + "\nexample: certificate import --scep_cert certificate.txt \n" + + " make sure you are providing a .txt file input.\n", + formatter_class=RawDescriptionHelpFormatter, + ) + import_parser.add_argument( + "--scep_cert", + dest="scep_cert", + help="Gets the https certificate whether self-signed or manually imported or issued by SCEP server", + action="store_true", + ) + import_parser.add_argument( + "--ca_cert", + dest="ca_cert", + help="Upload a X.509 formatted CA certificate to iLO.", + action="store_true", + ) + import_parser.add_argument( + "--crl_cert", + dest="crl_cert", + help="Provide iLO with a URL to retrieve the X.509 formatted CA certificate.", + action="store_true", + ) + import_parser.add_argument( + "--tls_cert", + dest="tls_cert", + help="Upload a X.509 TLS certificate to iLO.", + action="store_true", + ) + import_parser.add_argument( + "--idevid_cert", + dest="idevid_cert", + help="Upload an IDEVID certificate.", + action="store_true", + ) + import_parser.add_argument( + "--ldevid_cert", + dest="ldevid_cert", + help="Upload an LDEVID certificate.", + action="store_true", + ) + import_parser.add_argument( + "--systemiak_cert", + dest="systemiak_cert", + help="Upload an System IAK certificate.", + action="store_true", + ) + import_parser.add_argument( + "--systemidevid_cert", + dest="systemidevid_cert", + help="Upload an system IDEVID certificate.", + action="store_true", + ) + import_parser.add_argument( + "--platform_cert", + dest="platform_cert", + help="Upload a platform certificate.", + action="store_true", + ) + import_parser.add_argument( + "--from_url", + dest="from_url", + help="Use this flag to specify a URL for certificate import", + action="append", + default=None, + ) + import_parser.add_argument( + "certfile", + help="Certificate can be imported via POST action", + metavar="certfile", + ) + self.cmdbase.add_login_arguments_group(import_parser) + + # export cert + export_help = "Pull an X.509 formatted platform certificate from iLO." + export_parser = subcommand_parser.add_parser( + "export", + help=export_help, + description=export_help + "\nExample: certificate export --IDEVID -f " "myidevidfile\n", + formatter_class=RawDescriptionHelpFormatter, + ) + + export_parser.add_argument( + "--idevid_cert", + dest="idevid_cert", + help="Specify for an IDEVID certificate. ", + action="store_true", + default=None, + ) + export_parser.add_argument( + "--ldevid_cert", + dest="ldevid_cert", + help="Specify for an LDEVID certificate. ", + action="store_true", + default=None, + ) + export_parser.add_argument( + "--systemiak_cert", + dest="systemiak_cert", + help="Specify for a system IAK certificate.", + action="store_true", + default=None, + ) + export_parser.add_argument( + "--systemidevid_cert", + dest="systemidevid_cert", + help="Specify for a system IDEVID certificate.", + action="store_true", + default=None, + ) + export_parser.add_argument( + "--platform_cert", + dest="platform_cert", + help="Specify for a platform certificate.", + action="store_true", + default=None, + ) + export_parser.add_argument( + "--id", + dest="id", + help="Optionally specify the certificate instance, if multiples are available. If" + "the instance specified is not available, then the next is retrieved. Default is" + "the first instance", + default=1, + ) + export_parser.add_argument( + "-f", + "--filename", + dest="filename", + help="Use this flag to import a certificate from or export a certificate to a file.", + action="append", + default=None, + ) + + self.cmdbase.add_login_arguments_group(export_parser) + + # delete certificate + delete_help = "Deletes the https Certificate" + delete_parser = subcommand_parser.add_parser( + "delete", + help=delete_help, + description=delete_help + "\nexample: certificate delete \n delete the https_cert certificate ", + formatter_class=RawDescriptionHelpFormatter, + ) + + self.cmdbase.add_login_arguments_group(delete_parser) + + # gen csr sub-parser + gencsr_help = ( + "Generate a certificate signing request (CSR) for iLO SSL certificate " + "authentication.\nNote: iLO will create a Base64 encoded CSR in PKCS " + "#10 Format." + ) + gencsr_parser = subcommand_parser.add_parser( + "gen_csr", + help=gencsr_help, + description=gen_csr_help + "\nexample: certificate gen_csr [ORG_NAME] [ORG_UNIT]" + " [COMMON_NAME] [COUNTRY] [STATE] [CITY] [INCLUDEIP] \n\nNOTE: please make " + "certain the order of arguments is correct.", + formatter_class=RawDescriptionHelpFormatter, + ) + gencsr_parser.add_argument( + "gencsr_orgname", + help="Organization name. i.e. Hewlett Packard Enterprise.", + metavar="ORGNAME", + ) + gencsr_parser.add_argument( + "gencsr_orgunit", + help="Organization unit. i.e. Intelligent Provisioning.", + metavar="ORGUNIT", + ) + gencsr_parser.add_argument( + "gencsr_commonname", + help="Organization common name. i.e. Common Organization Name.", + metavar="ORGCNAME", + ) + gencsr_parser.add_argument( + "gencsr_country", + help="Organization country. i.e. United States.", + metavar="ORGCOUNTRY", + ) + gencsr_parser.add_argument("gencsr_state", help="Organization state. i.e. Texas.", metavar="ORGSTATE") + gencsr_parser.add_argument("gencsr_city", help="Organization city. i.e. Houston.", metavar="ORGCITY") + gencsr_parser.add_argument( + "gencsr_parser_includeIP", + help="Include IP. i.e. True or False.", + metavar="INCLUDEIP", + ) + self.cmdbase.add_login_arguments_group(gencsr_parser) + + # automatic enrollment sub-parser + autoenroll_help = ( + "Use this command for invoking the auto enroll the certificate enrollment process" + "\nMake sure you have imported the scep CA certificate " + "before head using certificate import --scep certifcate.txt" + ) + autoenroll_parser = subcommand_parser.add_parser( + "auto_enroll", + help=autoenroll_help, + description=autoenroll_help + "\nexample: certificate auto_enroll [ORG_NAME] [ORG_UNIT]" + " [COMMON_NAME] [COUNTRY] [STATE] [CITY] [SCEP_ADDRESS] [CHALLENGEPASSWORD] " + "[SERVICEENABLED] [INCLUDEIP]]\n\nNOTE: please make " + "certain the order of arguments is correct.", + formatter_class=RawDescriptionHelpFormatter, + ) + autoenroll_parser.add_argument( + "autoenroll_orgname", + help="Organization name. i.e. Hewlett Packard Enterprise.", + metavar="ORGNAME", + ) + autoenroll_parser.add_argument( + "autoenroll_orgunit", + help="Organization unit. i.e. Intelligent Provisioning.", + metavar="ORGUNIT", + ) + autoenroll_parser.add_argument( + "autoenroll_commonname", + help="Organization common name. i.e. Common Organization Name.", + metavar="ORGCNAME", + ) + autoenroll_parser.add_argument( + "autoenroll_country", + help="Organization country. i.e. United States.", + metavar="ORGCOUNTRY", + ) + autoenroll_parser.add_argument("autoenroll_state", help="Organization state. i.e. Texas.", metavar="ORGSTATE") + autoenroll_parser.add_argument("autoenroll_city", help="Organization city. i.e. Houston.", metavar="ORGCITY") + autoenroll_parser.add_argument( + "autoenroll_scep_enrollAddress", + help="Scep-enroll dll address", + metavar="AEADDRESS", + ) + autoenroll_parser.add_argument( + "autoenroll_challengepassword", + help="challenge password", + metavar="AECHALLPASS", + ) + autoenroll_parser.add_argument( + "autoenroll_ScepService", + help="Scep service enable or disable", + metavar="AESCEPSERVICE", + ) + autoenroll_parser.add_argument( + "autoenroll_includeIP", + help="Include IP. i.e. True or False.", + metavar="INCLUDEIP", + ) + self.cmdbase.add_login_arguments_group(autoenroll_parser) diff --git a/ilorest/extensions/iLO_COMMANDS/ClearRestApiStateCommand.py b/ilorest/extensions/iLO_COMMANDS/ClearRestApiStateCommand.py new file mode 100644 index 0000000..19000d5 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/ClearRestApiStateCommand.py @@ -0,0 +1,130 @@ +### +# Copyright 2016-2021 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 -*- +""" Clear Rest API State Command for rdmc """ + + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class ClearRestApiStateCommand: + """Clear the rest api state of the server""" + + def __init__(self): + self.ident = { + "name": "clearrestapistate", + "usage": None, + "description": "Clears the persistent rest api state.\n\texample: " + "clearrestapistate\n\n\tNote: Some types such as Bios, " + "Iscsi, and SmartStorageConfig will be unavailable until " + "a system reboot occurs after running this command.", + "summary": "Clears the persistent state of the REST API. Some " + "portions of the API may not be available until after the server reboots.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main clearrestapistate function. + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if args: + raise InvalidCommandLineError("clearrestapistate command takes no arguments.") + + self.clearrestapistatevalidation(options) + + select = "Manager." + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Manager. not found.") + + bodydict = results.resp.dict["Oem"][self.rdmc.app.typepath.defs.oemhp] + try: + for item in bodydict["Actions"]: + if "ClearRestApiState" in item: + if self.rdmc.app.typepath.defs.isgen10: + action = item.split("#")[-1] + else: + action = "ClearRestApiState" + path = bodydict["Actions"][item]["target"] + body = {"Action": action} + break + except: + body = {"Action": "ClearRestApiState", "Target": "/Oem/Hp"} + self.rdmc.app.post_handler(path, body) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def clearrestapistatevalidation(self, options): + """clearrestapistate method validation function. + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/iLO_COMMANDS/ComputeOpsManagementCommand.py b/ilorest/extensions/iLO_COMMANDS/ComputeOpsManagementCommand.py new file mode 100644 index 0000000..4e92fcb --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/ComputeOpsManagementCommand.py @@ -0,0 +1,394 @@ +### +# Copyright 2021-2022 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 -*- +""" computeopsmanagement Command for rdmc """ + +import time +from argparse import RawDescriptionHelpFormatter + +try: + from rdmc_helper import ( + CloudConnectFailedError, + CloudConnectTimeoutError, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoCurrentSessionEstablished, + ProxyConfigFailedError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + CloudConnectFailedError, + CloudConnectTimeoutError, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoCurrentSessionEstablished, + ProxyConfigFailedError, + ReturnCodes, + ) + +from redfish.ris.ris import SessionExpired + + +class ComputeOpsManagementCommand: + """Main new command template class""" + + def __init__(self): + self.ident = { + "name": "computeopsmanagement", + "usage": "computeopsmanagement\n\n", + "description": "Run to enable your servers to be discovered, " + "monitored and managed through ComputeOpsManagement\n\t" + "Example:\n\tcomputeopsmanagement connect or \n\t" + "computeopsmanagement connect --activationkey or \n\t" + "computeopsmanagement connect --activationkey --proxy http://proxy.abc.com:8080 or \n\t" + "computeopsmanagement disconnect or \n\t" + "computeopsmanagement status or \n\t" + "computeopsmanagement status -j\n", + "summary": "Enables the server to be discovered, monitored and managed through ComputeOpsManagement", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def proxy_config(self, proxy_server): + """Main cloudconnect worker function + + :param proxy_server: proxy + :type proxy_server: str. + """ + if proxy_server != "None": + try: + body = dict() + body["Oem"] = {} + body["Oem"]["Hpe"] = {} + body["Oem"]["Hpe"]["WebProxyConfiguration"] = {} + proxy_body = body["Oem"]["Hpe"]["WebProxyConfiguration"] + proxy_body["ProxyServer"] = None + proxy_body["ProxyUserName"] = None + proxy_body["ProxyPassword"] = None + if "https" in proxy_server: + proxy_body["ProxyPort"] = 443 + else: + proxy_body["ProxyPort"] = 80 + if "@" in proxy_server: + proxy = proxy_server.split("@") + proxy_usr_pass = proxy[0] + proxy_srv_port = proxy[1] + if "//" in proxy_usr_pass: + proxy_usr_pass = proxy_usr_pass.split("//")[1] + if ":" in proxy_srv_port: + proxy = proxy_srv_port.split(":") + proxy_body["ProxyServer"] = proxy[0] + proxy_body["ProxyPort"] = int(proxy[1]) + else: + proxy_body["ProxyServer"] = proxy_srv_port + if ":" in proxy_usr_pass: + proxy = proxy_usr_pass.split(":") + proxy_body["ProxyPassword"] = proxy[1] + proxy_body["ProxyUserName"] = proxy[0] + else: + proxy_body["ProxyUserName"] = proxy_usr_pass + else: + proxy_srv_port = proxy_server + if "//" in proxy_srv_port: + proxy_srv_port = proxy_srv_port.split("//")[1] + if ":" in proxy_srv_port: + proxy = proxy_srv_port.split(":") + proxy_body["ProxyServer"] = proxy[0] + proxy_body["ProxyPort"] = int(proxy[1]) + else: + proxy_body["ProxyServer"] = proxy_srv_port + + path = self.rdmc.app.getidbytype("NetworkProtocol.") + + if path and body: + self.rdmc.ui.printer("Setting Proxy configuration...\n", verbose_override=True) + self.rdmc.app.patch_handler(path[0], body, service=False, silent=True) + except: + raise ProxyConfigFailedError("Setting Proxy Server Configuration Failed.\n") + else: + try: + body = dict() + body["Oem"] = {} + body["Oem"]["Hpe"] = {} + body["Oem"]["Hpe"]["WebProxyConfiguration"] = {} + proxy_body = body["Oem"]["Hpe"]["WebProxyConfiguration"] + proxy_body["ProxyServer"] = "" + proxy_body["ProxyPort"] = None + proxy_body["ProxyUserName"] = "" + proxy_body["ProxyPassword"] = None + path = self.rdmc.app.getidbytype("NetworkProtocol.") + + if path and body: + self.rdmc.ui.printer("Clearing Proxy configuration...\n", verbose_override=True) + self.rdmc.app.patch_handler(path[0], body, service=False, silent=True) + except: + raise ProxyConfigFailedError("Clearing Proxy Server Configuration Failed.\n") + + def connect_cloud(self, activationkey=None): + """cloud connect function + + :param activationkey: activation key + :type activationkey: str. + """ + path = self.rdmc.app.typepath.defs.managerpath + resp = self.rdmc.app.get_handler(path, service=False, silent=True) + status = resp.dict["Oem"]["Hpe"]["CloudConnect"]["CloudConnectStatus"] + if status == "Connected": + self.rdmc.ui.printer("Warning: ComputeOpsManagement is already connected.\n") + return ReturnCodes.SUCCESS + body = dict() + # Temporary + # body['CloudActivateURL'] = "https://qa-devices.rugby.hpeserver.management/inventory/compute-provision" + if activationkey: + body["ActivationKey"] = activationkey + else: + body = {} + path = self.rdmc.app.typepath.defs.managerpath + "Actions" + self.rdmc.app.typepath.defs.oempath + path = path + "/HpeiLO.EnableCloudConnect" + try: + if path: + self.rdmc.ui.printer("Connecting to ComputeOpsManagement...", verbose_override=True) + self.rdmc.app.post_handler(path, body, service=False, silent=True) + except: + raise CloudConnectFailedError("ComputeOpsManagement connection Failed.\n") + start_time = time.time() + allowed_seconds = 120 + time_increment = 5 + i = 1 + while True: + # time.sleep(time_increment * i) + time.sleep(time_increment) + current_time = time.time() + elapsed_time = current_time - start_time + if elapsed_time > allowed_seconds: + self.rdmc.ui.printer("\n") + raise CloudConnectTimeoutError( + "ComputeOpsManagement connection timed out, Please check the " + "activation key and network or proxy settings and try again.\n" + ) + else: + path = self.rdmc.app.typepath.defs.managerpath + resp = self.rdmc.app.get_handler(path, service=False, silent=True) + status = resp.dict["Oem"]["Hpe"]["CloudConnect"]["CloudConnectStatus"] + # self.rdmc.ui.printer("ComputeOpsManagement connection status is %s.\n" % status) + if status == "Connected": + # Check again after 10 seconds before breaking the loop + time.sleep(10) + resp = self.rdmc.app.get_handler(path, service=False, silent=True) + status = resp.dict["Oem"]["Hpe"]["CloudConnect"]["CloudConnectStatus"] + if status == "Connected": + self.rdmc.ui.printer("\n") + self.rdmc.ui.printer("ComputeOpsManagement connection is successful.\n") + break + else: + self.rdmc.ui.printer("..") + i = i + 1 + + def disconnect_cloud(self): + """cloud disconnect function""" + path = self.rdmc.app.typepath.defs.managerpath + resp = self.rdmc.app.get_handler(path, service=False, silent=True) + if resp.status != 200: + raise SessionExpired("Invalid session. Please logout and log back in or include credentials.") + cloud_status = resp.dict["Oem"]["Hpe"]["CloudConnect"]["CloudConnectStatus"] + if cloud_status == "Connected": + path = self.rdmc.app.typepath.defs.managerpath + "Actions" + self.rdmc.app.typepath.defs.oempath + path = path + "/HpeiLO.DisableCloudConnect" + body = dict() + try: + if path: + self.rdmc.ui.printer("Disconnecting ComputeOpsManagement...\n", verbose_override=True) + self.rdmc.app.post_handler(path, body) + except: + raise CloudConnectFailedError("ComputeOpsManagement is not disconnected.\n") + else: + self.rdmc.ui.printer( + "Warning: ComputeOpsManagement is not at all connected.\n", + verbose_override=True, + ) + + def cloud_status(self, json=False): + """cloud connect function + + :param json: json + :type json: bool + """ + path = self.rdmc.app.typepath.defs.managerpath + resp = self.rdmc.app.get_handler(path, service=False, silent=True) + if resp.status != 200: + raise SessionExpired("Invalid session. Please logout and log back in or include credentials.") + cloud_info = resp.dict["Oem"]["Hpe"]["CloudConnect"] + output = "------------------------------------------------\n" + output += "ComputeOpsManagement connection status\n" + output += "------------------------------------------------\n" + output += "ComputeOpsManagement Status : %s\n" % (cloud_info["CloudConnectStatus"]) + if cloud_info["CloudConnectStatus"] != "NotEnabled": + if "CloudActivateURL" in cloud_info: + output += "CloudActivateURL : %s\n" % (cloud_info["CloudActivateURL"]) + output += "ActivationKey : %s\n" % (cloud_info["ActivationKey"]) + if not json: + self.rdmc.ui.printer(output, verbose_override=True) + else: + self.rdmc.ui.print_out_json(cloud_info) + + def run(self, line, help_disp=False): + """Wrapper function for cloudconnect main function + + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.cmdbase.login_select_validation(self, options) + if not self.rdmc.app.redfishinst: + raise NoCurrentSessionEstablished("Please login to iLO and retry the command") + + ilo_ver = self.rdmc.app.getiloversion() + if ilo_ver < 5.247: + raise IncompatibleiLOVersionError( + "ComputeOpsManagement Feature is only available with iLO 5 version 2.47 or higher.\n" + ) + + # validation checks + self.cloudconnectvalidation(options) + if options.command: + if options.command.lower() == "connect": + if options.proxy: + self.proxy_config(options.proxy) + if options.activationkey and options.activationkey.isalnum() and len(options.activationkey) <= 32: + self.connect_cloud(activationkey=options.activationkey) + elif not options.activationkey: + self.connect_cloud() + else: + raise InvalidCommandLineError( + "Activation Key %s is not alphanumeric or not of length 32." % str(options.activationkey) + ) + elif options.command.lower() == "disconnect": + self.disconnect_cloud() + elif options.command.lower() == "status": + if options.json: + self.cloud_status(json=True) + else: + self.cloud_status() + else: + raise InvalidCommandLineError("%s is not a valid option for this " "command." % str(options.command)) + else: + raise InvalidCommandLineError( + "Please provide either connect, disconnect or status as additional subcommand." + " For help or usage related information, use -h or --help" + ) + # logout routine + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def cloudconnectvalidation(self, options): + """new command method validation function""" + # Check if Cloud Connect feature is enabled in iLO. + path = self.rdmc.app.typepath.defs.managerpath + resp = self.rdmc.app.get_handler(path, service=True, silent=True) + if resp.status == 200: + oem_actions = resp.dict["Oem"]["Hpe"]["Actions"] + # print(oem_actions) + if "#HpeiLO.EnableCloudConnect" not in oem_actions or "#HpeiLO.DisableCloudConnect" not in oem_actions: + raise CloudConnectFailedError("ComputeOpsManagement is disabled in this iLO.\n") + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + subcommand_parser = customparser.add_subparsers(dest="command") + connect_help = "To connect to ComputeOpsManagement\n" + # connect sub-parser + connect_parser = subcommand_parser.add_parser( + "connect", + help=connect_help, + description=connect_help + "\n\tExample:\n\tcomputeopsmanagement connect or" + "\n\tcomputeopsmanagement connect --proxy http://proxy.abc.com:8080 or " + "\n\tcomputeopsmanagement connect --proxy None or " + "\n\tcomputeopsmanagement connect --activationkey 123456789EFGA or " + "\n\tcomputeopsmanagement connect --activationkey 123456789EFGA --proxy http://proxy.abc.com:8080 or " + "\n\tcomputeopsmanagement connect --activationkey 123456789EFGA --proxy None", + formatter_class=RawDescriptionHelpFormatter, + ) + connect_parser.add_argument( + "--activationkey", + dest="activationkey", + help="activation key is optional for connecting", + required=False, + type=str, + default=None, + ) + connect_parser.add_argument( + "--proxy", + dest="proxy", + help="to set or clear proxy while connecting", + type=str, + default=None, + ) + self.cmdbase.add_login_arguments_group(connect_parser) + status_help = "To check the ComputeOpsManagement connection status\n" + status_parser = subcommand_parser.add_parser( + "status", + help=status_help, + description=status_help + "\n\tExample:\n\tcomputeopsmanagement status or " + "\n\tcomputeopsmanagement status -j", + formatter_class=RawDescriptionHelpFormatter, + ) + status_parser.add_argument( + "-j", + "--json", + dest="json", + help="to print in json format", + action="store_true", + default=False, + ) + self.cmdbase.add_login_arguments_group(status_parser) + disconnect_help = "To disconnect from ComputeOpsManagement\n" + disconnect_parser = subcommand_parser.add_parser( + "disconnect", + help=disconnect_help, + description=disconnect_help + "\n\tExample:\n\tcomputeopsmanagement disconnect", + formatter_class=RawDescriptionHelpFormatter, + ) + self.cmdbase.add_login_arguments_group(disconnect_parser) diff --git a/ilorest/extensions/iLO_COMMANDS/DirectoryCommand.py b/ilorest/extensions/iLO_COMMANDS/DirectoryCommand.py new file mode 100644 index 0000000..e01eaf1 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/DirectoryCommand.py @@ -0,0 +1,870 @@ +### +# Copyright 2017 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 -*- +""" Directory Command for rdmc """ + +import re +import sys +from argparse import Action, RawDescriptionHelpFormatter + +from redfish.ris.rmc_helper import IdTokenError, IloResponseError + +try: + from rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ResourceExists, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ResourceExists, + ReturnCodes, + ) + +__subparsers__ = ["ldap", "kerberos", "test"] + +PRIVKEY = { + 1: ("Login", "AssignedPrivileges"), + 2: ("RemoteConsolePriv", "OemPrivileges"), + 3: ("ConfigureUsers", "AssignedPrivileges"), + 4: ("ConfigureManager", "AssignedPrivileges"), + 5: ("VirtualMediaPriv", "OemPrivileges"), + 6: ("VirtualPowerAndResetPriv", "OemPrivileges"), + 7: ("HostNICConfigPriv", "OemPrivileges"), + 8: ("HostBIOSConfigPriv", "OemPrivileges"), + 9: ("HostStorageConfigPriv", "OemPrivileges"), + 10: ("SystemRecoveryConfigPriv", "OemPrivileges"), + 11: ("ConfigureSelf", "AssignedPrivileges"), + 12: ("ConfigureComponents", "AssignedPrivileges"), +} + + +class _DirectoryParse(Action): + def __init__(self, option_strings, dest, nargs, **kwargs): + super(_DirectoryParse, self).__init__(option_strings, dest, nargs, **kwargs) + + def __call__(self, parser, namespace, values, option_strings): + """Helper for parsing options""" + if option_strings.endswith("disable"): + setattr(namespace, self.dest, False) + elif option_strings.endswith("enable"): + setattr(namespace, self.dest, True) + elif option_strings.endswith("enablelocalauth"): + setattr(namespace, self.dest, False) + elif option_strings.endswith("disablelocalauth"): + setattr(namespace, self.dest, True) + elif option_strings == "--removerolemap": + setattr(namespace, self.dest, {"remove": []}) + for role in next(iter(values)).split(";"): + role = role.replace('"', "") + if role: + namespace.roles["remove"].append(role) + elif option_strings == "--addrolemap": + setattr(namespace, self.dest, {"add": []}) + for role in next(iter(values)).split(";"): + role = role.replace('"', "") + if role and re.match(".*:.*", role): + privs = role.split(":")[0].split(";") + if len(privs) > 1: + for priv in privs: + try: + if priv and int(priv) > 12: + try: + parser.error("Invalid privilege number added %s." % priv) + except SystemExit: + raise InvalidCommandLineErrorOPTS("") + except ValueError: + try: + parser.error("Privileges must be added as numbers.") + except SystemExit: + raise InvalidCommandLineErrorOPTS("") + namespace.roles["add"].append(role) + else: + try: + parser.error("Supply roles to add in form :") + except SystemExit: + raise InvalidCommandLineErrorOPTS("") + elif option_strings == "--addsearch": + setattr(namespace, self.dest, {"add": []}) + for search in next(iter(values)).split(";"): + if search: + namespace.search["add"].append(search) + + elif option_strings == "--removesearch": + setattr(namespace, self.dest, {"remove": []}) + for search in next(iter(values)).split(";"): + if search: + namespace.search["remove"].append(search) + + +class DirectoryCommand: + """Update directory settings on the server""" + + def __init__(self): + self.ident = { + "name": "directory", + "usage": None, + "description": "\tAdd credentials, service address, two search strings, and enable" + "\n\tLDAP directory service, remote role groups (mapping), local custom role\n\t" + "IDs with privileges.\n\n\tTo view help on specific sub-commands" + " run: directory -h\n\n\tExample: directory ldap -h\n", + "summary": "Update directory settings, add/delete directory roles, and test directory " + "settings on the currently logged in server.", + "aliases": ["ad", "activedirectory"], + "auxcommands": ["IloAccountsCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main directory Function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + ident_subparser = False + for cmnd in __subparsers__: + if cmnd in line: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + ident_subparser = True + break + if not ident_subparser: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line, default=True) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.directoryvalidation(options) + + if self.rdmc.app.getiloversion() < 5.140: + raise IncompatibleiLOVersionError("Directory settings are only available on " "iLO 5 1.40 or greater.") + results = None + if options.command.lower() == "ldap" or ( + (True if options.ldap_kerberos == "ldap" else False) if hasattr(options, "ldap_kerberos") else False + ): + try: + results = self.rdmc.app.select(selector="AccountService.", path_refresh=True)[0].dict + path = results[self.rdmc.app.typepath.defs.hrefstring] + oem = results["Oem"][self.rdmc.app.typepath.defs.oemhp] + local_auth = results["LocalAccountAuth"] + results = results["LDAP"] + if "RemoteRoleMapping" not in results: + rolemap = {"RemoteRoleMapping": {}} + results.update(rolemap) + name = "LDAP" + except (KeyError, IndexError): + raise NoContentsFoundForOperationError("Unable to gather LDAP settings.") + + elif options.command.lower() == "kerberos" or ( + (True if options.ldap_kerberos == "kerberos" else False) if hasattr(options, "ldap_kerberos") else False + ): + try: + results = self.rdmc.app.select(selector="AccountService.", path_refresh=True)[0].dict + path = results[self.rdmc.app.typepath.defs.hrefstring] + oem = results["Oem"][self.rdmc.app.typepath.defs.oemhp] + local_auth = results["LocalAccountAuth"] + results = results["ActiveDirectory"] + if "RemoteRoleMapping" not in results: + rolemap = {"RemoteRoleMapping": {}} + results.update(rolemap) + name = "ActiveDirectory" + except (KeyError, IndexError): + raise NoContentsFoundForOperationError("Unable to gather Kerberos settings.") + + if results: + keytab = None + payload = {} + if hasattr(options, "keytab"): + keytab = options.keytab + try: + directory_settings = self.directory_helper(results, options) + except IndexError: + directory_settings = self.directory_helper(results, options) + + if directory_settings: + payload[name] = directory_settings + + if hasattr(options, "authmode"): + if options.authmode: + payload.update( + {"Oem": {"Hpe": {"DirectorySettings": {"LdapAuthenticationMode": options.authmode}}}} + ) + + if not payload and not keytab: + if getattr(options, "json", False): + # self.rdmc.ui.print_out_json({name: results, 'LocalAccountAuth': local_auth, + # "Oem": {"Hpe": oem}}) + text_content = self.print_s(results, oem, local_auth, name) + self.rdmc.ui.print_out_json(text_content) + else: + self.print_settings(results, oem, local_auth, name) + + if payload: + priv_patches = {} + try: + if hasattr(options, "localauth"): + if options.localauth: + payload["LocalAccountAuth"] = "Enabled" if options.localauth else "Disabled" + elif local_auth: + payload["LocalAccountAuth"] = "Enabled" if local_auth else "Disabled" + except (NameError, AttributeError): + payload["LocalAccountAuth"] = "Disabled" + try: + maps = {} + if payload.get("LDAP"): + maps = payload["LDAP"].get("RemoteRoleMapping", {}) + elif payload.get("ActiveDirectory"): + maps = payload["ActiveDirectory"].get("RemoteRoleMapping", {}) + # Check if we need to modify roles after creating + for mapping in maps: + privs = mapping["LocalRole"].split(";") + if len(privs) > 1: + privs = [int(priv) for priv in privs if priv] + + if 10 in privs: + user_privs = self.auxcommands["iloaccounts"].getsesprivs() + if "SystemRecoveryConfigPriv" not in list(user_privs.keys()): + raise IdTokenError( + "The currently logged in account " + "must have the System Recovery Config privilege to " + "add the System Recovery Config privilege to a local " + "role group." + ) + + priv_patches[mapping["RemoteGroup"]] = privs + mapping["LocalRole"] = "ReadOnly" + except Exception: + pass + self.rdmc.ui.printer("Changing settings...\n") + try: + self.rdmc.app.patch_handler(path, payload) + except IloResponseError as excp: + if not results["ServiceEnabled"]: + self.rdmc.ui.error( + "You must enable this directory service before or " + "during assignment of username and password. Try adding the flag " + "--enable.\n", + excp, + ) + else: + raise IloResponseError + if priv_patches: + self.update_mapping_privs(priv_patches) + if keytab: + path = oem["Actions"][next(iter(oem["Actions"]))]["target"] + self.rdmc.ui.printer("Adding keytab...\n") + self.rdmc.app.post_handler(path, {"ImportUri": keytab}) + elif options.command.lower() == "test": + self.test_directory(options, json=getattr(options, "json", False)) + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def update_mapping_privs(self, roles_to_update): + """Helper function to update created role mappings to match user privileges. + + :param roles_to_update: Dictionary of privileges to update. + :type roles_to_update: dict + """ + self.rdmc.ui.printer("Updating privileges of created role maps...\n") + try: + results = self.rdmc.app.select(selector="AccountService.", path_refresh=True)[0].dict + roles = self.rdmc.app.getcollectionmembers(self.rdmc.app.getidbytype("RoleCollection.")[0]) + except (KeyError, IndexError): + raise NoContentsFoundForOperationError( + "Unable to gather Role settings. Roles may not " "be updated to match privileges requested." + ) + for rolemap in results["LDAP"]["RemoteRoleMapping"]: + for role in roles: + if role["RoleId"] == rolemap["LocalRole"]: + role["RemoteGroup"] = rolemap["RemoteGroup"] + break + for role in roles: + privs = {"AssignedPrivileges": [], "OemPrivileges": []} + + for update_role in list(roles_to_update.keys()): + if role.get("RemoteGroup", None) == update_role: + for priv in roles_to_update[update_role]: + privs[PRIVKEY[priv][1]].append(PRIVKEY[priv][0]) + try: + self.rdmc.app.patch_handler(role["@odata.id"], privs) + self.rdmc.ui.printer("Updated privileges for %s\n" % update_role) + except IloResponseError as excp: + self.rdmc.ui.error("Unable to update privileges for %s\n" % update_role, excp) + break + + def directory_helper(self, settings, options): + """Helper function to set the payload based on options and arguments + + :param settings: dictionary to change + :type settings: dict. + :param options: list of options + :type options: list. + """ + + payload = {} + serviceaddress = None + + if hasattr(options, "serviceaddress"): + if isinstance(options.serviceaddress, str): + serviceaddress = options.serviceaddress + if serviceaddress == '""' or serviceaddress == "''": + serviceaddress = "" + if hasattr(options, "port"): + if isinstance(options.port, str): + if serviceaddress is None: + serviceaddress = settings["ServiceAddresses"][0] + serviceaddress = serviceaddress + ":" + options.port + if hasattr(options, "realm"): + if isinstance(options.realm, str): + if serviceaddress is None: + serviceaddress = settings["ServiceAddresses"][0] + if options.realm == '""' or options.realm == "''": + options.realm = "" + serviceaddress = serviceaddress + "@" + options.realm + if serviceaddress is not None: + payload["ServiceAddresses"] = [serviceaddress] + + if hasattr(options, "enable"): + if options.enable is not None: + payload["ServiceEnabled"] = options.enable + + if hasattr(options, "ldap_username") and hasattr(options, "ldap_password"): + if options.ldap_username and options.ldap_password: + payload.update( + { + "Authentication": { + "Username": options.ldap_username, + "Password": options.ldap_password, + } + } + ) + + if hasattr(options, "roles"): + if options.roles: + payload["RemoteRoleMapping"] = self.role_helper(options.roles, settings["RemoteRoleMapping"]) + + if hasattr(options, "search"): + if options.search: + payload.update( + { + "LDAPService": { + "SearchSettings": self.search_helper( + options.search, + settings["LDAPService"]["SearchSettings"], + ) + } + } + ) + return payload + + def test_directory(self, options, json=False): + """Function to perform directory testing + + :param options: namespace of custom parser attributes which contain the original command + arguments for 'start/stop/viewresults' + :type options: namespace + :param json: Bool to print in json format or not. + :type json: bool. + """ + results = self.rdmc.app.select(selector="HpeDirectoryTest.", path_refresh=True)[0].dict + if options.start_stop_view.lower() == "start": + path = None + for item in results["Actions"]: + if "StartTest" in item: + path = results["Actions"][item]["target"] + break + if not path: + raise NoContentsFoundForOperationError("Unable to start directory test.") + self.rdmc.ui.printer( + "Starting the directory test. Monitor results with " 'command: "directory test viewresults".\n' + ) + self.rdmc.app.post_handler(path, {}) + elif options.start_stop_view.lower() == "stop": + path = None + for item in results["Actions"]: + if "StopTest" in item: + path = results["Actions"][item]["target"] + break + if not path: + raise NoContentsFoundForOperationError("Unable to stop directory test.") + self.rdmc.ui.printer("Stopping the directory test.\n") + self.rdmc.app.post_handler(path, {}) + elif options.start_stop_view.lower() == "viewresults": + if getattr(options, "json", False): + self.rdmc.ui.print_out_json(results["TestResults"]) + else: + for test in results["TestResults"]: + self.rdmc.ui.printer("Test: %s\n" % test["TestName"]) + self.rdmc.ui.printer("------------------------\n") + self.rdmc.ui.printer("Status: %s\n" % test["Status"]) + self.rdmc.ui.printer("Notes: %s\n\n" % test["Notes"]) + + def print_s(self, settings, oem_settings, local_auth_setting, name): + setting = "Kerberos" if name == "ActiveDirectory" else name + enable = str(settings["ServiceEnabled"]) + serviceaddress = settings["ServiceAddresses"][0] + address = serviceaddress if serviceaddress else "Not Set" + username = settings["Authentication"]["Username"] + dis_name = username if username else "Not Set" + auth = local_auth_setting + content = { + "Settings": setting, + "Enabled": enable, + "Service Address": address, + "Distinguished Name": dis_name, + "Local Account Authorization": auth, + } + if name.lower() == "activedirectory": + address_settings = oem_settings["KerberosSettings"] + port = address_settings["KDCServerPort"] + realm = address_settings["KerberosRealm"] if address_settings["KerberosRealm"] else "Not Set" + content.update({"Port": port, "Realm": realm}) + + else: + address_settings = oem_settings["DirectorySettings"] + port = address_settings["LdapServerPort"] + authmode = address_settings["LdapAuthenticationMode"] + content.update({"Port": port, "Authentication Mode": authmode}) + addsearch = [] + try: + count = 1 + for search in settings["LDAPService"]["SearchSettings"]["BaseDistinguishedNames"]: + # search = (count, search) + search = "Search %s: %s" % (count, search) + addsearch.append(search) + count += 1 + content.update({"Search Settings": addsearch}) + except KeyError: + search = "No Search Settings" + content.update({"Search Settings": {search}}) + addval = [] + if "RemoteRoleMapping" in settings: + if len(settings["RemoteRoleMapping"]) > 0: + for role in settings["RemoteRoleMapping"]: + roleadd = { + "Local Role": role["LocalRole"], + "Remote Group": role["RemoteGroup"], + } + addval.append(roleadd) + content.update({"Remote Role Mapping(s)": addval}) + else: + addval = {"No Remote Role Mappings"} + content.update({"Remote Role Mapping(s)": addval}) + else: + content.update({"No Remote Role Mappings"}) + return content + + def print_settings(self, settings, oem_settings, local_auth_setting, name): + """Pretty print settings of LDAP or Kerberos + + :param settings: settings to print + :type settings: dict. + :param oem_settings: oem_settings to print + :type oem_settings: dict. + :param local_auth_settings: local authorization setting + :type local_auth_settings: str. + :param name: type of setting (activedirectory or ldap) + :type name: str. + """ + + self.rdmc.ui.printer("%s settings:\n" % ("Kerberos" if name == "ActiveDirectory" else name)) + self.rdmc.ui.printer("--------------------------------\n") + self.rdmc.ui.printer("Enabled: %s\n" % str(settings["ServiceEnabled"])) + + serviceaddress = settings["ServiceAddresses"][0] + + self.rdmc.ui.printer("Service Address: %s\n" % (serviceaddress if serviceaddress else "Not Set")) + username = settings["Authentication"]["Username"] + self.rdmc.ui.printer("Distinguished Name: %s\n" % (username if username else "Not Set")) + + self.rdmc.ui.printer("Local Account Authorization: %s\n" % local_auth_setting) + + if name.lower() == "activedirectory": + address_settings = oem_settings["KerberosSettings"] + self.rdmc.ui.printer("Port: %s\n" % address_settings["KDCServerPort"]) + + self.rdmc.ui.printer( + "Realm: %s\n" % (address_settings["KerberosRealm"] if address_settings["KerberosRealm"] else "Not Set") + ) + else: + address_settings = oem_settings["DirectorySettings"] + self.rdmc.ui.printer("Port: %s\n" % address_settings["LdapServerPort"]) + self.rdmc.ui.printer("Authentication Mode: %s\n" % address_settings["LdapAuthenticationMode"]) + + try: + self.rdmc.ui.printer("Search Settings:\n") + count = 1 + for search in settings["LDAPService"]["SearchSettings"]["BaseDistinguishedNames"]: + self.rdmc.ui.printer("\tSearch %s: %s\n" % (count, search)) + count += 1 + except KeyError: + self.rdmc.ui.printer("\tNo Search Settings\n") + + if "RemoteRoleMapping" in settings: + self.rdmc.ui.printer("Remote Role Mapping(s):\n") + if len(settings["RemoteRoleMapping"]) > 0: + for role in settings["RemoteRoleMapping"]: + self.rdmc.ui.printer("\tLocal Role: %s\n" % role["LocalRole"]) + self.rdmc.ui.printer("\tRemote Group: %s\n" % role["RemoteGroup"]) + else: + self.rdmc.ui.printer("\tNo Remote Role Mappings.\n") + else: + self.rdmc.ui.printer("No Remote Role Mappings.\n") + + def role_helper(self, new_roles, curr_roles): + """Helper to prepare adding and removing roles for patching + + :param new_roles: dictionary of new roles to add or remove + :type new_roles: dict. + :param curr_roles: list of current roles on the system + :type curr_roles: list. + """ + final_roles = curr_roles + if "add" in new_roles: + for role in new_roles["add"]: + role = role.split(":", 1) + if not self.duplicate_group(role[1], curr_roles): + if len(curr_roles) > 0: + final_roles.append({"LocalRole": role[0], "RemoteGroup": role[1]}) + else: + final_roles = [{"LocalRole": role[0], "RemoteGroup": role[1]}] + curr_roles = final_roles + + else: + raise ResourceExists('Group DN "%s" already exists.' % role[1].split(":")[0]) + if "remove" in new_roles: + removed = False + for role in new_roles["remove"]: + removed = False + for item in reversed(final_roles): + if item["LocalRole"] == role: + if len(curr_roles) > 1: + del final_roles[final_roles.index(item)] + removed = True + break + elif len(curr_roles) == 1: + del final_roles[final_roles.index(item)] + final_roles = [{}] + removed = True + break + if not removed: + raise InvalidCommandLineError("Unable to find local role %s to delete" % role) + return final_roles + + def duplicate_group(self, group_dn, curr_roles): + """Checks if new role is a duplicate + + :param group_dn: group domain name from user + :type group_dn: str. + :param curr_roles: list of current roles + :type curr_roles: list. + """ + group_dn = group_dn.split(":")[0] + for item in curr_roles: + comp_dn = item["RemoteGroup"].split(":")[0] + if comp_dn == group_dn: + return True + return False + + def search_helper(self, new_searches, curr_searches): + """Helper to prepare search strings for patching + + :param new_serches: dictionary of new searches to add + :type new_searches: dict. + :param curr_searches: list of current searches + :type curr_searches: dict. + """ + final_searches = curr_searches + + if "add" in new_searches: + if "BaseDistinguishedNames" in final_searches: + for search in new_searches["add"]: + for key, value in final_searches.items(): + for v in value: + if search.lower() == v.lower(): + raise ResourceExists('Search Setting "%s" already exists.\n' % search) + + final_searches["BaseDistinguishedNames"].append(search) + else: + final_searches["BaseDistinguishedNames"] = new_searches["add"] + elif "remove" in new_searches: + to_remove = [] + + if "BaseDistinguishedNames" not in curr_searches: + raise NoContentsFoundForOperationError("No search strings to remove") + + for search in new_searches["remove"]: + if search in curr_searches["BaseDistinguishedNames"]: + to_remove.append(search) + else: + raise InvalidCommandLineError("Unable to find search %s to delete" % search) + for item in to_remove: + final_searches["BaseDistinguishedNames"].remove(item) + + if not final_searches["BaseDistinguishedNames"]: + sys.stdout.write("Attempting to delete all searches.\n") + final_searches["BaseDistinguishedNames"].append("") + + return final_searches + + def directoryvalidation(self, options): + """directory validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def options_argument_group(self, parser): + """Additional argument + + :param parser: The parser to add the removeprivs option group to + :type parser: ArgumentParser/OptionParser + """ + + parser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + subcommand_parser = customparser.add_subparsers(dest="command") + default_parser = subcommand_parser.add_parser("default") + + default_parser.add_argument( + "ldap_kerberos", + help="Specify LDAP or Kerberos configuration settings", + metavar="LDAP_KERBEROS", + nargs="?", + type=str, + default=None, + ) + self.cmdbase.add_login_arguments_group(default_parser) + + privilege_help = ( + "\n\nPRIVILEGES:\n\t1: Login\n\t2: Remote Console\n\t" + "3: User Config\n\t4: iLO (Manager) Config\n\t5: Virtual Media\n\t" + "6: Virtual Power and Reset\n\t7: Host NIC Config\n\t8: Host Bios Config\n\t9: " + "Host Storage Config\n\t10: System Recovery Config\n\t11: Self Password Change\n\t" + "12: Configure Components\n\n\tLOCAL ROLES:\n\tReadOnly\n\tOperator\n\tAdministrator" + "\n\n\tNOTE: The Self Password Change privilege is automatically added to roles with " + "the Login privilege." + ) + ldap_help = "Show, add or modify properties pertaining to iLO LDAP Configuration." + ldap_parser = subcommand_parser.add_parser( + __subparsers__[0], + help=ldap_help, + description=ldap_help + "\n\n\tSimply show LDAP configuration:\n\t\tdirectory ldap\n\n" + "To modify the LDAP username, password, service address, search strings or " + "enable/disable LDAP.\n\t\tdirectory ldap " + "--serviceaddress x.x.y.z --addsearch string1, string2 --enable.\n\n\tTo add role " + 'mapping.\n\t\tdirectory ldap --addrolemap "LocalRole1:"' + '"RemoteGroup3,LocalRole2:RemoteGroup4:SID.\n\n\tTo remove role mapping.\n\t\t' + "directory ldap --removerolemap LocalRole1, LocalRole2." + privilege_help, + formatter_class=RawDescriptionHelpFormatter, + ) + ldap_parser.add_argument( + "ldap_username", + help="The LDAP username used in verifying AD (optional outside of '--enable' and" "'--disable')", + metavar="USERNAME", + nargs="?", + type=str, + default=None, + ) + ldap_parser.add_argument( + "ldap_password", + help="The LDAP password used in verifying AD (optional outside of '--enable' and" "'--disable')", + metavar="PASSWORD", + nargs="?", + type=str, + default=None, + ) + ldap_parser.add_argument( + "--enable", + "--disable", + dest="enable", + type=str, + nargs="*", + action=_DirectoryParse, + help="Optionally add this flag to enable LDAP services.", + default=None, + ) + ldap_parser.add_argument( + "--addsearch", + "--removesearch", + dest="search", + nargs="*", + action=_DirectoryParse, + help="Optionally add this flag to add or remove search strings for " "generic LDAP services.", + type=str, + default={}, + ) + ldap_parser.add_argument( + "--serviceaddress", + dest="serviceaddress", + help="Optionally include this flag to set the service address of the LDAP Services.", + default=None, + ) + ldap_parser.add_argument( + "--port", + dest="port", + help="Optionally include this flag to set the port of the LDAP services.", + default=None, + ) + ldap_parser.add_argument( + "--addrolemap", + "--removerolemap", + dest="roles", + nargs="*", + action=_DirectoryParse, + help="Optionally add this flag to add or remove Role Mapping(s) for the LDAP and " + "Kerberos services. Remove EX: --removerolemap LocalRole1,LocalRole2 " + 'Add EX: --addrolemap "LocalRole1:RemoteGroup3,LocalRole2:RemoteGroup4\n\n"' + 'SID EX: --addrolemap "LocalRole1:RemoteGroup2:SID,LocalRole2:RemoteGroup5:SID' + "\n\nNOTE 1: Create a custom local role group (and subsequently assign to a role map)" + "by adding the numbers associated with privilege(s) desired separated by a semicolon" + "(;)\n\nNOTE 2: SID is optional", + type=str, + default={}, + ) + ldap_parser.add_argument( + "--enablelocalauth", + "--disablelocalauth", + dest="localauth", + nargs="*", + type=str, + action=_DirectoryParse, + help="Optionally include this flag if you wish to enable or disable authentication " "for local accounts.", + default=None, + ) + ldap_parser.add_argument( + "--authentication", + dest="authmode", + choices=["DefaultSchema", "ExtendedSchema"], + help="Optionally include this flag if you would like to choose a LDAP authentication " + "mode Valid choices are: DefaultSchema (Directory Default Schema or Schema-free) or " + "ExtendedSchema (HPE Extended Schema).", + default=None, + ) + self.cmdbase.add_login_arguments_group(ldap_parser) + + self.options_argument_group(ldap_parser) + + kerberos_help = "Show, add or modify properties pertaining to AD Kerberos Configuration." + kerberos_parser = subcommand_parser.add_parser( + __subparsers__[1], + help=kerberos_help, + description=kerberos_help + "\n\nExamples:\n\nShow Kerberos specific AD/LDAP configuration " + "settings.\n\tdirectory kerberos\n\nShow current AD Kerberos configuration." + "\n\tdirectory kerberos\n\nAlter kerberos service address, AD realm and Port.\n\t" + "directory kerberos --serviceaddress x.x.y.z --port 8888 --realm adrealm1", + formatter_class=RawDescriptionHelpFormatter, + ) + kerberos_parser.add_argument( + "--serviceaddress", + dest="serviceaddress", + help="Optionally include this flag to set the Kerberos serviceaddress.", + default=None, + ) + kerberos_parser.add_argument( + "--port", + dest="port", + help="Optionally include this flag to set the Kerberos port.", + default=None, + ) + kerberos_parser.add_argument( + "--realm", + dest="realm", + help="Optionally include this flag to set the Kerberos realm.", + default=None, + ) + kerberos_parser.add_argument( + "--keytab", + dest="keytab", + help="Optionally include this flag to import a Kerberos Keytab by it's URI location.", + default="", + ) + kerberos_parser.add_argument( + "--enable", + "--disable", + dest="enable", + type=str, + nargs="*", + action=_DirectoryParse, + help="Optionally add this flag to enable or disable Kerberos services.", + default=None, + ) + self.cmdbase.add_login_arguments_group(kerberos_parser) + + self.options_argument_group(kerberos_parser) + + directory_test_help = ( + "Start, stop or view results of an AD/LDAP test which include: ICMP, " + "Domain Resolution, Connectivity, Authentication, Bindings, LOM Object and User " + "Context tests." + ) + directory_test_parser = subcommand_parser.add_parser( + __subparsers__[2], + help=directory_test_help, + description=directory_test_help + "\n\nExamples:\n\nStart a directory test:\n\tdirectory test " + "start\n\nStop a directory test:\n\tdirectory test stop\n\nView results of the last " + "directory test:\n\tdirectory test viewresults", + formatter_class=RawDescriptionHelpFormatter, + ) + directory_test_parser.add_argument( + "start_stop_view", + help="Start, stop, or view results on an AD/LDAP test.", + metavar="START, STOP, VIEWRESULTS", + default="viewresults", + ) + self.cmdbase.add_login_arguments_group(directory_test_parser) diff --git a/ilorest/extensions/iLO_COMMANDS/DisableIloFunctionalityCommand.py b/ilorest/extensions/iLO_COMMANDS/DisableIloFunctionalityCommand.py new file mode 100644 index 0000000..eb77fa5 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/DisableIloFunctionalityCommand.py @@ -0,0 +1,204 @@ +### +# Copyright 2016-2021 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 -*- +""" iLO Functionality Command for rdmc """ + +import json + +try: + from rdmc_helper import ( + IncompatableServerTypeError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatableServerTypeError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class DisableIloFunctionalityCommand: + """Disables iLO functionality to the server""" + + def __init__(self): + self.ident = { + "name": "disableilofunctionality", + "usage": None, + "description": "Disable iLO functionality on the current logged in server." + "\n\texample: disableilofunctionality\n\n\tWARNING: this will" + " render iLO unable to respond to network operations.\n\n\t" + "Add the --force flag to ignore critical task checking.", + "summary": "disables iLO's accessibility via the network and resets " + "iLO. WARNING: This should be used with caution as it will " + "render iLO unable to respond to further network operations " + "(including REST operations) until iLO is re-enabled using the" + " RBSU menu.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main DisableIloFunctionalityCommand function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if args: + raise InvalidCommandLineError("disableilofunctionality command takes no arguments.") + + self.ilofunctionalityvalidation(options) + + select = "Manager." + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Manager. not found.") + + bodydict = results.resp.dict["Oem"][self.rdmc.app.typepath.defs.oemhp] + if bodydict["iLOFunctionalityRequired"]: + raise IncompatableServerTypeError( + "disableilofunctionality" + " command is not available. iLO functionality is required" + " and can not be disabled on this platform." + ) + + try: + for item in bodydict["Actions"]: + if "iLOFunctionality" in item: + if self.rdmc.app.typepath.defs.isgen10: + action = item.split("#")[-1] + else: + action = "iLOFunctionality" + + path = bodydict["Actions"][item]["target"] + body = {"Action": action} + break + except: + body = {"Action": "iLOFunctionality", "Target": "/Oem/Hp"} + + if self.ilodisablechecks(options): + self.rdmc.ui.warn( + "Disabling iLO functionality. iLO will be unavailable on the logged " + " in server until it is re-enabled manually.\n" + ) + + results = self.rdmc.app.post_handler(path, body, silent=True, service=True) + + if results.status == 200: + self.rdmc.ui.printer("[%d] The operation completed successfully.\n" % results.status) + else: + self.rdmc.ui.printer("[%d] iLO responded with the following info: \n" % results.status) + json_payload = json.loads(results._http_response.data) + try: + self.rdmc.ui.error("%s" % json_payload["error"]["@Message.ExtendedInfo"][0]["MessageId"]) + except: + self.rdmc.ui.error("An invalid or incomplete response was received: %s\n" % json_payload) + + else: + self.rdmc.ui.error( + "iLO is currently performing a critical task and " + "can not be safely disabled at this time. Please try again later.\n" + ) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def ilodisablechecks(self, options): + """Verify it is safe to actually disable iLO + + :param options: command line options + :type options: values, attributes of class obj + """ + + if options.force: + self.rdmc.ui.warn("Force Enabled: Ignoring critical operation/mode checking.\n") + return True + + else: + keyword_list = "idle", "complete" + + try: + results = self.rdmc.app.select(selector="UpdateService.")[0] + except: + raise NoContentsFoundForOperationError("UpdateService. not found.") + + try: + state = results.resp.dict["Oem"]["Hpe"]["State"].lower() + for val in keyword_list: + if val in state: + return True + return False + + except: + raise NoContentsFoundForOperationError("iLO state not identified") + + def ilofunctionalityvalidation(self, options): + """ilofunctionalityvalidation method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--force", + dest="force", + help="Ignore any critical task checking and force disable iLO.", + action="store_true", + default=None, + ) diff --git a/ilorest/extensions/iLO_COMMANDS/ESKMCommand.py b/ilorest/extensions/iLO_COMMANDS/ESKMCommand.py new file mode 100644 index 0000000..7c9e8ac --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/ESKMCommand.py @@ -0,0 +1,134 @@ +### +# Copyright 2016-2021 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 -*- +""" ESKM Command for rdmc """ + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class ESKMCommand: + """Commands ESKM available actions""" + + def __init__(self): + self.ident = { + "name": "eskm", + "usage": None, + "description": "Clear the ESKM logs.\n\texample: eskm" + " clearlog\n\n\tTest the ESKM connections.\n\texample: eskm testconnections", + "summary": "Command for all ESKM available actions.", + "aliases": [], + "auxcommands": [], + } + + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main ESKMCommand function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if not len(args) == 1: + raise InvalidCommandLineError("eskm command only takes one parameter.") + + self.eskmvalidation(options) + + select = self.rdmc.app.typepath.defs.hpeskmtype + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("ESKM not found.") + + if args[0].lower() == "clearlog": + actionitem = "ClearESKMLog" + elif args[0].lower() == "testconnections": + actionitem = "TestESKMConnections" + else: + raise InvalidCommandLineError("Invalid command.") + + bodydict = results.resp.dict + try: + for item in bodydict["Actions"]: + if actionitem in item: + if self.rdmc.app.typepath.defs.isgen10: + actionitem = item.split("#")[-1] + + path = bodydict["Actions"][item]["target"] + break + except: + pass + + body = {"Action": actionitem} + self.rdmc.app.post_handler(path, body) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def eskmvalidation(self, options): + """eskmvalidation method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/iLO_COMMANDS/EthernetCommand.py b/ilorest/extensions/iLO_COMMANDS/EthernetCommand.py new file mode 100644 index 0000000..3f29b00 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/EthernetCommand.py @@ -0,0 +1,1173 @@ +### +# Copyright 2019 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 -*- +""" Certificates Command for rdmc """ + +import copy +import json +import re +from argparse import RawDescriptionHelpFormatter +from collections import OrderedDict + +import redfish.ris +from redfish.ris.ris import RisInstanceNotFoundError +from redfish.ris.rmc_helper import ( + IloResponseError, + IncompatibleiLOVersionError, + InvalidPathError, +) +from redfish.ris.utils import diffdict, json_traversal, json_traversal_delete_empty + +try: + from rdmc_helper import ( + UI, + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + InvalidPropertyError, + NoDifferencesFoundError, + RdmcError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + UI, + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + InvalidPropertyError, + NoDifferencesFoundError, + RdmcError, + ReturnCodes, + ) + +__eth_file__ = "eth.json" +__subparsers__ = ["save", "load"] + + +class EthernetCommand: + """ Commands for Ethernet Management Controller and RDE supported NIC card \ + configuration on server """ + + def __init__(self): + self.ident = { + "name": "ethernet", + "usage": None, + "description": "Save or load from a JSON formatted file containing " + "properties pertaining to a system's iLO ethernet management controller. " + "Additionally enable/disable individual management controller NICs (including VNIC), " + "IPv4 and IPv6 addressing configuration and domain name servers." + "\n\tBy default the JSON file will be named 'eth.json'.\n\t" + "\n\t***Credentials for an iLO administrator level account must be provided as" + " well as the iLO base URL.***" + "\n\n\tSave ethernet management controller data.\n\texample: ethernet save " + "\n\n\tLoad ethernet management controller data.\n\texample: ethernet load" + "\n\n\tSave ethernet management controller data to a different file, silently\n\t" + "ethernet save -f --silent" + "\n\n\tLoad etherent management controller data from a different file, silently\n\t" + "ethernet load -f --silent" + "\n\n\tEnable network interfaces by listing each interface to be enabled. **Note**: " + "Non-existent interfaces will be omitted from configuration.\n\t " + "ethernet --enable_nic 1,2,3" + "\n\n\tDisable network interfaces by listing each interface to be disabled. **Note**: " + "Non-existent interfaces will be omitted from configuration.\n\t " + "ethernet --disable_nic 1,2,3" + "\n\n\tEnable virtual network interface of management network.\n\t" + "ethernet --enable_vnic" + "\n\n\tDisable virtual network interface of management network.\n\t " + "ethernet --disable_vnic" + "\n\n\tEnable Enhanced Download Performance.\n\t" + "ethernet --enable_enhanced_downloads" + "\n\n\tDisable Enhanced Download Performance.\n\t " + "ethernet --disable_enhanced_downloads" + "\n\n\tConfigure Domain Name Servers (DNS) in a list: \n\t" + "ethernet --nameservers 8.8.8.8,1.1.1.1 OR ethernet --nameservers " + "dns_resolver1.aws.com,dns_resolver2.aws.com" + "\n\n\tConfigure Static IPv4 Settings. Provide a list of network settings\n\t" + "ethernet --network_ipv4 ,," + "\n\n\tConfigure Proxy Settings. Provide a proxy server and port\n\t" + "ethernet --proxy http://proxy.company.net:8080" + "\n\n\tClear Proxy Settings. \n\t" + "ethernet --proxy None" + "\n\n\tConfigure Static IPv6 Settings. Provide a list of network settings\n\t" + "ethernet --network_ipv6 ,,", + "summary": "Command for configuring Ethernet Management Controller Interfaces and " "associated properties", + "aliases": [], + "auxcommands": ["IloResetCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + self.save = None + self.load = None + self.eth_file = None # set in validation + self.eth_args = None + self.saved_inputline = None # set in validation + + def run(self, line, help_disp=False): + """Main Ethernet Management Controller Interfaces Run + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + ident_subparser = False + for cmnd in __subparsers__: + if cmnd in line: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + ident_subparser = True + break + if not ident_subparser: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line, default=True) + except: + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.ethernetvalidation(options) + + if "default" in options.command.lower(): + # flags are used to showing data with respect to enhance download + flag = True + return_flag = True + if options.enable_enhanced_downloads or options.disable_enhanced_downloads: + ilo_ver = self.rdmc.app.getiloversion() + self.rdmc.ui.printer("iLO Version is " + str(ilo_ver) + "\n") + if ilo_ver < 5.263: + raise IncompatibleiLOVersionError("Enhance download required minimum " "iLO Version iLO5.263") + # net_path = self.rdmc.app.getidbytype('NetworkProtocol.') + net_path = self.rdmc.app.typepath.defs.managerpath + "NetworkProtocol" + results = self.rdmc.app.get_handler(net_path, silent=True, service=True).dict + body = dict() + body["Oem"] = {} + body["Oem"]["Hpe"] = {} + if options.enable_enhanced_downloads: + if results["Oem"]["Hpe"]["EnhancedDownloadPerformanceEnabled"]: + self.rdmc.ui.printer( + "Enhanced Download Performance " "already enabled!! \n", + verbose_override=True, + ) + return_flag = False + else: + self.rdmc.ui.printer("Enabling enhanced " "downloads...\n", verbose_override=True) + body["Oem"]["Hpe"]["EnhancedDownloadPerformanceEnabled"] = True + flag = False + else: + if not results["Oem"]["Hpe"]["EnhancedDownloadPerformanceEnabled"]: + self.rdmc.ui.printer( + "Enhanced Download Performance " "already disabled!!\n\n", + verbose_override=True, + ) + return_flag = False + else: + self.rdmc.ui.printer("Disabling enhanced " "downloads...\n", verbose_override=True) + body["Oem"]["Hpe"]["EnhancedDownloadPerformanceEnabled"] = False + flag = False + if options.enable_vnic or options.disable_vnic: + self.rdmc.app.patch_handler(net_path, body, service=False, silent=True) + elif return_flag: + self.rdmc.app.patch_handler(net_path, body, service=False, silent=False) + if options.enable_vnic or options.disable_vnic: + results = self.rdmc.app.get_handler( + self.rdmc.app.typepath.defs.managerpath, service=True, silent=True + ).dict + body = dict() + body["Oem"] = {} + body["Oem"]["Hpe"] = {} + if options.enable_vnic: + if results["Oem"]["Hpe"]["VirtualNICEnabled"]: + self.rdmc.ui.printer("Virtual NIC already enabled!!\n", verbose_override=True) + return ReturnCodes.SUCCESS + self.rdmc.ui.printer("Enabling Virtual NIC...\n", verbose_override=True) + body["Oem"]["Hpe"]["VirtualNICEnabled"] = True + else: + if not results["Oem"]["Hpe"]["VirtualNICEnabled"]: + self.rdmc.ui.printer("Virtual NIC already disabled!!\n", verbose_override=True) + return ReturnCodes.SUCCESS + self.rdmc.ui.printer("Disabling Virtual NIC...\n", verbose_override=True) + body["Oem"]["Hpe"]["VirtualNICEnabled"] = False + self.rdmc.app.patch_handler( + self.rdmc.app.typepath.defs.managerpath, + body, + service=False, + silent=False, + ) + self.rdmc.ui.printer("Warning: Resetting iLO...\n") + self.auxcommands["iloreset"].run("") + self.rdmc.ui.printer("You will need to re-login to access this system...\n") + elif flag and return_flag: + data = self.get_data(options) + get_data = True + for inst in data: + if "ethernetinterface" in inst.lower() or "ethernetnetworkinterface" in inst.lower(): + for path in data[inst]: + if ( + "managers/1/ethernetinterfaces/1" in path.lower() + or "managers/1/ethernetnetworkinterfaces/1" in path.lower() + ): # only process managers interfaces + if options.enable_nic or options.disable_nic: + get_data = False + self.enable_disable_nics(data[inst][path], options) + + if ( + options.network_ipv4 or options.network_ipv6 or options.nameservers or options.proxy + ) and "virtual" not in data[inst][path]["Name"].lower(): + get_data = False + self.configure_static_settings(data[inst][path], options) + if options.proxy: + return ReturnCodes.SUCCESS + if "networkprotocol" in inst.lower(): + for path in data[inst]: + if "managers/1/networkprotocol" in path.lower(): + if options.enable_enhanced_downloads or options.disable_enhanced_downloads: + get_data = False + if get_data: + self.output_data(data, options, get_data) + else: + self.load_main_function(options, data) + + if "save" in options.command.lower(): + self.save = True + self.load = False + if "save" in options.command.lower() and not options.ethfilename: + self.rdmc.ui.printer("Saving configurations to default file '%s'.\n" % self.eth_file) + self.output_data(self.get_data(options), options, False) + + elif "load" in options.command.lower(): + self.save = False + self.load = True + if options.force_network_config: + self.load_main_function(options) + else: + self.rdmc.ui.printer("Skipping network configurations as " + "--force_network_config is not included.\n") + + return ReturnCodes.SUCCESS + + def get_data(self, options): + """Obtain data for Ethernet Management Interfaces, Ethernet System Interfaces, \ + Manager Network Services and iLO Date Time Types and subsequently save to a dictionary \ + :param options: command line options + :type options: attribute + """ + + network_data_collection = eth_iface = manager_network_services = ilo_date_time = OrderedDict() + + if self.rdmc.app.redfishinst.is_redfish: + try: + iface_results = self.rdmc.app.select(selector="ethernetinterface.") + except RisInstanceNotFoundError: + self.rdmc.ui.printer("Type 'EthernetInterface.' not available.\n") + else: + try: + iface_results = self.rdmc.app.select(selector="ethernetnetworkinterface.") + except RisInstanceNotFoundError: + self.rdmc.ui.printer("Type 'EthernetNetworkInterface.' not available.\n") + + for inst in iface_results: + try: + eth_iface[inst.type].update({inst.path: inst.dict}) + except KeyError: + eth_iface[inst.type] = {inst.path: inst.dict} + continue + except AttributeError: + self.rdmc.ui.printer("Missing instance data for type '%s' : '%s'" % (inst.type, inst.path)) + if self.rdmc.app.typepath.defs.isgen9: + selector_content = self.rdmc.app.select(selector="ManagerNetworkService.0.11.1.networkprotocol") + else: + selector_content = self.rdmc.app.select(selector="networkprotocol.") + for inst in selector_content: + try: + manager_network_services[inst.type].update({inst.path: inst.dict}) + except KeyError: + manager_network_services[inst.type] = {inst.path: inst.dict} + continue + except AttributeError: + self.rdmc.ui.printer("Missing instance data for type '%s' : '%s'" % (inst.type, inst.path)) + + for inst in self.rdmc.app.select(selector="datetime."): + try: + ilo_date_time[inst.type].update({inst.path: inst.dict}) + except KeyError: + ilo_date_time[inst.type] = {inst.path: inst.dict} + continue + except AttributeError: + self.rdmc.ui.printer("Missing instance data for type '%s' : '%s'" % (inst.type, inst.path)) + + network_data_collection.update(eth_iface) + network_data_collection.update(manager_network_services) + network_data_collection.update(ilo_date_time) + + return network_data_collection + + def output_data(self, data, options, get_only=False): + """ Output data in requested json file or on console + :param data: dictionary containing ethernet configuration data: + :type data: dictionary + :param options: command line options + :type options: attribute + :param get_only: parameter to only output to console (do not save data to file). \ + Essentially a targeted \'get\' command. + :type: boolean + """ + + outdata = list() + + outdata.append(self.rdmc.app.create_save_header()) + + for _type in data: + for path in data.get(_type): + temp = dict() + try: + temp[_type.split("#")[-1]].update({path: self.rdmc.app.removereadonlyprops(data[_type][path])}) + outdata.append(temp) + except KeyError: + temp[_type.split("#")[-1]] = {path: self.rdmc.app.removereadonlyprops(data[_type][path])} + outdata.append(temp) + except Exception: + pass + + if self.eth_file and not get_only: + if options.encryption: + with open(self.eth_file, "wb") as outfile: + outfile.write( + Encryption().encrypt_file( + json.dumps(outdata, indent=2, cls=redfish.ris.JSONEncoder), + options.encryption, + ) + ) + else: + with open(self.eth_file, "w") as outfile: + outfile.write(json.dumps(outdata, indent=2, cls=redfish.ris.JSONEncoder)) + else: + if options.json: + self.rdmc.ui.print_out_json_ordered(outdata) + else: + UI().print_out_human_readable(outdata) + + def enable_disable_nics(self, data, options): + if options.disable_nic: + for ident in re.split("[, ]", options.disable_nic): + try: + if int(data.get("Id")) == int(ident): + data.update({"InterfaceEnabled": False}) + break + except ValueError: + if data.get("Name") == ident: + data.update({"InterfaceEnabled": False}) + break + + if options.enable_nic: + for ident in re.split("[, ]", options.enable_nic): + try: + if int(data.get("Id")) == int(ident): + data.update({"InterfaceEnabled": True}) + break + except ValueError: + if data.get("Name") == ident: + data.update({"InterfaceEnabled": True}) + break + + def configure_static_settings(self, data, options): + if options.network_ipv4: + usr_data = re.split("[, ]", options.network_ipv4) + if len(usr_data) > 2: + data["IPv4Addresses"][0] = { + "Address": usr_data[0], + "Gateway": usr_data[1], + "SubnetMask": usr_data[2], + } + data["DHCPv4"].update({"DHCPEnabled": False}) + data["Oem"][self.rdmc.app.typepath.defs.oemhp]["DHCPv4"].update({"Enabled": False}) + else: + raise InvalidCommandLineErrorOPTS( + "An invalid number of arguments provided to " + " quick networking configuration. Check '--network_ipv4' entry." + ) + else: + del data["IPv4Addresses"] + + if options.network_ipv6: + usr_data = re.split("[, ]", options.network_ipv6) + if len(usr_data) > 2: + next(iter(data["IPv6Addresses"])).update({"Address": usr_data[0], "PrefixLength": usr_data[2]}) + data["IPv6DefaultGateway"] = options.network_ipv6[1] + data["DHCPv6"].update({"DHCPEnabled": False}) + data["Oem"][self.rdmc.app.typepath.defs.oemhp]["DHCPv6"].update({"Enabled": False}) + else: + raise InvalidCommandLineErrorOPTS( + "An invalid number of arguments provided to " + " quick networking configuration. Check '--network_ipv6' entry." + ) + else: + del data["IPv6Addresses"] + + if options.nameservers: + usr_data = re.split("[, ]", options.nameservers) + if len(usr_data) <= 2: + ipv6_list = list() + ipv4_list = list() + static_list = list() + for elem in usr_data: + oem_ipv4 = data["Oem"][self.rdmc.app.typepath.defs.oemhp]["IPv4"] + oem_ipv6 = data["Oem"][self.rdmc.app.typepath.defs.oemhp]["IPv6"] + if "::" in elem: + ipv6_list.append(elem) + elif "." in elem: + ipv4_list.append(elem) + static_list.append(elem) + oem_ipv6["DNSServers"] = ipv6_list + oem_ipv4["DNSServers"] = ipv4_list + data["StaticNameServers"] = static_list + else: + raise InvalidCommandLineErrorOPTS("Name Servers argument has to be less than or equal to 2") + else: + del data["StaticNameServers"] + del data["Oem"][self.rdmc.app.typepath.defs.oemhp]["IPv4"]["DNSServers"] + del data["Oem"][self.rdmc.app.typepath.defs.oemhp]["IPv6"]["DNSServers"] + + if options.proxy and options.proxy != "None": + body = dict() + body["Oem"] = {} + body["Oem"]["Hpe"] = {} + body["Oem"]["Hpe"]["WebProxyConfiguration"] = {} + proxy_body = body["Oem"]["Hpe"]["WebProxyConfiguration"] + proxy_body["ProxyServer"] = None + proxy_body["ProxyUserName"] = None + proxy_body["ProxyPassword"] = None + if "https" in options.proxy: + proxy_body["ProxyPort"] = 443 + else: + proxy_body["ProxyPort"] = 80 + if "@" in options.proxy: + proxy = options.proxy.split("@") + proxy_usr_pass = proxy[0] + proxy_srv_port = proxy[1] + if "//" in proxy_usr_pass: + proxy_usr_pass = proxy_usr_pass.split("//")[1] + if ":" in proxy_srv_port: + proxy = proxy_srv_port.split(":") + proxy_body["ProxyServer"] = proxy[0] + proxy_body["ProxyPort"] = int(proxy[1]) + else: + proxy_body["ProxyServer"] = proxy_srv_port + if ":" in proxy_usr_pass: + proxy = proxy_usr_pass.split(":") + proxy_body["ProxyPassword"] = proxy[1] + proxy_body["ProxyUserName"] = proxy[0] + else: + proxy_body["ProxyUserName"] = proxy_usr_pass + else: + proxy_srv_port = options.proxy + if "//" in proxy_srv_port: + proxy_srv_port = proxy_srv_port.split("//")[1] + if ":" in proxy_srv_port: + proxy = proxy_srv_port.split(":") + proxy_body["ProxyServer"] = proxy[0] + proxy_body["ProxyPort"] = int(proxy[1]) + else: + proxy_body["ProxyServer"] = proxy_srv_port + path = self.rdmc.app.getidbytype("NetworkProtocol.") + + if path and body: + self.rdmc.ui.printer("Enabling Proxy configuration...\n", verbose_override=True) + self.rdmc.app.patch_handler(path[0], body, service=False, silent=False) + elif options.proxy and options.proxy == "None": + body = dict() + body["Oem"] = {} + body["Oem"]["Hpe"] = {} + body["Oem"]["Hpe"]["WebProxyConfiguration"] = {} + proxy_body = body["Oem"]["Hpe"]["WebProxyConfiguration"] + proxy_body["ProxyServer"] = "" + proxy_body["ProxyPort"] = None + proxy_body["ProxyUserName"] = "" + proxy_body["ProxyPassword"] = None + path = self.rdmc.app.getidbytype("NetworkProtocol.") + + if path and body: + self.rdmc.ui.printer("Clearing Proxy configuration...\n", verbose_override=True) + self.rdmc.app.patch_handler(path[0], body, service=False, silent=False) + + json_traversal_delete_empty(data, None, None) + + def load_main_function(self, options, data=None): + """ + Load main function. Handles SSO and SSL/TLS Certificates, kickoff load + helper, load of patch file, get server status and issues system reboot + and iLO reset after completion of all post and patch commands. + :param options: command line options + :type options: attribute + :param data: data + :type data: optional + """ + + if self.rdmc.app.redfishinst.is_redfish: + _ = "ethernetinterface." + else: + _ = "ethernetnetworkinterface." + + if not data: + data = {} + try: + if options.encryption: + with open(self.eth_file, "rb") as file_handle: + data = json.loads(Encryption().decrypt_file(file_handle.read(), options.encryption)) + else: + with open(self.eth_file, "rb") as file_handle: + data = json.loads(file_handle.read()) + except: + raise InvalidFileInputError( + "Invalid file formatting found. Verify the file has a " "valid JSON format." + ) + for d in data: + for ilotype, subsect in d.items(): + _type = ilotype.split(".")[0] + for _path in subsect: + if not subsect[_path]: + continue + elif "ethernetinterface" in _type.lower() or "ethernetnetworkinterface" in _type.lower(): + if "managers" in _path.lower(): + self.load_ethernet_aux(_type, _path, data[ilotype][_path]) + elif "systems" in _path.lower(): + self.rdmc.ui.warn("Systems Ethernet Interfaces '%s' " "cannot be modified." % _path) + continue + elif "datetime" in _type.lower(): + if "StaticNTPServers" in list(subsect.get(_path).keys()): + # must set NTP Servers to static in OEM then reset iLO for StaticNTPServers + # property to appear in iLODateTime + if self.rdmc.app.redfishinst.is_redfish: + eth_config_type = "ethernetinterface" + else: + eth_config_type = "ethernetnetworkinterface" + for key in list(data.keys()): + if key.split(".")[0].lower() == eth_config_type: + eth_config_type = key + for _path in data[eth_config_type]: + if "managers" in _path.lower(): + try: + data[eth_config_type][_path]["DHCPv4"]["UseNTPServers"] = True + data[eth_config_type][_path]["DHCPv6"]["UseNTPServers"] = True + data[eth_config_type][_path]["Oem"][self.rdmc.app.typepath.defs.oemhp][ + "DHCPv4" + ]["UseNTPServers"] = True + data[eth_config_type][_path]["Oem"][self.rdmc.app.typepath.defs.oemhp][ + "DHCPv6" + ]["UseNTPServers"] = True + self.load_ethernet_aux( + eth_config_type, + _path, + data[eth_config_type][_path], + ) + except KeyError: + self.rdmc.ui.printer( + "Unable to configure " "'UseNTPServers' for '%s'.\n" % _path + ) + self.rdmc.ui.printer( + "iLO must be reset in order for " + "changes to static network time protocol servers to " + "take effect.\n" + ) + + def load_ethernet_aux(self, _type, _path, ethernet_data): + """helper function for parsing and bucketting ethernet properties. + :param _type: Redfish type used for querying the current server data + :type _type: string + :param _path: URI path to be patched. + :type _path: string + :param ethernet_data: JSON containing the ethernet instance (and valid, associated + properties) to be patched. + :type ethernet_data: JSON + + """ + + support_ipv6 = True + dhcpv4curr = dhcpv4conf = oem_dhcpv4conf = dict() + _ = dhcpv6conf = oem_dhcpv6conf = dict() + errors = [] + + ident_eth = False + if "EthernetInterface" in _type: + for curr_sel in self.rdmc.app.select( + _type.split(".")[0] + ".", + ( + self.rdmc.app.typepath.defs.hrefstring, + self.rdmc.app.typepath.defs.managerpath + "*", + ), + path_refresh=True, + ): + if curr_sel.path == _path: + ident_eth = True + break + # 'links/self/href' required when using iLO 4 (rest). + elif "EthernetNetworkInterface" in _type: + for curr_sel in self.rdmc.app.select( + _type.split(".")[0] + ".", + ( + "links/self/" + self.rdmc.app.typepath.defs.hrefstring, + self.rdmc.app.typepath.defs.managerpath + "*", + ), + path_refresh=True, + ): + if curr_sel.path == _path: + ident_eth = True + break + else: + raise Exception("Invalid type in management NIC load operation: '%s'" % _type) + + if not ident_eth: + raise InvalidPathError("Path: '%s' is invalid/not identified on this server.\n" % _path) + + ident_name = curr_sel.dict.get("Name") + ident_id = curr_sel.dict.get("Id") + # ENABLING ETHERNET INTERFACE SECTION + try: + # Enable the Interface if called for and not already enabled + if ethernet_data.get("InterfaceEnabled") and not curr_sel.dict.get("InterfaceEnabled"): + self.rdmc.app.patch_handler(_path, {"InterfaceEnabled": True}, silent=True) + self.rdmc.ui.printer("NIC Interface Enabled.\n") + # Disable the Interface if called for and not disabled already + # No need to do anything else, just return + elif not ethernet_data.get("InterfaceEnabled") and not curr_sel.dict.get("InterfaceEnabled"): + self.rdmc.app.patch_handler(_path, {"InterfaceEnabled": False}, silent=True) + self.rdmc.ui.warn("NIC Interface Disabled. All additional configurations " "omitted.") + return + except (KeyError, NameError, TypeError, AttributeError): + # check OEM for NICEnabled instead + if ( + not curr_sel.dict["Oem"][self.rdmc.app.typepath.defs.oemhp]["NICEnabled"] + and ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]["NICEnabled"] + ): + self.rdmc.app.patch_handler( + _path, + {"Oem": {self.rdmc.app.typepath.defs.oemhp: {"NICEnabled": True}}}, + silent=True, + ) + self.rdmc.ui.printer("NIC Interface Enabled.\n") + elif ( + not curr_sel.dict["Oem"][self.rdmc.app.typepath.defs.oemhp]["NICEnabled"] + and not ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]["NICEnabled"] + ): + self.rdmc.app.patch_handler( + _path, + {"Oem": {self.rdmc.app.typepath.defs.oemhp: {"NICEnabled": False}}}, + silent=True, + ) + self.rdmc.ui.printer("NIC Interface Disabled.\n") + return + # except IloResponseError should just be raised and captured by decorator. No point in + # performing any other operations if the interface can not be set. + + # END ENABLING ETHERNET INTEFACE SECTION + # --------------------------------------- + # DETERMINE DHCPv4 and DHCPv6 States and associated flags + + if "NICSupportsIPv6" in list(curr_sel.dict["Oem"][self.rdmc.app.typepath.defs.oemhp].keys()): + support_ipv6 = curr_sel.dict["Oem"][self.rdmc.app.typepath.defs.oemhp]["NICSupportsIPv6"] + + # obtain DHCPv4 Config and OEM + try: + if "DHCPv4" in list(curr_sel.dict.keys()) and "DHCPv4" in list(ethernet_data.keys()): + dhcpv4curr = copy.deepcopy(curr_sel.dict["DHCPv4"]) + dhcpv4conf = copy.deepcopy(ethernet_data["DHCPv4"]) + except (KeyError, NameError, TypeError, AttributeError): + errors.append("Unable to find Redfish DHCPv4 Settings.\n") + finally: + try: + oem_dhcpv4conf = copy.deepcopy(ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]["DHCPv4"]) + + except (KeyError, NameError): + errors.append("Unable to find OEM Keys for DHCPv4 or IPv4") + + try: + if support_ipv6: + if "DHCPv6" in list(curr_sel.dict.keys()) and "DHCPv6" in list(ethernet_data.keys()): + dhcpv6conf = copy.deepcopy(ethernet_data["DHCPv6"]) + else: + self.rdmc.ui.warn("NIC Does not support IPv6.") + except (KeyError, NameError, TypeError, AttributeError): + errors.append("Unable to find Redfish DHCPv6 Settings.\n") + finally: + try: + oem_dhcpv6conf = copy.deepcopy(ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]["DHCPv6"]) + except (KeyError, NameError): + errors.append("Unable to find OEM Keys for DHCPv6 or IPv6") + + try: + # if DHCP Enable request but not currently enabled + if dhcpv4conf.get("DHCPEnabled") and not curr_sel.dict["DHCPv4"]["DHCPEnabled"]: + self.rdmc.app.patch_handler(_path, {"DHCPv4": {"DHCPEnabled": True}}, silent=True) + self.rdmc.ui.printer("DHCP Enabled.\n") + # if DHCP Disable request but currently enabled + elif not dhcpv4conf["DHCPEnabled"] and curr_sel.dict["DHCPv4"]["DHCPEnabled"]: + self.rdmc.app.patch_handler(_path, {"DHCPv4": {"DHCPEnabled": False}}, silent=True) + dhcpv4conf["UseDNSServers"] = False + dhcpv4conf["UseNTPServers"] = False + dhcpv4conf["UseGateway"] = False + dhcpv4conf["UseDomainName"] = False + self.rdmc.ui.printer("DHCP Disabled.\n") + except (KeyError, NameError, TypeError, AttributeError): + # try with OEM + try: + if ( + oem_dhcpv4conf.get("Enabled") + and not curr_sel.dict["Oem"][self.rdmc.app.typepath.defs.oemhp]["DHCPv4"]["Enabled"] + ): + self.rdmc.app.patch_handler( + _path, + {"Oem": {self.rdmc.app.typepath.defs.oemhp: {"DHCPv4": {"DHCPEnabled": True}}}}, + silent=True, + ) + self.rdmc.ui.printer("DHCP Enabled.\n") + if "IPv4Addresses" in ethernet_data: + del ethernet_data["IPv4Addresses"] + elif ( + not oem_dhcpv4conf.get("Enabled") + and curr_sel.dict["Oem"][self.rdmc.app.typepath.defs.oemhp]["DHCPv4"]["Enabled"] + ): + oem_dhcpv4conf["UseDNSServers"] = False + oem_dhcpv4conf["UseNTPServers"] = False + oem_dhcpv4conf["UseGateway"] = False + oem_dhcpv4conf["UseDomainName"] = False + self.rdmc.ui.printer("DHCP Disabled.\n") + except (KeyError, NameError) as exp: + errors.append("Failure in parsing or removing data in OEM DHCPv4: %s.\n" % exp) + + try: + # if the ClientIDType is custom and we are missing the ClientID then this property can + # not be set. + if "ClientIdType" in list(dhcpv4conf.keys()): + if dhcpv4conf["ClientIdType"] == "Custom" and "ClientID" not in list(dhcpv4conf.keys()): + del ethernet_data["DHCPv4"]["ClientIdType"] + elif "ClientIdType" in list(oem_dhcpv4conf.keys()): + if oem_dhcpv4conf["ClientIdType"] == "Custom" and "ClientID" not in list(oem_dhcpv4conf.keys()): + del ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]["DHCPv4"]["ClientIdType"] + except (KeyError, NameError, TypeError, AttributeError): + try: + if "ClientIdType" in list(oem_dhcpv4conf.keys()): + if oem_dhcpv4conf["ClientIdType"] == "Custom" and "ClientID" not in list(oem_dhcpv4conf.keys()): + del ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]["DHCPv4"]["ClientIdType"] + except (KeyError, NameError) as exp: + errors.append("Unable to remove property %s.\n" % exp) + + # special considerations go here for things that need to stay despite diffdict + # EX: IPv4 addresses (aka bug). Changing only one property within the + # IPv4StaticAddresses or IPv4Addresses causes an issue during load. Must include IP, + # subnet mask and gateway (they can not be patched individually). + # spec_dict = {'Oem': {self.rdmc.app.typepath.defs.oemhp: {}}} + spec_dict = dict() + if "IPv4Addresses" in ethernet_data: + spec_dict["IPv4Addresses"] = copy.deepcopy(ethernet_data["IPv4Addresses"]) + try: + if "IPv4Addresses" in ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]: + spec_dict["Oem"][self.rdmc.app.typepath.defs.oemhp]["IPv4Addresses"] = copy.deepcopy( + ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]["IPv4StaticAddresses"] + ) + except (KeyError, NameError, TypeError, AttributeError): + pass + + # diff and overwrite the original payload + ethernet_data = diffdict(ethernet_data, curr_sel.dict) + ethernet_data.update(spec_dict) + + # verify dependencies on those flags which are to be applied are eliminated + try: + # delete Domain name and FQDN if UseDomainName for DHCPv4 or DHCPv6 + # is present. can wait to apply at the end + if dhcpv4conf.get("UseDomainName"): # or dhcpv6conf['UseDomainName']: + if "DomainName" in ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]: + del ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]["DomainName"] + if "FQDN" in ethernet_data: + del ethernet_data["FQDN"] + except (KeyError, NameError, TypeError, AttributeError): + # try again with OEM + try: + if oem_dhcpv4conf.get("UseDomainName") or oem_dhcpv6conf.get("UseDomainName"): + if "DomainName" in ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]: + del ethernet_data["Oem"][self.rdmc.app.typepath.defs.oemhp]["DomainName"] + if "FQDN" in ethernet_data: + del ethernet_data["FQDN"] + except (KeyError, NameError) as exp: + errors.append("Unable to remove property %s.\n" % exp) + + try: + # delete DHCP4 DNSServers from IPV4 dict if UseDNSServers Enabled + # can wait to apply at the end + if dhcpv4conf.get("UseDNSServers"): # and ethernet_data.get('NameServers'): + json_traversal_delete_empty(data=ethernet_data, remove_list=["NameServers"]) + except (KeyError, NameError, TypeError, AttributeError): + pass + finally: + try: + if oem_dhcpv4conf.get("UseDNSServers"): + # del_sections('DNSServers', ethernet_data) + json_traversal_delete_empty(data=ethernet_data, remove_list=["DNSServers"]) + except (KeyError, NameError) as exp: + errors.append("Unable to remove property %s.\n" % exp) + try: + if dhcpv4conf.get("UseWINSServers"): + json_traversal_delete_empty(data=ethernet_data, remove_list=["WINServers"]) + except (KeyError, NameError, TypeError, AttributeError): + pass + finally: + try: + if oem_dhcpv4conf.get("UseWINSServers"): + json_traversal_delete_empty( + data=ethernet_data, + remove_list=["WINServers", "WINSRegistration"], + ) + except (KeyError, NameError) as exp: + errors.append("Unable to remove property %s.\n" % exp) + + try: + if dhcpv4conf.get("UseStaticRoutes"): + json_traversal_delete_empty(data=ethernet_data, remove_list=["StaticRoutes"]) + except (KeyError, NameError, TypeError, AttributeError): + pass + finally: + try: + if oem_dhcpv4conf.get("UseStaticRoutes"): + json_traversal_delete_empty(data=ethernet_data, remove_list=["StaticRoutes"]) + except (KeyError, NameError) as exp: + errors.append("Unable to remove property %s.\n" % exp) + + try: + # if using DHCPv4, remove static addresses + if dhcpv4conf.get("DHCPEnabled"): + json_traversal_delete_empty( + data=ethernet_data, + remove_list=["IPv4Addresses", "IPv4StaticAddresses"], + ) + except (KeyError, NameError, TypeError, AttributeError): + pass + finally: + try: + if oem_dhcpv4conf.get("Enabled"): + json_traversal_delete_empty( + data=ethernet_data, + remove_list=["IPv4Addresses", "IPv4StaticAddresses"], + ) + except (KeyError, NameError) as exp: + errors.append("Unable to remove property %s.\n" % exp) + + try: + # if not using DHCPv6, remove static addresses from payload + if dhcpv6conf.get("OperatingMode") == "Disabled": + json_traversal_delete_empty( + data=ethernet_data, + remove_list=["IPv6Addresses", "IPv6StaticAddresses"], + ) + except (KeyError, NameError, TypeError, AttributeError): + pass + finally: + try: + if not oem_dhcpv6conf.get("StatefulModeEnabled"): + json_traversal_delete_empty( + data=ethernet_data, + remove_list=["IPv6Addresses", "IPv6StaticAddresses"], + ) + except (KeyError, NameError) as exp: + errors.append("Unable to remove property %s.\n" % exp) + + flags = ethernet_data + if "StaticNameServers" in flags: + if dhcpv4curr["UseDNSServers"]: + flags["DHCPv4"] = {"UseDNSServers": False} + + # verify dependencies on those flags which are to be applied are eliminated + + try: + self.rdmc.app.patch_handler(_path, flags, silent=True) + except IloResponseError as excp: + errors.append("iLO Responded with the following errors setting DHCP: %s.\n" % excp) + + try: + if "AutoNeg" not in list(ethernet_data.keys()): + json_traversal_delete_empty(data=ethernet_data, remove_list=["FullDuplex", "SpeedMbps"]) + + # if Full Duplex exists, check if FullDuplexing enabled. If so, + # remove Speed setting. + elif "FullDuplex" in list(ethernet_data.keys()): + json_traversal_delete_empty(data=ethernet_data, remove_list=["FullDuplex", "SpeedMbps"]) + except (KeyError, NameError) as exp: + errors.append("Unable to remove property %s.\n" % exp) + + try: + if "FrameSize" in list(ethernet_data.keys()): + json_traversal_delete_empty(data=ethernet_data, remove_list=["FrameSize"]) + except (KeyError, NameError) as exp: + errors.append("Unable to remove property %s.\n" % exp) + + self.patch_eth(_path, ethernet_data, errors) + + if errors and "Virtual" not in ident_name: + raise RdmcError( + "Ethernet configuration errors were found collectively on adapter: " + "'%s, %s'\ntype: %s\nerrors: %s" % (ident_name, ident_id, _type, errors) + ) + + def patch_eth(self, _path, eth_data, errors=[]): + """helper function for patching ethernet properties. Retry functionality with the ability + to remove the offending property. + :param _path: URI path to be patched. + :type _path: string + :param eth_data: JSON containing the ethernet instance (and valid, associated + properties) to be patched. + :type eth_data: JSON + :param errors: list of errors catalogued between attempts + :type errors: list + + """ + + try: + if eth_data: + # eth_data = json.dumps(eth_data) + # import ast + # eth_data = ast.literal_eval(eth_data) + tmp = self.rdmc.app.patch_handler(_path, eth_data, silent=False, service=False) + if tmp.status == 400: + raise InvalidPropertyError(tmp.dict["error"][next(iter(tmp.dict["error"]))]) + else: + raise NoDifferencesFoundError( + "No differences between existing iLO ethernet " + "configuration and new ethernet configuration.\nPath: %s\n" % _path + ) + + except InvalidPropertyError as excp: + errors.append("iLO Responded with the following error: %s.\n" % excp) + + def drill_to_data(data, list_o_keys): + if len(list_o_keys) > 1: + k = list_o_keys.pop(0) + else: + del data[k] + if isinstance(data, dict): + drill_to_data(data[k], list_o_keys) + + if hasattr(excp, "message"): + for key in excp.message[0]["MessageArgs"]: + try: + eth_data.pop(key) + except (AttributeError, KeyError, StopIteration): + try: + drill_to_data( + eth_data, + list_o_keys=json_traversal(eth_data, key, ret_key_path=True), + ) + except: + errors.append("Unable to find '%s'" % key) + return + self.patch_eth(_path, eth_data) + + except NoDifferencesFoundError as excp: + errors.append("%s" % excp) + + def ethernetvalidation(self, options): + """ethernet validation function + :param options: command line options + :type options: list. + """ + + self.cmdbase.login_select_validation(self, options) + + if options.ethfilename: + if len(options.ethfilename) < 2: + self.eth_file = options.ethfilename[0] + else: + raise InvalidCommandLineError("Only a single ethernet file may be specified.") + else: + self.eth_file = __eth_file__ + + @staticmethod + def options_argument_group(parser): + """Define option arguments group + :param parser: The parser to add the login option group to + :type parser: ArgumentParser/OptionParser + """ + + parser.add_argument( + "--encryption", + dest="encryption", + help="Optionally include this flag to encrypt/decrypt a file" " using the key provided.", + default=None, + ) + parser.add_argument( + "-f", + "--ethfile", + dest="ethfilename", + help="""Optionally specify a JSON file to store or load ethernet configuration data.""", + action="append", + default=None, + ) + + def definearguments(self, customparser): + """Wrapper function for certificates command main function + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + self.options_argument_group(customparser) + subcommand_parser = customparser.add_subparsers(dest="command") + default_parser = subcommand_parser.add_parser( + "default", + help="Obtain iLO management networking interface details and configure basic " + "properties such as enablement/disablement, domain name servers, ipv4 and ipv6 " + "networking configuration.", + formatter_class=RawDescriptionHelpFormatter, + ) + default_parser.add_argument( + "--enable_nic", + dest="enable_nic", + help="Enable network interfaces. List each interface to be enabled by ID: " + "Ex: ethernet --enable_nic 1,2,3. **Note**: Non-existent interfaces will be omitted from " + "configuration.", + type=str, + default=None, + ) + default_parser.add_argument( + "--disable_nic", + dest="disable_nic", + help="Disable network interfaces. List each interface to be disabled by ID: " + "Ex: ethernet --disable_nic 1,2,3. **Note**: Non-existent interfaces will be omitted from " + "configuration.", + type=str, + default=None, + ) + default_parser.add_argument( + "--enable_vnic", + dest="enable_vnic", + help="""Enable virtual network interfaces of management network. + Ex: ethernet --enable_vnic""", + action="store_true", + default=False, + ) + default_parser.add_argument( + "--disable_vnic", + dest="disable_vnic", + help="""Disable virtual network interfaces of management network. + Ex: ethernet --disable_vnic""", + action="store_true", + default=False, + ) + default_parser.add_argument( + "--disable_enhanced_downloads", + dest="disable_enhanced_downloads", + help="""Disable enhanced download for virtual media and firmware update. + Ex: ethernet --disable_enhanced_downloads""", + action="store_true", + default=False, + ) + default_parser.add_argument( + "--enable_enhanced_downloads", + dest="enable_enhanced_downloads", + help="""Enable enhanced download for virtual media and firmware update. + Ex: ethernet --enable_enhanced_downloads""", + action="store_true", + default=False, + ) + default_parser.add_argument( + "--nameservers", + dest="nameservers", + help="Configure physical and shared management network interface domain name " + "servers (DNS) in a list as follows: " + "Ex: ethernet --nameservers 8.8.8.8,1.1.1.1, ethernet --nameservers dns_resolver1.aws.com, " + "dns_resolver2.aws.com", + default=None, + ) + default_parser.add_argument( + "--proxy", + dest="proxy", + type=str, + help="Configure or clear proxy server for the network " + "Ex:\nTo set proxy\nethernet --proxy http://proxy.abc.net:8080 or \n" + "To clear proxy settings\n" + "ethernet --proxy None", + default=None, + ) + default_parser.add_argument( + "--network_ipv4", + dest="network_ipv4", + help="Configure physical and shared management network interface static IPv4 " + "settings. Settings provided in a list as follows: " + ", , . Ex: ethernet --network_ipv4 " + "192.168.1.10, 192.168.1.1, 255.255.0.0", + default=None, + ) + default_parser.add_argument( + "--network_ipv6", + dest="network_ipv6", + help="Configure physical and shared management network interface static IPv6 " + "settings. Settings provided in a list as follows: " + ", , . Ex: ethernet --network_ipv6 " + "0:0:0:0:ffff:c0a8:10e, 0:0:0:0:0:ffff:c0a8:101, 64. **Note**: IPv6 network mask" + "is restricted to '64' bits.", + default=None, + ) + default_parser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the " "displayed output to JSON format.", + default=False, + ) + self.cmdbase.add_login_arguments_group(default_parser) + save_help = "Save a Network Configuration." + save_parser = subcommand_parser.add_parser( + __subparsers__[0], + help=save_help, + description="{0}\n\texample: ethernet save\n\n\tSave iLO ethernet network management interface " + "settings to a non-default file name.\n\tEx: ethernet save -f networking.json\n\n\tSave " + "an encrypted iLO networking configuration file\n\texample: ethernet save --encryption " + "".format(save_help), + formatter_class=RawDescriptionHelpFormatter, + ) + self.cmdbase.add_login_arguments_group(save_parser) + self.options_argument_group(save_parser) + load_help = "Load a Network Configuration." + load_parser = subcommand_parser.add_parser( + __subparsers__[1], + help=load_help, + description="{0}\n\texample: ethernet load\n\n\tLoad iLO ethernet networking management interface " + "settings from a non-default file name.\n\tEx: ethernet load -f " + "networking.json\n\n\tLoad an encrypted iLO networking configuration file\n\texample: " + "ethernet load --encryption ".format(load_help), + formatter_class=RawDescriptionHelpFormatter, + ) + load_parser.add_argument( + "--force_network_config", + dest="force_network_config", + help="Use this flag to force set network configuration." + "Network settings will be skipped if the flag is not included.", + action="store_true", + default=None, + ) + self.cmdbase.add_login_arguments_group(load_parser) + self.options_argument_group(load_parser) diff --git a/ilorest/extensions/iLO_COMMANDS/FactoryDefaultsCommand.py b/ilorest/extensions/iLO_COMMANDS/FactoryDefaultsCommand.py new file mode 100644 index 0000000..a14fc12 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/FactoryDefaultsCommand.py @@ -0,0 +1,135 @@ +### +# Copyright 2016-2021 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 -*- +""" Factory Defaults Command for rdmc """ + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class FactoryDefaultsCommand: + """Reset server to factory default settings""" + + def __init__(self): + self.ident = { + "name": "factorydefaults", + "usage": None, + "description": "Reset iLO to factory defaults in the current logged in " + "server.\n\texample: factorydefaults", + "summary": "Resets iLO to factory defaults. WARNING: user data will " "be removed use with caution.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main factorydefaults function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if args: + raise InvalidCommandLineError("factorydefaults command takes no arguments.") + + self.factorydefaultsvalidation(options) + + self.rdmc.ui.warn("Resetting iLO to factory default settings.\n" "Current session will be terminated.\n") + + select = "Manager." + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Manager. not found.") + + bodydict = results.resp.dict["Oem"][self.rdmc.app.typepath.defs.oemhp] + + try: + for item in bodydict["Actions"]: + if "ResetToFactoryDefaults" in item: + if self.rdmc.app.typepath.defs.isgen10: + action = item.split("#")[-1] + else: + action = "ResetToFactoryDefaults" + + path = bodydict["Actions"][item]["target"] + body = {"Action": action, "ResetType": "Default"} + break + except: + body = { + "Action": "ResetToFactoryDefaults", + "Target": "/Oem/Hp", + "ResetType": "Default", + } + + self.rdmc.app.post_handler(path, body, service=True) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def factorydefaultsvalidation(self, options): + """factory defaults validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/iLO_COMMANDS/FirmwareIntegrityCheckCommand.py b/ilorest/extensions/iLO_COMMANDS/FirmwareIntegrityCheckCommand.py new file mode 100644 index 0000000..d859f9e --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/FirmwareIntegrityCheckCommand.py @@ -0,0 +1,168 @@ +### +# Copyright 2016-2021 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 -*- +""" Firmware Update Command for rdmc """ + +import time + +try: + from rdmc_helper import ( + IloLicenseError, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + TimeOutError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IloLicenseError, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + TimeOutError, + ) + + +class FirmwareIntegrityCheckCommand: + """Reboot server that is currently logged in""" + + def __init__(self): + self.ident = { + "name": "fwintegritycheck", + "usage": None, + "description": "Perform a firmware " + "integrity check on the current logged in server.\n\t" + "example: fwintegritycheck\n\n\tPerform a firmware integrity check and " + "return results of the check.\n\texmaple: fwintegritycheck --results", + "summary": "Perform a firmware integrity check on the currently logged in server.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main firmware update worker function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if args: + raise InvalidCommandLineError("fwintegritycheck command takes no arguments") + + self.firmwareintegritycheckvalidation(options) + if self.rdmc.app.typepath.defs.isgen9: + raise IncompatibleiLOVersionError("fwintegritycheck command is " "only available on iLO 5.") + + licenseres = self.rdmc.app.select(selector="HpeiLOLicense.") + try: + licenseres = licenseres[0] + except: + pass + if not licenseres.dict["LicenseFeatures"]["FWScan"]: + raise IloLicenseError("This command is not available with this iLO license.\n") + + select = self.rdmc.app.typepath.defs.hpilofirmwareupdatetype + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + bodydict = results.resp.dict + + path = bodydict["Oem"]["Hpe"]["Actions"]["#HpeiLOUpdateServiceExt.StartFirmwareIntegrityCheck"]["target"] + + self.rdmc.app.post_handler(path, {}) + + if options.results: + results_string = "Awaiting results of firmware integrity check..." + self.rdmc.ui.printer(results_string) + polling = 50 + found = False + while polling > 0: + if not polling % 5: + self.rdmc.ui.printer(".") + get_results = self.rdmc.app.get_handler(bodydict["@odata.id"], service=True, silent=True) + if get_results: + curr_time = time.strptime(bodydict["Oem"]["Hpe"]["CurrentTime"], "%Y-%m-%dT%H:%M:%SZ") + scan_time = time.strptime( + get_results.dict["Oem"]["Hpe"]["FirmwareIntegrity"]["LastScanTime"], + "%Y-%m-%dT%H:%M:%SZ", + ) + + if scan_time > curr_time: + self.rdmc.ui.printer( + "\nScan Result: %s\n" + % get_results.dict["Oem"]["Hpe"]["FirmwareIntegrity"]["LastScanResult"] + ) + found = True + break + + polling -= 1 + time.sleep(1) + if not found: + self.rdmc.ui.error("\nPolling timed out before scan completed.\n") + TimeOutError("") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def firmwareintegritycheckvalidation(self, options): + """Firmware update method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--results", + dest="results", + help="Optionally include this flag to show results of firmware integrity check.", + default=False, + action="store_true", + ) diff --git a/ilorest/extensions/iLO_COMMANDS/FirmwareUpdateCommand.py b/ilorest/extensions/iLO_COMMANDS/FirmwareUpdateCommand.py new file mode 100644 index 0000000..938d04d --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/FirmwareUpdateCommand.py @@ -0,0 +1,254 @@ +### +# Copyright 2016-2023 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 -*- +""" Firmware Update Command for rdmc """ + +import time + +from redfish.ris.resp_handler import ResponseHandler + +try: + from rdmc_helper import ( + FirmwareUpdateError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + FirmwareUpdateError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class FirmwareUpdateCommand: + """Reboot server that is currently logged in""" + + def __init__(self): + self.ident = { + "name": "firmwareupdate", + "usage": None, + "description": "Apply a firmware " + "update to the current logged in server.\n\texample: " + "firmwareupdate /images/image.bin", + "summary": "Perform a firmware update on the currently logged in server.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main firmware update worker function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.firmwareupdatevalidation(options) + # Supports only .bin Firmware files. + if args and (".bin" in args[0] or ".fwpkg" in args[0] or ".full" in args[0] or ".vme" in args[0]): + if args[0].startswith('"') and args[0].endswith('"'): + args[0] = args[0][1:-1] + else: + raise InvalidCommandLineErrorOPTS("Please input URL pointing to a .bin\.fwpkg\.full\.vme firmware") + + action = None + uri = "FirmwareURI" + + select = self.rdmc.app.typepath.defs.hpilofirmwareupdatetype + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + update_path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + bodydict = results.resp.dict + + try: + for item in bodydict["Actions"]: + if self.rdmc.app.typepath.defs.isgen10: + if "SimpleUpdate" in item: + action = item.split("#")[-1] + + uri = "ImageURI" + options.tpmenabled = False + elif "InstallFromURI" in item: + action = "InstallFromURI" + + if action: + put_path = bodydict["Actions"][item]["target"] + break + except: + put_path = update_path + action = "Reset" + + if options.tpmenabled: + body = {"Action": action, uri: args[0], "TPMOverrideFlag": True} + else: + body = {"Action": action, uri: args[0]} + + self.rdmc.app.post_handler(put_path, body, silent=True, service=True) + + self.rdmc.ui.printer("\nStarting upgrade process...\n\n") + + self.showupdateprogress(update_path) + # logoutobj.run("") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def showupdateprogress(self, path): + """handler function for updating the progress + + :param path: path to update service. + :tyep path: str + """ + counter = 0 + written = False + uploadingpost = False + spinner = ["|", "/", "-", "\\"] + position = 0 + + while True: + if counter == 100: + raise FirmwareUpdateError("Error occurred while updating the firmware.") + else: + counter += 1 + + results = self.rdmc.app.get_handler(path, silent=True) + results = results.dict + try: + results = results["Oem"]["Hpe"] + except: + pass + + if not results: + raise FirmwareUpdateError("Unable to contact Update Service. " "Please re-login and try again.") + + if results["State"].lower().startswith("idle"): + time.sleep(2) + elif results["State"].lower().startswith("uploading"): + counter = 0 + + if not uploadingpost: + uploadingpost = True + else: + if not written: + written = True + self.rdmc.ui.printer("\n iLO is uploading the necessary files. Please wait...\n") + + time.sleep(0.5) + elif results["State"].lower().startswith(("progressing", "updating", "verifying", "writing")): + counter = 0 + + for _ in range(2): + if position < 4: + self.rdmc.ui.printer("Updating: " + spinner[position] + "\r") + position += 1 + time.sleep(0.1) + else: + position = 0 + self.rdmc.ui.printer("Updating: " + spinner[position] + "\r") + position += 1 + time.sleep(0.1) + elif results["State"].lower().startswith("complete"): + self.rdmc.ui.printer( + "\n\nFirmware update has completed and iLO" + " may reset. \nIf iLO resets the" + " session will be terminated.\nPlease wait" + " for iLO to initialize completely before" + " logging in again.\nA reboot may be required" + " for firmware changes to take effect.\n" + ) + break + elif results["State"].lower().startswith("error"): + error = self.rdmc.app.get_handler(path, silent=True) + self.printerrmsg(error) + + def printerrmsg(self, error): + """raises and prints the detailed error message if possible""" + output = "Error occurred while updating the firmware." + + try: + error = error.dict["Oem"]["Hpe"]["Result"]["MessageId"].split(".") + # TODO: Update to new ResponseHandler Method 'return_reg' + errmessages = ResponseHandler( + self.rdmc.app.validation_manager, + self.rdmc.app.typepath.defs.messageregistrytype, + ).get_error_messages() + for messagetype in list(errmessages.keys()): + if error[0] == messagetype: + if errmessages[messagetype][error[-1]]["NumberOfArgs"] == 0: + output = "Firmware update error. %s" % errmessages[messagetype][error[-1]]["Message"] + else: + output = "Firmware update error. %s" % errmessages[messagetype][error[-1]]["Description"] + break + except: + pass + + raise FirmwareUpdateError(output) + + def firmwareupdatevalidation(self, options): + """Firmware update method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--tpmenabled", + dest="tpmenabled", + action="store_true", + help="Use this flag if the server you are currently logged into" " has a TPM chip installed.", + default=False, + ) + diff --git a/ilorest/extensions/iLO_COMMANDS/IPProfilesCommand.py b/ilorest/extensions/iLO_COMMANDS/IPProfilesCommand.py new file mode 100644 index 0000000..6f8921d --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/IPProfilesCommand.py @@ -0,0 +1,700 @@ +### +# Copyright 2016-2021 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 -*- +""" RawGet Command for rdmc """ + +import base64 +import gzip +import json +import os +import re +import time +from ctypes import create_string_buffer +from datetime import datetime +import six +from six import BytesIO +if six.PY3: + from datetime import timezone + + +import redfish +from redfish.hpilo.risblobstore2 import BlobStore2 + +try: + from rdmc_helper import ( + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + InvalidFileInputError, + NoContentsFoundForOperationError, + PathUnavailableError, + ReturnCodes, + UnableToDecodeError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + InvalidFileInputError, + NoContentsFoundForOperationError, + PathUnavailableError, + ReturnCodes, + UnableToDecodeError, + ) + + +class IPProfilesCommand: + """Raw form of the get command""" + + def __init__(self): + self.ident = { + "name": "ipprofiles", + "usage": None, + "description": "Decodes and lists " + "ipprofiles. This is default option. No argument required" + "\n\tExample: ipprofiles" + "\n\n\tAdds a new ipprofile from the provided json file." + "\n\tNOTE: Path can be absolute or from the " + "same path you launch iLOrest." + "\n\tipprofiles " + "\n\n\tDelete an ipprofile or list of profiles.\n\t" + "Provide the unique key that corresponds to the ipprofile" + " data you want to delete.\n\tSeveral IDs can be comma-separated" + " with no space in between to delete more than one profile. " + "\n\tipprofiles --delete ID1,ID2,ID3..." + "\n\n\tCopies ip profile with the specified ID into the ip job queue." + "and starts it.\n\texample: ipprofiles --start=", + "summary": "This is used to manage hpeipprofile data store.", + "aliases": [], + "auxcommands": ["BootOrderCommand", "RebootCommand"], + } + self.path = "" + self.ipjobs = "" + self.running_jobs = "" + self.hvt_output = "" + self.ipjobtype = ["langsel", "hvt", "ssa", "install", "rbsu"] + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main raw get worker function + + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.getpaths() + + self.validation(options) + + self.ipprofileworkerfunction(options, args) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def ipprofileworkerfunction(self, options, args): + """ + Ipprofile manager worker function. It calls appropriate function. + :param options: command line options + :type options: list. + :param args: command line args + :type args: string. + """ + + if options.running_jobs: + self.get_running_job() + return ReturnCodes.SUCCESS + if options.get_hvt: + if options.filename: + self.rdmc.ui.warn("iprprofiles -D option does not support -f, please remove -f and rerun\n") + return ReturnCodes.SUCCESS + self.get_hvt_output() + return ReturnCodes.SUCCESS + if options.del_key: + self.deletekeyfromipprofiledata(options) + return ReturnCodes.SUCCESS + + if options.start_ip: + self.addprofileandstartjob(options) + return ReturnCodes.SUCCESS + + if len(args) == 1: + self.encodeandpatchipprofiledata(args) + return ReturnCodes.SUCCESS + + self.getipprofiledataanddecode(options) + + def getipprofiledataanddecode(self, options): + """ + Retrieves and decodes, if encoded, data from hpeprofile data store + :param options: command line options + :type options: list. + :return returncode: int + """ + + results = self.rdmc.app.get_handler(self.path, silent=True) + if results.status == 404: + raise PathUnavailableError( + "The Intelligent Provisioning resource " + "is not available on this system. You may need" + " to run IP at least once to add the resource." + ) + + if results and results.status == 200: + j2python = json.loads(results.read) + for _, val in enumerate(j2python.keys()): + if isinstance(val, six.string_types): + result = self.decode_base64_string(str(j2python[val])) + if result is not None: + j2python[val] = result + + results.read = json.dumps(j2python, ensure_ascii=False, sort_keys=True) + if results.dict: + if options.filename: + output = json.dumps( + results.dict, + indent=2, + cls=redfish.ris.JSONEncoder, + sort_keys=True, + ) + + filehndl = open(options.filename[0], "w") + filehndl.write(output) + filehndl.close() + + self.rdmc.ui.printer("Results written out to '%s'.\n" % options.filename[0]) + else: + self.rdmc.ui.print_out_json(results.dict) + else: + self.rdmc.ui.warn("No IP profiles found\n") + + def get_running_job(self): + """ + Retrieves and decodes, running job + :return returncode: int + """ + + results = self.rdmc.app.get_handler(self.running_jobs, silent=True) + if results.status == 404: + raise PathUnavailableError( + "The Intelligent Provisioning resource " + "is not available on this system. You may need" + " to run IP at least once to add the resource." + ) + + if results and results.status == 200: + j2python = json.loads(results.read) + for _, val in enumerate(list(j2python.keys())): + if isinstance(val, six.string_types): + result = self.decode_base64_string(str(j2python[val])) + if result is not None: + j2python[val] = result + + results.read = json.dumps(j2python, ensure_ascii=False) + if results.dict: + self.rdmc.ui.print_out_json(results.dict) + else: + self.rdmc.ui.warn("No IP profiles found\n") + + def get_hvt_output(self): + """ + Retrieves and decodes, running job + :return returncode: int + """ + return_value = {} + results = self.rdmc.app.get_handler(self.hvt_output, silent=True) + if results.status == 404: + raise PathUnavailableError( + "The Intelligent Provisioning resource " + "is not available on this system. You may need" + " to run IP at least once to add the resource." + ) + + if results and results.status == 200: + j2python = json.loads(results.read) + for _, val in enumerate(list(j2python.keys())): + if isinstance(val, six.string_types) and "@" not in val: + return_value = json.loads(self.decode_base64_string(str(j2python[val]))) + self.rdmc.ui.print_out_json(return_value) + else: + self.rdmc.ui.error("No IP profiles found\n") + + def encodeandpatchipprofiledata(self, args): + """ + Reads file in the given path, encode it, + and apply it on iLO hpeipprofiles data store. + :param args: command line args + :type args: string. + :retirn returncode: int + """ + + contentsholder = self.encode_base64_string(args) + + if "path" in contentsholder and "body" in contentsholder: + self.rdmc.app.patch_handler(contentsholder["path"], contentsholder["body"]) + + return ReturnCodes.SUCCESS + + def deletekeyfromipprofiledata(self, options): + """ + Provide a string which represents a valid key in + hpeipprofiles data store. + :param options: command line options + :type options: list. + :return returncode: int + """ + + get_results = self.rdmc.app.get_handler(self.path, silent=True) + + j2python = json.loads(get_results.read) + all_keys = options.del_key[0].split(",") + for key in all_keys: + if isinstance(key, six.string_types) and j2python.get(key.strip(), False): + del j2python[key.strip()] + else: + raise InvalidFileFormattingError("%s was not found .\n" % key) + + payload = {"path": self.path, "body": j2python} + + self.rdmc.app.put_handler(payload["path"], payload["body"]) + + return ReturnCodes.SUCCESS + + def addprofileandstartjob(self, options): + """ + Adds ip profile into the job queue and start it. + :return returncode: int + """ + + ipprovider = self.hasipprovider() + if ipprovider is None: + raise PathUnavailableError("System does not support this feature of IP.\n") + + ipjob = self.hasipjobs() + if not ipjob: + raise InvalidFileFormattingError("System does not have any IP" " profile to copy to the job queue.\n") + + current_state = self.inipstate(ipprovider) + if current_state is None: + raise PathUnavailableError("System does not support this feature of IP.\n") + + later_state = False + ipstate = current_state["InIP"] + if isinstance(ipstate, bool) and ipstate: + # make sure we are in IP state. Reset and monitor + self.resetinipstate(ipprovider, current_state) + # if we are in ip, monitor should be fast, use 15 seconds + later_status = self.monitorinipstate(ipprovider, 3) + if later_status: + self.rdmc.ui.printer("Server is in IP state. Powering on...\n") + self.copyjobtoipqueue(ipjob, options.start_ip) + self.rdmc.ui.printer("Copy operation was successful...\n") + return ReturnCodes.SUCCESS + + if not isinstance(ipstate, bool): + # inip is in an unknown state, so ... + # patch to false, reboot, then monitor...if it turns true later... + # then we are in IP state otherwise, manually check system... + self.resetinipstate(ipprovider, current_state) + later_status = self.monitorinipstate(ipprovider, 3) + if later_status: + self.copyjobtoipqueue(ipjob, options.start_ip) + self.rdmc.ui.printer("Copy operation was successful...\n") + return ReturnCodes.SUCCESS + + # Check if the server is On or Off, If it is Off, make it on + path = self.rdmc.app.typepath.defs.systempath + get_results = self.rdmc.app.get_handler(path, silent=True) + result = json.loads(get_results.read) + if result and result.get("PowerState") == "Off": + self.rdmc.ui.printer("Server is in OFF state. Powering on...\n") + self.auxcommands["bootorder"].run("--onetimeboot=Utilities " "--reboot=On --commit") + elif result and result.get("Oem").get("Hpe").get("PostState") != "FinishedPost": + self.rdmc.ui.printer("Server is in POST state. Restarting...\n") + # Forcefully switch off as it is stuck in POST + self.auxcommands["reboot"].run("ForceOff") + time.sleep(5) + # Switch on with bootorder set + self.auxcommands["bootorder"].run("--onetimeboot=Utilities " "--reboot=On --commit") + # Check to find out if server in POST + # count = 0 + # in_post = True + # path = self.rdmc.app.typepath.defs.systempath + # while (count < 5): + # count = count + 1 + # get_results = self.rdmc.app.get_handler(path, silent=True) + # result = json.loads(get_results.read) + # if result and result.get("Oem").get("Hpe").get("PostState") == "FinishedPost": + # in_post = False + # break + # else: + # # Sleep for 1 minutes for 5 times. + # self.rdmc.ui.printer("System in POST...waiting for 1 min...\n") + # time.sleep(60) + + # if in_post: + # raise NoContentsFoundForOperationError( + # "Server still in POST even after 5 minutes - Please check" + # ) + else: + self.rdmc.ui.printer("Server is in OS state. ColdBooting...\n") + try: + self.auxcommands["bootorder"].run("--onetimeboot=Utilities " "--reboot=ColdBoot --commit") + except: + raise InvalidFileFormattingError("System failed to reboot") + + # After reboot, login again + time.sleep(options.sleep_time) # Sleep until reboot + self.validation(options) + + later_state = self.monitorinipstate(ipprovider) + if later_state: + self.copyjobtoipqueue(ipjob, options.start_ip) + self.rdmc.ui.printer("Copy operation was successful...\n") + else: + raise InvalidFileFormattingError( + "\nSystem reboot took longer than 4 minutes." + "something is wrong. You need to physically check this system.\n" + ) + + return ReturnCodes.SUCCESS + + def resetinipstate(self, ipprovider, current_state): + """ + Regardless of previous value, sets InIP value to False + :param ipprovider: url path of heip. + :type ipprovider: string. + :param current_state: the value of InIP. + :type current_state: dict + """ + + current_state["InIP"] = False + + payload = {"path": ipprovider, "body": current_state} + + self.rdmc.app.put_handler(payload["path"], payload["body"], silent=True) + + def monitorinipstate(self, ipprovider, timer=48): + """ + Monitor InIP value every 5 seconds until it turns true or time expires. + :param ipprovider: url path of heip. + :type ipprovider: string. + :param timer: time it takes iLO to boot into F10 assuming we are in boot state. + :type timer: int + :return ipstate: boolean + """ + + retry = timer # 48 * 5 = 4 minutes + ipstate = False + progress = self.progressbar() + self.rdmc.ui.printer("\n") + while retry > 0: + time.sleep(5) + next(progress) + status = self.inipstate(ipprovider) + if isinstance(status["InIP"], bool) and status["InIP"]: + ipstate = True + break + retry = retry - 1 + self.rdmc.ui.printer("\n") + + return ipstate + + def progressbar(self): + """ + An on demand function use to output the progress while iLO is booting into F10. + """ + while True: + yield self.rdmc.ui.printer(">>>") + + def copyjobtoipqueue(self, ipjobs, jobkey): + """ + Copies HpeIpJob to Job queue. Function assumes there is a job to copy. + A check was already done to make sure we have a job to copy in hasipjobs() + :param ipjobs: url path of heip. + :type ipjobs: list of dictionary. + :param jobkey: key of job to copy. + :type jokbey: str. + """ + + get_results = self.rdmc.app.get_handler(self.path, silent=True) + + j2python = json.loads(get_results.read) + copy_job = {} + for ipj in j2python: + if jobkey == ipj: + _decode = self.decode_base64_string(j2python[ipj]) + if _decode is not None: + _critical_props = { + "log": '[{"msg": "WAITINGTOBEPROCESSED", "percent": 0}]', + "status": "waiting", + } + copy_job.update( + { + k: v.update(_critical_props) or v + for k, v in json.loads(_decode).items() + if k in self.ipjobtype + } + ) + else: + raise NoContentsFoundForOperationError("Not supported profile content") + break + if not copy_job: + raise NoContentsFoundForOperationError("The ID %s does not match any ipprofile" % jobkey) + payload = {"path": self.ipjobs, "body": copy_job} + + self.rdmc.app.put_handler(payload["path"], payload["body"]) + + def inipstate(self, ipprovider): + """ + A check is done to determine if this version of iLO has InIP profile. + :param ipprovider: url path of heip. + :type ipprovider: string. + :return is_inip: None or dict + """ + + if ipprovider.startswith("/redfish/"): + get_results = self.rdmc.app.get_handler(ipprovider, silent=True) + result = json.loads(get_results.read) + + is_inip = None + try: + if "InIP" in list(result.keys()): + is_inip = result + except KeyError: + pass + + return is_inip + + def hasipjobs(self): + """ + A check is done to determine if there is a job in HpeIpJobs we can + copy to IP job queue + :param options: command line options + :type options: list. + :return list_dict: list of dicts + """ + + results = self.rdmc.app.get_handler(self.path, silent=True) + + j2python = json.loads(results.read) + for _, val in enumerate(j2python.keys()): + if isinstance(val, six.string_types): + result = self.decode_base64_string(str(j2python[val])) + if result is not None: + j2python[val] = result + + list_dict = [] + + for key, value in j2python.items(): + if not re.match("@odata", key): + if len(key) >= 13 and key.isdigit(): + list_dict.append({key: value}) # list of dict with valid key/value + + return list_dict + + def hasipprovider(self): + """ + A check is done here to determine if this version of iLO has IP provider + profile path using the "Oem.Hpe.Links.HpeIpProvider" + + :return is_provider: None or string. + """ + path = self.rdmc.app.typepath.defs.systempath + get_results = self.rdmc.app.get_handler(path, silent=True) + + result = json.loads(get_results.read) + + is_ipprovider = None + try: + is_ipprovider = list(result["Oem"]["Hpe"]["Links"]["HpeIpProvider"].values())[0] + except KeyError: + pass + + return is_ipprovider + + def validation(self, options): + """IPProfiles validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def decode_base64_string(self, str_b64): + """ + Decodes a given string that was encoded with base64 and gzipped. + :param str_b64: a string that was base64 encoded and the gzipped + :type str_b64: string. + """ + + read_data = None + if isinstance(str_b64, six.string_types) and str_b64: + try: + decoded_str = base64.decodebytes(str_b64.encode("utf-8")) + inbuffer = BytesIO(decoded_str) + gzffile = gzip.GzipFile(mode="rb", fileobj=inbuffer) + read_data = "" + for line in gzffile.readlines(): + read_data = read_data + line.decode("utf-8") + except: + pass + + return read_data + + def encode_base64_string(self, args): + """ + Encode a given string with base64 and gzip it. + :param args: command line args + :type args: string. + """ + + payload = {} + filename = args[0] + if filename: + if not os.path.isfile(filename): + raise InvalidFileInputError( + "File '%s' doesn't exist. " "Please create file by running 'save' command." % filename + ) + + try: + with open(filename, "r") as fh: + contentsholder = json.loads(fh.read()) + except: + raise InvalidFileFormattingError("Input file '%s' was not " "format properly." % filename) + + try: + text = json.dumps(contentsholder).encode("utf-8") + buf = BytesIO() + gzfile = gzip.GzipFile(mode="wb", fileobj=buf) + gzfile.write(text) + gzfile.close() + + en_text = base64.encodebytes(buf.getvalue()).decode("utf-8") + + if six.PY3: + epoch = datetime.fromtimestamp(0, tz=timezone.utc) + now = datetime.now(tz=timezone.utc) + elif six.PY2: + epoch = datetime.utcfromtimestamp(0) + now = datetime.utcnow() + delta = now - epoch + time_stamp = delta.total_seconds() * 1000 + time_stamp = repr(time_stamp).split(".")[0] + + body_text = {time_stamp: en_text.strip()} + + payload["body"] = body_text + if isinstance(self.path, bytes): + self.path = self.path.decode("utf-8") + payload["path"] = self.path + except Exception as excp: + raise UnableToDecodeError("Error while encoding string %s." % excp) + + return payload + + def getpaths(self): + """Get paths for ipprofiles command""" + if not all(iter([self.path, self.ipjobs, self.running_jobs, self.hvt_output])): + dll = BlobStore2.gethprestchifhandle() + + profiles_path = create_string_buffer(50) + jobs_path = create_string_buffer(50) + running_jobs_path = create_string_buffer(50) + hvt_output_path = create_string_buffer(50) + + dll.get_ip_profiles(profiles_path) + dll.get_ip_jobs(jobs_path) + dll.get_running_jobs(running_jobs_path) + dll.get_hvt_output(hvt_output_path) + + self.path = profiles_path.value + self.ipjobs = jobs_path.value + self.running_jobs = running_jobs_path.value + self.hvt_output = hvt_output_path.value + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-r", + "--running", + dest="running_jobs", + default=False, + action="store_true", + help="""Show status of the currently running or last job executed""", + ) + customparser.add_argument( + "-D", + "--diags", + help="""Get result of last HVT (diagnostics) run as part of an ipprofile job""", + default=False, + action="store_true", + dest="get_hvt", + ) + customparser.add_argument( + "-f", + "--filename", + dest="filename", + help="""Write results to the specified file.""", + action="append", + default=None, + ) + customparser.add_argument( + "--delete", + dest="del_key", + action="append", + help="Look for the key or keys in the ipprofile manager and delete", + default=None, + ) + customparser.add_argument( + "-s", + "--start", + dest="start_ip", + help="Copies the specified ip profile into the job queue and starts it", + default=None, + ) + customparser.add_argument( + "-t", + "--sleeptime", + dest="sleep_time", + type=int, + help="Sleep time in seconds when server in OS mode and rebooted", + default=320, + ) diff --git a/ilorest/extensions/iLO_COMMANDS/IloAccountsCommand.py b/ilorest/extensions/iLO_COMMANDS/IloAccountsCommand.py new file mode 100644 index 0000000..fae0980 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/IloAccountsCommand.py @@ -0,0 +1,741 @@ +### +# Copyright 2016-2021 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 -*- +""" Add Account Command for rdmc """ + +import getpass +import os +from argparse import Action, RawDescriptionHelpFormatter + +from redfish.ris.ris import SessionExpired +from redfish.ris.rmc_helper import IdTokenError + +try: + from rdmc_helper import ( + Encryption, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + NoContentsFoundForOperationError, + ResourceExists, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + Encryption, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + NoContentsFoundForOperationError, + ResourceExists, + ReturnCodes, + ) + +__subparsers__ = ["add", "modify", "changepass", "delete", "addcert", "deletecert"] + + +class _AccountParse(Action): + def __init__(self, option_strings, dest, nargs, **kwargs): + super(_AccountParse, self).__init__(option_strings, dest, nargs, **kwargs) + + def __call__(self, parser, namespace, values, option_strings): + """Account privileges option helper""" + + privkey = { + 1: "LoginPriv", + 2: "RemoteConsolePriv", + 3: "UserConfigPriv", + 4: "iLOConfigPriv", + 5: "VirtualMediaPriv", + 6: "VirtualPowerAndResetPriv", + 7: "HostNICConfigPriv", + 8: "HostBIOSConfigPriv", + 9: "HostStorageConfigPriv", + 10: "SystemRecoveryConfigPriv", + } + + for priv in next(iter(values)).split(","): + try: + priv = int(priv) + except ValueError: + try: + parser.error("Invalid privilege entered: %s. Privileges must " "be numbers." % priv) + except: + raise InvalidCommandLineErrorOPTS("") + try: + if not isinstance(namespace.optprivs, list): + namespace.optprivs = list() + if option_strings.startswith("--add"): + namespace.optprivs.append({privkey[priv]: True}) + elif option_strings.startswith("--remove"): + namespace.optprivs.append({privkey[priv]: False}) + except KeyError: + try: + parser.error( + "Invalid privilege entered: %s. Number does not " "match an available privilege." % priv + ) + except: + raise InvalidCommandLineErrorOPTS("") + + # account_parse.counter = 0 + + +class IloAccountsCommand: + + """command to manipulate/add ilo user accounts""" + + def __init__(self): + self.ident = { + "name": "iloaccounts", + "usage": None, + "description": "\tView, Add, Remove, and Modify iLO accounts based on the " + "sub-command used.\n\n\tTo view help on specific sub-commands run: " + "iloaccounts -h\n\t" + "Example: iloaccounts add -h\n\tNote: \n\t\t1. UserName and LoginName are reversed " + "in the iLO GUI for Redfish compatibility.\n\t\t" + "2. While executing the command: iloaccounts add in a Linux machine, " + "an escape character needs to be added before a special character in the password.\n\t\t\t" + "Example: iloaccount add rest rest 12iso*help\n", + "summary": "Views/Adds/deletes/modifies an iLO account on the currently logged in server.", + "aliases": ["iloaccount"], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main iloaccounts function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + acct = mod_acct = None + try: + ident_subparser = False + for cmnd in __subparsers__: + if cmnd in line: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + ident_subparser = True + break + if not ident_subparser: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line, default=True) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.iloaccountsvalidation(options) + + redfish = self.rdmc.app.monolith.is_redfish + path = self.rdmc.app.typepath.defs.accountspath + results = self.rdmc.app.get_handler(path, service=True, silent=True).dict + + if redfish: + results = results["Members"] + else: + if "Member" in results["links"]: + results = results["links"]["Member"] + else: + results = list() + + for indx, acct in enumerate(results): + acct = self.rdmc.app.get_handler( + acct[self.rdmc.app.typepath.defs.hrefstring], service=True, silent=True + ).dict + try: + if hasattr(options, "identifier"): + if acct["Id"] == options.identifier or acct["UserName"] == options.identifier: + if redfish: + path = acct["@odata.id"] + else: + path = acct["links"]["self"]["href"] + mod_acct = acct + elif options.command == "default": + results[indx] = acct + else: + raise KeyError + except KeyError: + continue + else: + if mod_acct: + acct = mod_acct + break + else: + acct = None + + if not results: + raise NoContentsFoundForOperationError("No iLO Management Accounts were found.") + + outdict = dict() + if options.command.lower() == "default": + if not options.json: + self.rdmc.ui.printer( + "\niLO Account info:\n\n[Id] UserName (LoginName): " "\nPrivileges\n-----------------\n\n", + verbose_override=True, + ) + for acct in sorted(results, key=lambda k: int(k["Id"])): + privstr = "" + privs = acct["Oem"][self.rdmc.app.typepath.defs.oemhp]["Privileges"] + + if ( + "ServiceAccount" in list(acct["Oem"][self.rdmc.app.typepath.defs.oemhp].keys()) + and acct["Oem"][self.rdmc.app.typepath.defs.oemhp]["ServiceAccount"] + ): + service = "ServiceAccount=True" + else: + service = "ServiceAccount=False" + if not options.json: + for priv in privs: + privstr += priv + "=" + str(privs[priv]) + "\n" + self.rdmc.ui.printer( + "[%s] %s (%s):\n%s\n%s\n" + % ( + acct["Id"], + acct["UserName"], + acct["Oem"][self.rdmc.app.typepath.defs.oemhp]["LoginName"], + service, + privstr, + ), + verbose_override=True, + ) + keyval = "[" + str(acct["Id"]) + "] " + acct["UserName"] + outdict[keyval] = privs + outdict[keyval]["ServiceAccount"] = service.split("=")[-1].lower() + if options.json: + self.rdmc.ui.print_out_json_ordered(outdict) + elif options.command.lower() == "changepass": + if not acct: + raise InvalidCommandLineError("Unable to find the specified account.") + if not options.acct_password: + self.rdmc.ui.printer("Please input the new password.\n", verbose_override=True) + tempinput = getpass.getpass() + self.credentialsvalidation("", "", tempinput, "", True, options) + options.acct_password = tempinput + + self.credentialsvalidation("", "", options.acct_password.split("\r")[0], "", True) + body = {"Password": options.acct_password.split("\r")[0]} + + if path and body: + self.rdmc.app.patch_handler(path, body) + else: + raise NoContentsFoundForOperationError("Unable to find the specified account.") + + elif options.command.lower() == "add": + if options.encode: + args[2] = Encryption.decode_credentials(args[2]) + if isinstance(args[2], bytes): + args[2] = args[2].decode("utf-8") + + privs = self.getprivs(options) + path = self.rdmc.app.typepath.defs.accountspath + + body = { + "UserName": options.identifier, + "Password": options.acct_password, + "Oem": {self.rdmc.app.typepath.defs.oemhp: {"LoginName": options.loginname}}, + } + if privs: + body["Oem"][self.rdmc.app.typepath.defs.oemhp].update({"Privileges": privs}) + self.credentialsvalidation(options.identifier, options.loginname, options.acct_password, acct, True) + if options.serviceacc: + body["Oem"][self.rdmc.app.typepath.defs.oemhp].update({"ServiceAccount": True}) + if options.role: + if self.rdmc.app.getiloversion() >= 5.140: + body["RoleId"] = options.role + else: + raise IncompatibleiLOVersionError("Roles can only be set in iLO 5" " 1.40 or greater.") + if path and body: + self.rdmc.app.post_handler(path, body) + self.rdmc.ui.printer("New iLO account %s is added successfully\n" % options.identifier) + elif options.command.lower() == "modify": + if not mod_acct: + raise InvalidCommandLineError("Unable to find the specified account.") + body = {} + + if options.optprivs: + body.update({"Oem": {self.rdmc.app.typepath.defs.oemhp: {"Privileges": {}}}}) + if any( + priv for priv in options.optprivs if "SystemRecoveryConfigPriv" in priv + ) and "SystemRecoveryConfigPriv" not in list(self.getsesprivs().keys()): + raise IdTokenError( + "The currently logged in account must have The System " + "Recovery Config privilege to add the System Recovery " + "Config privilege." + ) + privs = self.getprivs(options) + body["Oem"][self.rdmc.app.typepath.defs.oemhp]["Privileges"] = privs + + if options.role and self.rdmc.app.getiloversion() >= 5.140: + body["RoleId"] = options.role + + if not body: + raise InvalidCommandLineError( + "Valid Privileges/RoleID have not been provided; " + " no changes have been made to this account: %s\n" % options.identifier + ) + self.rdmc.app.patch_handler(path, body) + + elif options.command.lower() == "delete": + if not acct: + raise InvalidCommandLineError("Unable to find the specified account.") + self.rdmc.app.delete_handler(path) + + elif "cert" in options.command.lower(): + certpath = "/redfish/v1/AccountService/UserCertificateMapping/" + privs = self.getsesprivs() + if self.rdmc.app.typepath.defs.isgen9: + IncompatibleiLOVersionError("This operation is only available on gen 10 " "and newer machines.") + elif not privs["UserConfigPriv"]: + raise IdTokenError( + "The currently logged in account must have The User " + "Config privilege to manage certificates for users." + ) + else: + if options.command.lower() == "addcert": + if not acct: + raise InvalidCommandLineError("Unable to find the specified account.") + body = {} + username = acct["UserName"] + + fingerprintfile = options.certificate + if os.path.exists(fingerprintfile): + with open(fingerprintfile, "r") as fingerfile: + fingerprint = fingerfile.read() + else: + raise InvalidFileInputError("%s cannot be read." % fingerprintfile) + body = {"Fingerprint": fingerprint, "UserName": username} + self.rdmc.app.post_handler(certpath, body) + + elif options.command.lower() == "deletecert": + if not acct: + raise InvalidCommandLineError("Unable to find the specified account.") + certpath += acct["Id"] + self.rdmc.app.delete_handler(certpath) + + else: + raise InvalidCommandLineError("Invalid command.") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def getprivs(self, options): + """find and return the privileges to set + + :param options: command line options + :type options: list. + """ + sesprivs = self.getsesprivs() + setprivs = {} + availableprivs = self.getsesprivs(availableprivsopts=True) + + if "UserConfigPriv" not in list(sesprivs.keys()): + raise IdTokenError( + "The currently logged in account does not have the User Config " + "privilege and cannot add or modify user accounts." + ) + + if options.optprivs: + for priv in options.optprivs: + priv = next(iter(list(priv.keys()))) + if priv not in availableprivs: + raise IncompatibleiLOVersionError("Privilege %s is not available on this " "iLO version." % priv) + + if all(priv.values() for priv in options.optprivs): + if ( + any(priv for priv in options.optprivs if "SystemRecoveryConfigPriv" in priv) + and "SystemRecoveryConfigPriv" not in sesprivs.keys() + ): + raise IdTokenError( + "The currently logged in account must have The System " + "Recovery Config privilege to add the System Recovery " + "Config privilege." + ) + else: + setprivs = {} + for priv in options.optprivs: + setprivs.update(priv) + + return setprivs + + def getsesprivs(self, availableprivsopts=False): + """Finds and returns the curent session's privileges + + :param availableprivsopts: return available privileges + :type availableprivsopts: boolean. + """ + if self.rdmc.app.current_client: + sespath = self.rdmc.app.current_client.session_location + sespath = ( + self.rdmc.app.current_client.default_prefix + + sespath.split(self.rdmc.app.current_client.default_prefix)[-1] + ) + + ses = self.rdmc.app.get_handler(sespath, service=False, silent=True) + + if not ses: + raise SessionExpired("Invalid session. Please logout and " "log back in or include credentials.") + + sesprivs = { + "HostBIOSConfigPriv": True, + "HostNICConfigPriv": True, + "HostStorageConfigPriv": True, + "LoginPriv": True, + "RemoteConsolePriv": True, + "SystemRecoveryConfigPriv": True, + "UserConfigPriv": True, + "VirtualMediaPriv": True, + "VirtualPowerAndResetPriv": True, + "iLOConfigPriv": True, + } + if "Oem" in ses.dict: + sesoemhp = ses.dict["Oem"][self.rdmc.app.typepath.defs.oemhp] + if "Privileges" in list(sesoemhp.keys()): + sesprivs = sesoemhp["Privileges"] + availableprivs = list(sesprivs.keys()) + updated_privs = dict() + for priv, val in sesprivs.items(): + if val: + updated_privs[priv] = sesprivs[priv] + sesprivs = updated_privs + del updated_privs + else: + sesprivs = None + if availableprivsopts: + return availableprivs + else: + return sesprivs + + def credentialsvalidation( + self, + username="", + loginname="", + password="", + acct=None, + check_password=False, + options=None, + ): + """sanity validation of credentials + :param username: username to be added + :type username: str. + :param loginname: loginname to be added + :type loginname: str. + :param password: password to be added + :type password: str. + :param accounts: target federation account + :type accounts: dict. + :param check_password: flag to check password + :type check_password: bool. + """ + username_max_chars = 39 # 60 + loginname_max_chars = 39 # 60 + password_max_chars = 39 # PASSWORD MAX CHARS + password_min_chars = 8 # PASSWORD MIN CHARS + + password_min_chars = next(iter(self.rdmc.app.select("AccountService."))).dict["Oem"][ + self.rdmc.app.typepath.defs.oemhp + ]["MinPasswordLength"] + + if username != "" and loginname != "": + if len(username) > username_max_chars: + raise InvalidCommandLineError( + "Username exceeds maximum length" ". Use at most %s characters." % username_max_chars + ) + + if len(loginname) > loginname_max_chars: + raise InvalidCommandLineError( + "Login name exceeds maximum " "length. Use at most %s characters." % loginname_max_chars + ) + + try: + if acct: + if ( + acct["UserName"] == username + or acct["Oem"][self.rdmc.app.typepath.defs.oemhp]["LoginName"] == loginname + ): + raise ResourceExists("Username or login name is already in use.") + except KeyError: + pass + + if check_password: + if password == "" or password == "/r": + raise InvalidCommandLineError("An invalid password was entered.") + else: + if len(password) > password_max_chars: + raise InvalidCommandLineError( + "Password length is invalid." " Use at most %s characters." % password_max_chars + ) + if len(password) < password_min_chars: + raise InvalidCommandLineError( + "Password length is invalid." " Use at least %s characters." % password_min_chars + ) + + def iloaccountsvalidation(self, options): + """add account validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + @staticmethod + def options_argument_group(parser): + """Define optional arguments group + + :param parser: The parser to add the --addprivs option group to + :type parser: ArgumentParser/OptionParser + """ + group = parser.add_argument_group( + "GLOBAL OPTIONS", + "Options are available for all " "arguments within the scope of this command.", + ) + + group.add_argument( + "--addprivs", + dest="optprivs", + nargs="*", + action=_AccountParse, + type=str, + help="Optionally include this flag if you wish to specify " + "which privileges you want added to the iLO account. Pick " + "privileges from the privilege list in above help text. EX: --addprivs=1,2,4", + default=None, + metavar="Priv,", + ) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + subcommand_parser = customparser.add_subparsers(dest="command") + privilege_help = ( + "\n\n\tPRIVILEGES:\n\t1: Login\n\t2: Remote Console\n\t3: User Config\n\t4:" + " iLO Config\n\t5: Virtual Media\n\t6: Virtual Power and Reset\n\n\tiLO 5 added " + "privileges:\n\t7: Host NIC Config\n\t8: Host Bios Config\n\t9: Host Storage Config" + "\n\t10: System Recovery Config" + ) + # default sub-parser + default_parser = subcommand_parser.add_parser( + "default", + help="Running without any sub-command will return all account information on the\n" + "currently logged in server.", + ) + default_parser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + self.cmdbase.add_login_arguments_group(default_parser) + # add sub-parser + add_help = ( + "Adds an iLO user account to the currently logged in server with privileges\n" "specified in --addprivs." + ) + add_parser = subcommand_parser.add_parser( + __subparsers__[0], + help=add_help, + description=add_help + "\n\t*Note*:By default only the login privilege is added to the" + ' newly created account\n\twith role "ReadOnly"in iLO 5 and no privileges in iLO 4.' + + privilege_help + + "\n\n\tExamples:\n\n\tAdd an account with specific privileges:\n\t\tiloaccounts add " + "username accountname password --addprivs 1,2,4\n\n\tAdd an account and specify " + "privileges by role:\n\t\tiloaccounts add username accountname password --role " + "ReadOnly", + formatter_class=RawDescriptionHelpFormatter, + ) + # addprivs + add_parser.add_argument( + "identifier", + help="The username or ID of the iLO account to modify.", + metavar="USERNAMEorID#", + ) + add_parser.add_argument( + "loginname", + help="The loginname of the iLO account to add. This is NOT used to login to the newly " "created account.", + metavar="LOGINNAME", + ) + add_parser.add_argument( + "acct_password", + help="The password of the iLO account to add. If you do not include a password, you " + "will be prompted to enter one before an account is created. This is used to login to " + "the newly created account.", + metavar="PASSWORD", + nargs="?", + default="", + ) + add_parser.add_argument( + "--role", + dest="role", + choices=["Administrator", "ReadOnly", "Operator"], + help="Optionally include this option if you would like to specify Privileges by role." + " Roles are a set of privileges created based on the role of the account.", + default=None, + ) + add_parser.add_argument( + "--serviceaccount", + dest="serviceacc", + action="store_true", + help="Optionally include this flag if you wish to created account " "to be a service account.", + default=False, + ) + self.cmdbase.add_login_arguments_group(add_parser) + + self.options_argument_group(add_parser) + # modify sub-parser + modify_help = ( + "Modifies the provided iLO user account on the currently logged in server" + '\nadding privileges using "--addprivs" to include privileges and using\n' + '"--removeprivs" for removing privileges.' + ) + modify_parser = subcommand_parser.add_parser( + __subparsers__[1], + help=modify_help, + description=modify_help + privilege_help + "\n\n\tExamples:\n\n\tModify an iLO account's " + "privileges by adding:\n\tiloaccounts modify username --addprivs 3,5\n\n\t" + "Modify an iLO account's privileges by removal:\n\tiloaccounts modify username " + "--removeprivs 10\n\n\tOr modify an iLO account's privileges by both simultaneously " + "adding and removing privleges:\n\n\tiloaccounts modify username --addprivs 3,7 " + "--removeprivs 9,10", + formatter_class=RawDescriptionHelpFormatter, + ) + modify_parser.add_argument( + "identifier", + help="The username or ID of the iLO account to modify.", + metavar="USERNAMEorID#", + ) + self.cmdbase.add_login_arguments_group(modify_parser) + + self.options_argument_group(modify_parser) # addprivs + modify_parser.add_argument( + "--role", + dest="role", + choices=["Administrator", "ReadOnly", "Operator"], + help="Optionally include this option if you would like to specify Privileges by role." + " Roles are a set of privileges created based on the role of the account.", + default=None, + ) + modify_parser.add_argument( + "--removeprivs", + dest="optprivs", + nargs="*", + action=_AccountParse, + type=str, + help="Include this flag if you wish to specify " + "which privileges you want removed from the iLO account. Pick " + "privileges from the privilege list in the above help text. EX: --removeprivs=1,2,4", + default=None, + metavar="PRIV,", + ) + # changepass sub-parser + changepass_help = "Changes the password of the provided iLO user account on the currently " "logged in server." + changepass_parser = subcommand_parser.add_parser( + __subparsers__[2], + help=changepass_help, + description=changepass_help + "\n\nExamples:\n\nChange the password of an account:\n\t" + "iloaccounts changepass 2 newpassword", + formatter_class=RawDescriptionHelpFormatter, + ) + changepass_parser.add_argument( + "identifier", + help="The username or ID of the iLO account to change the password for.", + metavar="USERNAMEorID#", + ) + changepass_parser.add_argument( + "acct_password", + help="The password to change the selected iLO account to. If you do not include a " + "password, you will be prompted to enter one before an account is created. This is " + "used to login to the newly created account.", + metavar="PASSWORD", + nargs="?", + default="", + ) + self.cmdbase.add_login_arguments_group(changepass_parser) + + # delete sub-parser + delete_help = "Deletes the provided iLO user account on the currently logged in server." + delete_parser = subcommand_parser.add_parser( + __subparsers__[3], + help=delete_help, + description=delete_help + "\n\nExamples:\n\nDelete an iLO account:\n\t" "iloaccounts delete username", + formatter_class=RawDescriptionHelpFormatter, + ) + delete_parser.add_argument( + "identifier", + help="The username or ID of the iLO account to delete.", + metavar="USERNAMEorID#", + ) + self.cmdbase.add_login_arguments_group(delete_parser) + + # addcert sub-parser + addcert_help = "Adds a certificate to the provided iLO user account on the currently logged" " in server." + addcert_parser = subcommand_parser.add_parser( + __subparsers__[4], + help=addcert_help, + description=addcert_help + r"\n\nExamples:\n\nAdd a user certificate to the provided " + r"iLO account.\n\tiloaccounts addcert accountUserName C:\Users\user\cert.txt", + formatter_class=RawDescriptionHelpFormatter, + ) + addcert_parser.add_argument( + "identifier", + help="The username or ID of the iLO account to add a certificate to.", + metavar="USERNAMEorID#", + ) + addcert_parser.add_argument( + "certificate", + help="The certificate to add to the provided iLO account.", + metavar="X.509CERTIFICATE", + ) + self.cmdbase.add_login_arguments_group(addcert_parser) + + # deletecert sub-parser + deletecert_help = "Deletes a certificate to the provided iLO user account on the currently " "logged in server." + deletecert_parser = subcommand_parser.add_parser( + __subparsers__[5], + help=deletecert_help, + description=deletecert_help + "\n\nExamples:\n\nDelete a user certificate from the " + "provided iLO account.\n\tiloaccounts deletecert username", + formatter_class=RawDescriptionHelpFormatter, + ) + deletecert_parser.add_argument( + "identifier", + help="The username or ID of the iLO account to delete the certificate from.", + metavar="USERNAMEorID#", + ) + self.cmdbase.add_login_arguments_group(deletecert_parser) diff --git a/ilorest/extensions/iLO_COMMANDS/IloBackupRestoreCommand.py b/ilorest/extensions/iLO_COMMANDS/IloBackupRestoreCommand.py new file mode 100644 index 0000000..b4521f8 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/IloBackupRestoreCommand.py @@ -0,0 +1,308 @@ +### +# Copyright 2016-2021 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 -*- +""" Factory Defaults Command for rdmc """ +import os +from argparse import RawDescriptionHelpFormatter + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + InvalidPasswordLengthError, + NoContentsFoundForOperationError, + ReturnCodes, + UploadError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + InvalidFileInputError, + InvalidPasswordLengthError, + UploadError, + ) + +from redfish.ris.ris import SessionExpired + + +class IloBackupRestoreCommand: + """Backup and restore server using iLO's .bak file""" + + def __init__(self): + self.ident = { + "name": "backuprestore", + "usage": None, + "description": "Create a .bak file. \n\tExample: backuprestore backup\n\n\t" + "Restore a server using a .bak file. \n\texample: backuprestore " + "restore\n\n\tNOTE: This command is designed to only restore\n\tthe " + "machine from which the backup file was created against.\n\tIf you would like to " + "take one configuration and apply it\n\tto multiple systems see the " + "serverclone command.\n\tThis command is only available in remote mode.", + "summary": "Backup and restore iLO to a server using a .bak file.", + "aliases": ["br"], + "auxcommands": ["LogoutCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main factorydefaults function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + # if not len(args) == 1: + # raise InvalidCommandLineError("backuprestore command takes one argument.") + + self.ilobackuprestorevalidation(options) + + if "blobstore" in self.rdmc.app.current_client.base_url: + raise InvalidCommandLineError("This command is only available remotely.") + + sessionkey = self.rdmc.app.current_client.session_key + # sessionkey = (sessionkey).encode('ascii', 'ignore') + + if options.command == "backup": + self.backupserver(options, sessionkey) + elif options.command == "restore": + self.restoreserver(options, sessionkey) + else: + raise InvalidCommandLineError("Only options are backup or restore\n") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def backupserver(self, options, skey): + """Create .bak file for a server + + :param options: command options + :type options: list. + :param skey: sessionkey of the currently logged in server + :type skey: str. + """ + select = "HpeiLOBackupRestoreService." + backupfile = None + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + service = results.resp.dict + else: + raise NoContentsFoundForOperationError("%s not found.It may not " "be available on this system.\n" % select) + + backuplocation = service["BackupFileLocation"] + backupname = backuplocation.split("/")[-1] + + postdata = [] + postdata.append(("sessionKey", skey)) + + if options.fpass: + if len(options.fpass) > 32: + raise InvalidPasswordLengthError("Length of password cannot be greater than 32 characters.") + postdata.append(("password", options.fpass)) + self.rdmc.ui.printer("Downloading backup file %s...\n" % backupname) + backupfile = self.rdmc.app.post_handler(backuplocation, postdata, service=True, silent=True) + + if backupfile: + if (backupfile.status != 200) and backupfile.read != "": + if "Invalid Session" in backupfile.read: + raise SessionExpired("Invalid session. Please logout and log back in or include credentials.") + self.rdmc.ui.printer("Download complete.\n") + outfile = open(backupname, "wb") + outfile.write(backupfile.ori) + outfile.close() + else: + raise NoContentsFoundForOperationError("Unable to download file.\n") + + def restoreserver(self, options, skey): + """Use a .bak file to restore a server + + :param options: command options + :type options: list. + :param skey: sessionkey of the currently logged in server + :type skey: str. + """ + + select = "HpeiLOBackupRestoreService." + + if options.filename: + filename = options.filename[0] + else: + files = [] + files = [f for f in os.listdir(".") if os.path.isfile(f) and f.endswith(".bak")] + if files and len(files) > 1: + raise InvalidFileInputError( + "More than one .bak file found in " + "the current directory. Please specify " + "a file using the -f option." + ) + elif not files: + raise InvalidFileInputError( + "No .bak file found in current " "directory. Please specify a file using the -f option." + ) + else: + filename = files[0] + + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + service = results.resp.dict + else: + raise NoContentsFoundForOperationError("%s not found.It may not " "be available on this system." % select) + restorelocation = service["HttpPushUri"] + postdata = [] + + with open(filename, "rb") as fle: + bakfile = fle.read() + postdata.append(("sessionKey", skey)) + if options.fpass: + postdata.append(("password", options.fpass)) + postdata.append(("file", (filename, bakfile, "application/octet-stream"))) + if isinstance(skey, bytes): + skey = skey.decode("utf-8") + resp = self.rdmc.app.post_handler( + restorelocation, + postdata, + service=False, + silent=True, + headers={"Cookie": "sessionKey=" + skey}, + ) + + if not resp.status == 200: + resp_ori = resp.ori + if isinstance(resp_ori, bytes): + resp_ori = resp_ori.decode("utf-8") + if resp_ori == "invalid_restore_password": + raise UploadError( + "Invalid or no password supplied during restore. Please " + "supply the password used during creation of the backup file." + ) + else: + raise UploadError("Error while uploading the backup file.") + else: + self.rdmc.ui.printer( + "Restore in progress. iLO will be unresponsive while the " + "restore completes.\nYour session will be terminated.\n" + ) + self.auxcommands["logout"].run("") + + def ilobackuprestorevalidation(self, options): + """factory defaults validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + subcommand_parser = customparser.add_subparsers(dest="command") + subcommand_parser.required = True + backup_help = "Create a backup of a server. This option is iLO5 Onwards" + # backup sub-parser + backup_parser = subcommand_parser.add_parser( + "backup", + help=backup_help, + description=backup_help + "\n\texample: backuprestore backup " "--f --filepass ", + formatter_class=RawDescriptionHelpFormatter, + ) + + backup_parser.add_argument( + "-f", + "--filename", + dest="filename", + help="Use this flag to specify which backup file to restore. By " + "default the commmand will try to find a .bak file in the current " + "working directory.", + action="append", + default=None, + ) + backup_parser.add_argument( + "--filepass", + dest="fpass", + help="Optionally use the provided password when creating the " + "backup file. The same password must be used for restoring.", + default=None, + ) + + restore_help = "Restore a server with backup file. This option is for iLO5 Onwards" + # backup sub-parser + restore_parser = subcommand_parser.add_parser( + "restore", + help=restore_help, + description=restore_help + "\n\texample: backuprestore restore " "--f --filepass ", + formatter_class=RawDescriptionHelpFormatter, + ) + + restore_parser.add_argument( + "-f", + "--filename", + dest="filename", + help="Use this flag to specify which backup file to restore. By " + "default the commmand will try to find a .bak file in the current " + "working directory.", + action="append", + default=None, + ) + restore_parser.add_argument( + "--filepass", + dest="fpass", + help="Optionally use the provided password when creating the " + "backup file. The same password must be used for restoring.", + default=None, + ) diff --git a/ilorest/extensions/iLO_COMMANDS/IloFederationCommand.py b/ilorest/extensions/iLO_COMMANDS/IloFederationCommand.py new file mode 100644 index 0000000..388a470 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/IloFederationCommand.py @@ -0,0 +1,534 @@ +### +# Copyright 2016-2021 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 -*- +""" Add Federation Command for rdmc """ + + +from argparse import Action, RawDescriptionHelpFormatter + +from redfish.ris.ris import SessionExpired +from redfish.ris.rmc_helper import IdTokenError + +try: + from rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ResourceExists, + ReturnCodes, + UsernamePasswordRequiredError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ResourceExists, + ReturnCodes, + UsernamePasswordRequiredError, + ) + +__subparsers__ = ["add", "modify", "changekey", "delete"] + + +class _FederationParse(Action): + def __init__(self, option_strings, dest, nargs, **kwargs): + super(_FederationParse, self).__init__(option_strings, dest, nargs, **kwargs) + + def __call__(self, parser, namespace, values, option_strings): + """Federation privileges option helper""" + + privkey = { + 1: "LoginPriv", + 2: "RemoteConsolePriv", + 3: "UserConfigPriv", + 4: "iLOConfigPriv", + 5: "VirtualMediaPriv", + 6: "VirtualPowerAndResetPriv", + 7: "HostNICConfigPriv", + 8: "HostBIOSConfigPriv", + 9: "HostStorageConfigPriv", + 10: "SystemRecoveryConfigPriv", + } + + for priv in next(iter(values)).split(","): + try: + priv = int(priv) + except ValueError: + try: + parser.error("Invalid privilege entered: %s. Privileges must " "be numbers." % priv) + except: + raise InvalidCommandLineErrorOPTS("") + + try: + if not isinstance(namespace.optprivs, list): + namespace.optprivs = list() + if option_strings.startswith("--add"): + namespace.optprivs.append({privkey[priv]: True}) + elif option_strings.startswith("--remove"): + namespace.optprivs.append({privkey[priv]: False}) + except KeyError: + try: + parser.error( + "Invalid privilege entered: %s. Number does not " "match an available privlege." % priv + ) + except: + raise InvalidCommandLineErrorOPTS("") + + +class IloFederationCommand: + """Add a new ilo federation to the server""" + + def __init__(self): + self.ident = { + "name": "ilofederation", + "usage": None, + "description": "View, Add, Remove and Modify iLO Federation accoutns based on the " + "sub-command used.\nTo view help on specific sub-commands run: ilofederation " + " -h\n\nExample: ilofederation add -h\n" + "NOTE 1: By default only the login privilege is added to the newly created\n\t\t" + 'federation group with role "ReadOnly" in iLO 5 and no privileges in iLO 4.\n\t' + "NOTE 2: Federation credentials are case-sensitive.", + "summary": "Adds / deletes an iLO federation group on the currently logged in server.", + "aliases": [], + "auxcommands": [], + } + + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main addfederation function + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + body = dict() + try: + ident_subparser = False + for cmnd in __subparsers__: + if cmnd in line: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + ident_subparser = True + break + if not ident_subparser: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line, default=True) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.ilofederationvalidation(options) + + redfish = self.rdmc.app.monolith.is_redfish + path = self.rdmc.app.typepath.defs.federationpath + results = self.rdmc.app.get_handler(path, service=True, silent=True).dict + + if redfish: + results = results["Members"] + else: + if "Member" in results["links"]: + results = results["links"]["Member"] + else: + results = list() + + mod_fed = None + path = None + for indx, acct in enumerate(results): + fed = self.rdmc.app.get_handler( + acct[self.rdmc.app.typepath.defs.hrefstring], service=True, silent=True + ).dict + try: + if hasattr(options, "fedname"): + if fed["Name"].lower() == options.fedname.lower(): + if redfish: + path = acct["@odata.id"] + else: + path = acct["href"] + mod_fed = fed + elif options.command == "default": + results[indx] = fed + else: + raise KeyError + except KeyError: + continue + else: + if mod_fed: + break + + if mod_fed: + if options.command.lower() == "add": + raise ResourceExists("Federation name %s is already in use." % options.fedname) + else: + if options.command.lower() != "add" and options.command.lower() != "default": + raise InvalidCommandLineError("Unable to find the specified federation %s." % options.fedname) + + if options.command.lower() == "add": + privs = self.getprivs(options) + path = self.rdmc.app.typepath.defs.federationpath + + body = {"Name": options.fedname, "Key": options.fedkey} + if privs: + body.update({"Privileges": privs}) + self.addvalidation(options.fedname, options.fedkey, results) + + if path and body: + resp = self.rdmc.app.post_handler(path, body, silent=False, service=False) + + if resp and resp.dict: + if "resourcealreadyexist" in str(resp.dict).lower(): + raise ResourceExists("") + + elif options.command.lower() == "changekey": + try: + newkey = options.fedkey + except: + raise InvalidCommandLineError("Invalid number of parameters.") + + body = {"Key": newkey} + + if path and body: + self.rdmc.app.patch_handler(path, body, service=True) + else: + raise NoContentsFoundForOperationError("Unable to find " "the specified federation.") + elif options.command.lower() == "modify": + if options.optprivs: + body.update({"Privileges": {}}) + if any( + priv for priv in options.optprivs if "SystemRecoveryConfigPriv" in priv + ) and "SystemRecoveryConfigPriv" not in list(self.getsesprivs().keys()): + raise IdTokenError( + "The currently logged in federation must have The System " + "Recovery Config privilege to add the System Recovery " + "Config privilege." + ) + privs = self.getprivs(options) + body["Privileges"] = privs + + self.rdmc.app.patch_handler(path, body) + + elif options.command.lower() == "delete": + if path: + self.rdmc.app.delete_handler(path) + else: + if len(results) == 0: + self.rdmc.ui.printer("No iLO Federation accounts found.\n") + else: + self.rdmc.ui.printer("iLO Federation Id list with Privileges:\n") + if options.json: + outdict = dict() + for fed in sorted(results, key=lambda k: k["Name"]): + outdict[fed["Name"]] = fed["Privileges"] + self.rdmc.ui.print_out_json_ordered(outdict) + else: + for fed in sorted(results, key=lambda k: k["Name"]): + privstr = "" + privs = fed["Privileges"] + for priv in privs: + privstr += priv + "=" + str(privs[priv]) + "\n" + self.rdmc.ui.printer("\nName=%s:\n%s" % (fed["Name"], privstr)) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def getprivs(self, options): + """find and return the privileges to set + :param options: command line options + :type options: list. + """ + sesprivs = self.getsesprivs() + setprivs = {} + availableprivs = self.getsesprivs(availableprivsopts=True) + + if "UserConfigPriv" not in list(sesprivs.keys()): + raise IdTokenError( + "The currently logged in federation does not have the Config" + "Privilege and cannot add or modify federations." + ) + + if options.optprivs: + for priv in options.optprivs: + priv = next(iter(list(priv.keys()))) + if not options.user or not options.password: + if priv == "SystemRecoveryConfigPriv" and self.rdmc.app.current_client.base_url == "blobstore://.": + raise UsernamePasswordRequiredError( + "Privilege %s need username and password to be specified." % priv + ) + if priv not in availableprivs: + raise IncompatibleiLOVersionError("Privilege %s is not available on this " "iLO version." % priv) + + if all(priv.values() for priv in options.optprivs): + if ( + any(priv for priv in options.optprivs if "SystemRecoveryConfigPriv" in priv) + and "SystemRecoveryConfigPriv" not in sesprivs.keys() + ): + raise IdTokenError( + "The currently logged in account must have The System " + "Recovery Config privilege to add the System Recovery " + "Config privilege." + ) + else: + setprivs = {} + for priv in options.optprivs: + setprivs.update(priv) + + return setprivs + + def getsesprivs(self, availableprivsopts=False): + """Finds and returns the current session's privileges + :param availableprivsopts: return available privileges + :type availableprivsopts: boolean. + """ + if self.rdmc.app.current_client: + sespath = self.rdmc.app.current_client.session_location + sespath = ( + self.rdmc.app.current_client.default_prefix + + sespath.split(self.rdmc.app.current_client.default_prefix)[-1] + ) + + ses = self.rdmc.app.get_handler(sespath, service=False, silent=True) + + if ses.status != 200: + raise SessionExpired("Invalid session. Please logout and " "log back in or include credentials.") + + sesprivs = { + "HostBIOSConfigPriv": True, + "HostNICConfigPriv": True, + "HostStorageConfigPriv": True, + "LoginPriv": True, + "RemoteConsolePriv": True, + "SystemRecoveryConfigPriv": True, + "UserConfigPriv": True, + "VirtualMediaPriv": True, + "VirtualPowerAndResetPriv": True, + "iLOConfigPriv": True, + } + if "Oem" in ses.dict: + sesoemhp = ses.dict["Oem"][self.rdmc.app.typepath.defs.oemhp] + if "Privileges" in list(sesoemhp.keys()): + sesprivs = sesoemhp["Privileges"] + availableprivs = list(sesprivs.keys()) + keepprivs = dict() + for priv, val in sesprivs.items(): + if val: + keepprivs[priv] = sesprivs[priv] + sesprivs = keepprivs + else: + sesprivs = None + + if availableprivsopts: + return availableprivs + else: + return sesprivs + + def addvalidation(self, username, key, feds): + """add validation function + :param username: username to be added + :type username: str. + :param key: key to be added + :type key: str. + :param feds: list of federation accounts + :type feds: list. + """ + + if len(username) >= 32: + raise InvalidCommandLineError("User name exceeds maximum length.") + elif len(key) >= 32 or len(key) <= 7: + raise InvalidCommandLineError("Password is invalid length.") + + def ilofederationvalidation(self, options): + """addfederation validation function + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + @staticmethod + def options_addprivs_argument_group(parser): + """Define optional arguments group + :param parser: The parser to add the addprivs option group to + :type parser: ArgumentParser/OptionParser + """ + parser.add_argument( + "--addprivs", + dest="optprivs", + nargs="*", + action=_FederationParse, + type=str, + help="Optionally include this flag if you wish to specify " + "which privileges you want added to the iLO federation. This overrides the default of " + "duplicating privileges of the currently logged in federation on the new federation. " + "Pick privileges from the privilege list in the above help text. EX: --addprivs=1,2,4", + default=None, + ) + + @staticmethod + def options_removeprivs_argument_group(parser): + """Additional argument + :param parser: The parser to add the removeprivs option group to + :type parser: ArgumentParser/OptionParser + """ + + parser.add_argument( + "--removeprivs", + dest="optprivs", + nargs="*", + action=_FederationParse, + type=str, + help="Optionally include this flag if you wish to specify " + "which privileges you want removed from the iLO federation. This overrides the default" + " of duplicating privileges of the currently logged in federation on the new " + "federation. Pick privileges from the privilege list in the above help text. " + "EX: --removeprivs=1,2,4", + default=None, + ) + + def definearguments(self, customparser): + """Wrapper function for new command main function + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + subcommand_parser = customparser.add_subparsers(dest="command") + privilege_help = ( + "\n\nPRIVILEGES:\n\t1: Login\n\t2: Remote Console\n\t3: User Config\n\t4:" + " iLO Config\n\t5: Virtual Media\n\t6: Virtual Power and Reset\n\n\tiLO 5 added " + "privileges:\n\t7: Host NIC Config\n\t8: Host Bios Config\n\t9: Host Storage Config" + "\n\t10: System Recovery Config" + ) + # default sub-parser + default_parser = subcommand_parser.add_parser( + "default", + help="Running without any sub-command will return all federation group information " + " on the currently logged in server.", + ) + default_parser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + self.cmdbase.add_login_arguments_group(default_parser) # add sub-parser + add_help = ( + "Adds an iLO federation group to the currently logged in server. Federation " + 'group privileges may be specified with\n"--addprivs". If a federation key ' + "is not " + "provided, the user will be prompted to provide one prior to account creation." + ) + add_parser = subcommand_parser.add_parser( + __subparsers__[0], + help=add_help, + description=add_help + + "\n\tilofederation add [FEDERATIONNAME] [FEDERATIONKEY] " + + privilege_help + + "\n\tilofederation add newilofedname thisfedkey --addprivs 1,3,4", + formatter_class=RawDescriptionHelpFormatter, + ) + add_parser.add_argument( + "fedname", + help="Federation name of the federation group to add.", + type=str, + metavar="FEDERATION KEY", + ) + add_parser.add_argument( + "fedkey", + help="Federation key of the federation group to add.", + type=str, + metavar="FEDERATION KEY", + ) + self.options_addprivs_argument_group(add_parser) + self.cmdbase.add_login_arguments_group(add_parser) + + modify_help = "Modify the privileges on an existing federation group." + modify_parser = subcommand_parser.add_parser( + __subparsers__[1], + help=modify_help, + description=modify_help + "\n\nTo add privileges:\n\tilofederation modify " + "[FEDNAME] --addprivs \n\nTo remove privileges:\n\t" + "ilofederation modify [FEDNAME] --removeprivs \n\n" + privilege_help, + formatter_class=RawDescriptionHelpFormatter, + ) + modify_parser.add_argument( + "fedname", + help="The federation name of the iLO account to modify.", + metavar="FEDERATION NAME", + type=str, + ) + self.options_addprivs_argument_group(modify_parser) + self.options_removeprivs_argument_group(modify_parser) + self.cmdbase.add_login_arguments_group(modify_parser) + + # changepass sub-parser + changekey_help = "Change the key of an iLO federation group on the currently logged in " "server." + changekey_parser = subcommand_parser.add_parser( + __subparsers__[2], + help=changekey_help, + description=changekey_help + "\n\nexample:ilofederation changekey [FEDNAME] [NEWFEDKEY]", + formatter_class=RawDescriptionHelpFormatter, + ) + changekey_parser.add_argument( + "fedname", + help="The iLO federation account to be updated with a new federation key (password).", + metavar="FEDERATION NAME", + type=str, + ) + changekey_parser.add_argument( + "fedkey", + help="The federation key (password) to be altered for the selected iLO federation " + " account. If you do not include a federation key, you will be prompted to enter one.", + metavar="FEDERATION KEY", + type=str, + nargs="?", + default="", + ) + self.cmdbase.add_login_arguments_group(changekey_parser) + + # delete sub-parser + delete_help = "Deletes the provided iLO user account on the currently logged in server." + delete_parser = subcommand_parser.add_parser( + __subparsers__[3], + help=delete_help, + description=delete_help + "\n\nexample: ilofederation delete fedname", + formatter_class=RawDescriptionHelpFormatter, + ) + delete_parser.add_argument( + "fedname", + help="The iLO federation account to delete.", + metavar="FEDERATION NAME", + type=str, + ) + self.cmdbase.add_login_arguments_group(delete_parser) diff --git a/ilorest/extensions/iLO_COMMANDS/IloLicenseCommand.py b/ilorest/extensions/iLO_COMMANDS/IloLicenseCommand.py new file mode 100644 index 0000000..5cee58e --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/IloLicenseCommand.py @@ -0,0 +1,273 @@ +### +# Copyright 2016-2022 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 -*- +""" Add License Command for rdmc """ + +try: + from rdmc_helper import ( + IloLicenseError, + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + PathUnavailableError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineErrorOPTS, + PathUnavailableError, + IncompatibleiLOVersionError, + IloLicenseError, + ) + +from redfish.ris import rmc_helper + + +class IloLicenseCommand: + """Add an iLO license to the server""" + + def __init__(self): + self.ident = { + "name": "ilolicense", + "usage": None, + "description": "Set an iLO license on the current logged in server.\n\t", + "summary": "Adds an iLO license key to the currently logged in server.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main ilolicense Function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("Provide required argument") + + self.addlicensevalidation(options) + + code = self.ilolicenseworkerfunction(options, args) + + self.cmdbase.logout_routine(self, options) + # Return code + return code + + def ilolicenseworkerfunction(self, options, args): + """ + Ilolicense worker function. It calls appropriate function. + :param options: command line options + :type options: list. + :param args: command line args + :type args: string. + """ + path = self.rdmc.app.typepath.defs.addlicensepath + if options.check_license: + self.check_license(options, path) + return ReturnCodes.SUCCESS + if options.check_state: + if self.rdmc.app.typepath.defs.isgen10: + result = self.get_license(options, path) + state = result.dict["ConfirmationRequest"]["EON"]["State"] + self.rdmc.ui.print_out_json("State: " + state) + if state == "confirmed": + self.rdmc.ui.printer("License is confirmed\n") + if state == "unconfirmed": + self.rdmc.ui.printer("License is not confirmed\n") + if state == "unlicensed": + self.rdmc.ui.printer("Server is unlicensed\n") + if state == "evaluation": + self.rdmc.ui.printer("Server is in evaluation mode\n") + return ReturnCodes.SUCCESS + else: + self.rdmc.ui.printer("Feature supported only for Gen 10 and above\n") + return ReturnCodes.SUCCESS + if options.license_key: + return_code = self.license_key(options, path, args) + return return_code + if options.uninstall_license: + return_code = self.uninstall_license(options, path) + return return_code + if ( + len(args) == 0 + and options.license_key is None + and options.check_license is None + and options.check_state is False + and options.uninstall_license is False + ): + result = self.get_license(options, path) + self.print_license_info(result.dict) + return ReturnCodes.SUCCESS + if ( + len(args) == 1 + and options.license_key is None + and options.check_license is None + and options.check_state is False + and options.uninstall_license is False + ): + return_code = self.license_key(options, path, args) + return return_code + + def print_license_info(self, results): + """ + Prints the license info + """ + for key, value in results.items(): + if "@odata" not in key: + if type(value) is dict: + self.print_license_info(value) + else: + self.rdmc.ui.printer(key + ":" + str(value) + "\n") + + def uninstall_license(self, options, path): + """ + Deletes the license + """ + if self.rdmc.app.typepath.defs.isgen10: + path = path + "1/" + else: + path = path + "/1" + try: + results = self.rdmc.app.delete_handler(path, silent=True) + if results.status == 404: + raise PathUnavailableError("License is not installed") + # return ReturnCodes.SUCCESS + if results.status == 403: + self.rdmc.ui.error("Insufficient Privilege to uninstall license") + return ReturnCodes.RIS_MISSING_ID_TOKEN + if results.status == 400: + self.rdmc.ui.printer("There is no license available to uninstall\n") + return ReturnCodes.SUCCESS + if results.status == 200: + self.rdmc.ui.printer("Uninstalled license successfully\n") + return ReturnCodes.SUCCESS + except rmc_helper.IloLicenseError: + self.rdmc.ui.error("Error Occured while Uninstall") + return ReturnCodes.ILO_LICENSE_ERROR + except IncompatibleiLOVersionError: + self.rdmc.ui.error("iLO FW version on this server doesnt support this operation") + return ReturnCodes.INCOMPATIBLE_ILO_VERSION_ERROR + + def license_key(self, options, path, args): + """ + Installs the license + """ + if options.license_key is not None: + if options.license_key[0] is not None: + body = {"LicenseKey": "%s" % options.license_key[0]} + else: + self.rdmc.ui.printer("Provide license key to install\n") + if len(args) == 1 and options.license_key is None: + body = {"LicenseKey": "%s" % args[0]} + try: + results = self.rdmc.app.post_handler(path, body) + if results.status == 201: + return ReturnCodes.SUCCESS + except rmc_helper.IloLicenseError: + self.rdmc.ui.error("Error Occured while install") + return ReturnCodes.ILO_LICENSE_ERROR + except rmc_helper.IdTokenError: + self.rdmc.ui.error("Insufficient Privilege to update license") + return ReturnCodes.RIS_MISSING_ID_TOKEN + + def check_license(self, options, path): + """ + Checks and Displays the license + """ + if self.rdmc.app.typepath.defs.isgen10: + if options.check_license[0] is not None: + result = self.get_license(options, path) + + if result.dict["ConfirmationRequest"]["EON"]["LicenseKey"] == options.check_license[0]: + self.rdmc.ui.printer("Matched. Provided key is installed on this server\n") + else: + self.rdmc.ui.printer("Provided key is not installed on this server\n") + else: + self.rdmc.ui.printer("Provide license key to check\n") + else: + self.rdmc.ui.printer("Feature supported only on gen 10 and above\n") + + def get_license(self, options, path): + """ + Gets the license + """ + if self.rdmc.app.typepath.defs.isgen10: + path = path + "1/" + else: + path = path + "/1" + results = self.rdmc.app.get_handler(path, silent=True) + if results.status == 200: + return results + + def addlicensevalidation(self, options): + """ilolicense validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--install", + dest="license_key", + action="append", + help="""Installs the given normal or premium license""", + default=None, + ) + customparser.add_argument( + "--uninstall", + help="""Deletes the installed license""", + action="store_true", + dest="uninstall_license", + ) + customparser.add_argument( + "--check", + dest="check_license", + help="""Lists the specified license""", + action="append", + default=None, + ) + customparser.add_argument( + "--check_confirm", + dest="check_state", + action="store_true", + help="Checks the confirmed state and displays it", + ) diff --git a/ilorest/extensions/iLO_COMMANDS/IloResetCommand.py b/ilorest/extensions/iLO_COMMANDS/IloResetCommand.py new file mode 100644 index 0000000..fd3ed2b --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/IloResetCommand.py @@ -0,0 +1,138 @@ +### +# Copyright 2016-2021 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 -*- +""" iLO Reset Command for rdmc """ + +from redfish.ris.rmc_helper import IloResponseError + +try: + from rdmc_helper import ( + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class IloResetCommand: + """Reset iLO on the server that is currently logged in""" + + def __init__(self): + self.ident = { + "name": "iloreset", + "usage": None, + "description": "Reset iLO on the current logged in" " server.\n\tExample: iloreset", + "summary": "Reset iLO on the current logged in server.", + "aliases": [], + "auxcommands": [], + } + + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main iLO reset worker function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.iloresetvalidation(options) + + self.rdmc.ui.warn( + "\nAfter iLO resets, the session will be terminated." + "\nPlease wait for iLO to initialize completely before logging " + "in again.\nThis process may take up to 3 minutes to complete.\n\n" + ) + + select = "Manager." + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + post_path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + bodydict = results.resp.dict + + try: + for item in bodydict["Actions"]: + if "Reset" in item: + if self.rdmc.app.typepath.defs.isgen10: + action = item.split("#")[-1] + else: + action = "Reset" + + post_path = bodydict["Actions"][item]["target"] + break + except: + action = "Reset" + + body = {"Action": action} + + postres = self.rdmc.app.post_handler(post_path, body, silent=True, service=True) + if postres.status == 200: + self.rdmc.ui.printer("A management processor reset is in progress.\n") + else: + self.rdmc.ui.error("An error occured during iLO reset.\n") + raise IloResponseError("") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def iloresetvalidation(self, options): + """reboot method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/iLO_COMMANDS/OneButtonEraseCommand.py b/ilorest/extensions/iLO_COMMANDS/OneButtonEraseCommand.py new file mode 100644 index 0000000..52c5b6d --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/OneButtonEraseCommand.py @@ -0,0 +1,298 @@ +### +# Copyright 2016-2021 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 -*- +""" Factory Defaults Command for rdmc """ +import time +from collections import OrderedDict + +import colorama +from six.moves import input + +try: + from rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + +CURSOR_UP_ONE = "\x1b[1A" +ERASE_LINE = "\x1b[2K" + + +class OneButtonEraseCommand: + """Backup and restore server using iLO's .bak file""" + + def __init__(self): + self.ident = { + "name": "onebuttonerase", + "usage": None, + "description": "Erase all iLO settings, Bios settings, User Data, and iLO Repository data." + "\n\tExample: onebuttonerase\n\n\tSkip the confirmation before" + " erasing system data.\n\texample: onebuttonerase --confirm\n\nWARNING: This " + "command will erase user data! Use with extreme caution! Complete erase can take" + " up to 24 hours to complete.", + "summary": "Performs One Button Erase on a system.", + "aliases": [], + "auxcommands": ["RebootCommand"], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main onebuttonerase function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if args: + raise InvalidCommandLineError("onebuttonerase command takes no arguments.") + + self.onebuttonerasevalidation(options) + + select = "ComputerSystem." + results = self.rdmc.app.select(selector=select) + + if self.rdmc.app.getiloversion() < 5.140: + raise IncompatibleiLOVersionError("One Button Erase is only available on iLO 5 1.40 " "and greater.") + try: + results = results[0].dict + except: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + if ( + results["Oem"]["Hpe"]["SystemROMAndiLOEraseStatus"] == "Idle" + and results["Oem"]["Hpe"]["UserDataEraseStatus"] == "Idle" + ): + post_path = None + body_dict = {"SystemROMAndiLOErase": True, "UserDataErase": True} + for item in results["Oem"]["Hpe"]["Actions"]: + if "SecureSystemErase" in item: + post_path = results["Oem"]["Hpe"]["Actions"][item]["target"] + break + + if options.confirm: + userresp = "erase" + else: + userresp = input( + 'Please type "erase" to begin erase process. Any other input will' + " cancel the operation. If you wish to skip this prompt add the --confirm flag: " + ) + + if userresp == "erase": + if post_path and body_dict: + self.rdmc.app.post_handler(post_path, body_dict) + self.rdmc.app.post_handler( + results["Actions"]["#ComputerSystem.Reset"]["target"], + {"ResetType": "ForceRestart"}, + ) + if not options.nomonitor: + self.monitor_erase(results["@odata.id"]) + return ReturnCodes.SUCCESS + else: + NoContentsFoundForOperationError("Unable to start One Button Erase.") + else: + self.rdmc.ui.printer("Canceling One Button Erase.\n") + return ReturnCodes.SUCCESS + else: + self.rdmc.ui.warn("System is already undergoing a One Button Erase process...\n") + if not options.nomonitor: + self.monitor_erase(results["@odata.id"]) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def monitor_erase(self, path): + """Monitor the One Button Erase progress + + :param path: Path to the one button monitor path + :type path: str. + """ + print_dict = { + "BIOSSettingsEraseStatus": "Bios Settings Erase:", + "iLOSettingsEraseStatus": "iLO Settings Erase:", + "ElapsedEraseTimeInMinutes": "Elapsed Time in Minutes:", + "EstimatedEraseTimeInMinutes": "Estimated Remaining Time in Minutes:", + "NVDIMMEraseStatus": "NVDIMM Erase:", + "NVMeDrivesEraseStatus": "NVMe Drive Erase:", + "SATADrivesEraseStatus": "SATA Drive Erase:", + "TPMEraseStatus": "TPM Erase:", + "SmartStorageEraseStatus": "Smart Storage Erase:", + "SystemROMAndiLOEraseStatus": "Bios and iLO Erase:", + "UserDataEraseStatus": "User Data Erase:", + } + colorama.init() + + self.rdmc.ui.printer("\tOne Button Erase Status\n") + self.rdmc.ui.printer("==========================================================\n") + results = self.rdmc.app.get_handler(path, service=True, silent=True) + counter = 0 + eraselines = 0 + while True: + if not (counter + 1) % 8: + results = self.rdmc.app.get_handler(path, service=True, silent=True) + print_data = self.gather_data(results.dict["Oem"]["Hpe"]) + + self.reset_output(eraselines) + + for key in list(print_data.keys()): + self.print_line(print_dict[key], print_data[key], counter) + if counter == 7: + counter = 0 + else: + counter += 1 + if all( + [ + print_data[key].lower() in ["completedwithsuccess", "completedwitherrors", "failed"] + for key in list(print_data.keys()) + if not key.lower() in ["elapsederasetimeinminutes", "estimatederasetimeinminutes"] + ] + ): + break + eraselines = len(list(print_data.keys())) + time.sleep(0.5) + colorama.deinit() + options = {} + self.cmdbase.logout_routine(self, options) + + def gather_data(self, resdict): + """Gather information on current progress from response + + :param resdict: response dictionary to parse + :type resdict: dict. + """ + retdata = OrderedDict() + data = [ + ("ElapsedEraseTimeInMinutes", None), + ("EstimatedEraseTimeInMinutes", None), + ( + "SystemROMAndiLOEraseComponentStatus", + ["BIOSSettingsEraseStatus", "iLOSettingsEraseStatus"], + ), + ( + "UserDataEraseComponentStatus", + [ + "NVDIMMEraseStatus", + "NVMeDrivesEraseStatus", + "SATADrivesEraseStatus", + "SmartStorageEraseStatus", + "TPMEraseStatus", + ], + ), + ] + for key, val in data: + if val: + if key == "SystemROMAndiLOEraseComponentStatus": + try: + resdict[key] + except KeyError: + retdata["SystemROMAndiLOEraseStatus"] = resdict["SystemROMAndiLOEraseStatus"] + elif key == "UserDataEraseComponentStatus": + try: + if not resdict[key]: + raise KeyError() + except KeyError: + retdata["UserDataEraseStatus"] = resdict["UserDataEraseStatus"] + for item in val: + try: + retdata[item] = resdict[key][item] + except KeyError: + pass + else: + try: + retdata[key] = resdict[key] + except KeyError: + pass + + return retdata + + def reset_output(self, numlines=0): + """reset the output for the next print""" + for _ in range(numlines): + self.rdmc.ui.printer(CURSOR_UP_ONE) + self.rdmc.ui.printer(ERASE_LINE) + + def print_line(self, pstring, value, ctr): + """print the line from system monitoring""" + pline = "%s %s" % (pstring, value) + + spinner = ["|", "/", "-", "\\"] + if str(value).lower() in ["initiated", "inprogress"]: + pline += "\t%s" % spinner[ctr % 4] + pline += "\n" + + self.rdmc.ui.printer(pline) + + def onebuttonerasevalidation(self, options): + """one button erase validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--nomonitor", + dest="nomonitor", + help="Optionally include this flag to skip monitoring of the one button erase process " + "and simply trigger the operation.", + action="store_true", + default=False, + ) + customparser.add_argument( + "--confirm", + dest="confirm", + help="Optionally include this flag to skip the confirmation prompt before starting One" + " Button Erase and begin the operation.", + action="store_true", + default=False, + ) diff --git a/ilorest/extensions/iLO_COMMANDS/RebootCommand.py b/ilorest/extensions/iLO_COMMANDS/RebootCommand.py new file mode 100644 index 0000000..8468ea7 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/RebootCommand.py @@ -0,0 +1,271 @@ +### +# Copyright 2016-2021 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 -*- +""" Reboot Command for rdmc """ + +import time + +from six.moves import input + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class RebootCommand: + """Reboot server that is currently logged in""" + + def __init__(self): + self.ident = { + "name": "reboot", + "usage": None, + "description": "Remotely control system power state commands such as, " + "\n\t1. Turning the system on.\n\t2. Turning the system off.\n\t3. Power " + "cycling/rebooting.\n\t4. Issuing a Non-Maskable Interrupt (NMI).\n\t5. Any " + "number of pre-defined operations through virtual power-button presses." + "\n\n\tNote: By default a force " + "restart will occur, if the system is in an applicable power state.\n\texample: " + "reboot On\n\n\tOPTIONAL PARAMETERS AND DESCRIPTIONS:" + "\n\tOn \t\t(Turns the system on.)\n\tForceOff " + "\t(Performs an immediate non-graceful shutdown.)" + "\n\tForceRestart \t(DEFAULT) (Performs" + " an immediate non-graceful shutdown,\n\t\t\t" + " followed by a restart of the system.)\n\tNmi " + "\t\t(Generates a Non-Maskable Interrupt to cause" + " an\n\t\t\t immediate system halt.)\n\tPushPowerButton " + "(Simulates the pressing of the physical power " + "button\n\t\t\t on this system.)\n\n\tOEM PARAMETERS AND" + " DESCRIPTIONS:\n\tPress\t\t(Simulates the pressing of the" + " physical power button\n\t\t\t on this system.)\n\t" + "PressAndHold\t(Simulates pressing and holding of the power" + " button\n\t\t\t on this systems.)\n\tColdBoot\t(Immidiately" + " Removes power from the server,\n\t\t\tfollowed by a restart" + " of the system)", + "summary": "Reboot operations for the current logged in server.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main reboot worker function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if len(args) < 2: + self.rebootvalidation(options) + else: + raise InvalidCommandLineError("Invalid number of parameters." " Reboot takes a maximum of 1 parameter.") + + if not args: + self.rdmc.ui.warn( + "\nAfter the server is rebooted the session will be terminated." + "\nPlease wait for the server to boot completely before logging in " + "again.\nRebooting server in 3 seconds...\n" + ) + time.sleep(3) + else: + self.printreboothelp(args[0]) + time.sleep(3) + + select = "ComputerSystem." + results = self.rdmc.app.select(selector=select) + oemlist = ["press", "pressandhold", "coldboot"] + + try: + results = results[0] + except: + pass + + if results: + put_path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("Unable to find %s" % select) + + if args and args[0].lower() in oemlist: + bodydict = results.resp.dict["Oem"][self.rdmc.app.typepath.defs.oemhp] + + if args[0].lower() == "coldboot": + resettype = "SystemReset" + else: + resettype = "PowerButton" + else: + bodydict = results.resp.dict + resettype = "Reset" + + try: + for item in bodydict["Actions"]: + if resettype in item: + if self.rdmc.app.typepath.defs.isgen10: + action = item.split("#")[-1] + else: + action = resettype + + put_path = bodydict["Actions"][item]["target"] + break + except: + action = resettype + + if args and not args[0].lower() == "forcerestart": + if args[0].lower() == "on": + body = {"Action": action, "ResetType": "On"} + elif args[0].lower() == "forceoff": + body = {"Action": action, "ResetType": "ForceOff"} + elif args[0].lower() == "nmi": + body = {"Action": action, "ResetType": "Nmi"} + elif args[0].lower() == "pushpowerbutton": + body = {"Action": action, "ResetType": "PushPowerButton"} + elif args[0].lower() == "press": + body = {"Action": action, "PushType": "Press"} + elif args[0].lower() == "pressandhold": + body = {"Action": action, "PushType": "PressAndHold"} + elif args[0].lower() == "coldboot": + body = {"Action": action, "ResetType": "ColdBoot"} + else: + body = {"Action": action, "ResetType": "ForceRestart"} + + if options.confirm is True: + count = 0 + while True: + count = count + 1 + confirmation = input("Rebooting system, type yes to confirm or no to abort:") + if confirmation.lower() in ("no", "n") or count > 3: + self.rdmc.ui.printer("Aborting reboot.\n") + return ReturnCodes.SUCCESS + elif confirmation.lower() in ("yes", "y"): + break + + self.rdmc.app.post_handler(put_path, body) + + if not options.nologout: + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def printreboothelp(self, flag): + """helper print function for reboot function + + :param flag: command line option + :type flag: str + """ + if flag.upper() == "ON": + self.rdmc.ui.warn( + "\nThe server is powering on. Note, the current session will be " + "terminated.\nPlease wait for the server to boot completely before logging in " + "again.\nTurning on the server in 3 seconds...\n" + ) + elif flag.upper() == "FORCEOFF": + self.rdmc.ui.warn( + "\nThe server is powering off. Note, the current session will be " + "terminated.\nPlease wait for the server to power off completely before logging " + "in again.\nPowering off the server in 3 seconds...\n" + ) + elif flag.upper() == "FORCERESTART": + self.rdmc.ui.warn( + "\nForcing a server restart. Note, the current session will be " + "terminated.\nPlease wait for the server to boot completely before logging in " + "again.\nRebooting the server in 3 seconds...\n" + ) + elif flag.upper() == "NMI": + self.rdmc.ui.warn( + "\nA non-maskable interrupt will be issued to this server. Note, the " + "current session will be terminated.\nIssuing interrupt in 3 seconds...\n" + ) + elif flag.upper() == "PUSHPOWERBUTTON" or flag.upper() == "PRESS": + self.rdmc.ui.warn( + "\nThe server power button will be virtually pushed; the reaction " + "will be dependent on the current system power and boot state. Note the current " + "session will be terminated.\nVirtual push in 3 seconds...\n" + ) + elif flag.upper() == "COLDBOOT": + self.rdmc.ui.warn( + "\nThe server will be cold boot, power cycled. Note, the current " + "session will be terminated.\nPlease wait for the server to boot completely " + "before logging in again.\nCold Booting server in 3 seconds...\n" + ) + elif flag.upper() == "PRESSANDHOLD": + self.rdmc.ui.warn( + "\nThe server will be forcefully powered off. Note, the current " + "session will be terminated.\nPlease wait for the server to power off " + "completely before logging in again.\nPressing and holding the power button in " + "3 seconds...\n" + ) + else: + raise InvalidCommandLineError("Invalid parameter: '%s'. Please run" " 'help reboot' for parameters." % flag) + + def rebootvalidation(self, options): + """reboot method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--confirm", + dest="confirm", + action="store_true", + help="Optionally include to request user confirmation for reboot.", + default=False, + ) + customparser.add_argument( + "--nologout", + dest="nologout", + action="store_true", + help="Optionally include to not to logout of iLO connection.", + default=False, + ) diff --git a/ilorest/extensions/iLO_COMMANDS/SendTestCommand.py b/ilorest/extensions/iLO_COMMANDS/SendTestCommand.py new file mode 100644 index 0000000..4d162b6 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/SendTestCommand.py @@ -0,0 +1,155 @@ +### +# Copyright 2016-2021 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 -*- +""" SendTest Command for rdmc """ + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class SendTestCommand: + """Send syslog test to the logged in server""" + + def __init__(self): + self.ident = { + "name": "sendtest", + "usage": None, + "description": "Send syslog test to the " + "current logged in server.\n\tExample: sendtest syslog\n\n" + "\tSend alert mail test to the current logged in server.\n\t" + "sendtest alertmail\n\n\tSend SNMP test alert " + "to the current logged in server.\n\texample: sendtest snmpalert", + "summary": "Command for sending various tests to iLO.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main SentTestCommand function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if not len(args) == 1: + raise InvalidCommandLineError("sendtest command takes only one argument.") + + body = None + path = None + actionitem = None + + self.sendtestvalidation(options) + + if args[0].lower() == "snmpalert": + select = self.rdmc.app.typepath.defs.snmpservice + actionitem = "SendSNMPTestAlert" + elif args[0].lower() == "alertmail": + select = self.rdmc.app.typepath.defs.managernetworkservicetype + actionitem = "SendTestAlertMail" + elif args[0].lower() == "syslog": + select = self.rdmc.app.typepath.defs.managernetworkservicetype + actionitem = "SendTestSyslog" + else: + raise InvalidCommandLineError("sendtest command does not have " "parameter %s." % args[0]) + + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("%s not found.It may not " "be available on this system." % select) + + bodydict = results.resp.dict + + try: + if "Actions" in bodydict: + for item in bodydict["Actions"]: + if actionitem in item: + if self.rdmc.app.typepath.defs.isgen10: + actionitem = item.split("#")[-1] + + path = bodydict["Actions"][item]["target"] + break + else: + for item in bodydict["Oem"][self.rdmc.app.typepath.defs.oemhp]["Actions"]: + if actionitem in item: + if self.rdmc.app.typepath.defs.isgen10: + actionitem = item.split("#")[-1] + + path = bodydict["Oem"][self.rdmc.app.typepath.defs.oemhp]["Actions"][item]["target"] + break + + body = {"Action": actionitem} + except: + body = {"Action": actionitem, "Target": "/Oem/Hp"} + + self.rdmc.app.post_handler(path, body) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def sendtestvalidation(self, options): + """sendtestvalidation method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/iLO_COMMANDS/ServerCloneCommand.py b/ilorest/extensions/iLO_COMMANDS/ServerCloneCommand.py new file mode 100644 index 0000000..5cc4c10 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/ServerCloneCommand.py @@ -0,0 +1,2482 @@ +### +# Copyright 2016-2021 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 -*- +""" Server Clone Command for rdmc """ + +import getpass +import json +import os +import os.path +import re +import sys +import time +import traceback +from argparse import RawDescriptionHelpFormatter +from collections import OrderedDict + +import jsonpath_rw +from six.moves import input + +import redfish.ris +from redfish.ris.rmc_helper import IdTokenError, IloResponseError, InstanceNotFoundError +from redfish.ris.utils import iterateandclear, json_traversal_delete_empty + +try: + from rdmc_helper import ( + LOGGER, + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + InvalidKeyError, + NoChangesFoundOrMadeError, + NoContentsFoundForOperationError, + NoDifferencesFoundError, + ResourceExists, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + Encryption, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + InvalidKeyError, + NoChangesFoundOrMadeError, + NoContentsFoundForOperationError, + NoDifferencesFoundError, + ResourceExists, + ReturnCodes, + ) + +# default file name +__DEFAULT__ = "

" +__MINENCRYPTIONLEN__ = 16 +__clone_file__ = "ilorest_clone.json" +__tmp_clone_file__ = "_ilorest_clone_tmp" +__tmp_sel_file__ = "_ilorest_sel_tmp" +__error_log_file__ = "clone_error_logfile.log" +__changelog_file__ = "changelog.log" +__tempfoldername__ = "serverclone_data" + + +def log_decor(func): + """ + Log Decorator function + :param func: function to be decorated + :type func: class method + """ + + def func_wrap(*args, **kwargs): + """ + Log Decorator function wrapper + :param args: function to be decorated + :type args: * + :param kwargs: keyword arguments + :type kwargs: * + """ + try: + return func(*args, **kwargs) + except IdTokenError as excp: + sys.stderr.write( + "You have logged into iLO with an account which has insufficient " + " user access privileges to modify properties in this type:\n %s\n" % (excp) + ) + if args[0].rdmc.opts.debug: + logging( + func.func_name if hasattr(func, "func_name") else str(func), + traceback.format_exc(), + excp, + args, + ) + except NoDifferencesFoundError as excp: + sys.stderr.write("No differences identified from current configuration.\n") + if args[0].rdmc.opts.debug: + logging( + func.func_name if hasattr(func, "func_name") else str(func), + traceback.format_exc(), + excp, + args, + ) + except ExitHandler: + sys.stderr.write("Exiting Serverclone command...no further changes have been implemented.\n") + raise NoChangesFoundOrMadeError("Exiting Serverclone command...no further changes have been implemented.\n") + + except Exception as excp: + sys.stderr.write("Unhandled exception(s) occurred: %s\n" % str(excp)) + if not args[0].rdmc.opts.debug: + args[0].rdmc.ui.error( + "Check the ServerClone Error logfile for further info: %s\n" % __error_log_file__, + excp, + ) + logging( + func.func_name if hasattr(func, "func_name") else str(func), + traceback.format_exc(), + excp, + args, + ) + + return func_wrap + + +def logging(command, _trace, error, _args): + """ + Handler for error logging + :param command: command in error + :type command: method identifier + :param _trace: traceback data + :type _trace: object + :param error: error logged (simplified version) + :type error: string + :param _agrs: array of methods arguments + :type _args: array + """ + + sys.stderr.write( + "An error occurred: %s. Check the ServerClone Error logfile " + "for further info: %s\n" % (error, __error_log_file__) + ) + sys.stderr.write("Logging error to '%s'.\n" % __error_log_file__) + with open(__error_log_file__, "a+") as efh: + efh.write(command + ":\n") + efh.write("Simplified Error: " + str(error) + "\n") + efh.write("Traceback:\n") + efh.write(str(_trace) + "\n") + efh.write("Args State in: '%s'.\n" % command) + i = 0 + for _arg in _args: + efh.write("Arg %s: %s\n" % (str(i), _arg)) + i += 1 + efh.write("\n") + + +class ExitHandler(Exception): + pass + + +class ServerCloneCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "serverclone", + "usage": None, + "description": "Clone from a server or restore to a server a JSON formatted file " + "containing the configuration settings of a system's iLO and Bios configuration.\n" + "SSA controller settings and logical configurations can be optionally be included for " + "save.\nTo view help on specific sub-commands run: serverclone -h\n\n" + "Example: serverclone

': + user_counter = user_counter + 1 + else: + + self.c_log_write( + "Kindly modify the password from

to valid pass in the clone file for the user :" + + user["UserName"] + "\n") + if fed_data: + for fedkey in fed_data.items(): + fedacct = fedkey[1] + if not fedacct["FederationKey"] == '

': + fed_counter = fed_counter + 1 + else: + + self.c_log_write( + "Kindly modify the password from

to valid pass in the clone file for the user :" + + fedacct["FederationName"] + "\n") + else: + self.rdmc.ui.printer( + "No federation accounts available") + if user_counter == len(data.items()) or fed_counter == len(fed_data.items()): + self.loadfunction(options) + self.load_storageclone(options) + else: + self.rdmc.ui.error( + "Please modify the default password

to a valid password in the clone json file and rerun the load command") + + elif not options.iLOSSA: + for useracct in data.items(): + user = useracct[1] + if not user["Password"] == '

': + user_counter = user_counter + 1 + else: + + self.c_log_write( + "Kindly modify the password from

to valid pass in the clone file for the user :" + + user["UserName"] + "\n") + if fed_data: + for fedkey in fed_data.items(): + fedacct = fedkey[1] + if not fedacct["FederationKey"] == '

': + fed_counter = fed_counter + 1 + else: + + self.c_log_write( + "Kindly modify the password from

to valid pass in the clone file for the user :" + + fedacct["FederationName"] + "\n") + else: + self.rdmc.ui.printer( + "No federation accounts available") + if user_counter == len(data.items()) or fed_counter == len(fed_data.items()): + self.loadfunction(options) + else: + self.rdmc.ui.error( + "Please modify the default password

to a valid password in the clone json file and rerun the load command") + else: + self.load_storageclone(options) + + self.cleanup() + # self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + @log_decor + def file_handler(self, filename, operation, options, data=None, sk=None): + """ + Wrapper function to read or write data to a respective file + :param data: data to be written to output file + :type data: container (list of dictionaries, dictionary, etc.) + :param file: filename to be written + :type file: string (generally this should be self.clone_file or tmp_clone_file + :param operation: file operation to be performed + :type operation: string ('w+', 'a+', 'r+') + :param sk: sort keys flag + :type sk: boolean + :param options: command line options + :type options: attribute + :returns: json file data + """ + writeable_ops = ["w", "w+", "a", "a+"] + + fdata = None + + try: + if operation in writeable_ops: + if options.encryption: + with open(filename, operation + "b") as outfile: + outfile.write( + Encryption().encrypt_file( + json.dumps( + data, + indent=2, + cls=redfish.ris.JSONEncoder, + sort_keys=sk, + ), + options.encryption, + ) + ) + else: + with open(filename, operation) as outfile: + outfile.write( + json.dumps( + data, + indent=2, + cls=redfish.ris.JSONEncoder, + sort_keys=sk, + ) + ) + else: + if options.encryption: + with open(filename, operation + "b") as file_handle: + fdata = json.loads(Encryption().decrypt_file(file_handle.read(), options.encryption)) + else: + with open(filename, operation) as file_handle: + fdata = json.loads(file_handle.read()) + except Exception as excp: + self.cleanup() + raise InvalidFileInputError( + "Unable to open file: %s.\nVerify the file location " "and the file has a valid JSON format.\n" % excp + ) + else: + return fdata + + @log_decor + def controller_id(self, options): + """ + Get iLO types from server and save storageclone URL get Controller + :parm options: command line options + :type options: attribute + :returns: returns list + """ + self.auxcommands["select"].selectfunction("StorageControllerCollection.") + ctr_content = self.rdmc.app.getprops() + ctrl_data = [] + all_ctrl = dict() + + for ct_controller in ctr_content: + path = ct_controller["Members"] + for i in path: + res = i["@odata.id"] + if self.rdmc.opts.verbose and not self.load: + sys.stdout.write("Saving properties of type %s \t\n" % res) + ctrl_data.append(res) + ctrl_id_url = res + "?$expand=." + get_ctr = self.rdmc.app.get_handler(ctrl_id_url, silent=True, service=True).dict + ctrl_id = get_ctr["@odata.id"].split("/") + ctrl_id = ctrl_id[8] + get_ctr = self.rdmc.app.removereadonlyprops(get_ctr, False, True) + all_ctrl[get_ctr["Name"]] = get_ctr + return all_ctrl + + @log_decor + def get_volume(self, options): + """ + Get iLO types from server and save storageclone URL get volumes + :parm options: command line options + :type options: attribute + :returns: returns list + """ + self.auxcommands["select"].selectfunction("VolumeCollection.") + vol_content = self.rdmc.app.getprops() + vol_data = [] + all_vol = dict() + for st_volume in vol_content: + path = st_volume["Members"] + for i in path: + res = i["@odata.id"] + if self.rdmc.opts.verbose: + sys.stdout.write("Saving properties of type %s \t\n" % res) + vol_data.append(res) + vol_id_url = res + "?$expand=." + get_vol = self.rdmc.app.get_handler(vol_id_url, silent=True, service=True).dict + # print("Assigned drive", get_vol["Links"]["Drives"]) + get_vol = self.rdmc.app.removereadonlyprops(get_vol, False, True) + all_vol[get_vol["Name"]] = get_vol + return all_vol + + @log_decor + def save_storageclone(self, options): + """ + Get iLO types from server and save storageclone URL + :parm options: command line options + :type options: attribute + :returns: returns dict and save json format + """ + + self.auxcommands["select"].selectfunction("StorageCollection.") + st_content = self.rdmc.app.getprops() + st_flag = False + all_stgcntrl = {} + de_url = [] + if options.storageclonefilename: + outfilename = options.storageclonefilename[0] + else: + outfilename = "ilorest_storage_clone.json" + out_file = open(outfilename, "w") + self.rdmc.ui.printer("Saving of storage clone file to '%s'...... \n" % out_file.name) + for st_controller in st_content: + path = st_controller["Members"] + for i in path: + res = i["@odata.id"] + if "DE" in res: + de_url.append(res) + if self.rdmc.opts.verbose: + sys.stdout.write("Saving properties of type %s \t\n" % res) + st_flag = True + storage_id_url = res + "?$expand=." + get_storage = self.rdmc.app.get_handler(storage_id_url, silent=True, service=True).dict + vol = self.get_volume(options) + ctr = self.controller_id(options) + get_storage = self.rdmc.app.removereadonlyprops(get_storage, False, True) + # print("controller id", ctr["SATA Storage Controller"]["Id"]) + # Controller and volume update abve method called + get_storage["Controllers"]["Members"].append(ctr) + del get_storage["Controllers"]["Members"][0] + get_storage["Volumes"]["Members"].append(vol) + del get_storage["Volumes"]["Members"][0] + all_stgcntrl[get_storage["Id"]] = get_storage + else: + continue + if st_flag: + json.dump(all_stgcntrl, out_file, indent=6) + out_file.close() + self.rdmc.ui.printer("Saving of storage clone file to '%s' is complete.\n" % out_file.name) + else: + sys.stdout.write("\nNo Storage controllers found which is redfish enabled \n") + + @log_decor + def get_drives_capacityt(self, options): + self.auxcommands["select"].selectfunction("Drive.") + drive_content = self.rdmc.app.getprops() + drive_list = [] + loc_list = [] + for st_drive in drive_content: + drive_cap = st_drive["CapacityBytes"] + drive_list.append(drive_cap) + drive_loc = st_drive["PhysicalLocation"]["PartLocation"]["ServiceLabel"] + loc = drive_loc.split(":") + drv_loc = str(loc[1].split("=")[1] + ":" + loc[2].split("=")[1] + ":" + loc[3].split("=")[1]) + loc_list.append(drv_loc) + return drive_list, loc_list + + @log_decor + def controller_get_id(self, options): + self.auxcommands["select"].selectfunction("StorageController.") + ctr_content = self.rdmc.app.getprops() + for ct_data in ctr_content: + ct_id = ct_data["Id"] + return ct_id + + @log_decor + def get_storage_de_id(self, options): + self.auxcommands["select"].selectfunction("StorageCollection.") + st_content = self.rdmc.app.getprops() + try: + for st_controller in st_content: + path = st_controller["Members"] + for i in path: + res = i["@odata.id"].split("/") + res = res[6] + if "DE" in res: + res + return res + except: + self.rdmc.ui.printer("Storage id not available") + + @log_decor + def load_storageclone(self, options): + """ + Load iLO Server storage URL. + :parm ilovoldata: iLO Server Volume(logical drive) payload to be loaded + :type ilovoldata: dict of values + """ + if options.storageclonefilename: + filename = options.storageclonefilename[0] + else: + filename = "ilorest_storage_clone.json" + while True: + ans = input( + "A configuration file %s containing configuration changes will be " + "applied to this iLO server resulting in system setting changes for " + "Storage urls like controllers, volume, deletion and " + "rearrangement of logical disks...etc. Please confirm you acknowledge " + "and would like to perform this operation now? (y/n)\n" % filename + ) + if ans.lower() == "y": + self.rdmc.ui.printer("Proceeding with Storage Clone Load Operation...\n") + break + elif ans.lower() == "n": + self.rdmc.ui.warn("Aborting load operation. No changes made to the server.\n") + return ReturnCodes.NO_CHANGES_MADE_OR_FOUND + else: + self.rdmc.ui.warn("Invalid input...\n") + + ctr_id = self.controller_get_id(options) + print("Controller id", ctr_id) + st_id_de = self.get_storage_de_id(options) + print("Storage ID only DE", st_id_de) + with open(filename, "r+b") as file_handle: + if options.encryption: + data = json.load(Encryption().decrypt_file(file_handle.read(), options.encryption)) + else: + data = json.load(file_handle) + for k, v in data.items(): + while True: + ans = input("Do you want to delete current %s controller? (y/n)\n" % k) + if ans.lower() == "y": + self.rdmc.ui.printer("Proceeding with Deletion DE storage...\n") + break + elif ans.lower() == "n": + self.rdmc.ui.warn("Aborting load operation. No changes made to the server.\n") + return ReturnCodes.NO_CHANGES_MADE_OR_FOUND + else: + self.rdmc.ui.warn("Invalid input...\n") + self.auxcommands["factoryresetcontroller"].run("--reset_type resetall --storageid " + k) + if self.rdmc.opts.verbose: + self.rdmc.ui.printer("Deleted Volume using factoryresetcontroller %s command.\n" % k) + v_data = v["Volumes"]["Members"][0] + for i, j in v_data.items(): + # print("Key", i) + writecachepolicy = j["WriteCachePolicy"] + readcachepolicy = j["ReadCachePolicy"] + displayname = j["DisplayName"] + raidtype = j["RAIDType"] + capacitybytes = j["CapacityBytes"] + dr, loc = self.get_drives_capacityt(options) + # print("------", dr, loc) + i = 0 + for d in dr: + print("drives list", d) + if int(d) > int(capacitybytes): + create_val = ( + raidtype + + " " + + str(loc[i]) + + " " + + "DisplayName " + + displayname + + " --iOPerfModeEnabled False" + + " --ReadCachePolicy " + + readcachepolicy + + " --WriteCachePolicy " + + writecachepolicy + + " --controller=" + + ctr_id + + " --capacitybytes " + + str(capacitybytes) + ) + print("createvolume properties", create_val) + self.auxcommands["createvolume"].run("volume " + create_val) + break + elif int(d) < int(capacitybytes): + print("Implementation logic", d) + break + else: + sys.stdout.write("Drive capacity is lesser than present drives") + i = i + 1 + + @log_decor + def getilotypes(self, options): + """ + Get iLO types from server and return a list of types + :parm options: command line options + :type options: attribute + :returns: returns list of types + """ + supported_types_dict = { + "ManagerAccount": ["4", "5", "6"], + "AccountService": ["4", "5", "6"], + "Bios": ["4", "5", "6"], + "Manager": ["4", "5", "6"], + "SNMP": ["4", "5", "6"], + "iLOLicense": ["4", "5", "6"], + "ManagerNetworkService": ["4", "5", "6"], + "EthernetNetworkInterface": ["4", "5", "6"], + "iLODateTime": ["4", "5", "6"], + "iLOFederationGroup": ["4", "5", "6"], + "iLOSSO": ["4", "5", "6"], + "ESKM": ["4", "5", "6"], + "ComputerSystem": ["4", "5", "6"], + # "EthernetInterface": ["4", "5", "6"], + "ServerBootSettings": ["4", "5", "6"], + "SecureBoot": ["4", "5", "6"], + "SmartStorageConfig": ["5"], + "HpSmartStorage": ["4"], + "HpeSmartStorage": ["5"], + } + types_accepted = set() + if self.save: + if options.noBIOS: + self.rdmc.ui.warn("Bios configuration will be excluded.\n") + del supported_types_dict["Bios"] + if options.all: + self.rdmc.ui.printer("Note: Smart storage configuration and Storage included all.\n") + self.save_storageclone(options) + if not options.iLOSSA and not options.all: + self.rdmc.ui.printer("Note: Smart storage configuration will not be included.\n") + self.rdmc.ui.warn("Smart storage configuration will not be included.") + del supported_types_dict["SmartStorageConfig"] + del supported_types_dict["HpSmartStorage"] + del supported_types_dict["HpeSmartStorage"] + unsupported_types_list = [ + "Collection", + "PowerMeter", + "BiosMapping", + "Controller", + ] + + # supported types comparison + # types_accepted = set() + for _type in sorted(set(self.rdmc.app.types("--fulltypes"))): + if _type[:1].split(".")[0] == "#": + _type_mod = _type[1:].split(".")[0] + else: + _type_mod = _type.split(".")[0] + for stype in supported_types_dict: + if stype.lower() in _type_mod.lower(): + found = False + for ustype in unsupported_types_list: + if ustype.lower() in _type_mod.lower(): + found = True + break + if not found: + if self.curr_iloversion in supported_types_dict[stype]: + types_accepted.add(_type) + + return sorted(types_accepted) + + def loadfunction(self, options): + """ + Main load function. Handles SSO and SSL/TLS Certificates, kickoff load + helper, load of patch file, get server status and issues system reboot + and iLO reset after completion of all post and patch commands. + :param options: command line options + :type options: attribute + """ + + reset_confirm = True + + if not options.autocopy: + while True: + ans = input( + "A configuration file '%s' containing configuration changes will be " + "applied to this iLO server resulting in system setting changes for " + "BIOS, ethernet controllers, disk controllers, deletion and " + "rearrangement of logical disks...etc. Please confirm you acknowledge " + "and would like to perform this operation now? (y/n)\n" % self.clone_file + ) + if ans.lower() == "y": + self.rdmc.ui.printer("Proceeding with ServerClone Load Operation...\n") + break + elif ans.lower() == "n": + self.rdmc.ui.warn("Aborting load operation. No changes made to the server.\n") + return ReturnCodes.NO_CHANGES_MADE_OR_FOUND + else: + self.rdmc.ui.warn("Invalid input...\n") + + self._fdata = self.file_handler(self.clone_file, operation="r+", options=options) + self.loadhelper(options) + self.load_idleconnectiontime(options) + data = self._fdata['#ManagerAccount.v1_3_0.ManagerAccount'] + if '#HpeiLOFederationGroup.v2_0_0.HpeiLOFederationGroup' in self._fdata: + fed_data = self._fdata['#HpeiLOFederationGroup.v2_0_0.HpeiLOFederationGroup'] + for fedkey in fed_data.items(): + self.load_federation(fedkey[1], '#HpeiLOFederationGroup.v2_0_0.HpeiLOFederationGroup', + '/redfish/v1/Managers/1/FederationGroups') + else: + self.rdmc.ui.printer("No federation accounts to load\n") + pass + for useracct in data.items(): + self.load_accounts(useracct[1], '#ManagerAccount.v1_3_0.ManagerAccount', + '/redfish/v1/AccountService/Accounts') + + self.loadpatch(options) + self.getsystemstatus(options) + + if not options.autocopy: + while True: + ans = input("The system is ready to be reset. Perform a reset now? (y/n)\n") + if ans.lower() == "n": + reset_confirm = False + self.rdmc.ui.printer("Aborting Server Reboot and iLO reset...\n") + break + elif ans.lower() == "y": + break + else: + self.rdmc.ui.warn("Invalid input...\n") + else: + if options.noautorestart: + reset_confirm = False + + if reset_confirm: + if self.rdmc.app.current_client.base_url: # reset process in remote mode + self.rdmc.ui.printer("Resetting the server...\n") + self.auxcommands["reboot"].run("ColdBoot") # force restart, cold boot + self.rdmc.ui.printer("Waiting 3 minutes for reboot to complete...\n") + time.sleep(180) + self.rdmc.ui.printer("Resetting iLO...\n") + self.auxcommands["iloreset"].run("") + self.rdmc.ui.printer("You will need to re-login to access this system...\n") + else: # reset process in local mode + self.rdmc.ui.printer("Resetting local iLO...\n") + self.auxcommands["iloreset"].run("") + self.rdmc.ui.printer("Your system may require a reboot...use at your discretion\n") + + else: + self.rdmc.ui.printer("Your system may require a reboot...use at your discretion\n") + + self.rdmc.ui.printer( + "Loading of clonefile '%s' to server is complete. Review the " + "changelog file '%s'.\n" % (self.clone_file, self.change_log_file) + ) + + def loadhelper(self, options): + """ + Helper function for loading which calls additional helper functions for + Server BIOS and Firmware compatibility, type compatibility, patch or + postability (special functions). Data deemed for exclusive patching + (through load) is written into a temporary file, which is deleted unless + archived for later use. + :param options: command line options + :type options: attribute + """ + data = list() + + server_avail_types = self.getilotypes(options) + if not server_avail_types: + raise NoContentsFoundForOperationError("Unable to Obtain iLO Types from server.") + + if "Comments" in list(self._fdata.keys()): + self.system_compatibility_check(self._fdata["Comments"], options) + del self._fdata["Comments"] + else: + raise InvalidFileInputError("Clone File '%s' does not include a valid 'Comments' " "dictionary.") + if options.ssocert: + self.load_ssocertificate() # check and load sso certificates + if options.tlscert: + self.load_tlscertificate() # check and load tls certificates + + typelist = [] + for _x in self._fdata: + for _y in server_avail_types: + _x1 = re.split("#|\.", _x) + _y1 = re.split("#|\.", _y) + if _x1[0] == "": + _x1.pop(0) + if _y1[0] == "": + _y1.pop(0) + if _x1[0] == _y1[0]: + _comp_tuple = self.type_compare(_x, _y) + if _comp_tuple[0] and _comp_tuple[1]: + self.rdmc.ui.printer("Type '%s' is compatible with this system.\n" % _x) + typelist.append(_x) + else: + self.rdmc.ui.warn( + "The type: '%s' isn't compatible with the type: '%s'" + "found on this system. Associated properties can not " + "be applied...Skipping\n" % (_x, _y) + ) + + for _type in typelist: + # Skipping cloning of Datetime temporarily. This is an iLO issue. + if _type == "#HpeiLODateTime.v2_0_0.HpeiLODateTime": + continue + singlet = True + thispath = next(iter(self._fdata[_type].keys())) + try: + root_path_comps = self.get_rootpath(thispath) + + multi_sel = self.rdmc.app.select( + _type.split(".")[0] + ".", + (self.rdmc.app.typepath.defs.hrefstring, root_path_comps[0] + "*"), + path_refresh=True, + ) + + curr_sel = self.rdmc.app.select( + _type.split(".")[0] + ".", + (self.rdmc.app.typepath.defs.hrefstring, thispath), + path_refresh=True, + ) + + except InstanceNotFoundError: + curr_sel = self.rdmc.app.select(_type.split(".")[0] + ".") + + except Exception as excp: + self.rdmc.ui.error( + "Unable to find the correct path based on system " "type and clone file type: %s\n" % _type, + excp, + ) + continue + finally: + try: + if "multi_sel" in locals() and "curr_sel" in locals(): + if ( + len(multi_sel) > 1 + and len(curr_sel) == 1 + and ( + root_path_comps[1].isdigit() + or "iLOFederationGroup" in _type + or "ManagerAccount" in _type + or "Manager" in _type + ) + ): + singlet = False + curr_sel = multi_sel + except (ValueError, KeyError): + pass + + scanned_dict = dict() + for thing in curr_sel: + scanned_dict[thing.path] = { + "Origin": "Server", + "Scanned": False, + "Data": thing.dict, + } + for thing in self._fdata[_type]: + # if we only have a single path, the base path is in the path and only a single + # instance was retrieved from the server + if "ManagerAccount" in _type or "iLOFederationGroup" in _type: + scanned_dict[thing] = { + "Origin": "File", + "Scanned": False, + "Data": self._fdata[_type][thing], + } + elif singlet and root_path_comps[0] in thing and len(scanned_dict) == 1: + scanned_dict[next(iter(scanned_dict))] = { + "Origin": "File", + "Scanned": False, + "Data": self._fdata[_type][thing], + } + else: + scanned_dict[thing] = { + "Origin": "File", + "Scanned": False, + "Data": self._fdata[_type][thing], + } + for path in scanned_dict.keys(): + try: + if scanned_dict[path]["Origin"] == "Server": + raise KeyError(path) + else: + sys.stdout.write( + "---Check special loading for entry---\ntype: %s\npath: " "%s\n" % (_type, path) + ) + tmp = self.subhelper(scanned_dict[path]["Data"], _type, path, options) + if tmp: + sys.stdout.write("Special entry not applicable...reserving for " "patch loading stage.\n") + data.append(tmp) + else: + sys.stdout.write("---Special loading complete for entry---.\n") + except KeyError as excp: + if path in str(excp) and self._fdata.get(_type): + if self.delete( + scanned_dict[path]["Data"], + _type, + path, + self._fdata[_type], + options, + ): + # ok so this thing does not have a valid path, is not considered a + # deletable item so....idk what to do with you. You go to load. + # Goodluck + tmp = self.altsubhelper(scanned_dict[path]["Data"], _type, path) + if tmp: + data.append(tmp) + else: + # if the instance item was not replaced with an entry in the clone file then + # it will be deleted + self.rdmc.ui.warn("Entry at '%s' removed from this server.\n" % path) + + except Exception as excp: + self.rdmc.ui.error("An error occurred: '%s'" % excp) + continue + finally: + scanned_dict[path]["Scanned"] = True + + self.file_handler(self.tmp_clone_file, "w+", options, data, True) + + def subhelper(self, data, _type, path, options): + """ + Reusable code section for load helper + :param data: dict data (from file or server) + :type: dictionary + :param _type: cross compatible iLO type + :type: string + :param path: iLO schema path determined from system query + :type: string + :param prev + :parm options: command line options + :type options: attribute + """ + + # due to EthernetInterfaces OEM/HPE/DHCPv4 also having a key with 'Name' + # this is required, removing readonly types after POST commands have + # completed. Would be great if that was resolved... + prop_list = [ + "Modified", + "Type", + "Description", + "Status", + "Name", + "AttributeRegistry", + "links", + "SettingsResult", + "@odata.context", + "@odata.type", + "@odata.id", + "@odata.etag", + "Links", + "Actions", + "AvailableActions", + "MACAddress", + "BiosVersion", + ] + + tmp = dict() + tmp[_type] = {path: data} + tmp[_type][path] = self.rdmc.app.removereadonlyprops(tmp[_type][path], False, True, prop_list) + val_emp = tmp["#Bios.v1_0_4.Bios"]["/redfish/v1/systems/1/bios/settings/"]["Attributes"] + if val_emp["AdminName"] == "" and val_emp["AdminPhone"] == "" and val_emp["AdminEmail"] == "": + pass + else: + json_traversal_delete_empty(tmp, None, None) + # if not self.ilo_special_functions(tmp, _type, path, options): + return tmp + + @log_decor + def altsubhelper(self, file_data, _type, curr_path): + """ + Just for manipulating the file_data in the clone file and handing it off to load. + :param file_data: clone file data + :type: dictionary + :param _type: cross compatible iLO type + :type: string + :param curr_path: iLO schema path determined from system query + :type: string + :param file_path: iLO schema path as observed in the clone file + :type: string + :returns: a dictionary containing the data which will be passed off to load. + """ + + tmp = dict() + try: + tmp[_type] = {curr_path: file_data[_type][next(iter(file_data[_type]))]} + except KeyError: + tmp[_type] = {curr_path: file_data} + json_traversal_delete_empty(tmp, None, None) + return tmp + + def loadpatch(self, options): + """ + Load temporary patch file to server + :parm options: command line options + :type options: attribute + """ + self.rdmc.ui.printer("Patching remaining data.\n") + fdata = self.file_handler(self.tmp_clone_file, operation="r+", options=options) + for _sect in fdata: + _tmp_sel = {} + _key = next(iter(_sect)) + _tmp_sel[_key.split(".")[0] + "."] = _sect[_key] + self.file_handler(self.tmp_sel_file, "w", options, [_tmp_sel], True) + self.loadpatch_helper(_key, _sect, options) + + @log_decor + def loadpatch_helper(self, type, dict, options): + """ + Load temporary patch file to server + :parm options: command line options + :type options: attribute + """ + options_str = "" + if options.encryption: + options_str += " --encryption " + options.encryption + + if options.uniqueoverride: + options_str += " --uniqueoverride" + + self.rdmc.ui.printer("Patching '%s'.\n" % type) + try: + self.auxcommands["load"].run("-f " + self.tmp_sel_file + options_str) + except: + # placeholder for any exception + pass + + def gatherandsavefunction(self, typelist, options): + """ + Write parsed JSON save data to file + :param typelist: list of available types on iLO + :type typelist: list + :param options: command line options + :type options: attribute + """ + if options.iLOSSA: + self.save_storageclone(options) + else: + data = OrderedDict() + data.update(self.rdmc.app.create_save_header()) + self.rdmc.ui.printer("Saving of clone file to '%s'.......\n" % self.clone_file) + for _type in typelist: + self.gatherandsavehelper(_type, data, options) + self.file_handler(self.clone_file, "w+", options, data, False) + self.rdmc.ui.printer("Saving of clone file to '%s' is complete.\n" % self.clone_file) + + def gatherandsavehelper(self, _type, data, options): + """ + Collect data on types and parse properties (delete unnecessary/readonly/ + empty properties. + :param type: type for subsequent select and save + :type type: string + :param data: JSON data (to be written to file) + :type data: JSON + :param options: command line options + :type options: attribute + """ + _typep = _type.split(".")[0] + _spec_list = ["SmartStorageConfig", "iLOLicense", "Bios"] + + try: + if "EthernetInterface" in _type: + instances = self.rdmc.app.select( + _typep + ".", + ( + self.rdmc.app.typepath.defs.hrefstring, + self.rdmc.app.typepath.defs.managerpath + "*", + ), + path_refresh=True, + ) + elif "EthernetNetworkInterface" in _type: + instances = self.rdmc.app.select( + _typep + ".", + ( + "links/self/" + self.rdmc.app.typepath.defs.hrefstring, + self.rdmc.app.typepath.defs.managerpath + "*", + ), + path_refresh=True, + ) + else: + instances = self.rdmc.app.select(_typep + ".", path_refresh=True) + + for j, instance in enumerate(self.rdmc.app.getprops(insts=instances)): + if "#" in _typep: + _typep = _typep.split("#")[1] + if self.rdmc.app.typepath.defs.flagforrest: + try: + path = instance["links"]["self"][self.rdmc.app.typepath.defs.hrefstring] + except: + path = instance["links"][next(iter(instance["links"]))][self.rdmc.app.typepath.defs.hrefstring] + else: + path = instance[self.rdmc.app.typepath.defs.hrefstring] + + instance = self.ilo_special_functions(instance, _type, path, options) + + _itc_pass = True + for _itm in _spec_list: + if _itm.lower() in _type.lower(): + templist = [ + "Modified", + "Type", + "Description", + "Status", + "links", + "SettingsResult", + "@odata.context", + "@odata.type", + "@odata.id", + "@odata.etag", + "Links", + "Actions", + "AvailableActions", + "BiosVersion", + ] + instance = iterateandclear(instance, templist) + _itc_pass = False + break + if _itc_pass: + instance = self.rdmc.app.removereadonlyprops(instance, False, True) + + if instance and not options.iLOSSA: + if _typep != "SmartStorageConfig": + if self.rdmc.opts.verbose: + self.rdmc.ui.printer("Saving properties of type: %s, path: %s\n" % (_typep, path)) + elif _typep in options.iLOSSA: + sys.stdout.write("ILO SSA calling") + if _type not in data: + data[_type] = OrderedDict(sorted({path: instance}.items())) + else: + data[_type][path] = instance + else: + self.rdmc.ui.warn( + "Type: %s, path: %s does not contain any modifiable " + "properties on this system." % (_typep, path) + ) + + except Exception as excp: + self.rdmc.ui.printer('An error occurred saving type: %s\nError: "%s"' % (_typep, excp)) + + @log_decor + def getsystemstatus(self, options): + """ + Retrieve system status information and save to a changelog file. This + file will be added to an archive if the archive selection is made. + :parm options: command line options + :type options: attribute + """ + status_list = [] + + for item in self.rdmc.app.status(): + status_list.append(item) + + if status_list: + self.file_handler(self.change_log_file, "w", options, status_list, True) + with open(self.change_log_file, "rb") as myfile: + data = myfile.read() + if options.encryption: + data = Encryption().decrypt_file(data, options.encryption) + with open(self.change_log_file, "wb") as outfile: + outfile.write(data) + else: + self.rdmc.ui.printer("No changes pending.\n") + + def ilo_special_functions(self, data, _type, path, options): + """ + Function used by both load and save for Restful commands requing a + POST or PUT. + :param data: JSON payload for saved or loaded properties + :type data: json + :param _type: selected type + :type _type: string + :param path: path of the selected type + :type path: string + :parm options: command line options + :type options: attribute + :returns: returns boolean indicating if the type/path was found. + """ + identified = False + _typep = _type.split(".")[0] + if "#" in _typep: + _typep = _typep.split("#")[1] + + if "EthernetInterface" in _typep or "EthernetNetworkInterface" in _typep: + identified = True + # save function not needed + if self.load: + self.load_ethernet(data[_type][path], _type, path) + + elif "DateTime" in _typep: + # do not use identified=True. Kind of a hack to have additional items patched. + # save function not needed + if self.load: + self.load_datetime(data[_type][path], path) + + elif "LicenseService" in path and "License" in _typep: + identified = True + if self.save: + data = self.save_license(data, _type, options) + elif self.load: + self.load_license(data[_type][path]) + + elif "AccountService/Accounts" in path and "AccountService" not in _typep: + identified = True + if self.save: + data = self.save_accounts(data, _type, options) + elif self.load: + self.load_accounts(data[_type][path], _type, path) + + elif "FederationGroup" in _typep: + identified = True + if self.save: + data = self.save_federation(data, _type, options) + elif self.load: + self.load_federation(data[_type][path], _type, path) + + elif "StorageCollection" in _typep: + identified = True + # if self.save: + # data = self.save_smartstorage(data, _type, options) + # elif self.load: + # self.load_smartstorage(data[_type][path], _type, path) + + if self.save: + return data + + return identified + + def c_log_write(self, c_str): + with open(self.change_log_file, "a") as c_log: + c_log.write(c_str + "\n") + + @log_decor + def delete(self, data, _type, path, fdata, options): + """ + Delete operations to remove things on server + :param data: Data to be deleted from the server + :type data: dictionary + :parm _type: iLO type to be queried + :type _type: string + :param path: iLO schema path + :type path: string + :returns: boolean indicating if the delete option occurred. + """ + user_confirm = False + + if not options.autocopy and not options.iLOSSA and not options.all: + while True: + ans = input( + "\n%s\nAre you sure you would like to delete the entry?:\n" + % json.dumps(data, indent=1, sort_keys=True) + ) + if ans.lower() == "y": + self.rdmc.ui.printer("Proceeding with Deletion...\n") + user_confirm = True + break + elif ans.lower() == "n": + self.rdmc.ui.warn("Aborting deletion. No changes have been made made to " "the server.\n") + return False + else: + self.rdmc.ui.warn("Invalid input...\n") + + # if "StorageCollection" in _type "Storage" in _type or : + # sys.stdout.write("Storage related delete lun on particular controller") + + if "ManagerAccount" in _type: + user_name = data["UserName"] + + # obtaining account information on the current server as a check to verify the user + # provided a decent path to use. This can be re-factored. + try: + for curr_sel in self.rdmc.app.select(_type.split(".")[0] + "."): + try: + if "UserName" in list(curr_sel.dict.keys()): + _ = curr_sel.dict["UserName"] + # check file to make sure this is not to be added later? + for fpath in fdata: + try: + if fdata[fpath]["UserName"] == data["UserName"]: + self.rdmc.ui.warn( + "Account '%s' exists in '%s', not " + "deleting.\n" % (data["UserName"], self.clone_file) + ) + return False + except: + continue + + if data["UserName"] != "Administrator": + self.rdmc.ui.warn( + "Manager Account, '%s' was not found in the " + "clone file. Deleting entry from server.\n" % data["UserName"] + ) + if not options.autocopy and not options.iLOSSA and not options.all: + ans = user_confirm + else: + ans = True + if ans: + self.auxcommands["iloaccounts"].run("delete " + data["UserName"]) + self.c_log_write("[CHANGE]: Deleting user " + data["UserName"]) + time.sleep(2) + del fdata[fpath] + return False + else: + self.rdmc.ui.error( + "Deletion of the Default System Administrator " "account is not allowed.\n" + ) + except (KeyError, NameError): + self.rdmc.ui.error( + "Unable to obtain the account information " "for: '%s''s' account.\n" % user_name + ) + continue + except InstanceNotFoundError: + return True + return False + + if "FederationGroup" in _type: + fed_identifier = None + if "FederationName" in data: + fed_identifier = "FederationName" + elif "Name" in data: + fed_identifier = "Name" + else: + raise InvalidKeyError("An invalid key was provided for the Federation Group Name.") + if data[fed_identifier] != "DEFAULT": + self.rdmc.ui.warn( + "Federation Account, '%s' was not found in the clone file." + " Deleting entry from server.\n" % data[fed_identifier] + ) + for fpath in fdata: + if fdata[next(iter(fdata))].get("FederationName") == data[fed_identifier]: + self.rdmc.ui.warn("Account '%s' exists in file, not deleting." "\n" % data[fed_identifier]) + return False + if not options.autocopy and not options.iLOSSA and not options.all: + ans = user_confirm + else: + ans = True + if ans: + self.auxcommands["ilofederation"].run("delete " + data[fed_identifier]) + self.c_log_write("[CHANGE]: Deleting ilo federation user " + data[fed_identifier]) + else: + self.rdmc.ui.warn("Deletion of the Default iLO Federation Group is not allowed.\n") + return False + return True + + @log_decor + def load_ssocertificate(self): + """ + Load the SSO Certificate specified in the user defined options. + """ + self.rdmc.ui.printer("Uploading SSO Certificate...\n") + self.auxcommands["singlesignon"].run("importcert " + self.sso_cert_file) + + @log_decor + def load_tlscertificate(self): + """ + Load the SSO Certificate specified in the user defined options. + """ + self.rdmc.ui.printer("Uploading TLS Certificate...\n") + self.auxcommands["certificate"].run("tls " + self.https_cert_file) + + @log_decor + def load_ethernet(self, ethernet_data, _type, path): + """ + Load iLO Ethernet Adapters settings Payload. + :parm datetime_data: iLO Ethernet Adapters payload to be loaded + :type datetime_data: dict + :param _type: iLO schema type + :type _type: string + :param path: iLO schema path + :type path: string + """ + self.auxcommands["ethernet"].load_ethernet_aux(_type, path, ethernet_data) + + @log_decor + def load_idleconnectiontime(self, options): + """ + Load iLO IdleConnectionTimeoutMinutes. + :parm iloidlecondata: IdleConnectionTimeoutMinutes payload to be loaded + :type iloidlecondata: dict + :param path: iLO schema path + """ + filename = self.clone_file + idle_val = [] + cli_val = [] + + with open(filename, "r+b") as file_handle: + if options.encryption: + data = json.loads(Encryption().decrypt_file(file_handle.read(), options.encryption)) + else: + data = json.load(file_handle) + # data = json.load(file_handle, encoding='cp1252') + idle_con = data["#Manager.v1_5_1.Manager"]["/redfish/v1/Managers/1/"]["Oem"]["Hpe"][ + "IdleConnectionTimeoutMinutes" + ] + serialclispeed = data["#Manager.v1_5_1.Manager"]["/redfish/v1/Managers/1/"]["Oem"]["Hpe"]["SerialCLISpeed"] + idle_val.append(idle_con) + cli_val.append(serialclispeed) + for _t in self._fdata: + try: + if "Manager" in _t: + _t_path = next(iter(list(self._fdata.get(_t).keys()))) + pass_dict = {"Oem": {self.rdmc.app.typepath.defs.oemhp: {"IdleConnectionTimeoutMinutes": idle_con}}} + cli_dict = {"Oem": {self.rdmc.app.typepath.defs.oemhp: {"SerialCLISpeed": serialclispeed}}} + sys.stdout.write("IdleConnectionTimeoutMinutes data %s\n" % pass_dict) + sys.stdout.write("SerialCLISpeed data %s\n" % cli_dict) + sys.stdout.write("Manager data %s\n" % _t_path) + self.rdmc.app.patch_handler(_t_path, pass_dict) + self.c_log_write("[CHANGE]: " + _t_path + ":" + str(pass_dict)) + self.rdmc.app.patch_handler(_t_path, cli_dict) + self.c_log_write("[CHANGE]: " + _t_path + ":" + str(cli_dict)) + break + except KeyError: + pass + + @log_decor + def load_datetime(self, datetime_data, path): + """ + Load iLO NTP Servers, DateTime Locale Payload. + :parm datetime_data: iLO NTP Server and Datetime payload to be loaded + :type datetime_data: dict + :param path: iLO schema path + :type path: string + """ + errors = [] + + if "StaticNTPServers" in datetime_data: + self.rdmc.ui.printer( + "Attempting to modify 'UseNTPServers' in each iLO Management " + "Network Interface regarding the StaticNTPServers list in " + "section 'iLODateTime (DateTime)'\n" + ) + oem_str = self.rdmc.app.typepath.defs.oempath + prop_str = (oem_str + "/DHCPv4/UseNTPServers")[1:] + path_str = self.rdmc.app.typepath.defs.managerpath + "*" + _instances = self.rdmc.app.select("EthernetInterface", (self.rdmc.app.typepath.defs.hrefstring, path_str)) + _content = self.rdmc.app.getprops("EthernetInterface", [prop_str], None, True, True, _instances) + + for item in _content: + try: + if next(iter(jsonpath_rw.parse("$..UseNTPServers").find(item))).value: + self.rdmc.app.patch_handler( + path, + {oem_str: {"DHCPv4": {"UseNTPServers": True}}}, + ) + self.c_log_write( + "[CHANGE]: " + path + ":" + str({oem_str: {"DHCPv4": {"UseNTPServers": True}}}) + ) + else: + self.rdmc.app.patch_handler( + path, + {oem_str: {"DHCPv4": {"UseNTPServers": False}}}, + ) + self.c_log_write( + "[CHANGE]: " + path + ":" + str({oem_str: {"DHCPv4": {"UseNTPServers": False}}}) + ) + except IloResponseError as excp: + errors.append("iLO Responded with the following error: %s.\n" % excp) + + if errors: + self.rdmc.ui.error( + "iLO responded with an error while attempting to set values " + "for 'UseNTPServers'. An attempt to patch DateTime " + "properties will be performed, but may be unsuccessful.\n" + ) + raise IloResponseError("The following errors in, 'DateTime' were found " "collectively: %s" % errors) + + @log_decor + def save_license(self, license_data, _type, options): + """ + Save iLO Server License. + :parm license_data: iLO Server License payload to be saved + :type license_data: dict + :param _type: iLO schema type + :type _type: string + :param options: command line options + :type options: attribute + """ + key_found = False + valid_key = False + license_keys = [] + try: + if "LicenseKey" in list(license_data["ConfirmationRequest"]["EON"].keys()): + license_keys.append(license_data["ConfirmationRequest"]["EON"]["LicenseKey"]) + except: + pass + finally: + license_keys.append(license_data.get("LicenseKey")) + for lic in reversed(license_keys): + if lic != "" and lic is not None: + license_key = lic + key_found = True + if self.rdmc.opts.verbose: + self.rdmc.ui.printer("License Key Found ending in: %s\n" % license_key.split("-")[-1]) + segpass = [] + for seg in lic.split("-"): + if "XXXXX" in seg.upper(): + segpass.append(True) + + if True not in segpass: + valid_key = True + break + + if not key_found: + self.rdmc.ui.printer("A License Key was not found on this system.\n") + license_key = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" + + if not options.autocopy and not valid_key and not options.iLOSSA and not options.all: + while True: + segpass = [] + license_key = input("Provide your license key: (press enter to skip)\n") + + if license_key.count("X") == 25 or license_key.count("-") == 0: + break + + for seg in license_key[0].split("-"): + if len(seg) == 5: + segpass.append(True) + + if len(segpass) == 5: + break + else: + segpass = False + self.rdmc.ui.warn("An Invalid License Key was Provided: %s" % license_key) + else: + self.rdmc.ui.warn("Remember to verify your License Key...") + + # clear everything, we do not need and just keep license key + license_data = {"LicenseKey": license_key.upper()} + return license_data + + @log_decor + def load_license(self, ilolicdata): + """ + Load iLO Server License. + :parm ilolicdata: iLO Server License payload to be loaded + :type ilolicdata: dict + """ + license_error_list = "InvalidLicenseKey" + license_str = "" + try: + license_str = ilolicdata["LicenseKey"] + segpass = [] + for seg in license_str.split("-"): + if len(seg) == 5: + segpass.append(True) + + if len(segpass) == 5: + self.rdmc.ui.printer("Attempting to load a license key to the server.") + self.auxcommands["ilolicense"].run("" + license_str) + else: + raise ValueError + except IloResponseError as excp: + if str(excp) in license_error_list: + self.rdmc.ui.error("iLO is not accepting your license key ending in '%s'." % license_str.split("-")[-1]) + except ValueError: + self.rdmc.ui.error("An Invalid License Key ending in '%s' was provided." % license_str.split("-")[-1]) + + @log_decor + def save_accounts(self, accounts, _type, options): + """ + Load iLO User Account Data. + :parm accounts: iLO User Account payload to be saved + :type accounts: dict + :param _type: iLO schema type + :type _type: string + :param options: command line options + :type options: attribute + """ + try: + account_type = next(iter(jsonpath_rw.parse("$..Name").find(accounts))).value + except StopIteration: + account_type = None + + try: + account_un = next(iter(jsonpath_rw.parse("$..UserName").find(accounts))).value + except StopIteration: + account_un = None + + try: + account_ln = next(iter(jsonpath_rw.parse("$..LoginName").find(accounts))).value + except StopIteration: + account_ln = None + + try: + privileges = next(iter(jsonpath_rw.parse("$..Privileges").find(accounts))).value + except StopIteration: + privileges = None + + try: + role_id = next(iter(jsonpath_rw.parse("$..RoleId").find(accounts))).value + except StopIteration: + role_id = None + + password = [__DEFAULT__, __DEFAULT__] + if not options.autocopy and not options.iLOSSA and not options.all: + while True: + for i in range(2): + if i < 1: + self.rdmc.ui.printer("Please input the desired password for user: %s\n" % account_un) + else: + self.rdmc.ui.printer("Please re-enter the desired password for user: %s\n" % account_un) + + password[i] = getpass.getpass() + try: + [password[i], _] = password[i].split("\r") + except ValueError: + pass + + if password[0] == password[1] and (password[0] is not None or password[0] != ""): + break + else: + ans = input("You have entered two different passwords...Retry?(y/n)\n") + if ans.lower() != "y": + self.rdmc.ui.printer("Skipping Account Migration for: %s\n" % account_un) + return None + else: + if self.rdmc.opts.verbose: + self.rdmc.ui.printer( + "Remember to edit password for user: '%s', login name: '%s'" "." % (account_un, account_ln) + ) + + if not password[0]: + password[0] = __DEFAULT__ + self.rdmc.ui.printer("Using a placeholder password of '%s' in %s file.\n" % (password[0], self.clone_file)) + accounts = { + "AccountType": account_type, + "UserName": account_un, + "LoginName": account_ln, + "Password": password[0], + "RoleId": role_id, + "Privileges": privileges, + } + + return accounts + + @log_decor + def load_accounts(self, user_accounts, _type, path): + """ + Load iLO User Account Data. + :parm user_accounts: iLO User Account payload to be loaded + :type user_accounts: dict + :param _type: iLO schema type + :type _type: string + :param path: iLO schema path + :type path: string + """ + found_user = False + if "UserName" in user_accounts: + user_name = user_accounts["UserName"] + else: + user_name = user_accounts["User_Name"] + if "LoginName" in user_accounts: + login_name = user_accounts["LoginName"] + else: + login_name = user_accounts["Login_Name"] + + # set minimum password length: + for _t in self._fdata: + try: + if "AccountService" in _t: + _t_path = next(iter(list(self._fdata.get(_t).keys()))) + pass_dict = {"Oem": {self.rdmc.app.typepath.defs.oemhp: {}}} + pass_dict["Oem"][self.rdmc.app.typepath.defs.oemhp]["MinPasswordLength"] = self._fdata[_t][_t_path][ + "Oem" + ][self.rdmc.app.typepath.defs.oemhp]["MinPasswordLength"] + del self._fdata[_t][_t_path]["Oem"][self.rdmc.app.typepath.defs.oemhp]["MinPasswordLength"] + self.rdmc.app.patch_handler(_t_path, pass_dict) + self.c_log_write("[CHANGE]: " + _t_path + ":" + str(pass_dict)) + break + except KeyError: + pass + except Exception as excp: + self.rdmc.ui.error( + "Unable to set minimum password length for manager accounts.\n", + excp, + ) + + # set the current privileges to those in the clone file + curr_privs = user_accounts["Privileges"] + + # set the current role to that in the clone file + curr_role_id = role_id = None + role_id = user_accounts.get("RoleId") + + if self.rdmc.app.typepath.defs.flagforrest: + _ = "links/self/" + self.rdmc.app.typepath.defs.hrefstring + else: + _ = self.rdmc.app.typepath.defs.hrefstring + + # obtaining account information on the current server as a check to verify the user + # provided a decent path to use. This can be re-factored. + try: + for curr_sel in self.rdmc.app.select(_type.split(".")[0] + "."): + try: + curr_privs = curr_sel.dict["Oem"][self.rdmc.app.typepath.defs.oemhp]["Privileges"] + curr_role_id = curr_sel.dict.get("RoleId") + if "UserName" in list(curr_sel.dict.keys()): + curr_un = curr_sel.dict["UserName"] + else: + curr_un = curr_sel.dict["Oem"][self.rdmc.app.typepath.defs.oemhp]["LoginName"] + if curr_un != user_name: + continue + else: + found_user = True + break + except (KeyError, NameError): + self.rdmc.ui.error("Unable to obtain the account information for: '%s''s'" "account.\n" % user_name) + continue + except InstanceNotFoundError: + pass + + if not found_user: + self.rdmc.ui.printer("Account '%s' was not found on this system.\n" % user_name) + + user_pass = user_accounts["Password"] + + (add_privs_str, remove_privs_str) = self.priv_helper(user_accounts, curr_privs) + + if curr_role_id == role_id: + role_id = None + + # Don't think we need to rely on ResourceExists. Should be able to easily tell which + # operation should be performed before this point. + if user_pass: + if user_pass == __DEFAULT__: + self.rdmc.ui.warn("The default password will be attempted.") + try: + if found_user: + raise ResourceExists("") + + # issue here then we just let it happen and perform a modify on the account. + elif role_id: + self.auxcommands["iloaccounts"].run( + "add " + user_name + " " + login_name + " " + user_pass + " " + " --role " + role_id + ) + self.c_log_write("[CHANGE]: Added " + user_name + " with role id " + role_id) + time.sleep(2) + elif add_privs_str: + self.auxcommands["iloaccounts"].run( + "add " + user_name + " " + login_name + " " + user_pass + " " + " --addprivs " + add_privs_str + ) + self.c_log_write("[CHANGE]: Added " + user_name + " with privs string " + str(add_privs_str)) + time.sleep(2) + else: + self.auxcommands["iloaccounts"].run("add " + user_name + " " + login_name + " " + user_pass) + self.c_log_write("[CHANGE]: Added " + user_name) + time.sleep(2) + except ResourceExists: + self.rdmc.ui.warn( + "The account name '%s' exists on this system. " "Checking for account modifications.\n" % user_name + ) + self.rdmc.ui.printer("Changing account password for '%s'.\n" % user_name) + self.auxcommands["iloaccounts"].run("changepass " + user_name + " " + user_pass) + self.c_log_write("[CHANGE]: Changing password for " + user_name) + time.sleep(2) + # if the user includes both role_id and privileges then privileges are applied + # first skipping role, if they exist. Extra steps, yes, in certain cases + # but not necessarily. + if role_id: + self.rdmc.ui.printer("Changing roles for user: '%s'.\n" % user_name) + self.auxcommands["iloaccounts"].run("modify " + user_name + " --role " + role_id) + self.c_log_write("[CHANGE]: Modify role for " + user_name) + time.sleep(2) + else: + if "10" in add_privs_str and "blobstore" in self.rdmc.app.current_client.base_url: + self.c_log_write("Warning: In local mode recovery privileges might not get updated in Production mode , kindly add recovery privilege seperately for the user "+user_name+"\n" ) + self.rdmc.ui.error("Warning: In local mode recovery privileges might not get updated in Production mode , kindly add recovery privilege seperately for the user "+user_name+"\n" ) + if add_privs_str: + self.rdmc.ui.printer("Adding privileges for user: '%s'.\n" % user_name) + self.auxcommands["iloaccounts"].run("modify " + user_name + " --addprivs " + add_privs_str) + self.c_log_write("[CHANGE]: Adding privs for " + user_name) + time.sleep(2) + if remove_privs_str: + self.rdmc.ui.printer("Removing privileges for user: '%s'.\n" % user_name) + self.auxcommands["iloaccounts"].run( + "modify " + user_name + " --removeprivs " + remove_privs_str + ) + self.c_log_write("[CHANGE]: Removing privs for " + user_name) + time.sleep(2) + elif role_id: + self.auxcommands["iloaccounts"].run("modify " + user_name + " --role " + role_id) + self.c_log_write("[CHANGE]: Modify role id for " + user_name) + time.sleep(2) + else: + raise Exception( + "A password was not provided for account: '%s', path: '%s'. " + "iLO accounts will not be altered without a valid password.\n" % (user_name, path) + ) + + @log_decor + def save_federation(self, fedaccts, _type, options): + """ + Save of Federation Account Data. + :parm fedaccts: Federation account payload to be saved + :type fedaccts: dict + :param _type: iLO schema type + :type _type: string + :param options: command line options + :type options: attribute + """ + + try: + fed_name = next(iter(jsonpath_rw.parse("$..Name").find(fedaccts))).value + except StopIteration: + privileges = None + + try: + fed_id = next(iter(jsonpath_rw.parse("$..Id").find(fedaccts))).value + except StopIteration: + privileges = None + + try: + privileges = next(iter(jsonpath_rw.parse("$..Privileges").find(fedaccts))).value + except StopIteration: + privileges = None + + fedkey = [__DEFAULT__, __DEFAULT__] + # if options.iLOSSA: + # sys.stdout.write("Smart Storage Array functionality") + + if not options.autocopy and not options.iLOSSA and not options.all: + while True: + for i in range(2): + if i < 1: + self.rdmc.ui.printer("Please input the federation key for Federation " "user: %s\n" % fed_name) + else: + self.rdmc.ui.printer( + "Please re-enter the federation key for Federation " "user: %s\n" % fed_name + ) + + fedkey[i] = getpass.getpass() + try: + [fedkey[i], _] = fedkey[i].split("\r") + except ValueError: + pass + + if fedkey[0] == fedkey[1] and (fedkey[0] is not None or fedkey[0] != ""): + break + else: + ans = input("You have entered two different federation keys...Retry?(y/n)\n") + if ans.lower() != "y": + self.rdmc.ui.printer("Skipping Federation Account Migration for: " "%s\n" % fed_name) + return None + else: + self.rdmc.ui.warn("Remember to edit the Federation key for acct: '%s'." % fed_name) + + if not fedkey[0]: + fedkey[0] = __DEFAULT__ + self.rdmc.ui.warn("Using a placeholder federation key '%s' in %s file.\n" % (fedkey[0], self.clone_file)) + fedaccts = { + "AccountID": fed_id, + "FederationName": fed_name, + "FederationKey": fedkey[0], + "Privileges": privileges, + } + return fedaccts + + @log_decor + def load_federation(self, fed_accounts, _type, path): + """ + Load of Federation Account Data. + :parm fed_accounts: Federation account payload to be loaded + :type fed_accounts: dict + """ + + found_user = False + fed_name = fed_accounts["FederationName"] + fed_key = fed_accounts["FederationKey"] + + # set the current privileges to those in the clone file + curr_privs = fed_accounts["Privileges"] + + # obtaining account information on the current server as a check to verify the user + # provided a decent path to use. This can be re-factored. + try: + for curr_sel in self.rdmc.app.select(_type.split(".")[0] + "."): + try: + curr_privs = curr_sel.dict.get("Privileges") + curr_fed = curr_sel.dict.get("Name") + if curr_fed != fed_name: + continue + else: + found_user = True + break + except (KeyError, NameError): + self.rdmc.ui.error("Unable to obtain the account information for: '%s''s'" "account.\n" % fed_name) + continue + except InstanceNotFoundError: + pass + + if not found_user: + self.rdmc.ui.warn("Fed Account '%s' was not found on this system.\n" % fed_name) + + if fed_key: + if fed_key == __DEFAULT__: + self.rdmc.ui.warn("The default federation key will be attempted.") + (add_privs_str, remove_privs_str) = self.priv_helper(fed_accounts, curr_privs) + try: + if found_user: + raise ResourceExists("") + else: + self.rdmc.ui.printer("Adding '%s' to iLO Federation.\n" % fed_name) + self.auxcommands["ilofederation"].run("add " + fed_name + " " + fed_key + " " + add_privs_str) + time.sleep(2) + except ResourceExists: + self.rdmc.ui.warn("This account already exists on this system: '%s'\n" % fed_name) + self.rdmc.ui.printer("Changing Federation account: '%s's key\n" % fed_name) + self.auxcommands["ilofederation"].run("changekey " + fed_name + " " + fed_key) + except ValueError: + self.rdmc.ui.error("Some other error occured while attempting to create this " "account: %s" % fed_name) + finally: + if add_privs_str: + self.rdmc.ui.printer("Adding privs to Federation account: '%s'\n" % fed_name) + self.auxcommands["ilofederation"].run( + "modify " + fed_name + " " + fed_key + " --addprivs " + add_privs_str + ) + time.sleep(2) + if remove_privs_str: + self.rdmc.ui.printer("Removing privs from Federation account: '%s'\n" % fed_name) + self.auxcommands["ilofederation"].run( + "modify " + fed_name + " " + fed_key + " --removeprivs " + remove_privs_str + ) + time.sleep(2) + else: + self.rdmc.ui.warn( + "A valid Federation key was not provided...skipping account " + "creation or modification for Fed. Acct '%s'" % fed_name + ) + + @log_decor + def save_smartstorage(self, drive_data, _type): + """ + Smart Storage Disk and Array Controller Configuration save. + :parm drive_data: Smart Storage Configuration payload to be saved + :type drive_data: dict + :param _type: iLO schema type + :type _type: string + """ + + @log_decor + def load_smartstorage(self, controller_data, _type, path): + """ + Smart Storage Disk and Array Controller Configuration load. + :parm controller_data: Smart Storage Configuration payload to be loaded + :type controller_data: dict + :param _type: iLO schema type + :type _type: string + :param path: iLO schema path + :type path: string + """ + self.smartarrayobj.load(controller_data) + + # Helper Functions + @log_decor + def system_compatibility_check(self, sys_info, options): + """ + Check if files needed for serverclone are available + :param sys_info: dictionary of comments for iLO firmware and BIOS ROM + versions + :type sys_info: dict + :param options: command line options + :type options: attribute + """ + + checks = [] + try: + curr_sys_info = self.rdmc.app.create_save_header()["Comments"] + curr_ilorev = format(float(self.curr_ilorev[0] + "." + self.curr_ilorev[1:]), ".2f") + _, file_iloversion, file_ilorev = sys_info["iLOVersion"].split(" ") + file_ilorev = file_ilorev.split("v")[-1] + self.rdmc.ui.printer("This system has iLO Version %s. \n" % curr_sys_info["iLOVersion"]) + self.rdmc.ui.printer("This system has BIOS Version %s.\n" % curr_sys_info["BIOSFamily"]) + if curr_sys_info["BIOSFamily"] == sys_info["BIOSFamily"]: + self.rdmc.ui.printer("BIOS Versions are compatible.\n") + checks.append(True) + else: + self.rdmc.ui.warn( + "BIOS Versions are different. Suggest to have" + " '%s' in place before upgrading.\n" % sys_info["BIOSFamily"] + ) + checks.append(False) + # Commenting out this line as same inforation is being printed twice. + # self.rdmc.ui.printer( + # "This system has iLO %s with firmware revision %s.\n" + # % (self.curr_iloversion, curr_ilorev) + # ) + if self.curr_iloversion == file_iloversion and curr_ilorev == file_ilorev: + self.rdmc.ui.printer("iLO Versions are fully compatible.\n") + checks.append(True) + elif self.curr_iloversion == file_iloversion and curr_ilorev != file_ilorev: + self.rdmc.ui.warn( + "The iLO Versions are compatible; however, the revisions " + "differ (system version: iLO %s %s, file version: iLO %s %s). Some " + "differences in properties, schemas and incompatible dependencies may " + "exist. Proceed with caution.\n" % (self.curr_iloversion, curr_ilorev, file_iloversion, file_ilorev) + ) + checks.append(False) + else: + self.rdmc.ui.warn( + "The iLO Versions are different. Compatibility issues may exist " + "attempting to commit changes to this system.\n(System version: iLO %s %s, " + "file version: iLO %s %s)\n" % (self.curr_iloversion, curr_ilorev, file_iloversion, file_ilorev) + ) + checks.append(False) + except KeyError as exp: + if "iLOVersion" in str(exp): + self.rdmc.ui.warn("iLOVersion not found in clone file 'Comments' dictionary.\n") + elif "BIOSFamily" in str(exp): + self.rdmc.ui.warn("BIOS Family not found in clone file 'Comments' dictionary.\n") + else: + raise Exception("%s" % exp) + + if (len(checks) == 0 or False in checks) and not options.autocopy and not options.iLOSSA and not options.all: + while True: + ans = input( + "Would you like to continue with migration of iLO configuration from " + "'%s' to '%s'? (y/n)\n" % (sys_info["Model"], curr_sys_info["Model"]) + ) + if ans.lower() == "n": + raise ExitHandler("Aborting load operation. No changes made to the server.") + elif ans.lower() == "y": + break + else: + self.rdmc.ui.warn("Invalid input...\n") + + self.rdmc.ui.printer( + "Attempting system clone from a '%s' to a '%s'.\n" % (sys_info["Model"], curr_sys_info["Model"]) + ) + + def priv_helper(self, desired_priv, curr_privs): + """ + Privilege helper. Assigns privileges to a string for addition or removal when loading + iLO management account or iLO Federation data + :param desired_priv: dictionary of desired privileges + :type desired_priv: dict + :param curr_priv: dictionary of current system privileges + :type curr_priv: dict + """ + + add_privs_str = "" + remove_privs_str = "" + + if desired_priv.get("Privileges").get("HostBIOSConfigPriv") and curr_privs.get("HostBIOSConfigPriv"): + add_privs_str += "8," + else: + remove_privs_str += "8," + if desired_priv.get("Privileges").get("HostNICConfigPriv") and curr_privs.get("HostNICConfigPriv"): + add_privs_str += "7," + else: + remove_privs_str += "7," + if desired_priv.get("Privileges").get("HostStorageConfigPriv") and curr_privs.get("HostStorageConfigPriv"): + add_privs_str += "9," + else: + remove_privs_str += "9," + if desired_priv.get("Privileges").get("LoginPriv") and curr_privs.get("LoginPriv"): + add_privs_str += "1," + else: + remove_privs_str += "1," + if desired_priv.get("Privileges").get("RemoteConsolePriv") and curr_privs.get("RemoteConsolePriv"): + add_privs_str += "2," + else: + remove_privs_str += "2," + if desired_priv.get("Privileges").get("SystemRecoveryConfigPriv") and curr_privs.get( + "SystemRecoveryConfigPriv" + ): + add_privs_str += "10," + else: + remove_privs_str += "10," + if desired_priv.get("Privileges").get("UserConfigPriv") and curr_privs.get("UserConfigPriv"): + add_privs_str += "3," + else: + remove_privs_str += "3," + if desired_priv.get("Privileges").get("VirtualMediaPriv") and curr_privs.get("VirtualMediaPriv"): + add_privs_str += "5," + else: + remove_privs_str += "5," + if desired_priv.get("Privileges").get("VirtualPowerAndResetPriv") and curr_privs.get( + "VirtualPowerAndResetPriv" + ): + add_privs_str += "6," + else: + remove_privs_str += "6," + if desired_priv.get("Privileges").get("iLOConfigPriv") and curr_privs.get("iLOConfigPriv"): + add_privs_str += "4," + else: + remove_privs_str += "4," + + return (add_privs_str[:-1], remove_privs_str[:-1]) + + def get_rootpath(self, path): + """ + Obtain the root path of the current path (multiple instances within a path) + :param path: current type path + :returns: a tuple including either the root_path and the ending or the original path and + ending + """ + + root_path = "" + + if path[-1] == "/": + ending = path.split("/")[-2] + else: + ending = path.split("/")[-1] + + entries_list = [(pos.start(), pos.end()) for pos in list(re.finditer(ending, path))] + root_path, ident_ending = ( + path[: entries_list[-1][0]], + path[entries_list[-1][0] :], + ) + + # check to verify the root path + ending match the original path. + _ = "" + if len(root_path + ident_ending) == len(path): + return (root_path, _.join(ident_ending.split("/"))) + return (path, ident_ending) + + def get_filenames(self): + """ + Obtain a dictionary of filenames for clonefile, and cert files + :returns: returns dictionary of filenames + """ + return { + "clone_file": self.clone_file, + "https_cert_file": self.https_cert_file, + "sso_cert_file": self.sso_cert_file, + } + + def check_files(self, options): + """ + Check if files needed for serverclone are available + :param options: command line options + :type options: attribute + """ + if options.encryption: + if self.save: + self.rdmc.ui.printer("Serverclone JSON, '%s' will be encrypted.\n" % self.clone_file) + if self.load: + self.rdmc.ui.printer("Loading the encrypted JSON clone file: %s.\n" % self.clone_file) + self.rdmc.ui.printer("Note: Make sure %s is encrypted.\n" % self.clone_file) + + # delete anything in the change log file + with open(self.change_log_file, "w+") as clf: + clf.write("") + # delete anything in the error log file + with open(self.error_log_file, "w+") as elf: + elf.write("") + + # check the clone file exists (otherwise create) + try: + if options.encryption: + file_handle = open(self.clone_file, "r+b") + else: + file_handle = open(self.clone_file, "r+") + file_handle.close() + except: + if self.save: + if options.encryption: + file_handle = open(self.clone_file, "w+b") + else: + file_handle = open(self.clone_file, "w+") + file_handle.close() + else: + self.rdmc.ui.error("The clone file '%s', selected for loading," " was not found.\n" % self.clone_file) + raise IOError + + @log_decor + def type_compare(self, type1, type2): + """ + iLO schema type compatibility verification + :param type1 + :type string + :param type2 + :type string + :returns: return tuple with booleans of comparison checks + """ + _type1 = type1 + _type2 = type2 + checklist = ["Major"] # , 'Minor'] #No minor checking for now + + found_type = False + compatible = list() + + _type1 = self.type_break(_type1) + _type2 = self.type_break(_type2) + + if _type1[type1]["Type"].lower() == _type2[type2]["Type"].lower(): + found_type = True + + for item in checklist: + if _type1[type1]["Version"][item] == _type2[type2]["Version"][item]: + compatible.append("True") + else: + compatible.append("False") + + if "False" in compatible: + return (found_type, False) + return (found_type, True) + + @log_decor + def type_break(self, _type): + """ + Breakdown of each iLO schema type for version comparison + :param _type: iLO schema type + :type _type: string + """ + + _type2 = dict() + _type_breakdown = _type.split("#")[-1].split(".") + _type2[_type] = dict([("Type", _type_breakdown[0]), ("Version", {})]) + versioning = list() + if len(_type_breakdown) == 3 and "_" in _type_breakdown[1]: + rev = _type_breakdown[1].split("_") + _type2[_type]["Version"] = { + "Major": int(rev[0][-1]), + "Minor": int(rev[1]), + "Errata": int(rev[2]), + } + elif len(_type_breakdown) > 3 and "_" not in _type: + for value in _type_breakdown: + if value.isdigit(): + versioning.append(int(value)) + _type2[_type]["Version"] = { + "Major": versioning[0], + "Minor": versioning[1], + "Errata": versioning[2], + } + + return _type2 + + def serverclonevalidation(self, options): + """ + Serverclone validation function. Validates command line options and + initiates a login to the iLO Server. + :param options: command line options + :type options: list. + """ + + self._cache_dir = os.path.join(self.rdmc.app.cachedir, __tempfoldername__) + if not os.path.exists(self._cache_dir): + os.makedirs(self._cache_dir) + self.tmp_clone_file = os.path.join(self._cache_dir, __tmp_clone_file__) + self.tmp_sel_file = os.path.join(self._cache_dir, __tmp_sel_file__) + # self.change_log_file = os.path.join(self._cache_dir, __changelog_file__) + # self.error_log_file = os.path.join(self._cache_dir, __error_log_file__) + + self.cmdbase.login_select_validation(self, options) + + if options.clonefilename: + if len(options.clonefilename) < 2: + self.clone_file = options.clonefilename[0] + else: + raise InvalidCommandLineError("Only a single clone file may be specified.") + else: + self.clone_file = __clone_file__ + + if options.encryption: + if len(options.encryption.encode("utf8")) not in [16, 24, 32]: + raise InvalidKeyError( + "An invalid encryption key has been used with a length of: " + "%s chars....ensure the encryption key length is 16, 24 or " + "32 characters long." % len((options.encryption).encode("utf8")) + ) + # filenames + if self.load: + if options.ssocert: + if len(options.ssocert) < 2 and self.load: + self.sso_cert_file = options.ssocert[0] + else: + raise InvalidCommandLineError("Ensure you are loading a single SSO certificate" ".\n") + if options.tlscert: + if len(options.tlscert) < 2 and self.load: + self.https_cert_file = options.tlscert[0] + else: + raise InvalidCommandLineError("Ensure you are loading a single TLS certificate" ".\n") + if self.rdmc.opts.debug: + self.rdmc.ui.warn( + "Debug selected...all exceptions will be handled in an external log " + "file (check error log for automatic testing).\n" + ) + with open(self.error_log_file, "w+") as efh: + efh.write("") + + @staticmethod + def options_argument_group(parser): + """Define option arguments group + :param parser: The parser to add the login option group to + :type parser: ArgumentParser/OptionParser + """ + + parser.add_argument( + "--encryption", + dest="encryption", + help="Optionally include this flag to encrypt/decrypt a file" " using the key provided.", + default=None, + ) + parser.add_argument( + "-f", + "--clonefile", + dest="clonefilename", + help="Optionally rename the default clone file 'ilorest_clone.json'", + action="append", + default=None, + ) + parser.add_argument( + "-sf", + "--storageclonefile", + dest="storageclonefilename", + help="Optionally rename the default clone file 'ilorest_storage_clone.json'", + action="append", + default=None, + ) + parser.add_argument( + "--uniqueoverride", + dest="uniqueoverride", + action="store_true", + help="Override the measures stopping the tool from writing." "over items that are system unique.", + default=None, + ) + parser.add_argument( + "--auto", + dest="autocopy", + help="Optionally include this flag to ignore user prompts for save or load processes.", + action="store_true", + default=None, + ) + + def definearguments(self, customparser): + """Wrapper function for new command main function + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + # self.options_argument_group(customparser) + subcommand_parser = customparser.add_subparsers(dest="command") + subcommand_parser.required = True + save_help = "Save an iLO, Bios and SSA config." + # save sub-parser + save_parser = subcommand_parser.add_parser( + "save", + help=save_help, + description=save_help + "\n\texample: serverclone save" + "\n\n\tSave iLO config omitting BIOS attributes to a non-default file name.\n\t" + "example: serverclone save -f serv_clone.json --nobios" + "\n\n\tSave an encrypted iLO configuration file (to the default file name)\n\t" + "example: serverclone save --encryption ", + formatter_class=RawDescriptionHelpFormatter, + ) + save_parser.add_argument( + "--ilossa", + dest="iLOSSA", + help="Optionally include this flag to include configuration of" " iLO Smart Array Devices during save.", + action="store_true", + default=None, + ) + + save_parser.add_argument( + "--all", + dest="all", + help="Optionally include this flag to include all" " iLO Smart Array Devices and All during save.", + action="store_true", + default=None, + ) + + save_parser.add_argument( + "--nobios", + dest="noBIOS", + help="Optionally include this flag to omit save of Bios configuration.", + action="store_true", + default=None, + ) + self.cmdbase.add_login_arguments_group(save_parser) + self.options_argument_group(save_parser) + + load_help = "Load an iLO, Bios and/or SSA config." + # load sub-parser + load_parser = subcommand_parser.add_parser( + "load", + help=load_help, + description=load_help + "SSO and TLS certificates may be" + "added on load.\n\n\tLoad a clone file from a non-default file name.\n\t" + "example: serverclone load -f serv_clone.json" + "\n\n\tLoad a clone file with SSO and TLS certificates.\n\t" + "example: serverclone load -ssocert sso.txt --tlscert tls.txt" + "\n\n\tLoad a clone file which has been encrypted.\n\t" + "example: serverclone load --encryption abc12abc12abc123\n\n\t", + formatter_class=RawDescriptionHelpFormatter, + ) + load_parser.add_argument( + "--ssocert", + dest="ssocert", + help="Use this flag during 'load' to include an SSO certificate." + " This should be properly formatted in a simple text file.", + action="append", + default=None, + ) + load_parser.add_argument( + "--tlscert", + dest="tlscert", + help="Use this flag during 'load' to include a TLS certificate." + " This should be properly formatted in a simple text file.", + action="append", + default=None, + ) + load_parser.add_argument( + "--all", + dest="all", + help="Optionally include this flag to include all" " iLO Smart Array Devices and All during save.", + action="store_true", + default=None, + ) + load_parser.add_argument( + "--ilossa", + dest="iLOSSA", + help="Optionally include this flag to include all" " iLO Smart Array Devices and All during loadn.", + action="store_true", + default=None, + ) + load_parser.add_argument( + "--noautorestart", + dest="noautorestart", + help="Optionally noautorestart after loading", + action="store_true", + default=False, + ) + self.cmdbase.add_login_arguments_group(load_parser) + self.options_argument_group(load_parser) diff --git a/ilorest/extensions/iLO_COMMANDS/ServerConfigurationLockCommand.py b/ilorest/extensions/iLO_COMMANDS/ServerConfigurationLockCommand.py new file mode 100644 index 0000000..3232804 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/ServerConfigurationLockCommand.py @@ -0,0 +1,281 @@ +### +# Copyright 2016-2021 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 -*- +""" BiosDefaultsCommand for rdmc """ + +from argparse import RawDescriptionHelpFormatter + +try: + from rdmc_helper import ( + LOGGER, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidPasswordLengthError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + LOGGER, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidPasswordLengthError, + ReturnCodes, + ) + + +class ServerConfigurationLockCommand: + """Set BIOS settings back to default for the server that is currently + logged in""" + + def __init__(self): + self.ident = { + "name": "serverconfiglock", + "usage": None, + "description": "To to perform validation checks on raw JSON data " + "server.\n\texample: serverconfiglock\n\t" + "example: serverconfiglock enable --serverconfiglockpassword=AWS@123456789 " + "--serverconfiglockexcludefwrevs=True\t serverconfiglockexcludefwrevs set True or False\n\t" + "example: serverconfiglock disable --serverconfiglockpassword=AWS@1234 " + "--serverconfiglockdisable=True\t serverconfiglockdisable set True or False\n\t" + "example: serverconfiglock display", + "summary": "The BIOS feature “Server Configuration Lock” supports certain parameters," + "including a password. This password has a 16 to 31 character limit. " + "“Server Configuration Lock” is not one of the special commands in iLO REST.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main BIOS defaults worker function""" + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + bios_path = self.rdmc.app.typepath.defs.biospath + resp = self.rdmc.app.get_handler(bios_path, silent=True, service=True).dict + match_url = resp["Oem"]["Hpe"]["Links"]["ServerConfigLock"]["@odata.id"] + if "oem" in match_url: + scs_path = bios_path + "oem/hpe/serverconfiglock/settings/" + else: + scs_path = bios_path + "serverconfiglock/settings/" + self.serverconfiglockvalidation(options) + if options.command: + if options.command.lower() == "enable": + self.enable_scs(options, scs_path) + elif options.command.lower() == "disable": + self.disable_scs(options, scs_path) + elif options.command == "display": + self.display_scs(scs_path) + else: + raise InvalidCommandLineError("%s is not a valid option for this " "command." % str(options.command)) + else: + raise InvalidCommandLineError( + "Please provide either enable, disable and display subcommand. " + "For help or usage related information, use -h or --help" + ) + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def enable_scs(self, options, scs_path): + """ + Enable/Disable the SCL config function + """ + import re + if options.serverconfiglockpassword: + ser_cf_password = options.serverconfiglockpassword + self.serverconfiglockpassword_validation(options) + if options.serverconfiglockexcludefwrevs.lower() == "true": + serverconfrevs = "True" + elif options.serverconfiglockexcludefwrevs.lower() == "false": + serverconfrevs = "False" + else: + raise InvalidCommandLineError("ServerConfigLockExcludeFwRevs value invalid set only true or false") + body = {"ServerConfigLockPassword": ser_cf_password, "ServerConfigLockExcludeFwRevs": serverconfrevs} + try: + self.rdmc.ui.printer("payload: %s \n" % body) + LOGGER.info("payload", body) + resp = self.rdmc.app.patch_handler(scs_path, body) + if resp.status == 200: + self.rdmc.ui.printer("SCS enabled successfully\n") + LOGGER.info("SCS enabled successfully") + self.rdmc.ui.printer("Help: System Reboot is required after changing the serverconfiglock settings \n") + return ReturnCodes.SUCCESS + except IncompatibleiLOVersionError: + self.rdmc.ui.error("iLO FW version on this server doesnt support SCS") + LOGGER.error("iLO FW version on this server doesnt support SCS") + return ReturnCodes.INCOMPATIBLE_ILO_VERSION_ERROR + + def disable_scs(self, options, scs_path): + """ + Disable the SCL config function + """ + if options.serverconfiglockpassword: + ser_cf_password = options.serverconfiglockpassword + self.serverconfiglockpassword_validation(options) + if options.serverconfiglockdisable.lower() == "true": + sclockdisable = "True" + elif options.serverconfiglockdisable.lower() == "false": + sclockdisable = "False" + else: + raise InvalidCommandLineError("ServerConfigLockDisable value invalid set only true or false") + body = {"ServerConfigLockPassword": ser_cf_password, "ServerConfigLockDisable": sclockdisable} + try: + self.rdmc.ui.printer("payload: %s \n" % body) + LOGGER.info("payload", body) + resp = self.rdmc.app.patch_handler(scs_path, body) + if resp.status == 200: + self.rdmc.ui.printer("SCS Disabled successfully\n") + LOGGER.info("SCS Disabled successfully") + self.rdmc.ui.printer("Help: System Reboot is required after changing the serverconfiglock settings \n") + return ReturnCodes.SUCCESS + except IncompatibleiLOVersionError: + self.rdmc.ui.error("iLO FW version on this server doesnt support SCS") + LOGGER.error("iLO FW version on this server doesnt support SCS") + return ReturnCodes.INCOMPATIBLE_ILO_VERSION_ERROR + + def display_scs(self, scs_path): + """ + Display SCS details + """ + ilo_ver = self.rdmc.app.getiloversion() + if ilo_ver >= 5.290 or ilo_ver < 6.000: + results = self.rdmc.app.get_handler(scs_path, silent=True) + if results.status == 200: + self.rdmc.ui.printer("Server Configuration Lock setting details ...\n") + LOGGER.info("Server Configuration Lock setting details ...") + results = results.dict + self.print_scs_info(results) + else: + self.rdmc.ui.printer("For iLO6, this command is not yet supported.\n") + LOGGER.error("For iLO6, this command is not yet supported.") + + def print_scs_info(self, results): + """ + Prints the SCL: info + """ + for key, value in results.items(): + if "@odata" not in key: + if type(value) is dict: + self.print_cert_info(value) + else: + self.rdmc.ui.printer(key + ":" + str(value) + "\n") + + def serverconfiglockpassword_validation(self, options): + """ + Password validation for ServerConfigLockPassword method + """ + import re + + if options.serverconfiglockpassword: + password = options.serverconfiglockpassword + if len(password) < 16 or len(password) > 31: + LOGGER.info("Length of password requires min of 16 and max of 31 character " + "excluding '/' character.\n") + raise InvalidPasswordLengthError("Length of password requires min of 16 and max of 31 character " + "excluding '/' character.\n") + elif re.search("[0-9]", password) is None: + print("Make sure your password has a number in it\n") + elif re.search("[A-Z]", password) is None: + print("Make sure your password has a capital letter in it\n") + + + def serverconfiglockvalidation(self, options): + """new command method validation function""" + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + subcommand_parser = customparser.add_subparsers(dest="command") + enable_help = "To enable ServerConfigurationLock \n" + + enable_parser = subcommand_parser.add_parser( + "enable", + help=enable_help, + description=enable_help + "\n\tExample:\n\tserverconfiglock enable ", + formatter_class=RawDescriptionHelpFormatter, + ) + enable_parser.add_argument( + "--serverconfiglockpassword", + dest="serverconfiglockpassword", + help="Set a serverconfiglockpassword", + required=True, + type=str, + default=None, + ) + enable_parser.add_argument( + "--serverconfiglockexcludefwrevs", + dest="serverconfiglockexcludefwrevs", + help="to set/enable serverconfiglockexcludefwrevs", + default=False, + required=True, + ) + self.cmdbase.add_login_arguments_group(enable_parser) + + status_help = "To check the ServerConfigurationLock display\n" + status_parser = subcommand_parser.add_parser( + "display", + help=status_help, + description=status_help + "\n\tExample:\n\tserverconfiglock display or " "\n\tserverconfiglock status -j", + formatter_class=RawDescriptionHelpFormatter, + ) + + self.cmdbase.add_login_arguments_group(status_parser) + + disable_help = "To disable the ServerConfigurationLock\n" + disable_parser = subcommand_parser.add_parser( + "disable", + help=disable_help, + description=disable_help + "\n\tExample:\n\tserverconfiglock disable", + formatter_class=RawDescriptionHelpFormatter, + ) + disable_parser.add_argument( + "--serverconfiglockpassword", + dest="serverconfiglockpassword", + help="Server Configuration Lock password and to digitally fingerprint the system to enable Server " + "Configuration Lock.", + required=True, + type=str, + default=None, + ) + disable_parser.add_argument( + "--serverconfiglockdisable", + dest="serverconfiglockdisable", + help="Select this option to disable Server Configuration Lock.", + required=True, + default=False, + ) + self.cmdbase.add_login_arguments_group(disable_parser) diff --git a/ilorest/extensions/iLO_COMMANDS/ServerInfoCommand.py b/ilorest/extensions/iLO_COMMANDS/ServerInfoCommand.py new file mode 100644 index 0000000..4820256 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/ServerInfoCommand.py @@ -0,0 +1,1119 @@ +### +# Copyright 2016-2021 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 -*- +""" Server Info Command for rdmc """ +import re +import sys +from collections import OrderedDict + +import jsonpath_rw + +try: + from rdmc_helper import ( + UI, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + UI, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class ServerInfoCommand: + """Show details of a server""" + + def __init__(self): + self.ident = { + "name": "serverinfo", + "usage": None, + "description": "Shows all information.\n\tExample: serverinfo\n\t\t" + "serverinfo --all\n\n\t" + "Show enabled fan, processor, and thermal information.\n\texample: " + "serverinfo --fans --processors --thermals --proxy\n\n\tShow all memory " + "and fan information, including absent locations in json format.\n\t" + "example: serverinfo --proxy --firmware --software --memory --fans --showabsent -j\n", + "summary": "Shows aggregate health status and details of the currently logged in server.", + "aliases": ["health", "serverstatus", "systeminfo"], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main serverinfo function. + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if args: + raise InvalidCommandLineError("serverinfo command takes no " "arguments.") + + self.serverinfovalidation(options) + + self.optionsvalidation(options) + + info = self.gatherinfo(options) + + if not info: + raise InvalidCommandLineError("Please verify the commands entered " "and try again.") + if "proxy" in info and info["proxy"]: + if "WebProxyConfiguration" in info["proxy"]["Oem"]["Hpe"]: + info["proxy"] = info["proxy"]["Oem"]["Hpe"]["WebProxyConfiguration"] + + if options.json: + if options.processors and options.json and self.rdmc.app.typepath.defs.isgen9: + pass + elif options.memory and self.rdmc.app.typepath.defs.isgen9: + pass + else: + self.build_json_out(info, options.showabsent) + else: + self.prettyprintinfo(info, options.showabsent) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def gatherinfo(self, options): + """Gather info to printout based on options + + :param options: command line options + :type options: list. + """ + info = {} + path = self.rdmc.app.typepath.defs.systempath + if path == "/rest/v1/Systems/1" or "/redfish/v1/Systems/1/": + path = "/redfish/v1/Systems/" + else: + pass + if options.system: + info["system"] = OrderedDict() + csysresults = self.rdmc.app.get_handler(path, service=True, silent=True).dict + # csysresults = self.rdmc.app.select(selector="ComputerSystemCollection") + # csysresults = csysresults[0].dict + members = csysresults["Members"] + for id in members: + for mem_id in id.values(): + if path in mem_id: + csysresults = self.rdmc.app.get_handler(mem_id, service=True, silent=True).dict + try: + csysresults = csysresults[0].dict + except: + pass + if csysresults: + info["system"]["Model"] = "%s %s" % ( + csysresults["Manufacturer"], + csysresults["Model"], + ) + info["system"]["Bios Version"] = csysresults["BiosVersion"] + + biosresults = self.rdmc.app.select(selector=self.rdmc.app.typepath.defs.biostype) + + try: + biosresults = biosresults[0].dict + except: + pass + + if biosresults: + try: + info["system"]["Serial Number"] = biosresults["Attributes"]["SerialNumber"] + except: + if "SerialNumber" in biosresults: + info["system"]["Serial Number"] = biosresults["SerialNumber"] + ethresults = None + getloc = self.rdmc.app.getidbytype("EthernetInterfaceCollection.") + if getloc: + for loc in getloc: + if "/systems/1/" in loc.lower(): + ethresults = self.rdmc.app.getcollectionmembers(loc) + break + if ethresults: + niccount = 0 + info["system"]["ethernet"] = OrderedDict() + for eth in ethresults: + niccount += 1 + if eth["Name"] == "": + if "MACAddress" in eth: + info["system"]["ethernet"][niccount] = eth["MACAddress"] + else: + info["system"]["ethernet"][niccount] = eth["PermanentMACAddress"] + elif eth["Name"] != "": + if "MACAddress" in eth: + info["system"]["ethernet"][eth["Name"]] = eth["MACAddress"] + else: + info["system"]["ethernet"][eth["Name"]] = eth["PermanentMACAddress"] + info["system"]["NICCount"] = niccount + if options.thermals or options.fans: + data = None + if not self.rdmc.app.typepath.defs.isgen9: + getloc = self.rdmc.app.getidbytype("Thermal.") + else: + getloc = self.rdmc.app.getidbytype("ThermalMetrics.") + if getloc: + data = self.rdmc.app.get_handler(getloc[0], silent=True, service=True) + if options.thermals: + if not getloc: + info["thermals"] = None + else: + info["thermals"] = data.dict["Temperatures"] + if options.fans: + if not getloc: + info["fans"] = None + else: + info["fans"] = data.dict["Fans"] + if options.memory: + data = None + if self.rdmc.app.typepath.defs.isgen9: + mem_path = "/redfish/v1/Systems/1/Memory/" + "?$expand=." + getloc = self.rdmc.app.get_handler(mem_path, silent=True, service=True).dict + collectiondata = getloc["Oem"][self.rdmc.app.typepath.defs.oemhp] + if collectiondata and not options.json: + sys.stdout.write("---------------------------------\n") + sys.stdout.write("Memory/DIMM Board Information:\n") + sys.stdout.write("---------------------------------\n") + memory_status = "Advanced Memory Protection Status: %s \n" % collectiondata["AmpModeStatus"] + sys.stdout.write(memory_status) + sys.stdout.write("AmpModeActive: %s \n" % collectiondata["AmpModeActive"]) + sys.stdout.write("AmpModeSupported: %s \n" % collectiondata["AmpModeSupported"]) + sys.stdout.write("Type: %s \n" % collectiondata["Type"]) + if collectiondata and options.json: + tmp = dict() + tmp["Memory"] = dict() + tmp["Memory"]["Advanced Memory Protection Status"] = collectiondata["AmpModeStatus"] + tmp["Memory"]["AmpModeActive"] = collectiondata["AmpModeActive"] + tmp["Memory"]["AmpModeSupported"] = collectiondata["AmpModeSupported"] + tmp["Memory"]["Type"] = collectiondata["Type"] + UI().print_out_json(tmp) + getloc = self.rdmc.app.getidbytype("MemoryCollection.") + if getloc: + data = self.rdmc.app.getcollectionmembers(getloc[0], fullresp=True)[0] + info["memory"] = data + else: + info["memory"] = None + if options.proxy: + data = None + getloc = self.rdmc.app.getidbytype("NetworkProtocol.") + if getloc: + data = self.rdmc.app.getcollectionmembers(getloc[0], fullresp=True)[0] + info["proxy"] = data + else: + info["proxy"] = None + if options.processors: + data = None + if not self.rdmc.app.typepath.defs.isgen9: + getloc = self.rdmc.app.getidbytype("ProcessorCollection.") + else: + # getloc = self.rdmc.app.getidbytype("ProcessorCollection.") + getloc = self.rdmc.app.getidbytype("Processor.") + tot_proc=[] + if getloc == "/rest/v1/Systems/1/Processors/1" or "/rest/v1/Systems/1/Processors/2": + getloc = "/redfish/v1/Systems/1/Processors/" + get_p = self.rdmc.app.get_handler(getloc, service=True, silent=True).dict["Members"] + output="" + if get_p and not options.json: + sys.stdout.write("------------------------------------------------\n") + sys.stdout.write("Processor:\n") + sys.stdout.write("------------------------------------------------\n") + for pp in get_p: + dd = pp["@odata.id"] + data = self.rdmc.app.get_handler(dd, service=True, silent=True).dict + output += "Processor %s:\n" % data["Id"] + output += "\tModel: %s\n" % data["Model"] + output += "\tStep: %s\n" % data["ProcessorId"]["Step"] + output += "\tSocket: %s\n" % data["Socket"] + output += "\tMax Speed: %s MHz\n" % data["MaxSpeedMHz"] + try: + output += "\tSpeed: %s MHz\n" % data["Oem"][self.rdmc.app.typepath.defs.oemhp][ + "RatedSpeedMHz"] + except KeyError: + pass + output += "\tCores: %s\n" % data["TotalCores"] + output += "\tThreads: %s\n" % data["TotalThreads"] + try: + for cache in data["Oem"][self.rdmc.app.typepath.defs.oemhp]["Cache"]: + output += "\t%s: %s KB\n" % ( + cache["Name"], + cache["InstalledSizeKB"], + ) + except KeyError: + pass + try: + output += "\tHealth: %s\n" % data["Status"]["Health"] + except KeyError: + pass + self.rdmc.ui.printer(output, verbose_override=True) + if get_p and options.json: + for pp in get_p: + dd = pp["@odata.id"] + data = self.rdmc.app.get_handler(dd, service=True, silent=True).dict + process = "Processor %s" % data["Id"] + tmp = dict() + tmp[process] = dict() + tmp[process]["Processor"] = data["Id"] + tmp[process]["Model"] = data["Model"] + tmp[process]["Step"] = data["ProcessorId"]["Step"] + tmp[process]["Socket"] = data["Socket"] + tmp[process]["Max Speed"] = data["MaxSpeedMHz"] + try: + tmp[process].update( + {"Speed": "%s MHz" % data["Oem"][self.rdmc.app.typepath.defs.oemhp][ + "RatedSpeedMHz"]} + ) + except KeyError: + pass + tmp[process].update({"Cores": data["TotalCores"]}) + tmp[process].update({"Threads": data["TotalThreads"]}) + UI().print_out_json(tmp) + else: + pass + if getloc and not self.rdmc.app.typepath.defs.isgen9: + data = self.rdmc.app.getcollectionmembers(getloc[0]) + + info["processor"] = data + else: + info["processor"] = None + if options.power: + data = None + if not self.rdmc.app.typepath.defs.isgen9: + getloc = self.rdmc.app.getidbytype("Power.") + else: + getloc = self.rdmc.app.getidbytype("PowerMetrics.") + if getloc: + data = self.rdmc.app.get_handler(getloc[0], silent=True, service=True) + info["power"] = data.dict + else: + info["power"] = None + if options.firmware: + data = None + if not self.rdmc.app.typepath.defs.isgen10: + getloc = self.rdmc.app.getidbytype("FwSwVersionInventory") + else: + getloc = self.rdmc.app.getidbytype("SoftwareInventoryCollection.") + + for gloc in getloc: + if "FirmwareInventory" in gloc: + data = self.rdmc.app.getcollectionmembers(gloc) + # if not self.rdmc.app.typepath.defs.isgen10: + # data1 = list(data) + info["firmware"] = data + if options.software: + data = None + if not self.rdmc.app.typepath.defs.isgen10: + getloc = self.rdmc.app.getidbytype("SoftwareInventory") + else: + getloc = self.rdmc.app.getidbytype("SoftwareInventoryCollection.") + data_list = list() + for gloc in getloc: + if "SoftwareInventory" in gloc: + if not self.rdmc.app.typepath.defs.isgen10: + data = self.rdmc.app.getcollectionmembers(gloc) + data_list.append(data.dict) + else: + data = self.rdmc.app.getcollectionmembers(gloc) + if not self.rdmc.app.typepath.defs.isgen10: + info["software"] = data_list + else: + info["software"] = data + if not options.showabsent: + jsonpath_expr = jsonpath_rw.parse("$..State") + matches = jsonpath_expr.find(info) + matches.reverse() + + for match in matches: + if match.value.lower() == "absent": + arr = None + statepath = "/" + str(match.full_path).replace(".", "/") + arr = re.split("[\[\]]", statepath) + if arr: + removedict = None + start = arr[0].split("/") + for key in start: + if key: + if not removedict: + removedict = info[key] + else: + removedict = removedict[key] + del removedict[int(arr[1])] + return info + + def build_json_out(self, info, absent): + headers = list(info.keys()) + content = dict() + if "power" in headers and info["power"]: + data = info["power"] + if data is not None: + for control in data["PowerControl"]: + power_cap = {"Total Power Capacity": "%s W" % control["PowerCapacityWatts"]} + power_cap.update({"Total Power Consumed": "%s W" % control["PowerConsumedWatts"]}) + power_mertic = {"Average Power": "%s W" % control["PowerMetrics"]["AverageConsumedWatts"]} + power_mertic.update({"Max Consumed Power": "%s W" % control["PowerMetrics"]["MaxConsumedWatts"]}) + power_mertic.update( + {"Minimum Consumed Power": "%s W" % control["PowerMetrics"]["MinConsumedWatts"]} + ) + powercontent = "Power Metrics on %s min. Intervals" % control["PowerMetrics"]["IntervalInMin"] + test = {powercontent: power_mertic} + content = {"power": power_cap} + content["power"].update(test) + try: + for supply in data["PowerSupplies"]: + power_supply = "Power Supply %s" % supply["Oem"][self.rdmc.app.typepath.defs.oemhp]["BayNumber"] + powersupply = {"Power Capacity": "%s W" % supply["PowerCapacityWatts"]} + powersupply.update({"Last Power Output": "%s W" % supply["LastPowerOutputWatts"]}) + powersupply.update({"Input Voltage": "%s V" % supply["LineInputVoltage"]}) + powersupply.update({"Input Voltage Type": supply["LineInputVoltageType"]}) + powersupply.update( + {"Hotplug Capable": supply["Oem"][self.rdmc.app.typepath.defs.oemhp]["HotplugCapable"]} + ) + powersupply.update( + {"iPDU Capable": supply["Oem"][self.rdmc.app.typepath.defs.oemhp]["iPDUCapable"]} + ) + try: + powersupply.update({"Health": supply["Status"]["Health"]}) + except KeyError: + pass + # if absent: + try: + powersupply.update({"State": supply["Status"]["State"]}) + except KeyError: + pass + powerdetails = {power_supply: powersupply} + content["power"].update(powerdetails) + for redundancy in data["Redundancy"]: + redund_name = redundancy["Name"] + redundancy_dict = {"Redundancy Mode": redundancy["Mode"]} + try: + redundancy_dict.update({"Redundancy Health": redundancy["Status"]["Health"]}) + redundancy_dict.update({"Redundancy State": redundancy["Status"]["State"]}) + content.update({redund_name: redundancy_dict}) + except KeyError: + pass + except KeyError: + pass + + if "firmware" in headers and info["firmware"]: + firmware_info = [] + data = info["firmware"] + if data is not None: + if not self.rdmc.app.typepath.defs.isgen10: + for key, fw in data.items(): + for fw_gen9 in fw: + fw_str = fw_gen9["Name"] + ": " + fw_gen9["VersionString"] + firmware_info.append(fw_str) + else: + for fw in data: + fw_str = fw["Name"] + ": " + fw["Version"] + firmware_info.append(fw_str) + content.update({"firmware": firmware_info}) + + if "software" in headers and info["software"]: + output = "" + software_info = {} + data = info["software"] + if data is not None: + if isinstance(data, dict) or isinstance(data, list): + for sw in data: + software_info.update({sw["Name"]: sw["Version"]}) + else: + # if not options.json: + software_info = "No information available for the server\n" + self.rdmc.ui.printer(software_info, verbose_override=True) + content.update({"software": software_info}) + + if "memory" in headers and info["memory"]: + data = info["memory"] + count = 1 + if data is not None: + collectiondata = data["Oem"][self.rdmc.app.typepath.defs.oemhp] + output = "Memory/DIMM Board Information" + + memory_status = "Advanced Memory Protection Status: %s" % collectiondata["AmpModeStatus"] + memorylist = {} + for board in collectiondata["MemoryList"]: + board_detail = "Board CPU: %s" % board["BoardCpuNumber"] + memory_info = {"Total Memory Size": "%s MiB" % board["BoardTotalMemorySize"]} + memory_info.update({"Board Memory Frequency": "%s MHz" % board["BoardOperationalFrequency"]}) + memory_info.update({"Board Memory Voltage": "%s MiB" % board["BoardOperationalVoltage"]}) + memorylist.update({board_detail: memory_info}) + memorystatus = {memory_status: memorylist} + memoryinfo = {output: memorystatus} + + for dimm in data[self.rdmc.app.typepath.defs.collectionstring]: + memoryconfig = {"Location": dimm["DeviceLocator"]} + try: + memoryconfig.update( + { + "Memory Type": "%s %s" + % ( + dimm["MemoryType"], + dimm["MemoryDeviceType"], + ) + } + ) + except KeyError: + memoryconfig.update({"Memory Type": dimm["MemoryType"]}) + memoryconfig.update({"Capacity": "%s MiB" % dimm["CapacityMiB"]}) + try: + memoryconfig.update({"Speed": "%s MHz" % dimm["OperatingSpeedMhz"]}) + memoryconfig.update({"Status": dimm["Oem"][self.rdmc.app.typepath.defs.oemhp]["DIMMStatus"]}) + memoryconfig.update({"Health": dimm["Status"]["Health"]}) + except KeyError: + pass + + if absent: + try: + memoryconfig.update({"State": dimm["Status"]["State"]}) + except KeyError: + pass + + memory_config = "Memory/DIMM Configuration" + " " + str(count) + memoryinfo.update({memory_config: memoryconfig}) + content.update({"memory": memoryinfo}) + count = count + 1 + + if "fans" in headers and info["fans"]: + fan_output = {} + fan_details = {} + if info["fans"] is not None: + for fan in info["fans"]: + fan_name = "" + if not self.rdmc.app.typepath.defs.isgen9: + fan_name = "%s" % fan["Name"] + else: + fan_name = "%s" % fan["FanName"] + fan_output.update({"Location": fan["Oem"][self.rdmc.app.typepath.defs.oemhp]["Location"]}) + if "Reading" in fan: + fan_output.update({"Reading": "%s%%" % fan["Reading"]}) + fan_output.update({"Redundant": fan["Oem"][self.rdmc.app.typepath.defs.oemhp]["Redundant"]}) + fan_output.update( + {"Hot Pluggable": fan["Oem"][self.rdmc.app.typepath.defs.oemhp]["HotPluggable"]} + ) + try: + if "Health" in fan["Status"]: + fan_output.update({"Health": fan["Status"]["Health"]}) + except KeyError: + pass + + # if absent: + try: + fan_output.update({"State": fan["Status"]["State"]}) + except KeyError: + pass + fan_details.update({fan_name: fan_output}) + content.update({"fans": fan_details}) + + if "thermals" in headers and info["thermals"]: + if info["thermals"] is not None: + sensor = "" + thermal_detail = {} + for temp in info["thermals"]: + if "SensorNumber" in temp: + sensor = "Sensor #%s:" % temp["SensorNumber"] + if "PhysicalContext" in temp: + thermal_info = {"Location": temp["PhysicalContext"]} + thermal_info.update({"Current Temp": "%s C" % temp["ReadingCelsius"]}) + if "UpperThresholdCritical" in temp: + thermal_info.update({"Critical Threshold": "%s C" % temp["UpperThresholdCritical"]}) + else: + thermal_info.update({"Critical Threshold": "-"}) + if "UpperThresholdFatal" in temp: + thermal_info.update({"Fatal Threshold": "%s C" % temp["UpperThresholdFatal"]}) + else: + thermal_info.update({"Fatal Threshold": "-"}) + try: + if "Health" in temp["Status"]: + thermal_info.update({"Health": temp["Status"]["Health"]}) + except KeyError: + pass + if absent: + try: + thermal_info.update({"State": temp["Status"]["State"]}) + except KeyError: + pass + thermal_detail.update({sensor: thermal_info}) + content.update({"thermals": thermal_detail}) + + if "processor" in headers and info["processor"]: + data = info["processor"] + processor_info = {} + if data is not None: + if not self.rdmc.app.typepath.defs.isgen9: + for processor in data: + process = "Processor %s" % processor["Id"] + processor_date = {"Model": processor["Model"]} + processor_date.update({"Step": processor["ProcessorId"]["Step"]}) + processor_date.update({"Socket": processor["Socket"]}) + processor_date.update({"Max Speed": "%s MHz" % processor["MaxSpeedMHz"]}) + try: + processor_date.update( + { + "Speed": "%s MHz" + % processor["Oem"][self.rdmc.app.typepath.defs.oemhp]["RatedSpeedMHz"] + } + ) + except KeyError: + pass + processor_date.update({"Cores": processor["TotalCores"]}) + processor_date.update({"Threads": processor["TotalThreads"]}) + try: + for cache in processor["Oem"][self.rdmc.app.typepath.defs.oemhp]["Cache"]: + processor_date.update({cache["Name"]: "%s KB" % cache["InstalledSizeKB"]}) + except KeyError: + pass + try: + processor_date.update({"Health": processor["Status"]["Health"]}) + except KeyError: + pass + if absent: + try: + processor_date.update({"State": processor["Status"]["State"]}) + except KeyError: + pass + processor_info.update({process: processor_date}) + else: + data = data.dict + # for processor in data: + process = "Processor %s" % data["Id"] + processor_date = {"Model": data["Model"]} + processor_date.update({"Step": data["ProcessorId"]["Step"]}) + processor_date.update({"Socket": data["Socket"]}) + processor_date.update({"Max Speed": "%s MHz" % data["MaxSpeedMHz"]}) + try: + processor_date.update( + {"Speed": "%s MHz" % data["Oem"][self.rdmc.app.typepath.defs.oemhp]["RatedSpeedMHz"]} + ) + except KeyError: + pass + processor_date.update({"Cores": data["TotalCores"]}) + processor_date.update({"Threads": data["TotalThreads"]}) + try: + for cache in data["Oem"][self.rdmc.app.typepath.defs.oemhp]["Cache"]: + processor_date.update({cache["Name"]: "%s KB" % cache["InstalledSizeKB"]}) + except KeyError: + pass + try: + processor_date.update({"Health": data["Status"]["Health"]}) + except KeyError: + pass + if absent: + try: + processor_date.update({"State": data["Status"]["State"]}) + except KeyError: + pass + processor_info.update({process: processor_date}) + content.update({"processor": processor_info}) + + if "proxy" in headers and info["proxy"]: + proxy_info = {} + data = info["proxy"] + try: + if data is not None: + for k, v in data.items(): + proxy_info.update({k: v}) + except KeyError: + pass + content.update({"proxy": proxy_info}) + + if "system" in headers: + data = info["system"] + system = {} + if data is not None: + for key, val in list(data.items()): + if key == "ethernet": + embedded_nic = {"Embedded NIC Count": data["NICCount"]} + system.update(embedded_nic) + for name in sorted(data["ethernet"]): + mac_name = str(name) + " " + "MAC" + mac = {mac_name: data["ethernet"][name]} + system.update(mac) + elif not key == "NICCount": + nic = {key: val} + system.update(nic) + content.update({"system": system}) + + UI().print_out_json(content) + + def prettyprintinfo(self, info, absent): + """Print info in human readable form from json + + :param info: info data collected + :type info: dict. + :param absent: flag to show or hide absent components + :type absent: bool. + """ + output = "" + headers = list(info.keys()) + if "system" in headers: + data = info["system"] + output = "------------------------------------------------\n" + output += "System:\n" + output += "------------------------------------------------\n" + if data is not None: + for key, val in list(data.items()): + if key == "ethernet": + output += "Embedded NIC Count: %s\n" % data["NICCount"] + for name in sorted(data["ethernet"]): + output += "%s MAC: %s\n" % (name, data["ethernet"][name]) + elif not key == "NICCount": + output += "%s: %s\n" % (key, val) + self.rdmc.ui.printer(output, verbose_override=True) + + if "firmware" in headers and info["firmware"]: + data = info["firmware"] + output = "------------------------------------------------\n" + output += "Firmware: \n" + output += "------------------------------------------------\n" + if data is not None: + if not self.rdmc.app.typepath.defs.isgen10: + for key, fw in data.items(): + for fw_gen9 in fw: + output += "%s : %s\n" % ( + fw_gen9["Name"], + fw_gen9["VersionString"], + ) + else: + for fw in data: + output += "%s : %s\n" % (fw["Name"], fw["Version"]) + self.rdmc.ui.printer(output, verbose_override=True) + + if "software" in headers and info["software"]: + output = "" + data = info["software"] + if not isinstance(data, dict) and not isinstance(data, list): + data = data.dict + output = "------------------------------------------------\n" + output += "Software: \n" + output += "------------------------------------------------\n" + if data is not None: + if isinstance(data, dict) or isinstance(data, list): + for sw in data: + output += "%s : %s\n" % (sw["Name"], sw["Version"]) + else: + output = "No information available for the server\n" + self.rdmc.ui.printer(output, verbose_override=True) + + if "proxy" in headers and info["proxy"]: + output = "" + data = info["proxy"] + output = "------------------------------------------------\n" + output += "Proxy: \n" + output += "------------------------------------------------\n" + try: + if data is not None: + for k, v in data.items(): + output += "%s : %s\n" % (k, v) + except KeyError: + pass + self.rdmc.ui.printer(output, verbose_override=True) + + if "processor" in headers and info["processor"]: + output = "" + data = info["processor"] + output = "------------------------------------------------\n" + output += "Processor:\n" + output += "------------------------------------------------\n" + if data is not None: + if not self.rdmc.app.typepath.defs.isgen9: + for processor in data: + output += "Processor %s:\n" % processor["Id"] + output += "\tModel: %s\n" % processor["Model"] + output += "\tStep: %s\n" % processor["ProcessorId"]["Step"] + output += "\tSocket: %s\n" % processor["Socket"] + output += "\tMax Speed: %s MHz\n" % processor["MaxSpeedMHz"] + try: + output += ( + "\tSpeed: %s MHz\n" + % processor["Oem"][self.rdmc.app.typepath.defs.oemhp]["RatedSpeedMHz"] + ) + except KeyError: + pass + output += "\tCores: %s\n" % processor["TotalCores"] + output += "\tThreads: %s\n" % processor["TotalThreads"] + try: + for cache in processor["Oem"][self.rdmc.app.typepath.defs.oemhp]["Cache"]: + output += "\t%s: %s KB\n" % ( + cache["Name"], + cache["InstalledSizeKB"], + ) + except KeyError: + pass + try: + output += "\tHealth: %s\n" % processor["Status"]["Health"] + except KeyError: + pass + if absent: + try: + output += "\tState: %s\n" % processor["Status"]["State"] + except KeyError: + pass + self.rdmc.ui.printer(output, verbose_override=True) + else: + data = data.dict + output += "Processor %s:\n" % data["Id"] + output += "\tModel: %s\n" % data["Model"] + output += "\tStep: %s\n" % data["ProcessorId"]["Step"] + output += "\tSocket: %s\n" % data["Socket"] + output += "\tMax Speed: %s MHz\n" % data["MaxSpeedMHz"] + try: + output += "\tSpeed: %s MHz\n" % data["Oem"][self.rdmc.app.typepath.defs.oemhp]["RatedSpeedMHz"] + except KeyError: + pass + output += "\tCores: %s\n" % data["TotalCores"] + output += "\tThreads: %s\n" % data["TotalThreads"] + try: + for cache in data["Oem"][self.rdmc.app.typepath.defs.oemhp]["Cache"]: + output += "\t%s: %s KB\n" % ( + cache["Name"], + cache["InstalledSizeKB"], + ) + except KeyError: + pass + try: + output += "\tHealth: %s\n" % data["Status"]["Health"] + except KeyError: + pass + if absent: + try: + output += "\tState: %s\n" % data["Status"]["State"] + except KeyError: + pass + self.rdmc.ui.printer(output, verbose_override=True) + + if "memory" in headers and info["memory"]: + data = info["memory"] + if data is not None: + collectiondata = data["Oem"][self.rdmc.app.typepath.defs.oemhp] + output = "------------------------------------------------\n" + output += "Memory/DIMM Board Information:\n" + output += "------------------------------------------------\n" + output += "Advanced Memory Protection Status: %s\n" % collectiondata["AmpModeStatus"] + for board in collectiondata["MemoryList"]: + output += "Board CPU: %s \n" % board["BoardCpuNumber"] + output += "\tTotal Memory Size: %s MiB\n" % board["BoardTotalMemorySize"] + output += "\tBoard Memory Frequency: %s MHz\n" % board["BoardOperationalFrequency"] + output += "\tBoard Memory Voltage: %s MiB\n" % board["BoardOperationalVoltage"] + output += "------------------------------------------------\n" + output += "Memory/DIMM Configuration:\n" + output += "------------------------------------------------\n" + for dimm in data[self.rdmc.app.typepath.defs.collectionstring]: + output += "Location: %s\n" % dimm["DeviceLocator"] + try: + output += "Memory Type: %s %s\n" % ( + dimm["MemoryType"], + dimm["MemoryDeviceType"], + ) + except KeyError: + output += "Memory Type: %s\n" % dimm["MemoryType"] + output += "Capacity: %s MiB\n" % dimm["CapacityMiB"] + try: + output += "Speed: %s MHz\n" % dimm["OperatingSpeedMhz"] + + output += "Status: %s\n" % dimm["Oem"][self.rdmc.app.typepath.defs.oemhp]["DIMMStatus"] + output += "Health: %s\n" % dimm["Status"]["Health"] + except KeyError: + pass + if absent: + try: + output += "State: %s\n" % dimm["Status"]["State"] + except KeyError: + pass + output += "\n" + self.rdmc.ui.printer(output, verbose_override=True) + + if "power" in headers and info["power"]: + data = info["power"] + output = "------------------------------------------------\n" + output += "Power:\n" + output += "------------------------------------------------\n" + if data is not None: + for control in data["PowerControl"]: + output += "Total Power Capacity: %s W\n" % control["PowerCapacityWatts"] + output += "Total Power Consumed: %s W\n" % control["PowerConsumedWatts"] + output += "\n" + output += "Power Metrics on %s min. Intervals:\n" % control["PowerMetrics"]["IntervalInMin"] + output += "\tAverage Power: %s W\n" % control["PowerMetrics"]["AverageConsumedWatts"] + output += "\tMax Consumed Power: %s W\n" % control["PowerMetrics"]["MaxConsumedWatts"] + output += "\tMinimum Consumed Power: %s W\n" % control["PowerMetrics"]["MinConsumedWatts"] + try: + for supply in data["PowerSupplies"]: + output += "------------------------------------------------\n" + output += "Power Supply %s:\n" % supply["Oem"][self.rdmc.app.typepath.defs.oemhp]["BayNumber"] + output += "------------------------------------------------\n" + + output += "Power Capacity: %s W\n" % supply["PowerCapacityWatts"] + output += "Last Power Output: %s W\n" % supply["LastPowerOutputWatts"] + output += "Input Voltage: %s V\n" % supply["LineInputVoltage"] + output += "Input Voltage Type: %s\n" % supply["LineInputVoltageType"] + output += ( + "Hotplug Capable: %s\n" % supply["Oem"][self.rdmc.app.typepath.defs.oemhp]["HotplugCapable"] + ) + output += "iPDU Capable: %s\n" % supply["Oem"][self.rdmc.app.typepath.defs.oemhp]["iPDUCapable"] + try: + output += "Health: %s\n" % supply["Status"]["Health"] + except KeyError: + pass + + try: + output += "State: %s\n" % supply["Status"]["State"] + except KeyError: + pass + for redundancy in data["Redundancy"]: + output += "------------------------------------------------\n" + output += "%s\n" % redundancy["Name"] + output += "------------------------------------------------\n" + output += "Redundancy Mode: %s\n" % redundancy["Mode"] + try: + output += "Redundancy Health: %s\n" % redundancy["Status"]["Health"] + output += "Redundancy State: %s\n" % redundancy["Status"]["State"] + except KeyError: + pass + except KeyError: + pass + self.rdmc.ui.printer(output, verbose_override=True) + + if "fans" in headers and info["fans"]: + output = "------------------------------------------------\n" + output += "Fan(s):\n" + output += "------------------------------------------------\n" + if info["fans"] is not None: + for fan in info["fans"]: + if not self.rdmc.app.typepath.defs.isgen9: + output += "%s:\n" % fan["Name"] + else: + output += "%s:\n" % fan["FanName"] + output += "\tLocation: %s\n" % fan["Oem"][self.rdmc.app.typepath.defs.oemhp]["Location"] + if "Reading" in fan: + output += "\tReading: %s%%\n" % fan["Reading"] + output += "\tRedundant: %s\n" % fan["Oem"][self.rdmc.app.typepath.defs.oemhp]["Redundant"] + output += ( + "\tHot Pluggable: %s\n" % fan["Oem"][self.rdmc.app.typepath.defs.oemhp]["HotPluggable"] + ) + try: + if "Health" in fan["Status"]: + output += "\tHealth: %s\n" % fan["Status"]["Health"] + except KeyError: + pass + + try: + output += "\tState: %s\n" % fan["Status"]["State"] + except KeyError: + pass + self.rdmc.ui.printer(output, verbose_override=True) + + if "thermals" in headers and info["thermals"]: + output = "------------------------------------------------\n" + output += "Thermal:\n" + output += "------------------------------------------------\n" + if info["thermals"] is not None: + for temp in info["thermals"]: + if "SensorNumber" in temp: + output += "Sensor #%s:\n" % temp["SensorNumber"] + if "PhysicalContext" in temp: + output += "\tLocation: %s\n" % temp["PhysicalContext"] + output += "\tCurrent Temp: %s C\n" % temp["ReadingCelsius"] + if "UpperThresholdCritical" in temp: + output += "\tCritical Threshold: %s C\n" % temp["UpperThresholdCritical"] + else: + output += "\tCritical Threshold: -\n" + if "UpperThresholdFatal" in temp: + output += "\tFatal Threshold: %s C\n" % temp["UpperThresholdFatal"] + else: + output += "\tFatal Threshold: -\n" + try: + if "Health" in temp["Status"]: + output += "\tHealth: %s\n" % temp["Status"]["Health"] + except KeyError: + pass + if absent: + try: + output += "\tState: %s\n" % temp["Status"]["State"] + except KeyError: + pass + self.rdmc.ui.printer(output, verbose_override=True) + + def serverinfovalidation(self, options): + """serverinfo method validation function. + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def optionsvalidation(self, options): + """Checks/updates options. + :param options: command line options + :type options: list + """ + optlist = [ + options.proxy, + options.firmware, + options.software, + options.memory, + options.thermals, + options.fans, + options.power, + options.processors, + options.system, + ] + + if not any(optlist): + self.setalloptionstrue(options) + if options.all: + self.setalloptionstrue(options) + return options + + def setalloptionstrue(self, options): + """Updates all selector options values to be True. + :param options: command line options + :type options: list + """ + options.memory = True + options.thermals = True + options.firmware = True + options.software = True + options.fans = True + options.processors = True + options.power = True + options.system = True + options.proxy = True + options.showabsent = True + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-all", + "--all", + dest="all", + action="store_true", + help="Add information for all types.", + default=False, + ) + customparser.add_argument( + "-fw", + "--firmware", + dest="firmware", + action="store_true", + help="Add firmware information to the output.", + default=False, + ) + customparser.add_argument( + "-sw", + "--software", + dest="software", + action="store_true", + help="Add software information to the output.", + default=False, + ) + customparser.add_argument( + "-memory", + "--memory", + dest="memory", + action="store_true", + help="Add memory DIMM information to the output.", + default=False, + ) + customparser.add_argument( + "-fans", + "--fans", + dest="fans", + action="store_true", + help="Add fans information to the output.", + default=False, + ) + customparser.add_argument( + "-processors", + "--processors", + dest="processors", + action="store_true", + help="Add processor(s) information to the output.", + default=False, + ) + customparser.add_argument( + "-thermals", + "--thermals", + dest="thermals", + action="store_true", + help="Add thermal information to the output.", + default=False, + ) + customparser.add_argument( + "-power", + "--power", + dest="power", + action="store_true", + help="Add power information to the output.", + default=False, + ) + customparser.add_argument( + "-system", + "--system", + dest="system", + action="store_true", + help="Add basic system information to the output.", + default=False, + ) + customparser.add_argument( + "--showabsent", + dest="showabsent", + action="store_true", + help="Include information on absent components in the output.", + default=False, + ) + customparser.add_argument( + "-proxy", + "--proxy", + dest="proxy", + action="store_true", + help="Add proxy information to the output", + default=False, + ) + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) diff --git a/ilorest/extensions/iLO_COMMANDS/ServerStateCommand.py b/ilorest/extensions/iLO_COMMANDS/ServerStateCommand.py new file mode 100644 index 0000000..8a85d8b --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/ServerStateCommand.py @@ -0,0 +1,106 @@ +### +# Copyright 2016-2021 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 -*- +""" Server State Command for rdmc """ + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class ServerStateCommand: + """Returns the current state of the server that is currently logged in""" + + def __init__(self): + self.ident = { + "name": "serverstate", + "usage": None, + "description": "Returns the current state of the" " server\n\n\tExample: serverstate", + "summary": "Returns the current state of the server.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main serverstate function + + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if args: + raise InvalidCommandLineError( + "Invalid number of parameters, " "serverstate command does not take any parameters." + ) + + self.serverstatevalidation(options) + + path = self.rdmc.app.typepath.defs.systempath + results = self.rdmc.app.get_handler(path, silent=True, uncache=True).dict + + if results: + results = results["Oem"][self.rdmc.app.typepath.defs.oemhp]["PostState"] + self.rdmc.ui.printer("The server is currently in state: " + results + "\n") + else: + raise NoContentsFoundForOperationError("Unable to retrieve server state") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def serverstatevalidation(self, options): + """Server state method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/iLO_COMMANDS/ServerlogsCommand.py b/ilorest/extensions/iLO_COMMANDS/ServerlogsCommand.py new file mode 100644 index 0000000..1bea2b6 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/ServerlogsCommand.py @@ -0,0 +1,1454 @@ +### +# Copyright 2016-2021 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 -*- +""" Log Operations Command for rdmc """ + +import ctypes +import datetime +import itertools +import json +import os +import platform +import shlex +import string +import subprocess +import sys +import tempfile +import time + +from six.moves import queue + +import redfish.hpilo.risblobstore2 as risblobstore2 +from redfish.rest.connections import SecurityStateError +from redfish.ris.utils import filter_output + +try: + from rdmc_helper import ( + LOGGER, + UI, + IncompatibleiLOVersionError, + InvalidCListFileError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + InvalidKeyError, + InvalidMSCfileInputError, + MultipleServerConfigError, + NoContentsFoundForOperationError, + PartitionMoutingError, + ReturnCodes, + UnabletoFindDriveError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + LOGGER, + UI, + IncompatibleiLOVersionError, + InvalidCListFileError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + InvalidKeyError, + InvalidMSCfileInputError, + MultipleServerConfigError, + NoContentsFoundForOperationError, + PartitionMoutingError, + ReturnCodes, + UnabletoFindDriveError, + ) + +if os.name == "nt": + import win32api +elif sys.platform != "darwin" and "VMkernel" not in platform.uname(): + import pyudev + + +class ServerlogsCommand: + """Download logs from the server that is currently logged in""" + + def __init__(self): + self.ident = { + "name": "serverlogs", + "usage": None, + "description": "Download the AHS" + " logs from the logged in server.\n\texample: serverlogs " + "--selectlog=AHS \n\n\tClear the AHS logs " + "from the logged in server.\n\texample: serverlogs " + "--selectlog=AHS --clearlog\n\n\tDownload the IEL" + " logs from the logged in server.\n\texample: serverlogs " + "--selectlog=IEL -f IELlog.txt\n\n\tClear the IEL logs " + "from the logged in server.\n\texample: serverlogs " + "--selectlog=IEL --clearlog\n\n\tDownload the IML" + " logs from the logged in server.\n\texample: serverlogs " + "--selectlog=IML -f IMLlog.txt\n\n\tClear the IML logs " + "from the logged in server.\n\texample: serverlogs " + "--selectlog=IML --clearlog\n\n\t(IML LOGS ONLY FEATURE)" + "\n\tInsert entry in the IML logs from the logged in " + 'server.\n\texample: serverlogs --selectlog=IML -m "Text' + ' message for maintenance"\n\n\tDownload the iLO Security' + " logs from the logged in server.\n\texample: serverlogs " + "--selectlog=SL -f SLlog.txt\n\n\tClear the iLO Security logs " + "from the logged in server.\n\texample: serverlogs " + "--selectlog=SL --clearlog\n\n\tDownload logs from multiple servers" + "\n\texample: serverlogs --mpfile mpfilename.txt -o output" + "directorypath --mplog=IEL,IML\n\n\tNote: multiple server file " + "format (1 server per new line)\n\t--url " + "-u admin -p password\n\t--url -u admin -" + "p password\n\t--url -u admin -p password" + "\n\n\tInsert customised string " + "if required for AHS\n\texample: serverlogs --selectlog=" + 'AHS --customiseAHS "from=2014-03-01&&to=2014' + '-03-30"\n\n\t(AHS LOGS ONLY FEATURE)\n\tInsert the location/' + "path of directory where AHS log needs to be saved." + " \n\texample: serverlogs --selectlog=AHS " + "--directorypath=C:\\Python38\\DataFiles\n\n\tRepair IML log." + "\n\texample: serverlogs --selectlog=IML --repair IMLlogID", + "summary": "Download and perform log operations.", + "aliases": ["logservices"], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + self.dontunmount = None + self.queue = queue.Queue() + self.abspath = None + self.lib = None + + def run(self, line, help_disp=False): + """Main serverlogs function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if not getattr(options, "sessionid", False): + self.serverlogsvalidation(options) + + if options.mpfilename: + self.rdmc.ui.printer("Downloading logs for multiple servers...\n") + return self.gotompfunc(options) + + self.serverlogsworkerfunction(options) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def serverlogsworkerfunction(self, options): + """ "Main worker function outlining the process + + :param options: command line options + :type options: list. + """ + if not options.service: + raise InvalidCommandLineError("Please select a log type using the --selectlog option.") + + if options.service.lower() == "iml": + path = self.returnimlpath(options=options) + elif options.service.lower() == "iel": + path = self.returnielpath(options=options) + elif options.service.lower() == "sl": + path = self.returnslpath(options=options) + elif options.service.lower() == "ahs" and options.filter: + raise InvalidCommandLineError("Cannot filter AHS logs.") + elif ( + options.service.lower() == "ahs" + and (not self.rdmc.app.typepath.url or self.rdmc.app.typepath.url.startswith("blobstore")) + and not options.clearlog + ): + if options.customiseAHS is not None: + current_date = str(datetime.datetime.now()).split()[0] + date_check = options.customiseAHS + from_date = date_check.split("&")[0].split("=")[1] + to_date = date_check.split("to")[-1].split("=")[-1] + if to_date < from_date or from_date > current_date: + raise InvalidCommandLineError("Please provide valid date to customiseAHS") + self.downloadahslocally(options=options) + return + elif options.service.lower() == "ahs": + path = self.returnahspath(options) + else: + raise InvalidCommandLineError("Log opted does not exist!") + + data = None + + if options.clearlog: + self.clearlog(path) + elif options.mainmes: + self.addmaintenancelogentry(options, path=path) + elif options.repiml: + self.repairlogentry(options, path=path) + else: + data = self.downloaddata(path=path, options=options) + + self.savedata(options=options, data=data) + + def gotompfunc(self, options): + """ "Function to download logs from multiple servers concurrently + + :param options: command line options + :type options: list. + """ + if options.mpfilename: + mfile = options.mpfilename + outputdir = None + + if options.outdirectory: + outputdir = options.outdirectory + + if self.runmpfunc(mpfile=mfile, outputdir=outputdir, options=options): + return ReturnCodes.SUCCESS + else: + raise MultipleServerConfigError("One or more servers failed to download logs.") + + def runmpfunc(self, mpfile=None, outputdir=None, options=None): + """Main worker function for multi file command + + :param mpfile: configuration file + :type mpfile: string. + :param outputdir: custom output directory + :type outputdir: string. + """ + self.logoutobj.run("") + LOGGER.info("Validating input server collection file.") + data = self.validatempfile(mpfile=mpfile, options=options) + + if not data: + return False + + processes = [] + finalreturncode = True + outputform = "%Y-%m-%d-%H-%M-%S" + + if outputdir: + if outputdir.endswith(('"', "'")) and outputdir.startswith(('"', "'")): + outputdir = outputdir[1:-1] + + if not os.path.isdir(outputdir): + raise InvalidCommandLineError("The given output folder path does not exist.") + + dirpath = outputdir + else: + dirpath = os.getcwd() + + dirname = "%s_%s" % (datetime.datetime.now().strftime(outputform), "MSClogs") + createdir = os.path.join(dirpath, dirname) + os.mkdir(createdir) + + oofile = open(os.path.join(createdir, "CompleteOutputfile.txt"), "w+") + self.rdmc.ui.printer("Creating multiple processes to load configuration concurrently to all servers...\n") + + while True: + if not self.queue.empty(): + line = self.queue.get() + else: + break + + finput = "\n" + "Output for " + line[line.index("--url") + 1] + ": \n\n" + urlvar = line[line.index("--url") + 1] + urlfilename = urlvar.split("//")[-1] + line[line.index("-f") + 1] = str(line[line.index("-f") + 1]) + urlfilename + + if "python" in os.path.basename(sys.executable.lower()): + # If we are running from source we have to add the python file to the command + listargforsubprocess = [sys.executable, sys.argv[0]] + line + else: + listargforsubprocess = [sys.executable] + line + + if os.name != "nt": + listargforsubprocess = " ".join(listargforsubprocess) + + logfile = open(os.path.join(createdir, urlfilename + ".txt"), "w+") + pinput = subprocess.Popen(listargforsubprocess, shell=True, stdout=logfile, stderr=logfile) + + processes.append((pinput, finput, urlvar, logfile)) + + for pinput, finput, urlvar, logfile in processes: + pinput.wait() + returncode = pinput.returncode + finalreturncode = finalreturncode and not returncode + + logfile.close() + logfile = open(os.path.join(createdir, urlvar + ".txt"), "r+") + oofile.write(finput + str(logfile.read())) + oofile.write("-x+x-" * 16) + logfile.close() + + if returncode == 0: + self.rdmc.ui.printer("Loading Configuration for {} : SUCCESS\n".format(urlvar)) + else: + self.rdmc.ui.error("Loading Configuration for {} : FAILED\n".format(urlvar)) + self.rdmc.ui.error( + "ILORest return code : {}.\nFor more details please check {}" + ".txt under {} directory.\n".format(returncode, urlvar, createdir) + ) + + oofile.close() + + if finalreturncode: + self.rdmc.ui.printer("All servers have been successfully configured.\n") + + return finalreturncode + + def validatempfile(self, mpfile=None, options=None): + """Validate temporary file + + :param mpfile: configuration file + :type mpfile: string. + :param lfile: custom file name + :type lfile: string. + """ + self.rdmc.ui.printer("Checking given server information...\n") + + if not mpfile: + return False + elif mpfile.startswith(('"', "'")) and mpfile[0] == mpfile[-1]: + mpfile = mpfile[1:-1] + + if not os.path.isfile(mpfile) or not options.mplog: + raise InvalidFileInputError( + "File '%s' doesn't exist, please " "create file by running save command." % mpfile + ) + + logs = self.checkmplog(options) + + try: + with open(mpfile, "r") as myfile: + data = list() + cmdtorun = ["serverlogs"] + globalargs = ["-v", "--nocache"] + + while True: + line = myfile.readline() + if not line: + break + + for logval in logs: + cmdargs = ["--selectlog=" + str(logval), "-f", str(logval)] + if line.endswith(os.linesep): + line.rstrip(os.linesep) + + args = shlex.split(line, posix=False) + + if len(args) < 5: + self.rdmc.ui.error("Incomplete data in input file: {}\n".format(line)) + raise InvalidMSCfileInputError("Please verify the " "contents of the %s file" % mpfile) + else: + linelist = globalargs + cmdtorun + args + cmdargs + line = str(line).replace("\n", "") + self.queue.put(linelist) + data.append(linelist) + except Exception as excp: + LOGGER.info("%s", str(excp)) + raise excp + + if data: + return data + + return False + + def checkmplog(self, options): + """Function to validate mplogs options + + :param options: command line options + :type options: list. + """ + if options.mplog: + logs = str(options.mplog) + if "," in logs: + logs = logs.split(",") + return logs + if logs in ("all", "IEL", "IML", "AHS"): + if logs == "all": + logs = ["IEL", "IML", "AHS"] + else: + logs = [logs] + return logs + raise InvalidCommandLineError("Error in mplogs options") + + def addmaintenancelogentry(self, options, path=None): + """Worker function to add maintenance log + + :param options: command line options + :type options: list. + :param path: path to post maintenance log + :type path: str + """ + LOGGER.info("Adding maintenance logs") + if options.mainmes is None: + raise InvalidCommandLineError("") + + if options.service != "IML": + raise InvalidCommandLineError("Log opted cannot make maintenance entries!") + + message = options.mainmes + + if message.endswith(('"', "'")) and message.startswith(('"', "'")): + message = message[1:-1] + + if path: + bodydict = dict() + bodydict["path"] = path + bodydict["body"] = {"EntryCode": "Maintenance", "Message": message} + + LOGGER.info( + "Writing maintenance post to %s with %s", + str(path), + str(bodydict["body"]), + ) + + self.rdmc.app.post_handler(path, bodydict["body"]) + + def repairlogentry(self, options, path=None): + """Worker function to repair maintenance log + + :param options: command line options + :type options: list. + :param path: path of IML log Entries + :type path: str + """ + if options.repiml is None or (options.clearlog and options.repiml): + raise InvalidCommandLineError("") + + if options.service != "IML": + raise InvalidCommandLineError("Log opted cannot repair maintenance entries.") + + imlid = options.repiml + + if imlid.endswith(('"', "'")) and imlid.startswith(('"', "'")): + imlid = imlid[1:-1] + + if path: + imlidpath = [path if path[-1] == "/" else path + "/"][0] + str(imlid) + "/" + bodydict = dict() + bodydict["path"] = imlidpath + if self.rdmc.app.typepath.defs.isgen9: + bodydict["body"] = {"Oem": {"Hp": {"Repaired": True}}} + else: + bodydict["body"] = {"Oem": {"Hpe": {"Repaired": True}}} + LOGGER.info( + "Repairing maintenance log at %s with %s", + str(imlidpath), + str(bodydict["body"]), + ) + + self.rdmc.app.patch_handler(imlidpath, bodydict["body"]) + + def clearlog(self, path): + """Worker function to clear logs. + + :param path: path to post clear log action + :type path: str + """ + LOGGER.info("Clearing logs.") + if path and self.rdmc.app.typepath.defs.isgen9: + if path.endswith("/Entries"): + path = path[: -len("/Entries")] + elif path.endswith("Entries/"): + path = path[: -len("Entries/")] + + bodydict = dict() + bodydict["path"] = path + bodydict["body"] = {"Action": "ClearLog"} + self.rdmc.app.post_handler(path, bodydict["body"]) + elif path: + action = path.split("/")[-2] + bodydict = dict() + bodydict["path"] = path + bodydict["body"] = {"Action": action} + self.rdmc.app.post_handler(path, bodydict["body"]) + + def downloaddata(self, path=None, options=None): + """Worker function to download the log files + + :param options: command line options + :type options: list. + :param path: path to download logs + :type path: str + """ + if path: + LOGGER.info("Getting data from %s", str(path)) + if options.service == "AHS": + data = self.rdmc.app.get_handler(path, silent=True, uncache=True) + if data: + return data.ori + else: + raise NoContentsFoundForOperationError("Unable to retrieve AHS logs.") + else: + data = self.rdmc.app.get_handler(path, silent=True) + + datadict = data.dict + + try: + completedatadictlist = datadict["Items"] if "Items" in datadict else datadict["Members"] + except: + self.rdmc.ui.error("No data available within log.\n") + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + + if self.rdmc.app.typepath.defs.flagforrest: + morepages = True + + while morepages: + if "links" in datadict and "NextPage" in datadict["links"]: + next_link_uri = path + "?page=" + str(datadict["links"]["NextPage"]["page"]) + + href = "%s" % next_link_uri + data = self.rdmc.app.get_handler(href, silent=True) + datadict = data.dict + + try: + completedatadictlist = completedatadictlist + datadict["Items"] + except: + self.rdmc.ui.error("No data available within log.\n") + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + else: + morepages = False + else: + datadict = list() + + for members in completedatadictlist: + if len(list(members.keys())) == 1: + memberpath = members[self.rdmc.app.typepath.defs.hrefstring] + data = self.rdmc.app.get_handler(memberpath, silent=True) + datadict = datadict + [data.dict] + else: + datadict = datadict + [members] + completedatadictlist = datadict + + if completedatadictlist: + try: + return completedatadictlist + except Exception: + self.rdmc.ui.error("Could not get the data from server.\n") + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + else: + self.rdmc.ui.error("No log data present.\n") + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + else: + self.rdmc.ui.error("Path not found for input log.\n") + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + + def returnimlpath(self, options=None): + """Return the requested path of the IML logs + + :param options: command line options + :type options: list. + """ + LOGGER.info("Obtaining IML path for download.") + path = "" + # val = self.rdmc.app.typepath.defs.logservicetype + # filtereddatainstance = self.rdmc.app.select(selector=val) + val ="/redfish/v1/Systems/1/LogServices/IML/" + filtereddatainstance = self.rdmc.app.get_handler(val, silent=True).dict + try: + filtereddictslists = filtereddatainstance + if not filtereddictslists: + raise NoContentsFoundForOperationError("Unable to retrieve instance.") + # for filtereddict in filtereddictslists: + if filtereddictslists["Name"] == "Integrated Management Log": + if options.clearlog: + if self.rdmc.app.typepath.defs.flagforrest: + linkpath = filtereddictslists["links"] + selfpath = linkpath["self"] + path = selfpath["href"] + elif self.rdmc.app.typepath.defs.isgen9: + path = filtereddictslists[self.rdmc.app.typepath.defs.hrefstring] + else: + actiondict = filtereddictslists["Actions"] + clearkey = [x for x in actiondict if x.endswith("ClearLog")] + path = actiondict[clearkey[0]]["target"] + else: + linkpath = filtereddictslists["links"] if "links" in filtereddictslists else filtereddictslists + dictpath = linkpath["Entries"] + dictpath = dictpath[0] if isinstance(dictpath, list) else dictpath + path = dictpath[self.rdmc.app.typepath.defs.hrefstring] + if not path: + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + except NoContentsFoundForOperationError as excp: + raise excp + except Exception: + self.rdmc.ui.error("No path found for the entry.\n") + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + + if self.rdmc.opts.verbose: + self.rdmc.ui.printer(str(path) + "\n") + + return path + + def returnielpath(self, options=None): + """Return the requested path of the IEL logs + + :param options: command line options + :type options: list. + """ + LOGGER.info("Obtaining IEL path for download.") + path = "" + # val = self.rdmc.app.typepath.defs.logservicetype + # filtereddatainstance = self.rdmc.app.select(selector=val) + val = "/redfish/v1/Managers/1/LogServices/IEL/" + filtereddatainstance = self.rdmc.app.get_handler(val, silent=True).dict + + try: + filtereddictslists = filtereddatainstance + if not filtereddictslists: + raise NoContentsFoundForOperationError("Unable to retrieve instance.") + # for filtereddict in filtereddictslists: + if filtereddictslists["Name"] == "iLO Event Log": + if options.clearlog: + if self.rdmc.app.typepath.defs.flagforrest: + linkpath = filtereddictslists["links"] + selfpath = linkpath["self"] + path = selfpath["href"] + elif self.rdmc.app.typepath.defs.isgen9: + path = filtereddictslists[self.rdmc.app.typepath.defs.hrefstring] + else: + actiondict = filtereddictslists["Actions"] + clearkey = [x for x in actiondict if x.endswith("ClearLog")] + path = actiondict[clearkey[0]]["target"] + else: + linkpath = filtereddictslists["links"] if "links" in filtereddictslists else filtereddictslists + dictpath = linkpath["Entries"] + dictpath = dictpath[0] if isinstance(dictpath, list) else dictpath + path = dictpath[self.rdmc.app.typepath.defs.hrefstring] + + if not path: + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + except NoContentsFoundForOperationError as excp: + raise excp + except Exception: + self.rdmc.ui.error("No path found for the entry.\n") + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + + if self.rdmc.opts.verbose: + self.rdmc.ui.printer(str(path) + "\n") + + return path + + def returnslpath(self, options=None): + """Return the requested path of the SL logs + + :param options: command line options + :type options: list. + """ + + if not self.rdmc.app.typepath.defs.isgen10 or not self.rdmc.app.getiloversion() >= 5.210: + raise IncompatibleiLOVersionError("Security logs are only available on iLO 5 2.10 or" " greater.") + LOGGER.info("Obtaining SL path for download.") + path = "" + # val = self.rdmc.app.typepath.defs.logservicetype + # filtereddatainstance = self.rdmc.app.select(selector=val) + val = "/redfish/v1/Systems/1/LogServices/SL/" + filtereddatainstance = self.rdmc.app.get_handler(val, silent=True).dict + try: + filtereddictslists = filtereddatainstance + if not filtereddictslists: + raise NoContentsFoundForOperationError("Unable to retrieve instance.") + # for filtereddict in filtereddictslists: + if filtereddictslists["Id"] == "SL": + if options.clearlog: + actiondict = filtereddictslists["Actions"] + clearkey = [x for x in actiondict if x.endswith("ClearLog")] + path = actiondict[clearkey[0]]["target"] + else: + dictpath = filtereddictslists["Entries"] + dictpath = dictpath[0] if isinstance(dictpath, list) else dictpath + path = dictpath[self.rdmc.app.typepath.defs.hrefstring] + + if not path: + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + except NoContentsFoundForOperationError as excp: + raise excp + except Exception: + self.rdmc.ui.error("No path found for the entry.\n") + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + + if self.rdmc.opts.verbose: + self.rdmc.ui.printer(str(path) + "\n") + + return path + + def returnahspath(self, options): + """Return the requested path of the AHS logs + + :param options: command line options + :type options: list. + """ + LOGGER.info("Obtaining AHS path for download.") + path = "" + + if options.filename: + raise InvalidCommandLineError( + "AHS logs must be downloaded with " "default name. Please re-run command without filename option." + ) + + # val = self.rdmc.app.typepath.defs.hpiloactivehealthsystemtype + # filtereddatainstance = self.rdmc.app.select(selector=val) + val = "/redfish/v1/Managers/1/ActiveHealthSystem/" + filtereddatainstance = self.rdmc.app.get_handler(val, silent=True).dict + + try: + filtereddictslists = filtereddatainstance + + if not filtereddictslists: + raise NoContentsFoundForOperationError("Unable to retrieve log instance.") + + # for filtereddict in filtereddictslists: + if options.clearlog: + if self.rdmc.app.typepath.defs.flagforrest: + linkpath = filtereddictslists["links"] + selfpath = linkpath["self"] + path = selfpath["href"] + elif self.rdmc.app.typepath.defs.isgen9: + path = filtereddictslists[self.rdmc.app.typepath.defs.hrefstring] + else: + actiondict = filtereddictslists["Actions"] + clearkey = [x for x in actiondict if x.endswith("ClearLog")] + path = actiondict[clearkey[0]]["target"] + else: + linkpath = filtereddictslists["links"] if "links" in filtereddictslists else filtereddictslists["Links"] + + ahslocpath = linkpath["AHSLocation"] + path = ahslocpath["extref"] + weekpath = None + + if options.downloadallahs: + path = path + elif options.customiseAHS: + custr = options.customiseAHS + if custr.startswith(("'", '"')) and custr.endswith(("'", '"')): + custr = custr[1:-1] + + if custr.startswith("from="): + path = path.split("downloadAll=1")[0] + + path = path + custr + else: + if "RecentWeek" in list(linkpath.keys()): + weekpath = linkpath["RecentWeek"]["extref"] + elif "AHSFileStart" in list(filtereddictslists.keys()): + enddate = filtereddictslists["AHSFileEnd"].split("T")[0] + startdate = filtereddictslists["AHSFileStart"].split("T")[0] + + enddat = list(map(int, enddate.split("-"))) + startdat = list(map(int, startdate.split("-"))) + + weekago = datetime.datetime.now() - datetime.timedelta(days=6) + weekagostr = list(map(int, (str(weekago).split()[0]).split("-"))) + + strdate = min( + max( + datetime.date(weekagostr[0], weekagostr[1], weekagostr[2]), + datetime.date(startdat[0], startdat[1], startdat[2]), + ), + datetime.date(enddat[0], enddat[1], enddat[2]), + ) + + aweekstr = "from=" + str(strdate) + "&&to=" + enddate + else: + week_ago = datetime.datetime.now() - datetime.timedelta(days=6) + aweekstr = ( + "from=" + str(week_ago).split()[0] + "&&to=" + str(datetime.datetime.now()).split()[0] + ) + + path = path.split("downloadAll=1")[0] + path = weekpath if weekpath else path + aweekstr + + if not path: + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + except NoContentsFoundForOperationError as excp: + raise excp + except Exception: + self.rdmc.ui.error("No path found for the entry.\n") + raise NoContentsFoundForOperationError("Unable to retrieve logs.") + + if self.rdmc.opts.verbose: + self.rdmc.ui.printer(str(path) + "\n") + + return path + + def savedata(self, options=None, data=None): + """Save logs into the specified filename + + :param options: command line options + :type options: list. + :param data: log data + :type data: dict + """ + LOGGER.info("Saving/Writing data...") + if data: + data = self.filterdata(data=data, tofilter=options.filter) + if options.service == "AHS": + filename = self.getahsfilename(options) + + with open(filename, "wb") as foutput: + foutput.write(data) + elif options.filename: + with open(options.filename[0], "w") as foutput: + if options.json: + foutput.write(str(json.dumps(data, indent=2, sort_keys=True))) + else: + foutput.write(str(json.dumps(data))) + else: + if options.json: + UI().print_out_json(data) + else: + UI().print_out_human_readable(data) + + def downloadahslocally(self, options=None): + """Download AHS logs locally + + :param options: command line options + :type options: list. + """ + try: + self.downloadahslocalworker(options) + except: + self.unmountbb() + raise + return + + def downloadahslocalworker(self, options): + """Worker function to download AHS logs locally + + :param options: command line options + :type options: list. + """ + self.rdmc.ui.printer("Downloading AHS logs. Please wait...\n") + LOGGER.info("Downloading AHS via Local Chif...") + self.dontunmount = True + + if self.rdmc.app.typepath.ilogen and self.rdmc.app.typepath.ilogen < 4: + raise IncompatibleiLOVersionError("Need at least iLO 4 for this program to run!\n") + + if sys.platform == "darwin": + raise InvalidCommandLineError("AHS local download is not supported on MacOS") + elif "VMkernel" in platform.uname(): + raise InvalidCommandLineError("AHS local download is not supported on VMWare") + + if options.filename: + raise InvalidCommandLineError( + "AHS logs must be downloaded with default name! Re-run command without filename!" + ) + + secstate = risblobstore2.BlobStore2(log_dir=self.rdmc.log_dir).get_security_state() + + if isinstance(secstate, bytes): + secstate = secstate.decode("utf-8") + if isinstance(secstate, str): + secstate = secstate.replace("\x00", "").strip() + if secstate == "": + secstate = 0 + LOGGER.info("Security State is {}...".format(secstate)) + if int(secstate) > 3: + raise SecurityStateError("AHS logs cannot be downloaded locally in high security state.\n") + + self.lib = risblobstore2.BlobStore2.gethprestchifhandle() + + LOGGER.info("Mounting AHS partition...") + + try: + (manual_ovr, abspath) = self.getbbabspath() + except (PartitionMoutingError, IOError): + ret = self.mountbb() + if ret: + LOGGER.info("Successfully exposed the blackbox partition via chif") + else: + LOGGER.error("Could not expose the blackbox partition via chif") + (manual_ovr, abspath) = self.getbbabspath() + self.dontunmount = False + + LOGGER.info("Blackbox folder path:%s", ",".join(next(os.walk(abspath))[2])) + if "data" not in abspath: + self.abspath = os.path.join(abspath, "data") + LOGGER.info("Blackbox data files path:%s", self.abspath) + + if self.rdmc.app.typepath.ilogen: + self.updateiloversion() + try: + cfilelist = self.getclistfilelisting() + allfiles = self.getfilenames(options=options, cfilelist=cfilelist) + self.getdatfilelisting(cfilelist=cfilelist, allfile=allfiles) + ahsfile = self.getahsfilename(options) + self.createahsfile(ahsfile=ahsfile) + self.rdmc.ui.printer("Successfully downloaded AHS logs {}\n".format(ahsfile)) + except Exception as excp: + raise PartitionMoutingError( + "An exception occurred obtaining Blackbox data files. The directory may be empty: %s.\n" % excp + ) + LOGGER.info("Unmounting AHS partition...\n") + if not manual_ovr: + self.unmountbb() + else: + self.unmountbb() + self.manualunmountbb(abspath) + LOGGER.info("Successfully unmounted AHS partition.\n") + + def updateiloversion(self): + """Update iloversion to create appropriate headers.""" + LOGGER.info("Updating iloversion to format data appropriately") + self.lib.updateiloversion.argtypes = [ctypes.c_float] + self.lib.updateiloversion(float("2." + str(self.rdmc.app.typepath.ilogen))) + + def createahsfile(self, ahsfile=None): + """Create the AHS file + + :param ahsfile: ahsfilename + :type ahsfile: str + """ + LOGGER.info("Creating AHS file from the formatted data.") + self.clearahsfile(ahsfile=ahsfile) + self.lib.setAHSFilepath.argtypes = [ctypes.c_char_p] + self.lib.setAHSFilepath(os.path.abspath(ahsfile).encode("utf-8", "ignore")) + self.lib.setBBdatapath.argtypes = [ctypes.c_char_p] + self.lib.setBBdatapath(self.abspath.encode("utf-8", "ignore")) + self.lib.createAHSLogFile_G9() + + def clearahsfile(self, ahsfile=None): + """Clear the ahslog file if already present in filesystem + + :param ahsfile: ahsfilename + :type ahsfile: str + """ + LOGGER.info("Clear redundant AHS file in current folder.") + try: + os.remove(ahsfile) + except: + pass + + def getdatfilelisting(self, cfilelist=None, allfile=None): + """Create headers based on the AHS log files within blackbox + + :param cfilelist: configuration files in blackbox + :type cfilelist: list of strings + :param allfile: all files within blackbox + :type allfile: list + """ + LOGGER.info("Reading Blackbox to determine data files.") + allfile = list(set(allfile) | set(cfilelist)) + LOGGER.info("Final filelist %s", str(allfile)) + for files in allfile: + if files.startswith((".", "..")): + continue + + bisrequiredfile = False + + if files.split(".")[0] in [x.split(".")[0] for x in cfilelist]: + if files.endswith("bb"): + bisrequiredfile = True + self.lib.updatenfileoptions() + + self.lib.gendatlisting.argtypes = [ + ctypes.c_char_p, + ctypes.c_bool, + ctypes.c_uint, + ] + + filesize = os.stat(os.path.join(self.abspath, files)).st_size + self.lib.gendatlisting(files.encode("ascii", "ignore"), bisrequiredfile, filesize) + + def getfilenames(self, options=None, cfilelist=None): + """Get all file names from the blackbox directory.""" + LOGGER.info("Obtaining all relevant file names from Blackbox.") + datelist = list() + allfiles = list() + + filenames = next(os.walk(self.abspath))[2] + timenow = (str(datetime.datetime.now()).split()[0]).split("-") + strdate = enddate = datetime.date(int(timenow[0]), int(timenow[1]), int(timenow[2])) + + if not options.downloadallahs and not options.customiseAHS: + weekago = datetime.datetime.now() - datetime.timedelta(days=6) + weekagostr = (str(weekago).split()[0]).split("-") + strdate = datetime.date(int(weekagostr[0]), int(weekagostr[1]), int(weekagostr[2])) + + if options.customiseAHS: + instring = options.customiseAHS + if instring.startswith(("'", '"')) and instring.endswith(("'", '"')): + instring = instring[1:-1] + try: + (strdatestr, enddatestr) = [e.split("-") for e in instring.split("from=")[-1].split("&&to=")] + strdate = datetime.date(int(strdatestr[0]), int(strdatestr[1]), int(strdatestr[2])) + enddate = datetime.date(int(enddatestr[0]), int(enddatestr[1]), int(enddatestr[2])) + except Exception as excp: + LOGGER.warning(excp) + raise InvalidCommandLineError("Cannot parse customised AHSinput.") + + atleastonefile = False + for files in list(filenames): + if not files.endswith("bb"): + # Check the logic for the non bb files + allfiles.append(files) + continue + + if options.downloadallahs: + atleastonefile = True + if files in ("ilo_boot_support.zbb", "sys_boot_support.zbb"): + allfiles.append(files) + filenames.append(files) + LOGGER.info("%s, number of files%s", files, len(filenames)) + continue + filenoext = files.rsplit(".", 1)[0] + filesplit = filenoext.split("-") + + try: + newdate = datetime.date(int(filesplit[1]), int(filesplit[2]), int(filesplit[3])) + datelist.append(newdate) + + if (strdate <= newdate) and (newdate <= enddate) and not options.downloadallahs: + allfiles.append(files) + atleastonefile = True + except: + pass + + _ = [cfilelist.remove(fil) for fil in list(cfilelist) if fil not in filenames] + + if options.customiseAHS: + for files in reversed(cfilelist): + try: + filenoext = files.rsplit(".", 1)[0] + filesplit = filenoext.split("-") + newdate = datetime.date(int(filesplit[1]), int(filesplit[2]), int(filesplit[3])) + if not ((strdate <= newdate) and (newdate <= enddate)): + cfilelist.remove(files) + except: + pass + + if options.downloadallahs: + strdate = min(datelist) if datelist else strdate + enddate = max(datelist) if datelist else enddate + else: + strdate = max(min(datelist), strdate) if datelist else strdate + enddate = min(max(datelist), enddate) if datelist else enddate + + LOGGER.info("All filenames: %s; Download files: %s", str(filenames), str(allfiles)) + + if atleastonefile: + self.updateminmaxdate(strdate=strdate, enddate=enddate) + return filenames if options.downloadallahs else allfiles + else: + raise NoContentsFoundForOperationError("No AHS log files found.") + + def updateminmaxdate(self, strdate=None, enddate=None): + """Get the minimum and maximum date of files into header + + :param strdate: starting date of ahs logs + :type strdate: dateime obj + :param enddate: ending date of ahs logs + :type enddate: datetime obj + """ + LOGGER.info("Updating min and max dates for download.") + self.lib.updateMinDate.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] + self.lib.updateMinDate(strdate.year, strdate.month, strdate.day) + self.lib.updateMaxDate.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] + self.lib.updateMaxDate(enddate.year, enddate.month, enddate.day) + + def getclistfilelisting(self): + """Get files present within clist.pkg .""" + LOGGER.info("Getting all config files that are required.") + sclistpath = os.path.join(self.abspath, "clist.pkg") + cfilelist = [] + if os.path.isfile(sclistpath): + cfile = open(sclistpath, "rb") + data = cfile.read() + + if data == "": + raise InvalidCListFileError("Could not read Cfile\n") + + sizeofcfile = len(str(data)) + sizeofrecord = self.lib.sizeofchifbbfilecfgrecord() + count = sizeofcfile / sizeofrecord + revcount = 0 + + while count >= 1: + dat = data[revcount * sizeofrecord : (revcount + 1) * sizeofrecord] + dat = ctypes.create_string_buffer(dat) + self.lib.getbbfilecfgrecordname.argtypes = [ctypes.c_char_p] + self.lib.getbbfilecfgrecordname.restype = ctypes.c_char_p + ptrname = self.lib.getbbfilecfgrecordname(dat) + name = str(bytearray(ptrname[:32][:])) + + if name not in cfilelist: + cfilelist.append(name) + + count = count - 1 + revcount = revcount + 1 + else: + cfilelist = [f for f in os.listdir(self.abspath) if f.endswith(".zbb")] + + LOGGER.info("CLIST files %s", str(cfilelist)) + return cfilelist + + def getbbabspath(self): + """Get blackbox folder path.""" + LOGGER.info("Obtaining the absolute path of blackbox.") + count = 0 + + while count < 10: + if os.name == "nt": + drives = self.get_available_drives() + + for i in drives: + try: + label = win32api.GetVolumeInformation(i + ":")[0] + + if label == "BLACKBOX": + abspathbb = i + ":\\data\\" + self.abspath = abspathbb + cfilelist = self.getclistfilelisting() + if not cfilelist: + self.unmountbb() + self.manualmountbb() + else: + return False, abspathbb + except: + pass + else: + with open("/proc/mounts", "r") as fmount: + while True: + lin = fmount.readline() + + if len(lin.strip()) == 0: + break + + if r"/BLACKBOX" in lin: + abspathbb = lin.split()[1] + self.abspath = abspathbb + cfilelist = self.getclistfilelisting() + if not cfilelist: + self.unmountbb() + self.manualmountbb() + else: + return False, abspathbb + + if count > 5: + found, path = self.manualmountbb() + if found: + return True, path + + count = count + 1 + time.sleep(2) + + raise PartitionMoutingError("Failed to find BLACKBOX mount location in OS") + + def manualmountbb(self): + """Manually mount blackbox when after fixed time.""" + + try: + context = pyudev.Context() + for device in context.list_devices(subsystem="block", DEVTYPE='partition'): + if device.get("ID_FS_LABEL") == "BLACKBOX" or "iLO" in device.parent.get("ID_VENDOR"): + dirpath = os.path.join(tempfile.gettempdir(), "BLACKBOX") + + if not os.path.exists(dirpath): + try: + os.makedirs(dirpath) + except Exception as excp: + raise excp + + pmount = subprocess.Popen( + ["mount", device.device_node, dirpath], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + _, _ = pmount.communicate() + + LOGGER.info("Successfully mounted the blackbox partition to temp/BLACKBOX folder.") + return True, dirpath + except UnicodeDecodeError as excp: + self.unmountbb() + raise UnabletoFindDriveError(excp) + + return False, None + + def manualunmountbb(self, dirpath): + """Manually unmount blackbox when after fixed time + + :param dirpath: mounted directory path + :type dirpath: str + """ + LOGGER.info("Unmounting the blackbox partition in temp/BLACKBOX folder.") + pmount = subprocess.Popen(["umount", dirpath], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + _, _ = pmount.communicate() + + def mountbb(self): + """Mount blackbox.""" + LOGGER.info("Making blackbox available for OS...") + bs2 = risblobstore2.BlobStore2(log_dir=self.rdmc.log_dir) + count = 0 + mount_flag = False + while count < 3: + resp = bs2.mount_blackbox() + if resp[12] == 0: + mount_flag = True + LOGGER.info("Blackbox is available to OS now.") + break + time.sleep(30) + count = count + 1 + if not mount_flag: + LOGGER.error("Blackbox not available to OS, error = " + resp) + bs2.channel.close() + return mount_flag + + def unmountbb(self): + """Unmount blacbox.""" + if not self.dontunmount: + bs2 = risblobstore2.BlobStore2(log_dir=self.rdmc.log_dir) + bs2.bb_media_unmount() + LOGGER.info("Blackbox is removed and not available to OS now.") + bs2.channel.close() + + def get_available_drives(self): + """Obtain all drives""" + LOGGER.info("Getting available drives...") + if "Windows" not in platform.system(): + return [] + + drive_bitmask = ctypes.cdll.kernel32.GetLogicalDrives() + + return list( + itertools.compress( + string.ascii_uppercase, + [ord(drive) - ord("0") for drive in bin(drive_bitmask)[:1:-1]], + ) + ) + + def filterdata(self, data=None, tofilter=None): + """Filter the logs + + :param data: log data + :type data: dict + :param tofilter: command line filter option + :type tofilter: str + """ + LOGGER.info("Filtering logs based on requsted options.") + if tofilter and data: + try: + if (str(tofilter)[0] == str(tofilter)[-1]) and str(tofilter).startswith(("'", '"')): + tofilter = tofilter[1:-1] + + (sel, val) = tofilter.split("=") + sel = sel.strip() + val = val.strip() + + if val.lower() == "true" or val.lower() == "false": + val = val.lower() in ("yes", "true", "t", "1") + except: + raise InvalidCommandLineError("Invalid filter" " parameter format [filter_attribute]=[filter_value]") + + # Severity inside Oem/Hpe should be filtered + if sel == "Severity": + if not self.rdmc.app.typepath.defs.isgen9: + sel = "Oem/Hpe/" + sel + else: + sel = "/" + sel + data = filter_output(data, sel, val) + if not data: + raise NoContentsFoundForOperationError("Filter returned no matches.") + + return data + + def getahsfilename(self, options): + """Create a default name if no ahsfilename is passed + + :param options: command line options + :type options: list. + """ + LOGGER.info("Obtaining Serialnumber from iLO for AHS filename.") + + if not options.sessionid: + val = "ComputerSystem." + filtereddatainstance = self.rdmc.app.select(selector=val) + snum = None + + try: + filtereddictslists = [x.resp.dict for x in filtereddatainstance] + + if not filtereddictslists: + raise NoContentsFoundForOperationError("") + except Exception: + try: + resp = self.rdmc.app.get_handler( + self.rdmc.app.typepath.defs.systempath, + silent=True, + service=True, + uncache=True, + ) + snum = resp.dict["SerialNumber"] if resp else snum + except KeyError: + raise InvalidKeyError( + "Unable to find key SerialNumber, please check path %s" % self.rdmc.app.typepath.defs.systempath + ) + except: + raise NoContentsFoundForOperationError("Unable to retrieve log instance.") + + snum = filtereddictslists[0]["SerialNumber"] if not snum else snum + else: + resp = self.rdmc.app.get_handler( + "/redfish/v1/systems/1", + sessionid=options.sessionid, + silent=True, + service=True, + uncache=True, + ) + if "SerialNumber" in resp.dict: + snum = resp.dict["SerialNumber"] if resp else snum + snum = "UNKNOWN" if snum.isspace() else snum + timenow = (str(datetime.datetime.now()).split()[0]).split("-") + todaysdate = "".join(timenow) + + ahsdefaultfilename = "HPE_" + snum + "_" + todaysdate + ".ahs" + + if options.directorypath: + dir_name = options.directorypath + dir_name = dir_name.encode("utf-8").decode("utf-8") + if not os.path.exists(dir_name): + os.makedirs(dir_name) + ahsdefaultfilename = os.path.join(dir_name, ahsdefaultfilename) + + return ahsdefaultfilename + + def serverlogsvalidation(self, options): + """Serverlogs method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-f", + "--filename", + dest="filename", + help="Use this flag if you wish to use a different" + " filename than the default one. The default filename is" + " ilorest.json.", + action="append", + default=None, + ) + customparser.add_argument( + "--filter", + dest="filter", + help="Optionally set a filter value for a filter attribute." + " This uses the provided filter for the currently selected" + " type. Note: Use this flag to narrow down your results. For" + " example, selecting a common type might return multiple" + " objects that are all of that type. If you want to modify" + " the properties of only one of those objects, use the filter" + " flag to narrow down results based on properties." + "\t\t\t\t\t Usage: --filter [ATTRIBUTE]=[VALUE]", + default=None, + ) + customparser.add_argument( + "--selectlog", + dest="service", + help="""Read log from the given log service. Options: IML, """ """IEL or AHS.""", + default=None, + ) + customparser.add_argument( + "--clearlog", + "-c", + dest="clearlog", + action="store_true", + help="""Clears the logs for a the selected option.""", + default=None, + ) + customparser.add_argument( + "--maintenancemessage", + "-m", + dest="mainmes", + help="""Maintenance message to be inserted into the log. """ """(IML LOGS ONLY FEATURE)""", + default=None, + ) + customparser.add_argument( + "--customiseAHS", + dest="customiseAHS", + help="""Allows customised AHS log data to be downloaded.""", + default=None, + ) + customparser.add_argument( + "--downloadallahs", + dest="downloadallahs", + action="store_true", + help="""Allows complete AHS log data to be downloaded.""", + default=None, + ) + customparser.add_argument( + "--directorypath", + dest="directorypath", + help="""Directory path for the ahs file.""", + default=None, + ) + customparser.add_argument( + "--mpfile", + dest="mpfilename", + help="""use the provided filename to obtain server information.""", + default=None, + ) + customparser.add_argument( + "--outputdirectory", + dest="outdirectory", + help="""use the provided directory to output data for multiple server downloads.""", + default=None, + ) + customparser.add_argument( + "--mplog", + dest="mplog", + help="""used to indicate the logs to be downloaded on multiple servers. """ + """Allowable values: IEL, IML, AHS, all or combination of any two.""", + default=None, + ) + customparser.add_argument( + "--repair", + "-r", + dest="repiml", + help="""Repair the IML logs with the given ID.""", + default=None, + ) + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) diff --git a/ilorest/extensions/iLO_COMMANDS/SetTwoFactorAuthenticationCommand.py b/ilorest/extensions/iLO_COMMANDS/SetTwoFactorAuthenticationCommand.py new file mode 100644 index 0000000..d11f998 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/SetTwoFactorAuthenticationCommand.py @@ -0,0 +1,378 @@ +### +# Copyright 2021-2023 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 -*- +""" TwoFactorAuthentication Command for rdmc """ + +from argparse import RawDescriptionHelpFormatter + +from redfish.ris import IdTokenError +from redfish.ris.rmc_helper import IloResponseError + +try: + from rdmc_helper import ( + IloLicenseError, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoCurrentSessionEstablished, + ReturnCodes, + TfaEnablePreRequisiteError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IloLicenseError, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoCurrentSessionEstablished, + ReturnCodes, + TfaEnablePreRequisiteError, + ) + +from redfish.ris.ris import SessionExpired + + +class SetTwoFactorAuthenticationCommand: + """Main new command template class""" + + def __init__(self): + self.ident = { + "name": "settwofactorauthentication", + "usage": "settwofactorauthentication\n\n", + "description": "Run to enable ,disable ,get status or setup smtp settings for Two factor " + "authentication\n\t" + "Example:\n\tsettwofactorauthentication enable or \n\t" + "settwofactorauthentication disable \n\t" + "settwofactorauthentication status or \n\t" + "settwofactorauthentication status -j\n" + "settwofactorauthentication smtp --smtpfortfaenabled true --alertmailsenderdomain 'test@hpe.com' " + "--alertmailsmtpserver 'smtp3.hpe.com' --alertmailsmtpport 587\n", + "summary": "Enables the server to use Two factor authentication, monitored", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def twofactorauth(self, options): + """two authentication function + + :param enable\disable + """ + + # pre requisite checks + + path = self.rdmc.app.typepath.defs.managerpath + "NetworkProtocol" + resp = self.rdmc.app.get_handler(path, service=False, silent=True) + + status = resp.dict["Oem"]["Hpe"]["SMTPForTFAEnabled"] + if not status: + self.rdmc.ui.error("SMTP for TFA is not enabled\n") + raise TfaEnablePreRequisiteError( + "\nTo be able to enable\disable TFA\nKindly enable and set the SMTP details prior to enabling TFA " + "for the server . You can use our command as given below for enabling smtp for tfa \n\n" + "SetTwoFactorAuthentication smtp --smtpfortfaenabled true --alertmailsenderdomain 'testuser@test.com' " + "--alertmailsmtpserver 'smtp.server.com' \n" + ) + + results = self.rdmc.app.select(selector="AccountService.", path_refresh=True)[0].dict + path = results[self.rdmc.app.typepath.defs.hrefstring] + + # check whether default schema is selected authentication tyep + resp = self.rdmc.app.get_handler(path, service=False, silent=True) + if not resp.dict["LDAP"]["ServiceEnabled"]: + self.rdmc.ui.error("Ldap service is not enabled\n") + raise TfaEnablePreRequisiteError( + "\nTo be able to enable\disable TFA\nKindly enable LDAP Directory Authentication to use Directory " + "Default Schema. Same can be set using below command\n\ndirectory ldap enable " + "--authentication=DefaultSchema\n" + ) + + body = dict() + if options.command.lower() == "enable": + serviceEnabled = "Enabled" + else: + serviceEnabled = "Disabled" + body.update({"Oem": {"Hpe": {"TwoFactorAuth": serviceEnabled}}}) + + try: + self.rdmc.app.patch_handler(path, body) + return ReturnCodes.SUCCESS + except IloLicenseError: + self.rdmc.ui.error("Error Occured while enable\disable operation for TFA") + return ReturnCodes.ILO_LICENSE_ERROR + except IdTokenError: + self.rdmc.ui.error("Insufficient Privilege to enable \ disable TFA ") + return ReturnCodes.RIS_MISSING_ID_TOKEN + except IloResponseError: + self.rdmc.ui.error("Provided values are invalid , iLO threw error") + return ReturnCodes.RIS_ILO_RESPONSE_ERROR + + def twofactorauth_smtp(self, options): + """two authentication smtp setup function + + :param smtpfortfaenabled : true\false + :type smtpfortfaenabled: str. + """ + path = self.rdmc.app.typepath.defs.managerpath + "NetworkProtocol" + resp_get = self.rdmc.app.get_handler(path, service=False, silent=True) + body = dict() + range1 = range(1, 65536, 1) + if options.SMTPPort is not None: + if options.SMTPPort in range1: + SMTPPort = options.SMTPPort + else: + raise InvalidCommandLineError("Enter correct server port value in the range 1 to 65535") + else: + SMTPPort = 25 + + if options.SMTPForTFA[0].lower() == "true": + SMTPForTFAEnabled = True + else: + SMTPForTFAEnabled = False + + if options.SenderDomain is None: + if resp_get.dict["Oem"]["Hpe"]["AlertMailSenderDomain"] is not None: + SenderDomain = resp_get.dict["Oem"]["Hpe"]["AlertMailSenderDomain"] + else: + SenderDomain= "" + else: + SenderDomain = options.SenderDomain[0] + + if options.SMTPServer is None: + if resp_get.dict["Oem"]["Hpe"]["AlertMailSMTPServer"] is not None: + SMTPServer = resp_get.dict["Oem"]["Hpe"]["AlertMailSMTPServer"] + else: + SMTPServer = "" + else: + SMTPServer = options.SMTPServer[0] + + body.update( + { + "Oem": { + "Hpe": { + "SMTPForTFAEnabled": SMTPForTFAEnabled, + "AlertMailSenderDomain": SenderDomain, + "AlertMailSMTPServer": SMTPServer, + "AlertMailSMTPPort": SMTPPort, + } + } + } + ) + try: + self.rdmc.app.patch_handler(path, body) + return ReturnCodes.SUCCESS + + except IloResponseError: + self.rdmc.ui.error("Provided values are invalid , iLO threw error") + return ReturnCodes.RIS_ILO_RESPONSE_ERROR + except IloLicenseError: + self.rdmc.ui.error("Error Occured while setting smtp settings of TFA ") + return ReturnCodes.ILO_LICENSE_ERROR + except IdTokenError: + self.rdmc.ui.error("Insufficient Privilege to setting smtp settings of TFA ") + return ReturnCodes.RIS_MISSING_ID_TOKEN + + def twofactorauth_status(self, json=False): + """two authentication status function + + :param json: json + :type json: bool + """ + results = self.rdmc.app.select(selector="AccountService.", path_refresh=True)[0].dict + path = results[self.rdmc.app.typepath.defs.hrefstring] + resp = self.rdmc.app.get_handler(path, service=False, silent=True) + if resp.status != 200: + self.rdmc.ui.error("session seems to be expired\n") + raise SessionExpired("Invalid session. Please logout and log back in or include credentials.") + tfa_info = resp.dict["Oem"]["Hpe"] + output = "------------------------------------------------\n" + output += "TFA Status : %s\n" % (tfa_info["TwoFactorAuth"]) + if not json: + self.rdmc.ui.printer(output, verbose_override=True) + else: + self.rdmc.ui.printer("\n--- TFA status ---\n") + self.rdmc.ui.print_out_json(tfa_info) + + path = self.rdmc.app.typepath.defs.managerpath + "NetworkProtocol" + resp = self.rdmc.app.get_handler(path, service=False, silent=True) + if resp.status != 200: + raise SessionExpired("Invalid session. Please logout and log back in or include credentials.") + smtp_info = resp.dict["Oem"]["Hpe"] + output = "\n------------------------------------------------\n" + output += "SMTP for TFA Status : %s\n" % (smtp_info["SMTPForTFAEnabled"]) + if not json: + self.rdmc.ui.printer(output, verbose_override=True) + else: + self.rdmc.ui.printer("\n--- SMTP for TFA status ---\n") + self.rdmc.ui.print_out_json(smtp_info) + + return ReturnCodes.SUCCESS + + def run(self, line, help_disp=False): + """Wrapper function for two factor authentication main function + + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.cmdbase.login_select_validation(self, options) + if not self.rdmc.app.redfishinst: + raise NoCurrentSessionEstablished("Please login to iLO and retry the command") + + ilo_ver = self.rdmc.app.getiloversion() + if ilo_ver < 5.290 or (ilo_ver < 6.150 and ilo_ver > 5.999): + raise IncompatibleiLOVersionError( + "TFA Feature is only available with iLO 5 version 2.90 or higher and iLO 6 version 1.50 or higher.\n" + ) + + # validation checks + self.twofactorauthenticationvalidation(options) + if options.command: + if options.command.lower() == "enable": + returncode = self.twofactorauth(options) + elif options.command.lower() == "disable": + returncode = self.twofactorauth(options) + elif options.command.lower() == "smtp": + returncode = self.twofactorauth_smtp(options) + elif options.command.lower() == "status": + if options.json: + returncode = self.twofactorauth_status(json=True) + else: + returncode = self.twofactorauth_status() + else: + raise InvalidCommandLineError("%s is not a valid option for this " "command." % str(options.command)) + else: + raise InvalidCommandLineError( + "Please provide either enable, disable , status or smtp as additional subcommand. " + "For help or usage related information, use -h or --help" + ) + # logout routine + self.cmdbase.logout_routine(self, options) + # Return code + return returncode + + def twofactorauthenticationvalidation(self, options): + """new command method validation function""" + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + subcommand_parser = customparser.add_subparsers(dest="command") + enable_help = "To enable two factor authentication\n" + # connect sub-parser + enable_parser = subcommand_parser.add_parser( + "enable", + help=enable_help, + description=enable_help + "\n\tExample:\n\tsettwofactorauthentication enable ", + formatter_class=RawDescriptionHelpFormatter, + ) + + self.cmdbase.add_login_arguments_group(enable_parser) + + status_help = "To check the settwofactorauthentication status\n" + status_parser = subcommand_parser.add_parser( + "status", + help=status_help, + description=status_help + "\n\tExample:\n\tsettwofactorauthentication status or " + "\n\tsettwofactorauthentication status -j", + formatter_class=RawDescriptionHelpFormatter, + ) + status_parser.add_argument( + "-j", + "--json", + dest="json", + help="to print in json format", + action="store_true", + default=False, + ) + self.cmdbase.add_login_arguments_group(status_parser) + + disable_help = "To disable the two factor authentication\n" + disable_parser = subcommand_parser.add_parser( + "disable", + help=disable_help, + description=disable_help + "\n\tExample:\n\tsettwofactorauthentication disable", + formatter_class=RawDescriptionHelpFormatter, + ) + self.cmdbase.add_login_arguments_group(disable_parser) + + emailconfig_help = "To set the smtp setting for settwofactorauthentication\n" + emailconfig_parser = subcommand_parser.add_parser( + "smtp", + help=emailconfig_help, + description=emailconfig_help + + "\n\tExample:\n\tsettwofactorauthentication smtp --smtpfortfaenabled true --alertmailsenderdomain " + "'test@hpe.com' --alertmailsmtpserver 'smtp3.hpe.com' --alertmailsmtpport 587 ", + formatter_class=RawDescriptionHelpFormatter, + ) + emailconfig_parser.add_argument( + "--smtpfortfaenabled", + dest="SMTPForTFA", + help="smtp service enable , supply either true or false as input. " + "If true then remaining inputs cant be null or empty", + action="append", + default=None, + ) + emailconfig_parser.add_argument( + "--alertmailsenderdomain", + dest="SenderDomain", + action="append", + help="smtp user name , supply a valid aduser for this field", + default=None, + ) + emailconfig_parser.add_argument( + "--alertmailsmtpserver", + dest="SMTPServer", + action="append", + help="smtp server , supply a valid smtp server for this parameter", + default=None, + ) + + emailconfig_parser.add_argument( + "--alertmailsmtpport", + dest="SMTPPort", + type=int, + help="smtp port , supply a valid smtp port for this parameter, default is 25", + default=25, + ) + + self.cmdbase.add_login_arguments_group(emailconfig_parser) diff --git a/ilorest/extensions/iLO_COMMANDS/SigRecomputeCommand.py b/ilorest/extensions/iLO_COMMANDS/SigRecomputeCommand.py new file mode 100644 index 0000000..3b57296 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/SigRecomputeCommand.py @@ -0,0 +1,107 @@ +### +# Copyright 2016-2021 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 -*- +""" SigRecompute Command for rdmc """ + +try: + from rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class SigRecomputeCommand: + """Recalculate the signature of the servers configuration""" + + def __init__(self): + self.ident = { + "name": "sigrecompute", + "usage": None, + "description": "Recalculate the signature on " + "the computers configuration.\n\texample: sigrecompute\n\n" + "\tNote: sigrecompute command is not available on Redfish systems.", + "summary": "Command to recalculate the signature of the computer's " "configuration.", + "aliases": [], + "auxcommands": [], + } + + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main sigrecompute function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if args: + raise InvalidCommandLineError("Sigrecompute command takes no arguments.\n") + + self.sigrecomputevalidation(options) + path = self.rdmc.app.typepath.defs.systempath + + if self.rdmc.app.typepath.defs.flagforrest: + body = {"Action": "ServerSigRecompute", "Target": "/Oem/Hp"} + self.rdmc.app.post_handler(path, body) + else: + raise IncompatibleiLOVersionError("Sigrecompute action not available on redfish.\n") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def sigrecomputevalidation(self, options): + """sigrecomputevalidation method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) diff --git a/ilorest/extensions/iLO_COMMANDS/SingleSignOnCommand.py b/ilorest/extensions/iLO_COMMANDS/SingleSignOnCommand.py new file mode 100644 index 0000000..5367c1e --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/SingleSignOnCommand.py @@ -0,0 +1,210 @@ +### +# Copyright 2016-2021 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 -*- +""" Single Sign On Command for rdmc """ +from argparse import RawDescriptionHelpFormatter + +try: + from rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + + +class SingleSignOnCommand: + """Commands Single Sign On actions to the server""" + + def __init__(self): + self.ident = { + "name": "singlesignon", + "usage": None, + "description": "Add or remove single sign on (SSO) records.\nTo view help on specific " + "sub-commands run: singlesignon -h\n\nExample: singlesignon " + "importcert -h\n\n", + "summary": "Command for all single sign on available actions. ", + "aliases": ["sso"], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main SingleSignOnCommand function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.singlesignonvalidation(options) + + actionitem = None + select = self.rdmc.app.typepath.defs.hpilossotype + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + path = results.resp.request.path + else: + raise NoContentsFoundForOperationError("%s not found." % select) + + bodydict = results.resp.dict + + if not options.command: + return ReturnCodes.SUCCESS + + if options.command.lower() == "importdns": + actionitem = "ImportDNSName" + body = {"Action": actionitem, "DNSName": options.dnsname} + elif options.command.lower() == "importcert": + cert = None + certtype = None + actionitem = "ImportCertificate" + + try: + cert = open(options.cert, "r") + + if cert: + certtext = cert.read() + cert.close() + + if certtext: + certtype = "DirectImportCert" + except: + pass + + if not certtype: + certtype = "ImportCertUri" + certtext = options.cert + + body = {"Action": actionitem, "CertType": certtype, "CertInput": certtext} + + elif options.command.lower() == "deleterecord": + if options.record.lower() == "all": + actionitem = "DeleteAllSSORecords" + body = {"Action": actionitem} + else: + actionitem = "DeleteSSORecordbyNumber" + + try: + body = {"Action": actionitem, "RecordNumber": int(options.record)} + except: + raise InvalidCommandLineError("Record to delete must be a number") + + try: + for item in bodydict["Actions"]: + if actionitem in item: + if self.rdmc.app.typepath.defs.isgen10: + actionitem = item.split("#")[-1] + body["Action"] = actionitem + + path = bodydict["Actions"][item]["target"] + break + except: + pass + + self.rdmc.app.post_handler(path, body) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def singlesignonvalidation(self, options): + """single sign on validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + subcommand_parser = customparser.add_subparsers(dest="command") + save_import_dns_help = "Import a DNS name." + # importdns sub-parser + import_dns_parser = subcommand_parser.add_parser( + "importdns", + help=save_import_dns_help, + description=save_import_dns_help + "\n\texample singlesignon importdns dnsname", + formatter_class=RawDescriptionHelpFormatter, + ) + import_dns_parser.add_argument("dnsname", help="DNS Name to be imported", metavar="DNSNAME") + save_import_cert_help = "Import certificate from URI or file." + # importcert sub-parser + import_cert_parser = subcommand_parser.add_parser( + "importcert", + help=save_import_cert_help, + description=save_import_cert_help + "\n\texample singlesignon importcert cert.txt", + formatter_class=RawDescriptionHelpFormatter, + ) + import_cert_parser.add_argument( + "cert", + help="Certificate URI or Certificate File to be imported.", + metavar="CERTIFICATE", + ) + # delete sub-parser + delete_sso_help = "Delete a single or all SSO records." + delete_sso_parser = subcommand_parser.add_parser( + "deleterecord", + help=delete_sso_help, + description=delete_sso_help + "\nDelete a single record:\nexample deleterecord 2\n" + "Delete all records:\nexample singlesignon deleterecord all", + formatter_class=RawDescriptionHelpFormatter, + ) + delete_sso_parser.add_argument( + "record", + help="Record to be deleted (or use keyword 'all' to delete all records)", + metavar="RECORD", + ) diff --git a/ilorest/extensions/iLO_COMMANDS/VirtualMediaCommand.py b/ilorest/extensions/iLO_COMMANDS/VirtualMediaCommand.py new file mode 100644 index 0000000..092e914 --- /dev/null +++ b/ilorest/extensions/iLO_COMMANDS/VirtualMediaCommand.py @@ -0,0 +1,430 @@ +### +# Copyright 2016-2023 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 -*- +""" Virtual Media Command for rdmc """ +import sys + +try: + from rdmc_helper import ( + UI, + IloLicenseError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + ReturnCodes, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + UI, + IloLicenseError, + ) + +from redfish.ris import rmc_helper + + +class VirtualMediaCommand: + """ Changes the iscsi configuration for the server that is currently """ """ logged in """ + + def __init__(self): + self.ident = { + "name": "virtualmedia", + "usage": None, + "description": "Run without" + " arguments to view the available virtual media sources." + "\n\tExample: virtualmedia\n\n\tInsert virtual media and " + "set to boot on next restart.\n\texample: virtualmedia 2 " + "http://xx.xx.xx.xx/vm.iso --bootnextreset\n\n\tRemove " + "current inserted media.\n\texample: virtualmedia 2 --remove", + "summary": "Command for inserting and removing virtual media.", + "aliases": [], + "auxcommands": [ + "GetCommand", + "SetCommand", + "SelectCommand", + "RebootCommand", + ], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main iscsi configuration worker function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + if len(args) > 2: + raise InvalidCommandLineError( + "Invalid number of parameters. " "virtualmedia command takes a maximum of 2 parameters." + ) + else: + self.virtualmediavalidation(options) + + resp = self.rdmc.app.get_handler("/rest/v1/Managers/1/VirtualMedia/1", silent=True) + + if not resp.status == 200: + raise IloLicenseError("") + + self.auxcommands["select"].run("VirtualMedia.") + ilover = self.rdmc.app.getiloversion() + + if self.rdmc.app.monolith.is_redfish: + isredfish = True + paths = self.auxcommands["get"].getworkerfunction("@odata.id", options, results=True, uselist=False) + ids = self.auxcommands["get"].getworkerfunction("Id", options, results=True, uselist=False) + paths = {ind: path for ind, path in enumerate(paths)} + ids = {ind: id for ind, id in enumerate(ids)} + for path in paths: + paths[path] = paths[path]["@odata.id"] + else: + isredfish = False + paths = self.auxcommands["get"].getworkerfunction("links/self/href", options, results=True, uselist=False) + ids = self.auxcommands["get"].getworkerfunction("Id", options, results=True, uselist=False) + paths = {ind: path for ind, path in enumerate(paths)} + ids = {ind: id for ind, id in enumerate(ids)} + for path in paths: + paths[path] = paths[path]["links"]["self"]["href"] + # To keep indexes consistent between versions + if not list(ids.keys())[0] == list(list(ids.values())[0].values())[0]: + finalpaths = {} + for path in paths: + finalpaths.update({int(list(ids[path].values())[0]): paths[path]}) + paths = finalpaths + if options.removevm: + self.vmremovehelper(args, options, paths, isredfish, ilover) + elif len(args) == 2: + self.vminserthelper(args, options, paths, isredfish, ilover) + elif options.bootnextreset: + self.vmbootnextreset(args, paths) + elif not args: + self.vmdefaulthelper(options, paths) + else: + raise InvalidCommandLineError("Invalid parameter(s). Please run" " 'help virtualmedia' for parameters.") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def vmremovehelper(self, args, options, paths, isredfish, ilover): + """Worker function to remove virtual media + + :param args: arguments passed from command line + :type args: list + :param paths: virtual media paths + :type paths: list + :param isredfish: redfish flag + :type isredfish: bool + :param ilover: iloversion + :type ilover: int + :param options: command line options + :type options: list. + """ + path = None + + if isredfish: + path, body = self.vmredfishhelper("remove", args[0]) + else: + if ilover <= 4.230: + body = {"Image": None} + else: + body = {"Action": "EjectVirtualMedia", "Target": "/Oem/Hp"} + + try: + path = paths[int(args[0])] if not path else path + except: + raise InvalidCommandLineError( + "Invalid input value for virtual " + "media please run the command with no " + "arguments for possible values." + ) + + if ilover <= 4.230: + self.rdmc.app.patch_handler(path, body) + else: + self.rdmc.app.post_handler(path, body) + + if options.reboot: + self.auxcommands["reboot"].run(options.reboot) + + def vminserthelper(self, args, options, paths, isredfish, ilover): + """Worker function to insert virtual media + + :param args: arguments passed from command line + :type args: list + :param paths: virtual media paths + :type paths: list + :param isredfish: redfish flag + :type isredfish: bool + :param ilover: iloversion + :type ilover: int + :param options: command line options + :type options: list. + """ + path = None + if not args[1].startswith("http://") and not args[1].startswith("https://"): + raise InvalidCommandLineError("Virtual media path must be a URL.") + if args[0] in "1": + if not args[1].endswith(".img"): + raise InvalidCommandLineError("Only .img files are allowed") + if args[0] in "2": + if not args[1].endswith(".iso"): + raise InvalidCommandLineError("Only .iso files are allowed") + if isredfish: + path, body = self.vmredfishhelper("insert", args[0], args[1]) + else: + if ilover <= 4.230: + body = {"Image": args[1]} + else: + body = { + "Action": "InsertVirtualMedia", + "Target": "/Oem/Hp", + "Image": args[1], + } + + try: + path = paths[int(args[0])] if not path else path + except: + raise InvalidCommandLineError( + "Invalid input value for virtual " + "media please run the command with " + "no arguments for possible values." + ) + + if ilover <= 4.230: + self.rdmc.app.patch_handler(path, body) + else: + try: + results = self.rdmc.app.post_handler(path, body) + if results.status == 200: + if options.bootnextreset: + self.vmbootnextreset(args, paths) + if options.reboot: + self.auxcommands["reboot"].run(options.reboot) + return ReturnCodes.SUCCESS + except rmc_helper.IloLicenseError: + raise IloLicenseError("Error:License Key Required\n") + # except Exception: + # self.rdmc.ui.printer("Please unmount/Eject virtual media and try it again\n") + + def vmdefaulthelper(self, options, paths): + """Worker function to reset virtual media config to default + + :param paths: virtual media paths + :type paths: list + :param options: command line options + :type options: list. + """ + images = {} + count = 0 + mediatypes = self.auxcommands["get"].getworkerfunction("MediaTypes", options, results=True, uselist=False) + ids = self.auxcommands["get"].getworkerfunction("Id", options, results=True, uselist=False) + ids = {ind: id for ind, id in enumerate(ids)} + mediatypes = {ind: med for ind, med in enumerate(mediatypes)} + # To keep indexes consistent between versions + if not list(ids.keys())[0] == list(list(ids.values())[0].values())[0]: + finalmet = {} + for mount in mediatypes: + finalmet.update({int(list(ids[mount].values())[0]): mediatypes[mount]}) + mediatypes = finalmet + + for path in paths: + count += 1 + image = self.rdmc.app.get_handler(paths[path], service=True, silent=True) + image = image.dict["Image"] + images.update({path: image}) + + self.rdmc.ui.printer("Available Virtual Media Options:\n") + if getattr(options, "json", False): + json_str = dict() + json_str["MediaTypes"] = dict() + # else: + # self.rdmc.ui.printer("Available Virtual Media Options:\n") + + for image in images: + media = "" + + if images[image]: + imagestr = images[image] + else: + imagestr = "None" + + for medtypes in mediatypes[image]["MediaTypes"]: + media += medtypes + " " + + if getattr(options, "json", False): + json_str["MediaTypes"][str(media)] = imagestr + else: + self.rdmc.ui.printer( + "[%s] Media Types Available: %s Image Inserted:" " %s\n" % (str(image), str(media), imagestr) + ) + if getattr(options, "json", False): + UI().print_out_json(json_str) + + def vmbootnextreset(self, args, paths): + """Worker function to boot virtual media on next serverreset + + :param args: arguments passed from command line + :type args: list + :param paths: all virtual media paths + :type paths: list + """ + try: + path = paths[int(args[0])] + except: + raise InvalidCommandLineError( + "Invalid input value for virtual media" + " please run the command with no " + "arguments for possible values." + ) + + self.rdmc.app.patch_handler( + path, + {"Oem": {self.rdmc.app.typepath.defs.oemhp: {"BootOnNextServerReset": True}}}, + service=True, + silent=True, + ) + + def vmredfishhelper(self, action, number, image=None): + """Redfish version of the worker function + + :param action: action item + :type action: str + :param number: virtual media ID + :type number: int + """ + + results = self.rdmc.app.select(selector="VirtualMedia.") + bodydict = None + + try: + for result in results: + if result.resp.dict["Id"] == number: + bodydict = result.resp.dict + break + except: + pass + + if not bodydict: + raise InvalidCommandLineError( + "Invalid input value for virtual media" + " please run the command with no " + "arguments for possible values." + ) + if action == "remove" and not bodydict["Inserted"]: + raise InvalidCommandLineError( + "Invalid input value for virtual media." + " No media present in this drive to unmount. Please recheck " + "arguments for possible values." + ) + + if action == "insert" and image: + for item in bodydict["Oem"][self.rdmc.app.typepath.defs.oemhp]["Actions"]: + if "InsertVirtualMedia" in item: + if self.rdmc.app.typepath.defs.isgen10: + action = item.split("#")[-1] + else: + action = "InsertVirtualMedia" + + path = bodydict["Oem"][self.rdmc.app.typepath.defs.oemhp]["Actions"][item]["target"] + body = {"Action": action, "Image": image} + break + elif action == "remove": + for item in bodydict["Oem"][self.rdmc.app.typepath.defs.oemhp]["Actions"]: + if "EjectVirtualMedia" in item: + if self.rdmc.app.typepath.defs.isgen10: + action = item.split("#")[-1] + else: + action = "EjectVirtualMedia" + + path = bodydict["Oem"][self.rdmc.app.typepath.defs.oemhp]["Actions"][item]["target"] + body = {"Action": action} + break + else: + return None, None + + return path, body + + def virtualmediavalidation(self, options): + """sigrecomputevalidation method validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "--reboot", + dest="reboot", + help="Use this flag to perform a reboot command function after" + " completion of operations. For help with parameters and" + " descriptions regarding the reboot flag, run help reboot.", + default=None, + ) + customparser.add_argument( + "--remove", + dest="removevm", + action="store_true", + help="Use this flag to remove the media from the selection.", + default=False, + ) + customparser.add_argument( + "--bootnextreset", + dest="bootnextreset", + action="store_true", + help="Use this flag if you wish to boot from the image on " + "next server reboot. NOTE: The image will be ejected " + "automatically on the second server reboot so that the server " + "does not boot to this image twice.", + default=False, + ) + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) diff --git a/ilorest/extensions/iLO_COMMANDS/__init__.py b/ilorest/extensions/iLO_COMMANDS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/extensions/iLO_REPOSITORY_COMMANDS/DeleteComponentCommand.py b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/DeleteComponentCommand.py new file mode 100644 index 0000000..ea44a39 --- /dev/null +++ b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/DeleteComponentCommand.py @@ -0,0 +1,180 @@ +### +# Copyright 2016-2021 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 -*- +""" Delete Component Command for rdmc """ + +try: + from rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class DeleteComponentCommand: + """Main download command class""" + + def __init__(self): + self.ident = { + "name": "deletecomp", + "usage": None, + "description": "Run to delete component(s) of the currently logged in system.\n\n\t" + "Delete a single component by name.\n\texample: deletecomp " + "CP327.zip\n\n\tDelete multiple components by " + "id.\n\tExample: deletecomp 377fg6c4 327cf4c7\n\n\tDelete " + "multiple components by filename.\n\texample: deletecomp " + "CP327.exe CP99.exe", + "summary": "Deletes components/binaries from the iLO Repository.", + "aliases": [], + "auxcommands": [], + } + + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main deletecomp worker function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.deletecomponentvalidation(options) + + if self.rdmc.app.typepath.defs.isgen9: + raise IncompatibleiLOVersionError("iLO Repository commands are " "only available on iLO 5.") + + comps = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/ComponentRepository/") + + if not comps: + self.rdmc.ui.printer("No components found to delete\n") + + elif options.deleteall: + delopts = [] + + for comp in comps: + try: + if comp["Locked"]: + self.rdmc.ui.warn( + "Unable to delete %s. It is in use by " + "an install set or update task.\n" % comp["Filename"] + ) + continue + except KeyError: + pass + delopts.append(comp["@odata.id"]) + + self.deletecomponents(comps, delopts) + elif args: + self.deletecomponents(comps, args) + else: + InvalidCommandLineError("Please include the component(s) you wish to delete") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def deletecomponents(self, comps, delopts): + """component validation function + + :param comps: component list + :type comps: list. + :param delopts: delete items list + :type delopts: list. + """ + if "," in delopts: + delopts = delopts.split(",") + elif not isinstance(delopts, list): + delopts = [delopts] + + for opt in delopts: + deleted = False + + if "/" in opt: + self.rdmc.app.delete_handler(opt) + deleted = True + else: + for comp in comps: + if opt == comp["Id"] or opt == comp["Filename"]: + try: + if comp["Locked"]: + self.rdmc.ui.error( + "Unable to delete %s. It is in use by " + "an install set or update task.\n" % comp["Filename"] + ) + continue + except KeyError: + pass + + self.rdmc.app.delete_handler(comp["@odata.id"]) + deleted = True + + if deleted: + self.rdmc.ui.printer("Deleted %s\n" % opt) + self.rdmc.ui.printer("Component " + opt + " deleted successfully.\n") + # self.rdmc.ui.printer("[200] The operation completed successfully.\n") + else: + raise InvalidCommandLineError("Cannot find or unable to delete component %s" % opt) + + def deletecomponentvalidation(self, options): + """component validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-a", + "--all", + dest="deleteall", + action="store_true", + help="""Delete all components.""", + default=False, + ) diff --git a/ilorest/extensions/iLO_REPOSITORY_COMMANDS/DownloadComponentCommand.py b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/DownloadComponentCommand.py new file mode 100644 index 0000000..3e16be8 --- /dev/null +++ b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/DownloadComponentCommand.py @@ -0,0 +1,229 @@ +### +# Copyright 2016-2021 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 -*- +""" Download Component Command for rdmc """ + +import ctypes +import os +import time +from ctypes import c_char_p, c_int + +try: + from rdmc_helper import ( + DownloadError, + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + DownloadError, + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + ReturnCodes, + ) + + +def human_readable_time(seconds): + """Returns human readable time + + :param seconds: Amount of seconds to parse. + :type seconds: string. + """ + seconds = int(seconds) + hours = seconds / 3600 + seconds = seconds % 3600 + minutes = seconds / 60 + seconds = seconds % 60 + + return "{:02.0f} hour(s) {:02.0f} minute(s) {:02.0f} second(s) ".format(hours, minutes, seconds) + + +class DownloadComponentCommand: + """Main download component command class""" + + def __init__(self): + self.ident = { + "name": "downloadcomp", + "usage": None, + "description": "Run to " + "download the file from path\n\texample: downloadcomp " + "/fwrepo/filename.exe --outdir " + "download the file by name\n\texample: downloadcomp " + "filename.exe --outdir ", + "summary": "Downloads components/binaries from the iLO Repository.", + "aliases": [], + "auxcommands": [], + } + + def run(self, line, help_disp=False): + """Wrapper function for download command main function + + :param line: command line input + :type line: string. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.downloadcomponentvalidation(options) + + if self.rdmc.app.typepath.defs.isgen9: + raise IncompatibleiLOVersionError("iLO Repository commands are " "only available on iLO 5.") + + start_time = time.time() + ret = ReturnCodes.FAILED_TO_DOWNLOAD_COMPONENT + + self.rdmc.ui.printer("Downloading component, this may take a while...\n") + + if "blobstore" in self.rdmc.app.current_client.base_url: + ret = self.downloadlocally(options) + else: + ret = self.downloadfunction(options) + + self.rdmc.ui.printer("%s\n" % human_readable_time(time.time() - start_time)) + + self.cmdbase.logout_routine(self, options) + # Return code + return ret + + def downloadfunction(self, options): + """Main download command worker function + + :param options: command options (argparse) + :type options: options. + """ + filename = options.component.rsplit("/", 1)[-1] + + if not options.outdir: + destination = os.path.join(os.getcwd(), filename) + else: + destination = os.path.join(options.outdir, filename) + + if not os.path.exists(os.path.join(os.path.split(destination)[0])): + raise InvalidFileInputError("Invalid output file location.") + if not os.access(os.path.join(os.path.split(destination)[0]), os.W_OK): + raise InvalidFileInputError("File location is not writable.") + if os.access(destination, os.F_OK) and not os.access(destination, os.W_OK): + raise InvalidFileInputError("Existing File cannot be overwritten.") + + if options.component[0] != "/": + options.component = "/" + options.component + + if "fwrepo" not in options.component: + options.component = "/fwrepo/" + options.component + + results = self.rdmc.app.get_handler(options.component, uncache=True) + + if results.status == 404: + raise DownloadError( + "Downloading of component %s failed, please check the component name and check if the " + "component exists in the repository\n" % options.component + ) + + with open(destination, "wb") as local_file: + local_file.write(results.ori) + + self.rdmc.ui.printer("Download complete\n") + + return ReturnCodes.SUCCESS + + def downloadlocally(self, options=None): + """Used to download a component from the iLO Repo locally + + :param options: command options (argparse) + :type options: options. + """ + try: + dll = self.rdmc.app.current_client.connection._conn.channel.dll + dll.downloadComponent.argtypes = [c_char_p, c_char_p] + dll.downloadComponent.restype = c_int + + filename = options.component.rsplit("/", 1)[-1] + if not options.outdir: + destination = os.path.join(os.getcwd(), filename) + else: + destination = os.path.join(options.outdir, filename) + + if not os.path.exists(os.path.join(os.path.split(destination)[0])): + raise InvalidFileInputError("Invalid output file location.") + if not os.access(os.path.join(os.path.split(destination)[0]), os.W_OK): + raise InvalidFileInputError("File location is not writable.") + if os.access(destination, os.F_OK) and not os.access(destination, os.W_OK): + raise InvalidFileInputError("Existing File cannot be overwritten.") + + ret = dll.downloadComponent( + ctypes.create_string_buffer(filename.encode("utf-8")), + ctypes.create_string_buffer(destination.encode("utf-8")), + ) + + if ret != 0: + self.rdmc.ui.error( + "Component " + filename + " download failed, please check the " + "component name and check if the component exists in the respository.\n" + ) + return ReturnCodes.FAILED_TO_DOWNLOAD_COMPONENT + else: + self.rdmc.ui.printer("Component " + filename + " downloaded successfully.\n") + self.rdmc.ui.printer("[200] The operation completed successfully.\n") + + except Exception as excep: + raise DownloadError(str(excep)) + + return ReturnCodes.SUCCESS + + def downloadcomponentvalidation(self, options): + """Download command method validation function + + :param options: command options + :type options: options. + """ + self.rdmc.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for download command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "component", + help="""Component name (starting with path '/fwrepo/') of the target""" """ component.""", + metavar="[COMPONENT URI]", + ) + customparser.add_argument( + "--outdir", + dest="outdir", + help="""Output directory for saving the file.""", + default="", + ) diff --git a/ilorest/extensions/iLO_REPOSITORY_COMMANDS/FwpkgCommand.py b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/FwpkgCommand.py new file mode 100644 index 0000000..943d0e3 --- /dev/null +++ b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/FwpkgCommand.py @@ -0,0 +1,503 @@ +# ## +# Copyright 2016-2021 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 -*- +""" Fwpkg Command for rdmc """ + +import os +import json +from random import randint +import shutil +import zipfile +import tempfile +from io import open + +import ctypes +from ctypes import c_char_p, c_int, c_bool + +from redfish.hpilo.risblobstore2 import BlobStore2 + +try: + from rdmc_helper import ( + LOGGER, + LERR, + LOUT, + IncompatibleiLOVersionError, + ReturnCodes, + Encryption, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + UploadError, + TaskQueueError, + FirmwareUpdateError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + LOGGER, + LERR, + LOUT, + IncompatibleiLOVersionError, + ReturnCodes, + Encryption, + InvalidCommandLineErrorOPTS, + InvalidCommandLineError, + InvalidFileInputError, + UploadError, + TaskQueueError, + FirmwareUpdateError, + ) + + +class FwpkgCommand: + """Fwpkg command class""" + + def __init__(self): + self.ident = { + "name": "flashfwpkg", + "usage": None, + "description": "Run to upload and flash " + "components from fwpkg files.\n\n\tUpload component and flashes it or sets a task" + "queue to flash.\n\texample: flashfwpkg component.fwpkg.\n\n\t" + "Skip extra checks before adding taskqueue. (Useful when adding " + "many flashfwpkg taskqueue items in sequence.)\n\texample: flashfwpkg " + "component.fwpkg --ignorechecks", + "summary": "Flashes fwpkg components using the iLO repository.", + "aliases": ["fwpkg"], + "auxcommands": [ + "UploadComponentCommand", + "UpdateTaskQueueCommand", + "FirmwareUpdateCommand", + "FwpkgCommand", + ], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main fwpkg worker function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.fwpkgvalidation(options) + + if self.rdmc.app.typepath.defs.isgen9: + LOGGER.error("iLO Repository commands are only available on iLO 5.") + raise IncompatibleiLOVersionError("iLO Repository commands are only available on iLO 5.") + + if self.rdmc.app.getiloversion() <= 5.120 and options.fwpkg.lower().startswith("iegen10"): + raise IncompatibleiLOVersionError( + "Please upgrade to iLO 5 1.20 or greater to ensure correct flash of this firmware." + ) + tempdir = "" + if ( + not options.fwpkg.endswith(".fwpkg") + and not options.fwpkg.lower().endswith(".fup") + and not options.fwpkg.lower().endswith(".hpb") + ): + LOGGER.error("Invalid file type. Please make sure the file provided is a valid .fwpkg file type.") + raise InvalidFileInputError( + "Invalid file type. Please make sure the file provided is a valid .fwpkg file type." + ) + + try: + components, tempdir, comptype, _ = self.preparefwpkg(self, options.fwpkg) + if comptype == "D": + LOGGER.error("Component Type D, Unable to flash this fwpkg file.") + raise InvalidFileInputError("Unable to flash this fwpkg file.") + elif comptype in ["C", "BC"]: + try: + self.taskqueuecheck() + except TaskQueueError as excp: + if options.ignore: + self.rdmc.ui.warn(str(excp) + "\n") + else: + raise excp + self.applyfwpkg(options, tempdir, components, comptype) + + if comptype == "A": + message = "Firmware has successfully been flashed.\n" + if "ilo" in options.fwpkg.lower(): + message += "iLO will reboot to complete flashing. Session will be" " terminated.\n" + elif comptype in ["B", "BC"]: + message = ( + "Firmware has successfully been flashed and a reboot is required for " + "this firmware to take effect.\n" + ) + elif comptype in ["C", "BC"]: + message = "This firmware is set to flash on reboot.\n" + + if not self.auxcommands["uploadcomp"].wait_for_state_change(): + # Failed to upload the component. + raise FirmwareUpdateError("Error while processing the component.") + + self.rdmc.ui.printer(message) + + except (FirmwareUpdateError, UploadError) as excp: + raise excp + + finally: + if tempdir: + shutil.rmtree(tempdir) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def taskqueuecheck(self): + """Check taskqueue for potential issues before starting""" + + select = "ComputerSystem." + results = self.rdmc.app.select(selector=select, path_refresh=True) + + try: + results = results[0] + except: + pass + + powerstate = results.resp.dict["PowerState"] + tasks = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/UpdateTaskQueue/") + + for task in tasks: + if task["State"] == "Exception": + raise TaskQueueError( + "Exception found in taskqueue which will " + "prevent firmware from flashing. Please run " + "iLOrest command: taskqueue --cleanqueue to clear" + " any errors before continuing." + ) + if task["UpdatableBy"] == "Uefi" and not powerstate == "Off" or task["Command"] == "Wait": + raise TaskQueueError( + "Taskqueue item found that will " + "prevent firmware from flashing immediately. Please " + "run iLOrest command: taskqueue --resetqueue to " + "reset the queue if you wish to flash immediately " + "or include --ignorechecks to add this firmware " + "into the task queue anyway." + ) + if tasks: + self.rdmc.ui.warn( + "Items are in the taskqueue that may delay the flash until they " + "are finished processing. Use the taskqueue command to monitor updates.\n" + ) + + def get_comp_type(self, payload): + """Get's the component type and returns it + + :param payload: json payload of .fwpkg file + :type payload: dict. + :returns: returns the type of component. Either A,B,C, or D. + :rtype: string + """ + ctype = "" + if "Uefi" in payload["UpdatableBy"] and "RuntimeAgent" in payload["UpdatableBy"]: + ctype = "D" + elif "Uefi" in payload["UpdatableBy"] and "Bmc" in payload["UpdatableBy"]: + fw_url = "/redfish/v1/UpdateService/FirmwareInventory/" + "?$expand=." + data = self.rdmc.app.get_handler(fw_url, silent=True).dict["Members"] + da_flag = False + cc_flag = False + if data is not None: + type_set = None + for fw in data: + for device in payload["Devices"]["Device"]: + if fw["Oem"]["Hpe"].get("Targets") is not None: + if device["Target"] in fw["Oem"]["Hpe"]["Targets"]: + if "Slot=" in fw["Oem"]["Hpe"]["DeviceContext"]: + cc_flag = True + elif "N/A" in fw["Oem"]["Hpe"]["DeviceContext"]: + da_flag = True + elif "Slot=" not in fw["Oem"]["Hpe"]["DeviceContext"]: + da_flag = True + if cc_flag and da_flag: + ctype = 'BC' + type_set = True + elif cc_flag and not da_flag: + ctype = 'B' + type_set = True + elif not cc_flag and da_flag: + ctype = 'C' + type_set = True + if type_set is None: + LOGGER.error("Component type is not identified, Please check if the particular H/W is present") + ilo_ver_int = self.rdmc.app.getiloversion() + ilo_ver = str(ilo_ver_int) + error_msg = "Cannot flash the component on this server, check whether the component is fwpkg-v2 " \ + "or check whether the server is iLO" + if ilo_ver_int >= 6: + error_msg = error_msg + ilo_ver[0] + ", FW is above 1.50 or the particular drive HW is present\n" + else: + error_msg = error_msg + ilo_ver[0] + ", FW is above 2.30 or the particular drive HW is present\n" + raise IncompatibleiLOVersionError(error_msg) + else: + for device in payload["Devices"]["Device"]: + for image in device["FirmwareImages"]: + if "DirectFlashOk" not in list(image.keys()): + raise InvalidFileInputError("Cannot flash this firmware.") + if image["DirectFlashOk"]: + ctype = "A" + if image["ResetRequired"]: + ctype = "B" + break + elif image["UefiFlashable"]: + ctype = "C" + break + else: + ctype = "D" + LOGGER.info("Component Type identified is {}".format(ctype)) + return ctype + + @staticmethod + def preparefwpkg(self, pkgfile): + """Prepare fwpkg file for flashing + + :param pkgfile: Location of the .fwpkg file + :type pkgfile: string. + :returns: returns the files needed to flash, directory they are located + in, and type of file. + :rtype: string, string, string + """ + files = [] + imagefiles = [] + payloaddata = None + tempdir = tempfile.mkdtemp() + pldmflag = False + if not pkgfile.lower().endswith(".fup") and not pkgfile.lower().endswith(".hpb"): + try: + zfile = zipfile.ZipFile(pkgfile) + zfile.extractall(tempdir) + zfile.close() + except Exception as excp: + raise InvalidFileInputError("Unable to unpack file. " + str(excp)) + + files = os.listdir(tempdir) + + if "payload.json" in files: + with open(os.path.join(tempdir, "payload.json"), encoding="utf-8") as pfile: + data = pfile.read() + payloaddata = json.loads(data) + else: + raise InvalidFileInputError("Unable to find payload.json in fwpkg file.") + + if not pkgfile.lower().endswith(".fup") and not pkgfile.lower().endswith(".hpb"): + comptype = self.auxcommands["flashfwpkg"].get_comp_type(payloaddata) + else: + comptype = "A" + + results = self.rdmc.app.getprops(selector="UpdateService.", props=["Oem/Hpe/Capabilities"]) + if comptype in ["C", "BC"]: + imagefiles = [self.auxcommands["flashfwpkg"].type_c_change(tempdir, pkgfile)] + else: + if not pkgfile.lower().endswith("fup") and not pkgfile.lower().endswith(".hpb"): + for device in payloaddata["Devices"]["Device"]: + for firmwareimage in device["FirmwareImages"]: + if firmwareimage["PLDMImage"]: + pldmflag = True + if firmwareimage["FileName"] not in imagefiles: + imagefiles.append(firmwareimage["FileName"]) + + if ( + "blobstore" in self.rdmc.app.redfishinst.base_url + and comptype in ["A", "B", "BC"] + and results + and "UpdateFWPKG" in results[0]["Oem"]["Hpe"]["Capabilities"] + ): + dll = BlobStore2.gethprestchifhandle() + dll.isFwpkg20.argtypes = [c_char_p, c_int] + dll.isFwpkg20.restype = c_bool + + with open(pkgfile, "rb") as fwpkgfile: + fwpkgdata = fwpkgfile.read() + + fwpkg_buffer = ctypes.create_string_buffer(fwpkgdata) + if dll.isFwpkg20(fwpkg_buffer, 2048): + imagefiles = [pkgfile] + tempdir = "" + if pkgfile.lower().endswith(".fup") or pkgfile.lower().endswith(".hpb"): + imagefiles = [pkgfile] + elif ( + self.rdmc.app.getiloversion() > 5.230 + and payloaddata.get("PackageFormat") == "FWPKG-v2" + ): + imagefiles = [pkgfile] + return imagefiles, tempdir, comptype, pldmflag + + def type_c_change(self, tdir, pkgloc): + """Special changes for type C + + :param tempdir: path to temp directory + :type tempdir: string. + :param components: components to upload + :type components: list. + + :returns: The location of the type C file to upload + :rtype: string. + """ + + shutil.copy(pkgloc, tdir) + + fwpkgfile = os.path.split(pkgloc)[1] + zfile = fwpkgfile[:-6] + ".zip" + zipfileloc = os.path.join(tdir, zfile) + + os.rename(os.path.join(tdir, fwpkgfile), zipfileloc) + + return zipfileloc + + def applyfwpkg(self, options, tempdir, components, comptype): + """Apply the component to iLO + + :param options: command line options + :type options: list. + :param tempdir: path to temp directory + :type tempdir: string. + :param components: components to upload + :type components: list. + :param comptype: type of component. Either A,B,C, or D. + :type comptype: str. + """ + + for component in components: + taskqueuecommand = " create %s " % os.path.basename(component) + if options.tover: + taskqueuecommand = " create %s --tpmover" % os.path.basename(component) + if component.endswith(".fwpkg") or component.lower().endswith(".hpb") or component.lower().endswith(".fup") or component.endswith(".zip"): + uploadcommand = "--component %s" % component + else: + uploadcommand = "--component %s" % os.path.join(tempdir, component) + + if options.forceupload: + uploadcommand += " --forceupload" + if comptype in ["A", "B"]: + LOGGER.info("Setting --update_target --update_repository options as it is A or B") + uploadcommand += " --update_target --update_repository" + elif comptype in ["BC"]: + LOGGER.info("Setting --update_target option as it is BC.") + uploadcommand += " --update_target" + if options.update_srs: + LOGGER.info("Setting --update_srs to store as recovery set.") + uploadcommand += " --update_srs" + if options.tover: + LOGGER.info("Setting --tpmover if tpm enabled.") + uploadcommand += " --tpmover" + + self.rdmc.ui.printer("Uploading firmware: %s\n" % os.path.basename(component)) + try: + ret = self.auxcommands["uploadcomp"].run(uploadcommand) + if ret != ReturnCodes.SUCCESS: + raise UploadError + except UploadError: + if comptype in ["A", "B", "BC"]: + select = self.rdmc.app.typepath.defs.hpilofirmwareupdatetype + results = self.rdmc.app.select(selector=select) + + try: + results = results[0] + except: + pass + + if results: + update_path = results.resp.request.path + error = self.rdmc.app.get_handler(update_path, silent=True) + self.auxcommands["firmwareupdate"].printerrmsg(error) + else: + raise FirmwareUpdateError("Error occurred while updating the firmware.") + else: + raise UploadError("Error uploading component.") + + if comptype in ["C", "BC"]: + self.rdmc.ui.warn("Setting a taskqueue item to flash UEFI flashable firmware.\n") + path = "/redfish/v1/updateservice/updatetaskqueue" + newtask = { + "Name": "Update-%s" + % ( + str(randint(0, 1000000)), + ), + "Command": "ApplyUpdate", + "Filename": os.path.basename(component), + "UpdatableBy": ["Uefi"], + "TPMOverride": options.tover, + } + res = self.rdmc.app.post_handler(path, newtask) + + if res.status != 201: + raise TaskQueueError("Not able create UEFI task.\n") + else: + self.rdmc.ui.printer("Created UEFI Task for Component " + os.path.basename(component) + " successfully.\n") + + def fwpkgvalidation(self, options): + """fwpkg validation function + + :param options: command line options + :type options: list. + """ + self.rdmc.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument("fwpkg", help="""fwpkg file path""", metavar="[FWPKG]") + customparser.add_argument( + "--forceupload", + dest="forceupload", + action="store_true", + help="Add this flag to force upload firmware with the same name already on the repository.", + default=False, + ) + customparser.add_argument( + "--ignorechecks", + dest="ignore", + action="store_true", + help="Add this flag to ignore all checks to the taskqueue before attempting to process the .fwpkg file.", + default=False, + ) + customparser.add_argument( + "--tpmover", + dest="tover", + action="store_true", + help="If set then the TPMOverrideFlag is passed in on the associated flash operations", + default=False, + ) + customparser.add_argument( + "--update_srs", + dest="update_srs", + action="store_true", + help="Add this flag to update the System Recovery Set with the uploaded firmware. " + "NOTE: This requires an account login with the system recovery set privilege.", + default=False, + ) diff --git a/ilorest/extensions/iLO_REPOSITORY_COMMANDS/InstallSetCommand.py b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/InstallSetCommand.py new file mode 100644 index 0000000..3ea5435 --- /dev/null +++ b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/InstallSetCommand.py @@ -0,0 +1,599 @@ +# ## +# Copyright 2016-2021 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 -*- +""" Install Set Command for rdmc """ + +import json +import re +from argparse import RawDescriptionHelpFormatter +from datetime import datetime + +try: + from rdmc_helper import ( + LOGGER, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + NoContentsFoundForOperationError, + ReturnCodes, + InstallsetError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + LOGGER, + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileInputError, + NoContentsFoundForOperationError, + ReturnCodes, + InstallsetError, + ) + + +class InstallSetCommand: + """Main download command class""" + + def __init__(self): + self.ident = { + "name": "installset", + "usage": None, + "description": "Run to perform operations on install sets.\nTo view help on specific " + "sub-commands run: installset -h\n\n\tExample: installset " + "add -h\nNote: iLO 5 required.", + "summary": "Manages install sets for iLO.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main listcomp worker function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + _subcommands = ["add", "delete", "invoke"] + found = False + for i, arg in enumerate(line): + if arg in _subcommands: + found = True + try: + if line[i + 1] not in list(self.parser._option_string_actions.keys()): + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + else: + raise IndexError + except (KeyError, IndexError): + (options, args) = self.rdmc.rdmc_parse_arglist(self, line, default=True) + else: + continue + if not found: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line, default=True) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.installsetvalidation(options) + + if self.rdmc.app.typepath.defs.isgen9: + raise IncompatibleiLOVersionError("iLO Repository commands are " "only available on iLO 5.") + + if hasattr(options, "name"): + if options.name: + if options.name.startswith('"') and options.name.endswith('"'): + options.name = options.name[1:-1] + if hasattr(options, "removeall"): + if options.removeall: + self.removeinstallsets(options) + if options.command.lower() == "add": + if not options.json: + raise InvalidCommandLineError("add command requires an install set file.") + else: + self.addinstallset(options.json, options) + elif options.command.lower() == "delete" and (hasattr(options, "removeall") and not options.removeall): + self.deleteinstallset(options) + # elif options.command.lower() == "delete" and not hasattr(options, "removeall"): + # self.deleteinstallset(options.name) + elif options.command.lower() == "invoke": + self.invokeinstallset(options) + else: + self.printinstallsets(options) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def addinstallset(self, setfile, options): + """Adds an install set + :param setfile: filename of install set + :type setfile: str. + :param name: string of the install set name to add + :type name: str. + """ + path = "/redfish/v1/UpdateService/InstallSets/" + comps = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/ComponentRepository/") + + sets = self.rdmc.app.getcollectionmembers(path) + + if not options.name: + options.name = str(datetime.now()) + options.name = options.name.replace(":", "") + options.name = options.name.replace(" ", "T") + try: + inputfile = open(setfile, "r") + sequences = json.loads(inputfile.read()) + except Exception as excp: + raise InvalidFileInputError("%s" % excp) + + listtype = self.validatefile(sequences) + + if listtype: + body = {"Name": options.name, "Sequence": sequences} + else: + body = sequences + if "Sequence" in body: + sequences = body["Sequence"] + else: + raise InvalidFileInputError("Invalid install set file.") + sequences = body["Sequence"] + + filenamelist = [x["Filename"] for x in comps] + + for sequence in sequences: + if sequence["Command"] == "ApplyUpdate": + if "Filename" in list(sequence.keys()): + if not sequence["Filename"] in filenamelist: + raise NoContentsFoundForOperationError( + "Component" + " referenced in install set is not present on" + " iLO Drive: %s" % sequence["Filename"] + ) + elif sequence["Command"] == "WaitTimeSeconds": + sequence["WaitTimeSeconds"] = int(sequence["WaitTimeSeconds"]) + for setvar in sets: + if setvar["Name"] == body["Name"]: + raise InvalidCommandLineError("Install set name is already in use.") + + # self.rdmc.app.post_handler(path, body) + if "IsRecovery" in body and body["IsRecovery"]: + self.rdmc.ui.printer("This is a system recovery install set: %s\n" % body["Name"]) + self.rdmc.ui.warn("Adding system recovery set: %s\n" % body["Name"]) + if not (options.user and options.password): + raise InstallsetError( + "Adding system recovery set needs to be passed with " + "--user and --password options, add failed\n" + ) + if "blobstore" in self.rdmc.app.current_client.base_url: + LOGGER.info("Logging out of the session without user and password") + self.rdmc.app.current_client.logout() + LOGGER.info("Logging in with user and password for deleting system recovery set") + self.rdmc.app.current_client.login(self.rdmc.app.current_client.auth_type) + self.rdmc.app.post_handler(path, body) + LOGGER.info("Logging out of the session with user and password") + self.rdmc.app.current_client.logout() + LOGGER.info("Restoring old Logging in without user and password to restore") + options.user = None + options.password = None + self.rdmc.app.current_client.login(self.rdmc.app.current_client.auth_type) + else: + self.rdmc.app.post_handler(path, body) + else: + self.rdmc.app.post_handler(path, body) + + def invokeinstallset(self, options): + """Invokes the named set + :param name: string of the install set name to invoke + :type name: str. + """ + path = None + name = options.name + sets = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/InstallSets/") + + for setvar in sets: + if setvar["Name"] == name: + path = setvar["Actions"]["#HpeComponentInstallSet.Invoke"]["target"] + + if not path: + raise NoContentsFoundForOperationError("No install set with the" " provided name could be found.") + + self.rdmc.ui.printer("Invoking install set:%s\n" % name) + + payload = self.checkpayloadoptions(options) + self.rdmc.app.post_handler(path, payload) + + def deleteinstallset(self, options): + """Deletes the named set + + :param name: string of the install set name to delete + :type name: str. + """ + self.rdmc.ui.printer("Deleting install set: %s...\n" % options.name) + sets = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/InstallSets/") + found = False + + for setvar in sets: + if setvar["Name"] == options.name: + found = True + path = setvar["@odata.id"] + if "IsRecovery" in setvar and setvar["IsRecovery"]: + self.rdmc.ui.printer("This is a system recovery install set: %s\n" % setvar["Name"]) + self.rdmc.ui.warn("Deleting system recovery set: %s\n" % setvar["Name"]) + if not (options.user and options.password): + raise InstallsetError( + "Deleting system recovery set needs to be passed with " + "--user and --password options, delete failed\n" + ) + if "blobstore" in self.rdmc.app.current_client.base_url: + LOGGER.info("Logging out of the session without user and password") + self.rdmc.app.current_client.logout() + LOGGER.info("Logging in with user and password for deleting system recovery set") + self.rdmc.app.current_client.login(self.rdmc.app.current_client.auth_type) + self.rdmc.app.delete_handler(path) + LOGGER.info("Logging out of the session with user and password") + self.rdmc.app.current_client.logout() + LOGGER.info("Restoring old Logging in without user and password to restore") + options.user = None + options.password = None + self.rdmc.app.current_client.login(self.rdmc.app.current_client.auth_type) + else: + self.rdmc.app.delete_handler(path) + else: + self.rdmc.app.delete_handler(path) + if not found: + raise NoContentsFoundForOperationError("Unable to find the specified install set.") + + def removeinstallsets(self, options): + """Removes all install sets""" + sets = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/InstallSets/") + + if not sets: + self.rdmc.ui.printer("No install sets found.\n") + + self.rdmc.ui.printer("Deleting all install sets...\n") + + for setvar in sets: + if setvar["IsRecovery"]: + self.rdmc.ui.warn("Attempting to delete recovery set: %s\n" % setvar["Name"]) + ans = input("Would you like to delete recovery set?(y/n): ") + if ans == "n": + self.rdmc.ui.printer("\n Skipping delete of recovery set.\n") + elif ans == "y": + self.rdmc.ui.warn("Deleting system recovery set: %s\n" % setvar["Name"]) + if not (options.user and options.password): + raise InstallsetError( + "Deleting system recovery set needs to be passed with " + "--user and --password options, delete failed\n" + ) + if "blobstore" in self.rdmc.app.current_client.base_url: + LOGGER.info("Logging out of the session without user and password") + self.rdmc.app.current_client.logout() + LOGGER.info("Logging in with user and password for deleting system recovery set") + self.rdmc.app.current_client.login(self.rdmc.app.current_client.auth_type) + self.rdmc.app.delete_handler(setvar["@odata.id"]) + LOGGER.info("Logging out of the session with user and password") + self.rdmc.app.current_client.logout() + LOGGER.info("Restoring old Logging in without user and password to restore") + options.user = None + options.password = None + self.rdmc.app.current_client.login(self.rdmc.app.current_client.auth_type) + else: + self.rdmc.app.delete_handler(setvar["@odata.id"]) + else: + self.rdmc.ui.printer("Deleting install set: %s\n" % setvar["Name"]) + self.rdmc.app.delete_handler(setvar["@odata.id"]) + + def build_json_out(self, options): + sets = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/InstallSets/") + list_content = [] + for setvar in sets: + if setvar["IsRecovery"]: + recovery = "[Recovery Set]" + else: + recovery = "" + content = {setvar["Name"]: {recovery}} + list_content.append(content) + + if "Sequence" not in list(setvar.keys()): + content.update({"No Sequences in set."}) + continue + + for item in setvar["Sequence"]: + if "Filename" in list(item.keys()): + name = item["Name"] + filename_cmd = "%s %s" % (item["Command"], item["Filename"]) + content.update({name: {filename_cmd}}) + elif "WaitTimeSeconds" in list(item.keys()): + wait_name = item["Name"] + wait_command = "%s %s seconds" % ( + item["Command"], + str(item["WaitTimeSeconds"]), + ) + content.update({wait_name: {wait_command}}) + else: + content.update({item["Name"]: {item["Command"]}}) + return list_content + + def printinstallsets(self, options): + """Prints install sets""" + sets = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/InstallSets/") + if not options.json: + self.rdmc.ui.printer("Install Sets:\n") + + if not sets: + self.rdmc.ui.printer("No install sets found.\n") + elif not options.json: + for setvar in sets: + if setvar["IsRecovery"]: + recovery = "[Recovery Set]" + else: + recovery = "" + self.rdmc.ui.printer("\n%s: %s\n" % (setvar["Name"], recovery)) + + if "Sequence" not in list(setvar.keys()): + self.rdmc.ui.warn("\tNo Sequences in set.\n") + continue + + for item in setvar["Sequence"]: + if "Filename" in list(item.keys()): + self.rdmc.ui.printer("\t%s: %s %s\n" % (item["Name"], item["Command"], item["Filename"])) + elif "WaitTimeSeconds" in list(item.keys()): + self.rdmc.ui.printer( + "\t%s: %s %s seconds\n" + % ( + item["Name"], + item["Command"], + str(item["WaitTimeSeconds"]), + ) + ) + else: + self.rdmc.ui.printer("\t%s: %s\n" % (item["Name"], item["Command"])) + elif options.json: + json_content = self.build_json_out(options) + self.rdmc.ui.print_out_json(json_content) + + def validatefile(self, installsetfile): + """validates json file + + :param file: json file to validate + :type file: string. + """ + listtype = True + keylist = ["Name", "UpdatableBy", "Command", "WaitTimeSeconds", "Filename"] + + if isinstance(installsetfile, list): + for item in installsetfile: + for key in item: + if key not in keylist: + raise InvalidFileInputError("Property %s is invalid " "for an install set." % key) + else: + listtype = False + + return listtype + + def checkpayloadoptions(self, options): + """checks for payload options and adds needed ones to payload + + :param options: command line options + :type options: list. + """ + + payload = {} + if options.exafter: + self.checktime(options.exafter) + payload["Expire"] = options.exafter + if options.safter: + self.checktime(options.safter) + payload["StartAfter"] = options.safter + if options.tover: + payload["TPMOverride"] = True + if options.uset: + payload["UpdateRecoverySet"] = True + if options.ctakeq: + payload["ClearTaskQueue"] = True + + return payload + + def checktime(self, timestr): + """check the time string for valid format + + :param timestr: time string to check + :type timestr: string. + """ + rfdtregex = "\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ?" + if not re.match(rfdtregex, timestr): + raise InvalidCommandLineError( + "Invalid redfish date-time format. " "Accepted formats: YYYY-MM-DDThh:mm:ss, YYYY-MM-DDThh:mm:ssZ" + ) + + def installsetvalidation(self, options): + """installset validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + subcommand_parser = customparser.add_subparsers(dest="command") + # default sub-parser + default_parser = subcommand_parser.add_parser( + "default", + help="Running without any sub-command will return a list of all available install " + "sets on the currently logged in server.", + ) + default_parser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + default_parser.add_argument( + "--removeall", + dest="removeall", + help="Optionally include this flag to remove all install sets on the currently logged " "in server.", + action="store_true", + default=False, + ) + self.cmdbase.add_login_arguments_group(default_parser) + + # add sub-parser + add_help = "Adds an install set on the currently logged in server." + add_parser = subcommand_parser.add_parser( + "add", + help=add_help, + description=add_help + "\n\texample: installset add installsetfile.json " + "--name=newinstallsetname.\n\n\tNote: iLO will provide a default " + "install set name if the flag '--name' is not provided.", + formatter_class=RawDescriptionHelpFormatter, + ) + add_parser.add_argument( + "json", + help="Json file containing the install set tasks and sequencing" + '\n\n\texample install set JSON file:\n\t[\n\t\t{\n\t\t\t"Name": ' + '"Wait",\n\t\t\t"UpdatableBy": ["RuntimeAgent"],\n\t\t\t' + '"Command": "Wait",\n\t\t\t"WaitTimeSeconds": 60\n\t\t},\n\t\t{' + '\n\t\t\t"Name": "uniqueName",\n\t\t\t"UpdatableBy": ' + '["RuntimeAgent"],\n\t\t\t"Command": "ApplyUpdate",\n\t\t\t"' + 'Filename": "filename.exe"\n\t\t},\n\t\t{\n\t\t\t"Name": ' + '"Reboot",\n\t\t\t"UpdatableBy": ["RuntimeAgent"],\n\t\t\t' + '"Command": "ResetServer"\n\t\t}\n\t]', + metavar="JSONFILE", + ) + add_parser.add_argument( + "-n", + "--name", + dest="name", + help="Install set name.", + default=None + ) + + self.cmdbase.add_login_arguments_group(add_parser) + + # invoke sub-parser + invoke_help = "Invoke execution of an install script on the currently logged in server." + invoke_parser = subcommand_parser.add_parser( + "invoke", + help=invoke_help, + description=invoke_help + "\n\nExamples:\n\tTo simply invoke an install set\n\t" + "installset invoke --name=installsetname", + formatter_class=RawDescriptionHelpFormatter, + ) + invoke_parser.add_argument( + "-n", + "--name", + help="Install set name.", + metavar="NAME", + nargs="?", + default="", + required=True, + ) + invoke_parser.add_argument( + "--cleartaskqueue", + dest="ctakeq", + action="store_true", + help="This option allows previous items in the task queue to" + " be cleared before the Install Set is invoked", + default=False, + ) + invoke_parser.add_argument( + "--expire", + dest="exafter", + help="Optionally include this flag if you wish to set the" + " time for installset to expire. ISO 8601 Redfish-style time " + "string to be written after which iLO will automatically change " + "state to Expired", + default=None, + ) + invoke_parser.add_argument( + "--startafter", + dest="safter", + help="Optionally include this flag if you wish to set the" + " earliest execution time for installset. ISO 8601 Redfish-style " + "time string to be used.", + default=None, + ) + invoke_parser.add_argument( + "--tpmover", + dest="tover", + action="store_true", + help="If set then the TPMOverrideFlag is passed in on the " "associated flash operations", + default=False, + ) + invoke_parser.add_argument( + "--updaterecoveryset", + dest="uset", + action="store_true", + help="If set then the components in the flash operations are used" + " to replace matching contents in the Recovery Set.", + default=False, + ) + self.cmdbase.add_login_arguments_group(invoke_parser) + + # delete sub-parser + delete_help = "Delete one or more install sets from the currently logged in server." + delete_parser = subcommand_parser.add_parser( + "delete", + help=delete_help, + description=delete_help + "\n\nExamples:\n\nTo delete a single install set:\n\t" + "installset delete --name=installsetname.\n\nTo delete all install sets\n\t" + "installset delete --removeall", + formatter_class=RawDescriptionHelpFormatter, + ) + delete_parser.add_argument( + "-n", + "--name", + help="Install set name", + metavar="NAME", + nargs="?", + default="", + ) + delete_parser.add_argument( + "--removeall", + dest="removeall", + help="Optionally include this flag to remove all install sets on the currently logged " "in server.", + action="store_true", + default=False, + ) + self.cmdbase.add_login_arguments_group(delete_parser) diff --git a/ilorest/extensions/iLO_REPOSITORY_COMMANDS/ListComponentCommand.py b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/ListComponentCommand.py new file mode 100644 index 0000000..0ca495f --- /dev/null +++ b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/ListComponentCommand.py @@ -0,0 +1,140 @@ +# ## +# Copyright 2016-2021 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 -*- +""" List Component Command for rdmc """ + +try: + from rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class ListComponentCommand: + """Main download command class""" + + def __init__(self): + self.ident = { + "name": "listcomp", + "usage": None, + "description": "Run to list the components of " "the currently logged in system.\n\texample: listcomp", + "summary": "Lists components/binaries from the iLO Repository.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main listcomp worker function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.listcomponentvalidation(options) + + if self.rdmc.app.typepath.defs.isgen9: + raise IncompatibleiLOVersionError("iLO Repository commands are " "only available on iLO 5.") + + comps = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/ComponentRepository/") + + if comps: + self.printcomponents(comps, options) + else: + self.rdmc.ui.printer("No components found.\n") + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def printcomponents(self, comps, options): + """Print components function + + :param comps: list of components + :type comps: list. + """ + if options.json: + jsonout = dict() + for comp in comps: + jsonout[comp["Id"]] = comp + self.rdmc.ui.print_out_json(jsonout) + else: + for comp in comps: + self.rdmc.ui.printer( + "Id: %s\nName: %s\nVersion: %s\nLocked:%s\nComponent " + "Uri:%s\nFile Path: %s\nSizeBytes: %s\n\n" + % ( + comp["Id"], + comp["Name"], + comp["Version"], + "Yes" if comp["Locked"] else "No", + comp["ComponentUri"], + comp["Filepath"], + str(comp["SizeBytes"]), + ) + ) + + def listcomponentvalidation(self, options): + """listcomp validation function + + :param options: command line options + :type options: list. + """ + self.rdmc.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) diff --git a/ilorest/extensions/iLO_REPOSITORY_COMMANDS/MaintenanceWindowCommand.py b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/MaintenanceWindowCommand.py new file mode 100644 index 0000000..77c4041 --- /dev/null +++ b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/MaintenanceWindowCommand.py @@ -0,0 +1,326 @@ +# ## +# Copyright 2016-2021 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 -*- +""" Update Task Queue Command for rdmc """ + +import re +from argparse import RawDescriptionHelpFormatter +from random import randint + +from redfish.ris.rmc_helper import ValidationError + +try: + from rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + ) + +__subparsers__ = ["add", "delete"] + + +class MaintenanceWindowCommand: + """Main maintenancewindow command class""" + + def __init__(self): + self.ident = { + "name": "maintenancewindow", + "usage": None, + "description": "Add or delete maintenance windows from the iLO repository.\nTo " + " view help on specific sub-commands run: maintenancewindow -h\n\n" + "NOTE: iLO 5 required.", + "summary": "Manages the maintenance windows for iLO.", + "aliases": [], + "auxcommands": [], + } + + def run(self, line, help_disp=False): + """Main update maintenance window worker function + + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + ident_subparser = False + for cmnd in __subparsers__: + if cmnd in line: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + ident_subparser = True + break + if not ident_subparser: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line, default=True) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.maintenancewindowvalidation(options) + + if self.rdmc.app.typepath.defs.isgen9: + raise IncompatibleiLOVersionError("iLO Repository commands are only available on iLO 5.") + + windows = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/MaintenanceWindows/") + + if options.command.lower() == "add": + self.addmaintenancewindow(options, windows, options.time_window) + elif options.command.lower() == "delete": + self.deletemaintenancewindow(windows, options.identifier) + else: + self.listmainenancewindows(options, windows) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def addmaintenancewindow(self, options, windows, startafter): + """Add a maintenance window + + :param options: command line options + :type options: list. + :param windows: list of maintenance windows on the system + :type windows: list. + :param startafter: redfish date-time string to start the maintenance window + :type startafter: str. + """ + adddata = {"StartAfter": startafter} + + if options.name: + adddata["Name"] = options.name + else: + adddata["Name"] = "MW-%s" % str(randint(0, 1000000)) + + if options.description: + if options.description.startswith('"') and options.description.endswith('"'): + options.description = options.description[1:-1] + adddata["Description"] = options.description + + if options.expire: + adddata["Expire"] = options.expire + + errors = self.validatewindow(adddata, windows) + + if not errors: + path = "/redfish/v1/UpdateService/MaintenanceWindows/" + self.rdmc.app.post_handler(path, adddata) + else: + self.rdmc.ui.error("Invalid Maintenance Window:\n") + for error in errors: + self.rdmc.ui.error("\t" + error + "\n") + raise ValidationError("") + + def deletemaintenancewindow(self, windows, nameid): + """Delete a maintenance window by Id or Name + + :param windows: list of maintenance windows on the system + :type windows: list. + :param nameid: id or name string to remove + :type nameid: str. + """ + + deleted = False + for window in windows: + if window["Name"] == nameid or window["Id"] == nameid: + path = window["@odata.id"] + self.rdmc.ui.printer("Deleting %s\n" % nameid) + self.rdmc.app.delete_handler(path) + deleted = True + break + + if not deleted: + raise NoContentsFoundForOperationError("No maintenance window found with that Name/Id.") + + def listmainenancewindows(self, options, windows): + """Lists the maintenance windows + + :param options: command line options + :type options: list. + :param windows: list of maintenance windows on the system + :type windows: list. + """ + + outstring = "" + jsonwindows = [] + + if windows: + for window in windows: + if options.json: + jsonwindows.append(dict((key, val) for key, val in window.items() if "@odata." not in key)) + else: + outstring += "%s:" % window["Name"] + outstring += "%s:" % window["Id"] + if "Description" in list(window.keys()) and window["Description"]: + outstring += "\n\tDescription: %s" % window["Description"] + else: + outstring += "\n\tDescription: %s" % "No description." + outstring += "\n\tStart After: %s" % window["StartAfter"] + if "Expire" in list(window.keys()): + outstring += "\n\tExpires at: %s" % window["Expire"] + else: + outstring += "\n\tExpires at: %s" % "No expire time set." + outstring += "\n" + if jsonwindows: + self.rdmc.ui.print_out_json(jsonwindows) + else: + self.rdmc.ui.printer(outstring) + else: + self.rdmc.ui.warn("No maintenance windows found on system.\n") + + def validatewindow(self, cmw, windows): + """Validate the maintenance window before adding + + :param cmw: a maintenance window candidate + :type cmw: dict. + :param windows: list of maintenance windows on the system + :type windows: list. + :returns: returns a list of errors or a empty list if no errors + """ + errorlist = [] + rfdtregex = "\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ?" + + for window in windows: + if cmw["Name"] == window["Name"]: + errorlist.append("Maintenance window with Name: %s already exists." % cmw["Name"]) + + if "Name" in list(cmw.keys()): + if len(cmw["Name"]) > 64: + errorlist.append("Name must be 64 characters or less.") + if "Description" in list(cmw.keys()): + if len(cmw["Description"]) > 64: + errorlist.append("Description must be 64 characters or less.") + if "Expire" in list(cmw.keys()): + if not re.match(rfdtregex, cmw["Expire"]): + errorlist.append( + "Invalid redfish date-time format in Expire. " + "Accepted formats: YYYY-MM-DDThh:mm:ss, YYYY-MM-DDThh:mm:ssZ" + ) + if not re.match(rfdtregex, cmw["StartAfter"]): + errorlist.append( + "Invalid redfish date-time format in StartAfter. " + "Accepted formats YYYY-MM-DDThh:mm:ss, YYYY-MM-DDThh:mm:ssZ" + ) + + return errorlist + + def maintenancewindowvalidation(self, options): + """maintenencewindow validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + subcommand_parser = customparser.add_subparsers(dest="command") + + # default sub-parser + default_parser = subcommand_parser.add_parser( + "default", + help="Running without any sub-command will return all maintenace windows on the " + "currently logged in server.", + ) + default_parser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + self.cmdbase.add_login_arguments_group(default_parser) + + # add sub-parser + add_help = "Adds a maintenance window to iLO" + add_parser = subcommand_parser.add_parser( + "add", + help=add_help, + description=add_help + "\nexample: maintenancewindow add 1998-11-21T00:00:00 " + "--expire=1998-11-22T00:00:00 --name=MyMaintenanceWindow --description " + '"My maintenance window description.,"', + formatter_class=RawDescriptionHelpFormatter, + ) + add_parser.add_argument( + "time_window", + help="Specify the time window start period in DateTime format.\ni.e. YEAR-MONTH-DAY" + "THOUR:MINUTE:SECOND.\nexample: 1998-11-21T10:59:58", + metavar="TIMEWINDOW", + ) + add_parser.add_argument( + "--description", + dest="description", + help="Optionally include this flag if you would like to add a " + "description to the maintenance window you create", + default=None, + ) + add_parser.add_argument( + "--name", + dest="name", + help="Optionally include this flag if you would like to add a " + "name to the maintenance window you create. If you do not specify one" + " a unique name will be added.", + default=None, + ) + add_parser.add_argument( + "--expire", + dest="expire", + help="Optionally include this flag if you would like to add a " "time the maintenance window expires.", + default=None, + ) + self.cmdbase.add_login_arguments_group(add_parser) + + # delete sub-parser + delete_help = "Deletes the specified maintenance window on the currently logged in server." + delete_parser = subcommand_parser.add_parser( + "delete", + help=delete_help, + description=delete_help + "\nexample: maintenancewindow delete mymaintenancewindowname\n" + "Note: The maintenance window identifier can be referenced by Name or ID#." + "maintenancewindow delete name", + formatter_class=RawDescriptionHelpFormatter, + ) + delete_parser.add_argument( + "identifier", + help="The unique identifier provided by iLO or the identifier provided by '--name' " + "when the maintenance window was created.", + ) + self.cmdbase.add_login_arguments_group(delete_parser) diff --git a/ilorest/extensions/iLO_REPOSITORY_COMMANDS/MakeInstallSetCommand.py b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/MakeInstallSetCommand.py new file mode 100644 index 0000000..5a31d0a --- /dev/null +++ b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/MakeInstallSetCommand.py @@ -0,0 +1,307 @@ +# ## +# Copyright 2016-2021 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 -*- +""" Install Set Command for rdmc """ + +import json +import os + +from six.moves import input + +try: + from rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + ReturnCodes, + ) + + +class MakeInstallSetCommand: + """Command class to create installset payload""" + + def __init__(self): + self.ident = { + "name": "makeinstallset", + "usage": None, + "description": "Run to enter a guided shell for making " + "install sets. If not currently\n\tlogged into a server will perform " + "basic guidance on making an installset,\n\tif logged into a server " + "will provide guidance based on the current\n\tcomponents on the system. " + "If you wish to use this command on a logged in\n\tserver upload the " + "components before running for best results.", + "summary": "Creates install sets for iLO.", + "aliases": ["minstallset"], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + self.defaultprops = { + "UpdatableBy": ["Bmc"], + "Command": "ApplyUpdate", + "WaitTimeSeconds": 0, + "Filename": "", + } + self.helptext = { + "Command": "Possible Commands: ApplyUpdate, ResetServer, " "ResetBmc, Wait", + "UpdatableBy": "Possible Update parameter(s)" + ":\nBmc: Updatable by iLO\nUefi: Updatable by Uefi\n" + "RuntimeAgent: Updatable by runtime agent such as SUM/SUT", + "WaitTimeSeconds": "Number of seconds to pause in Wait " "command.", + "Filename": "Unique filename of component on " "iLO repository", + } + self.loggedin = None + self.comps = None + + def run(self, line, help_disp=False): + """Main installset worker function + + :param line: string of arguments passed in + :type line: str. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.loggedin = None + self.comps = None + self.loggedin = self.minstallsetvalidation() + + if self.loggedin and self.rdmc.app.typepath.defs.isgen9: + raise IncompatibleiLOVersionError("iLO Repository commands are " "only available on iLO 5.") + + self.rdmc.ui.warn("This command will run in interactive mode.\n") + if args: + raise InvalidCommandLineError("makeinstallset command takes no arguments.") + + self.minstallsetworker(options) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def minstallsetworker(self, options): + """Main installset creation worker + + :param options: command line options + :type options: list. + """ + comps = {} + totcomps = [] + count = -1 + totcount = 0 + self.rdmc.ui.warn("Entering new shell, type quit to leave!\n") + if self.loggedin: + self.rdmc.ui.printer("Running in logged in mode.") + self.comps = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/ComponentRepository/") + else: + self.rdmc.ui.printer("Running in basic guidance mode.") + while True: + comps = {} + count = -1 + reqdprops = ["Command"] + while True: + if len(reqdprops) <= count: + break + if count == -1: + line = input("\nEnter a name for this command: ") + else: + self.rdmc.ui.printer("\n" + self.helptext[reqdprops[count]] + "\n") + if self.loggedin and reqdprops[count].lower() == "filename": + filenm, updateby = self.checkfiles() + comps["Filename"] = filenm + comps["UpdatableBy"] = updateby + break + else: + line = input("Enter " + reqdprops[count] + " for " + comps["Name"] + ": ") + if line.endswith(os.linesep): + line.rstrip(os.linesep) + if line == "quit": + break + elif line == "" and not count == -1: + line = self.defaultprops[reqdprops[count]] + if count == -1: + comps["Name"] = line + else: + while True: + validated = self.validatepropvalue(reqdprops[count], line, reqdprops) + if not validated: + if line == "quit": + break + line = input( + "Input %s is not a valid property " "for %s. Try again: " % (line, reqdprops[count]) + ) + else: + comps[reqdprops[count]] = validated + break + count = count + 1 + if line == "quit": + break + else: + totcomps.append(comps) + totcount = totcount + 1 + + if not totcount: + self.rdmc.ui.warn("No sequences created. Exiting without creating an installset.\n") + else: + while True: + isrecovery = input("Is this a recovery installset? ") + isrecovery = True if str(isrecovery).lower() in ["true", "t", "yes", "y"] else isrecovery + isrecovery = False if str(isrecovery).lower() in ["false", "f", "no", "n"] else isrecovery + if not isinstance(isrecovery, bool): + self.rdmc.ui.warn("'Isrecovery' should be either true or false.\n") + continue + else: + break + + if isrecovery: + installsetname = "System Recovery Set" + else: + while True: + installsetname = input("Enter installset name: ") + if not installsetname: + self.rdmc.ui.warn("Install set must have a name.\n") + else: + break + + description = input("Enter description for the installset: ") + + body = { + "Name": installsetname, + "Description": description, + "IsRecovery": isrecovery, + "Sequence": totcomps, + } + + self.rdmc.ui.print_out_json(body) + with open(options.filename, "w") as outfile: + json.dump(body, outfile, indent=2, sort_keys=True) + + self.rdmc.ui.printer("installset saved to %s\n" % options.filename) + + def validatepropvalue(self, propvalue, givenvalue, reqdprops): + """Validates a string returning the correct type + + :param propvalue: property to validate + :type propvalue: string. + :param givenvalue: value to validate + :type givenvalue: string. + :param reqdprops: the required property list + :type reqdprops: list. + """ + validated_property = None + if propvalue == "WaitTimeSeconds": + validated_property = int(givenvalue) + elif propvalue == "UpdatableBy": + if isinstance(givenvalue, list): + validated_property = givenvalue + value = [x.strip() for x in givenvalue.split(",")] + for ind, item in enumerate(value): + if item.lower() == "runtimeagent": + value[ind] = "RuntimeAgent" + elif item.lower() == "uefi": + value[ind] = "Uefi" + elif item.lower() == "bmc": + value[ind] = "Bmc" + validated_property = value + elif propvalue == "Command": + if givenvalue.lower() == "applyupdate": + if self.loggedin and not self.comps: + self.rdmc.ui.printer("All components on the system are already " "added to the installset.\n") + else: + reqdprops.append("Filename") + reqdprops.append("UpdatableBy") + validated_property = "ApplyUpdate" + elif givenvalue.lower() == "resetserver": + validated_property = "ResetServer" + elif givenvalue.lower() == "resetbmc": + validated_property = "ResetBmc" + elif givenvalue.lower() == "wait": + reqdprops.append("WaitTimeSeconds") + validated_property = "Wait" + elif propvalue == "Filename": + if givenvalue: + validated_property = givenvalue + + return validated_property + + def checkfiles(self): + count = 0 + self.rdmc.ui.printer("Components currently in the repository that have not " "been added to the installset:\n") + for comp in self.comps: + count += 1 + self.rdmc.ui.printer("[%d] %s\n" % (count, comp["Name"])) + while True: + userinput = input("Select the number of the component you want to add to " "the install set: ") + try: + userinput = int(userinput) + if userinput > count or userinput == 0: + raise + break + except: + self.rdmc.ui.warn("Input is not a valid number.\n") + filename = self.comps[userinput - 1]["Filename"] + updatableby = self.comps[userinput - 1]["UpdatableBy"] + del self.comps[userinput - 1] + return filename, updatableby + + def minstallsetvalidation(self): + """makeinstallset validation function""" + + try: + _ = self.rdmc.app.current_client + loggedin = True + except: + loggedin = False + + return loggedin + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-f", + "--filename", + dest="filename", + help="Use this flag if you wish to use a different" + " filename than the default one. The default filename is" + " myinstallset.json", + default="myinstallset.json", + ) diff --git a/ilorest/extensions/iLO_REPOSITORY_COMMANDS/UpdateTaskQueueCommand.py b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/UpdateTaskQueueCommand.py new file mode 100644 index 0000000..263261c --- /dev/null +++ b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/UpdateTaskQueueCommand.py @@ -0,0 +1,391 @@ +# ## +# Copyright 2016-2023 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 -*- +""" Update Task Queue Command for rdmc """ + +from argparse import RawDescriptionHelpFormatter +from random import randint + +from redfish.ris.rmc_helper import IdTokenError + +try: + from rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + TaskQueueError, + LOGGER, + ) +except ImportError: + from ilorest.rdmc_helper import ( + IncompatibleiLOVersionError, + InvalidCommandLineErrorOPTS, + NoContentsFoundForOperationError, + ReturnCodes, + TaskQueueError, + LOGGER, + ) + +__subparsers__ = ["create"] + + +class UpdateTaskQueueCommand: + """Main download command class""" + + def __init__(self): + self.ident = { + "name": "taskqueue", + "usage": None, + "description": "Run to add or remove tasks from the task queue. Added tasks are " + "appended to the end of the queue. Note: iLO 5 required.\n" + "Example:\n\ttaskqueue create 30\n\t" + "taskqueue create \n\t" + "taskqueue --cleanqueue\n\t" + "taskqueue --resetqueue\n\t" + "taskqueue\n", + "summary": "Manages the update task queue for iLO.", + "aliases": [], + "auxcommands": [], + } + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Main update task queue worker function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + line.append("-h") + try: + (_, _) = self.rdmc.rdmc_parse_arglist(self, line) + except: + return ReturnCodes.SUCCESS + return ReturnCodes.SUCCESS + try: + ident_subparser = False + for cmnd in __subparsers__: + if cmnd in line: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line) + ident_subparser = True + break + if not ident_subparser: + (options, args) = self.rdmc.rdmc_parse_arglist(self, line, default=True) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.updatetaskqueuevalidation(options) + + if self.rdmc.app.typepath.defs.isgen9: + raise IncompatibleiLOVersionError("iLO Repository commands are only available on iLO 5.") + + if options.command.lower() == "create": + self.createtask(options.keywords, options) + else: + if options.resetqueue: + self.resetqueue(options) + elif options.cleanqueue: + self.cleanqueue() + self.printqueue(options) + + self.cmdbase.logout_routine(self, options) + # Return code + return ReturnCodes.SUCCESS + + def resetqueue(self, options): + """Deletes everything in the update task queue""" + tasks = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/UpdateTaskQueue/") + if not tasks: + self.rdmc.ui.printer("No tasks found.\n") + + self.rdmc.ui.printer("Deleting all update tasks...\n") + + for task in tasks: + self.rdmc.ui.printer("Deleting: %s\n" % task["Name"]) + if "RecoveryPrivilege" in task and task["RecoveryPrivilege"]: + self.rdmc.ui.printer("This task is associated with updating recovery set\n") + if not (options.user and options.password): + raise TaskQueueError( + "Deleting this task needs " + "--user and --password options, delete failed\n" + ) + if "blobstore" in self.rdmc.app.current_client.base_url: + LOGGER.info("Logging out of the session without user and password") + self.rdmc.app.current_client.logout() + LOGGER.info("Logging in with user and password for deleting system recovery set") + self.rdmc.app.current_client.login(self.rdmc.app.current_client.auth_type) + self.rdmc.app.delete_handler(task["@odata.id"]) + LOGGER.info("Logging out of the session with user and password") + self.rdmc.app.current_client.logout() + LOGGER.info("Restoring old Logging in without user and password to restore") + options.user = None + options.password = None + self.rdmc.app.current_client.login(self.rdmc.app.current_client.auth_type) + else: + self.rdmc.app.delete_handler(task["@odata.id"]) + else: + self.rdmc.app.delete_handler(task["@odata.id"]) + + + def cleanqueue(self): + """Deletes all finished or errored tasks in the update task queue""" + tasks = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/UpdateTaskQueue/") + if not tasks: + self.rdmc.ui.printer("No tasks found.\n") + + self.rdmc.ui.printer("Cleaning update task queue...\n") + + for task in tasks: + if task["State"] == "Complete" or task["State"] == "Exception": + self.rdmc.ui.printer("Deleting %s...\n" % task["Name"]) + self.rdmc.app.delete_handler(task["@odata.id"]) + + def createtask(self, tasks, options): + """Creates a task in the update task queue + + :param tasks: arguments for creating tasks + :type tasks: list. + :param options: command line options + :type options: list. + """ + + tpmflag = None + + path = "/redfish/v1/UpdateService/UpdateTaskQueue/" + comps = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/" "ComponentRepository/") + curr_tasks = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/UpdateTaskQueue/") + for task in tasks: + usedcomp = None + newtask = None + + try: + usedcomp = int(task) + newtask = { + "Name": "Wait-%s %s seconds" % (str(randint(0, 1000000)), str(usedcomp)), + "Command": "Wait", + "WaitTimeSeconds": usedcomp, + "UpdatableBy": ["Bmc"], + } + except ValueError: + pass + + if task.lower() == "reboot": + newtask = { + "Name": "Reboot-%s" % str(randint(0, 1000000)), + "Command": "ResetServer", + "UpdatableBy": ["RuntimeAgent"], + } + elif not newtask: + if tpmflag is None: + if options.tover: + tpmflag = True + else: + tpmflag = False + # TODO: Update to monolith check + results = self.rdmc.app.get_handler(self.rdmc.app.typepath.defs.biospath, silent=True) + if results.status == 200: + contents = results.dict if self.rdmc.app.typepath.defs.isgen9 else results.dict["Attributes"] + tpmstate = contents["TpmState"] + if "Enabled" in tpmstate and not tpmflag: + raise IdTokenError("") + + for curr_task in curr_tasks: + if ( + "Filename" in curr_task + and curr_task["Filename"] == task + and curr_task["State"].lower() != "exception" + ): + raise TaskQueueError( + "This file already has a task queue for flashing " + "associated with it. Reset the taskqueue and " + "retry if you need to add this task again." + ) + for comp in comps: + if comp["Filename"] == task: + usedcomp = comp + break + + if not usedcomp: + raise NoContentsFoundForOperationError( + "Component " "referenced is not present on iLO Drive: %s" % task + ) + + newtask = { + "Name": "Update-%s %s" + % ( + str(randint(0, 1000000)), + usedcomp["Name"].encode("ascii", "ignore"), + ), + "Command": "ApplyUpdate", + "Filename": usedcomp["Filename"], + "UpdatableBy": usedcomp["UpdatableBy"], + "TPMOverride": tpmflag, + } + + self.rdmc.ui.printer('Creating task: "%s"\n' % newtask["Name"]) + + self.rdmc.app.post_handler(path, newtask) + + def printqueue(self, options): + """Prints the update task queue + + :param options: command line options + :type options: list. + """ + tasks = self.rdmc.app.getcollectionmembers("/redfish/v1/UpdateService/UpdateTaskQueue/") + if not tasks: + self.rdmc.ui.printer("No tasks found.\n") + return + + if not options.json: + self.rdmc.ui.printer("\nCurrent Update Task Queue:\n\n") + + if not options.json: + for task in tasks: + self.rdmc.ui.printer("Task %s:\n" % task["Name"]) + if "Filename" in list(task.keys()): + self.rdmc.ui.printer( + "\tCommand: %s\n\tFilename: %s\n\t" + "State:%s\n" % (task["Command"], task["Filename"], task["State"]) + ) + elif "WaitTimeSeconds" in list(task.keys()): + self.rdmc.ui.printer( + "\tCommand: %s %s seconds\n\tState:%s\n" + % (task["Command"], str(task["WaitTimeSeconds"]), task["State"]) + ) + else: + self.rdmc.ui.printer("\tCommand:%s\n\tState: %s\n" % (task["Command"], task["State"])) + elif options.json: + outjson = dict() + for task in tasks: + outjson[task["Name"]] = dict() + outjson[task["Name"]]["Command"] = task["Command"] + if "Filename" in task: + outjson[task["Name"]]["Filename"] = task["Filename"] + if "WaitTimeSeconds" in task: + outjson[task["Name"]]["WaitTimeSeconds"] = task["WaitTimeSeconds"] + outjson[task["Name"]]["State"] = task["State"] + self.rdmc.ui.print_out_json(outjson) + + def updatetaskqueuevalidation(self, options): + """taskqueue validation function + + :param options: command line options + :type options: list. + """ + self.cmdbase.login_select_validation(self, options) + + @staticmethod + def options_argument_group(parser): + """Define optional arguments group + + :param parser: The parser to add the --addprivs option group to + :type parser: ArgumentParser/OptionParser + """ + group = parser.add_argument_group( + "GLOBAL OPTIONS", + "Options are available for all " "arguments within the scope of this command.", + ) + + group.add_argument( + "--tpmover", + dest="tover", + action="store_true", + help="If set then the TPMOverrideFlag is passed in on the " "associated flash operations", + default=False, + ) + + def definearguments(self, customparser): + """Wrapper function for new command main function + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + subcommand_parser = customparser.add_subparsers(dest="command") + + default_parser = subcommand_parser.add_parser( + "default", + help="Running without any sub-command will return the current task \n" + "queue information on the currently logged in server.", + ) + default_parser.add_argument( + "-r", + "--resetqueue", + action="store_true", + dest="resetqueue", + help="Remove all update tasks in the queue.\n\texample: taskqueue --resetqueue or taskqueue -r", + default=False, + ) + default_parser.add_argument( + "-c", + "--cleanqueue", + action="store_true", + dest="cleanqueue", + help="Clean up all finished or errored tasks left pending.\n\texample: taskqueue " + "--cleanqueue or taskqueue -c", + default=False, + ) + default_parser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + self.cmdbase.add_login_arguments_group(default_parser) + self.options_argument_group(default_parser) + + # create + create_help = "Create a new task queue task." + create_parser = subcommand_parser.add_parser( + "create", + help=create_help, + description=create_help + "\n\n\tCreate a new task for 30 secs:\n\t\ttaskqueue " + "create 30\n\n\tCreate a new reboot task.\n\t\ttaskqueue create reboot" + "\n\n\tCreate a new component task.\n\t\ttaskqueue create compname.exe" + "\n\n\tCreate multiple tasks at once.\n\t\ttaskqueue create 30 " + '"compname.exe compname2.exe reboot"', + formatter_class=RawDescriptionHelpFormatter, + ) + create_parser.add_argument( + "keywords", + help="Keyword for a task queue item. *Note*: Multiple tasks can be created by " + "using quotations wrapping all tasks, delimited by whitespace.", + metavar="KEYWORD", + type=str, + nargs="+", + default="", + ) + self.cmdbase.add_login_arguments_group(create_parser) + self.options_argument_group(create_parser) + diff --git a/ilorest/extensions/iLO_REPOSITORY_COMMANDS/UploadComponentCommand.py b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/UploadComponentCommand.py new file mode 100644 index 0000000..f620101 --- /dev/null +++ b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/UploadComponentCommand.py @@ -0,0 +1,793 @@ +# ## +# Copyright 2016 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 -*- +""" Upload Component Command for rdmc """ + +import os +import json +import sys +import time +import shutil +from random import choice +from string import ascii_lowercase +import ctypes +from six.moves import input +from ctypes import ( + c_char_p, + c_void_p, + byref, + c_ubyte, + c_int, + c_uint32, + POINTER, + create_string_buffer, +) +from redfish.hpilo.rishpilo import ( + HpIloInitialError, + HpIloChifAccessDeniedError, + HpIloNoChifDriverError, +) +from redfish.hpilo.rishpilo import BlobReturnCodes +from redfish.hpilo.risblobstore2 import BlobStore2 + +try: + from rdmc_helper import ( + LOGGER, + ReturnCodes, + InvalidCommandLineErrorOPTS, + UploadError, + IncompatibleiLOVersionError, + TimeOutError, + InvalidFileInputError, + DeviceDiscoveryInProgress, + FirmwareUpdateError, + ) +except ImportError: + from ilorest.rdmc_helper import ( + LOGGER, + ReturnCodes, + InvalidCommandLineErrorOPTS, + Encryption, + UploadError, + InvalidCommandLineError, + IncompatibleiLOVersionError, + TimeOutError, + InvalidFileInputError, + DeviceDiscoveryInProgress, + FirmwareUpdateError, + ) + + +def human_readable_time(seconds): + """Returns human readable time + + :param seconds: Amount of seconds to parse. + :type seconds: string. + """ + seconds = int(seconds) + hours = seconds / 3600 + seconds = seconds % 3600 + minutes = seconds / 60 + seconds = seconds % 60 + + return "{:02.0f} hour(s) {:02.0f} minute(s) {:02.0f} second(s) ".format(hours, minutes, seconds) + + +class UploadComponentCommand: + """Constructor""" + + def __init__(self): + self.ident = { + "name": "uploadcomp", + "usage": None, + "description": "Run to upload the component on " + "to iLO Repository\n\n\tUpload component to the iLO " + "repository.\n\texample: uploadcomp --component " + "--compsig \n\n\tFlash the component " + "instead of add to the iLO repository.\n\texample: " + "uploadcomp --component --update_target " + "--update_repository", + "summary": "Upload components/binary to the iLO Repository.", + "aliases": [], + "auxcommands": ["FwpkgCommand"], + } + + self.cmdbase = None + self.rdmc = None + self.auxcommands = dict() + + def run(self, line, help_disp=False): + """Wrapper function for upload command main function + + :param line: string of arguments passed in + :type line: str. + :param help_disp: display help flag + :type line: bool. + """ + if help_disp: + self.parser.print_help() + return ReturnCodes.SUCCESS + try: + (options, _) = self.rdmc.rdmc_parse_arglist(self, line) + except (InvalidCommandLineErrorOPTS, SystemExit): + if ("-h" in line) or ("--help" in line): + return ReturnCodes.SUCCESS + else: + raise InvalidCommandLineErrorOPTS("") + + self.uploadcommandvalidation(options) + fwpkg = False + if ( + options.component.endswith(".fwpkg") + or options.component.endswith(".fup") + or options.component.endswith(".hpb") + ): + fwpkg = True + comp, loc, ctype, pldmfw = self.auxcommands["flashfwpkg"].preparefwpkg(self, options.component) + # if pldm firmware + if pldmfw: + path = self.rdmc.app.typepath.defs.systempath + results = self.rdmc.app.get_handler(path, service=True, silent=True).dict + device_discovery_status = results["Oem"]["Hpe"]["DeviceDiscoveryComplete"]["DeviceDiscovery"] + LOGGER.info("Device Discovery Status is {}".format(device_discovery_status)) + # check for device discovery + if ("DeviceDiscoveryComplete" not in device_discovery_status): + raise DeviceDiscoveryInProgress( + "Device Discovery in progress...Please retry flashing firmware after 10 minutes" + ) + if ctype in ["C", "BC"]: + options.component = comp[0] + # else: + # options.component = os.path.join(loc, comp[0]) + + if self.rdmc.app.typepath.defs.isgen9: + raise IncompatibleiLOVersionError("iLO Repository commands are only available on iLO 5.") + + filestoupload = self._check_and_split_files(options) + validation = self.componentvalidation(options, filestoupload) + if validation: + start_time = time.time() + ret = ReturnCodes.FAILED_TO_UPLOAD_COMPONENT + + # ret = self.uploadfunction(filestoupload, options) + if "blobstore" in self.rdmc.app.current_client.base_url: + ret = self.uploadlocally(filestoupload, options) + else: + ret = self.uploadfunction(filestoupload, options) + + if ret == ReturnCodes.SUCCESS and not options.update_target: + self.rdmc.ui.printer("Uploading took %s\n" % human_readable_time(time.time() - start_time)) + + if len(filestoupload) > 1: + path, _ = os.path.split((filestoupload[0])[1]) + shutil.rmtree(path) + elif fwpkg: + if os.path.exists(loc): + shutil.rmtree(loc) + elif not validation and options.forceupload: + self.rdmc.ui.printer("Component " +filestoupload[0][0] +" is already present in repository ,Hence skipping the re upload\n") + ret= ReturnCodes.SUCCESS + else: + ret = ReturnCodes.FAILED_TO_UPLOAD_COMPONENT + self.cmdbase.logout_routine(self, options) + # Return code + return ret + + def componentvalidation(self, options, filelist): + """Check for duplicate component in repository + + :param options: command line options + :type options: list. + :param filelist: list of files to be uploaded (multiple files will be + generated for items over 32K in size) + :type filelist: list of strings + """ + validation = True + prevfile = None + + path = "/redfish/v1/UpdateService/ComponentRepository/?$expand=." + results = self.rdmc.app.get_handler(path, service=True, silent=True) + + results = results.dict + + if "Members" in results and results["Members"]: + for comp in results["Members"]: + for filehndl in filelist: + if ( + comp["Filename"].upper() == str(filehndl[0]).upper() + and not options.forceupload + and prevfile != filehndl[0].upper() + ): + ans = input( + "A component with the same name (%s) has " + "been found. Would you like to upload and " + "overwrite this file? (y/n)" % comp["Filename"] + ) + + while ans.lower() != "n" and ans.lower() != "y": + ans = input("Please enter valid option (y/n)") + + if ans.lower() == "n": + self.rdmc.ui.printer( + "Error: Upload stopped by user due to filename conflict." + " If you would like to bypass this check include the" + ' "--forceupload" flag.\n' + ) + validation = False + break + elif ( + comp["Filename"].upper() == str(filehndl[0]).upper() + and options.forceupload + and prevfile != filehndl[0].upper() + ): + options.update_repository = True + validation = False + break + if options.update_repository: + if ( + comp["Filename"].upper() == str(filehndl[0]).upper() + and prevfile != filehndl[0].upper() + and comp["Locked"] + ): + self.rdmc.ui.printer( + "Error: Component is currently locked by a taskqueue task or " + "installset. Remove any installsets or taskqueue tasks " + "containing the file and try again OR use taskqueue command " + "to put the component to installation queue\n" + ) + validation = False + break + elif ( + comp["Filename"].upper() == str(filehndl[0]).upper() + and prevfile != filehndl[0].upper() + and comp["Locked"] + and options.forceupload + ): + validation = False + break + prevfile = str(comp["Filename"].upper()) + return validation + + def _check_and_split_files(self, options): + """Check and split the file to upload on to iLO Repository + + :param options: command line options + :type options: list. + """ + + def check_file_rw(filename, rw): + try: + fd = open(filename, rw) + fd.close() + except IOError: + raise InvalidFileInputError("The file '%s' could not be opened for upload" % filename) + + maxcompsize = 32 * 1024 * 1024 + filelist = [] + + # Lets get the component filename + _, filename = os.path.split(options.component) + check_file_rw(os.path.normpath(options.component), "r") + self.rdmc.ui.printer("Successfully checked '%s'.\n" % filename) + size = os.path.getsize(options.component) + + # This is to upload the binary directly to flash scenario + if not options.componentsig: + if not self.findcompsig(filename): + return [(filename, options.component, options.componentsig, 0)] + + if size > maxcompsize: + self.rdmc.ui.printer("Component is more than 32MB in size.\n") + self.rdmc.ui.printer("Component size = %s\n" % str(size)) + section = 1 + + sigpath, _ = os.path.split(options.componentsig) + check_file_rw(os.path.normpath(options.componentsig), "r") + filebasename = filename[: filename.rfind(".")] + tempfoldername = "bmn" + "".join(choice(ascii_lowercase) for i in range(12)) + + if self.rdmc.app.cache: + tempdir = os.path.join(self.rdmc.app.cachedir, tempfoldername) + else: + tempdir = os.path.join(sys.executable, tempfoldername) + + self.rdmc.ui.printer("Spliting component. Temporary " "cache directory at %s\n" % tempdir) + + if not os.path.exists(tempdir): + os.makedirs(tempdir) + + with open(options.component, "rb") as component: + while True: + data = component.read(maxcompsize) + if len(data) != 0: + sectionfilename = filebasename + "_part" + str(section) + sectionfilepath = os.path.join(tempdir, sectionfilename) + + sectioncompsigpath = os.path.join(sigpath, sectionfilename + ".compsig") + sigfullpath = os.path.join(tempdir, sigpath) + if not os.path.exists(sigfullpath): + os.makedirs(sigfullpath) + writefile = open(sectionfilepath, "wb") + writefile.write(data) + writefile.close() + + item = ( + filename, + sectionfilepath, + sectioncompsigpath, + section - 1, + ) + + filelist.append(item) + section += 1 + + if len(data) != maxcompsize: + break + + return filelist + else: + return [(filename, options.component, options.componentsig, 0)] + + def uploadfunction(self, filelist, options=None): + """Main upload command worker function + + :param filelist: List of files to upload. + :type filelist: list. + :param options: command line options + :type options: list. + """ + + # returns a tuple with the state and the result dict + state, result = self.get_update_service_state() + + if state != "COMPLETED" and state != "COMPLETE" and state != "ERROR" and state != "IDLE": + self.rdmc.ui.error("iLO UpdateService is busy. Please try again.") + + return ReturnCodes.UPDATE_SERVICE_BUSY + + sessionkey = self.rdmc.app.current_client.session_key + + etag = "" + hpe = result["Oem"]["Hpe"] + urltosend = "/cgi-bin/uploadFile" + + if "PushUpdateUri" in hpe: + urltosend = hpe["PushUpdateUri"] + elif "HttpPushUri" in result: + urltosend = result["HttpPushUri"] + else: + return ReturnCodes.FAILED_TO_UPLOAD_COMPONENT + + for item in filelist: + ilo_upload_filename = item[0] + + ilo_upload_compsig_filename = ilo_upload_filename[: ilo_upload_filename.rfind(".")] + ".compsig" + + componentpath = item[1] + compsigpath = item[2] + + _, filename = os.path.split(componentpath) + + if not etag: + etag = "sum" + filename.replace(".", "") + etag = etag.replace("-", "") + etag = etag.replace("_", "") + + section_num = item[3] + if isinstance(sessionkey, bytes): + sessionkey = sessionkey.decode("utf-8") + + parameters = { + "UpdateRepository": options.update_repository, + "UpdateTarget": options.update_target, + "ETag": etag, + "Section": section_num, + "UpdateRecoverySet": options.update_srs, + "TPMOverride": options.tover, + } + + data = [("sessionKey", sessionkey), ("parameters", json.dumps(parameters))] + + if not compsigpath: + compsigpath = self.findcompsig(componentpath) + if compsigpath: + with open(compsigpath, "rb") as fle: + output = fle.read() + data.append( + ( + "compsig", + ( + ilo_upload_compsig_filename, + output, + "application/octet-stream", + ), + ) + ) + output = None + + with open(componentpath, "rb") as fle: + output = fle.read() + data.append(("file", (ilo_upload_filename, output, "application/octet-stream"))) + + self.rdmc.ui.printer("Uploading component " + filename + ".\n") + res = self.rdmc.app.post_handler( + str(urltosend), + data, + headers={"Cookie": "sessionKey=" + sessionkey}, + silent=False, + service=False, + ) + + if res.status == 400 and res.dict is None: + self.rdmc.ui.error("Component " + filename + " was not uploaded , iLO returned 400 error code. Check if the user has all privileges to perform the operation.\n") + return ReturnCodes.FAILED_TO_UPLOAD_COMPONENT + + if res.status != 200: + return ReturnCodes.FAILED_TO_UPLOAD_COMPONENT + else: + self.rdmc.ui.printer("Component " + filename + " uploading successfully.\n") + + if not self.wait_for_state_change(): + # Failed to upload the component. + raise UploadError("Error while processing the component.") + + return ReturnCodes.SUCCESS + + def wait_for_state_change(self, wait_time=4800): + """Wait for the iLO UpdateService to a move to terminal state. + :param options: command line options + :type options: list. + :param wait_time: time to wait on upload + :type wait_time: int. + """ + total_time = 0 + spinner = ["|", "/", "-", "\\"] + state = "" + self.rdmc.ui.printer("Waiting for iLO UpdateService to finish processing the component\n") + + while total_time < wait_time: + state, _ = self.get_update_service_state() + + if state == "ERROR": + return False + elif state != "COMPLETED" and state != "IDLE" and state != "COMPLETE": + # Lets try again after 8 seconds + count = 0 + + # fancy spinner + while count <= 32: + self.rdmc.ui.printer("Updating: %s\r" % spinner[count % 4]) + time.sleep(0.25) + count += 1 + + total_time += 8 + else: + break + + if total_time > wait_time: + raise TimeOutError("UpdateService in " + state + " state for " + str(wait_time) + "s") + + return True + + def get_update_service_state(self): + """Get the current UpdateService state + + :param options: command line options + :type options: list. + """ + path = "/redfish/v1/UpdateService" + results = self.rdmc.app.get_handler(path, service=True, silent=True) + + if results and results.status == 200 and results.dict: + output = results.dict + + if self.rdmc.opts.verbose: + self.rdmc.ui.printer("UpdateService state = " + (output["Oem"]["Hpe"]["State"]).upper() + "\n") + + return (output["Oem"]["Hpe"]["State"]).upper(), results.dict + else: + return "UNKNOWN", {} + + def findcompsig(self, comppath): + """Try to find compsig if not included + :param comppath: Path of file to find compsig for. + :type comppath: str. + """ + compsig = "" + + cutpath = comppath.split(os.sep) + _file = cutpath[-1] + _file_rev = _file[::-1] + filename = _file[: ((_file_rev.find(".")) * -1) - 1] + + try: + location = os.sep.join(cutpath[:-1]) + except: + location = os.curdir + + if not location: + location = os.curdir + + files = [f for f in os.listdir(location) if os.path.isfile(os.path.join(location, f))] + + for filehndl in files: + if filehndl.startswith(filename) and filehndl.endswith(".compsig"): + self.rdmc.ui.printer("Compsig found for file.\n") + + if location != ".": + compsig = location + os.sep + filehndl + else: + compsig = filehndl + + break + + return compsig + + def uploadlocally(self, filelist, options=None): + """Upload component locally + + :param filelist: List of files to upload. + :type filelist: list. + :param options: command line options + :type options: list. + """ + new_chif_needed = False + upload_failed = False + if not options.update_target: + options.upload_srs = False + + if options.update_srs: + if options.user and options.password: + new_chif_needed = True + else: + self.rdmc.ui.error( + "ERROR: --update_srs option needs to be passed with " + "--username and --password options, upload failed\n" + ) + return ReturnCodes.FAILED_TO_UPLOAD_COMPONENT + try: + dll = self.rdmc.app.current_client.connection._conn.channel.dll + multiupload = False + + if new_chif_needed: + # Backup old chif channel + dll_bk = dll + dll = None + user = options.user + passwrd = options.password + dll, fhandle = self.create_new_chif_for_upload(user, passwrd) + self.rdmc.app.current_client.connection._conn.channel.dll = dll + + dll.uploadComponent.argtypes = [c_char_p, c_char_p, c_char_p, c_uint32] + dll.uploadComponent.restype = c_int + + for item in filelist: + ilo_upload_filename = item[0] + componentpath = item[1] + compsigpath = item[2] + + if not compsigpath: + compsigpath = self.findcompsig(componentpath) + + _, filename = os.path.split(componentpath) + + # 0x00000001 // FUM_WRITE_NAND + # 0x00000002 // FUM_USE_NAND + # 0x00000004 // FUM_NO_FLASH + # 0x00000008 // FUM_FORCE + # 0x00000010 // FUM_SIDECAR + # 0x00000020 // FUM_APPEND + # 0x40 // FUM_UPDATE_RECOVERY + # 0x00000080 //FUM_RECOVERY + # 0x00000100 // FUM_TASK + # 0x00000200 //FUM_RECO_PRIV + + if not compsigpath and options.update_target: + if not options.update_repository: + # Just update the firmware + if options.update_srs: + dispatchflag = ctypes.c_uint32(0x00000000 | 0x40) + else: + dispatchflag = ctypes.c_uint32(0x00000000) + else: + # Update the firmware and Upload to Repository + if options.update_srs: + dispatchflag = ctypes.c_uint32(0x00000000 | 0x00000001 | 0x40) + else: + dispatchflag = ctypes.c_uint32(0x00000000 | 0x00000001) + elif not compsigpath and not options.update_target and options.update_repository: + # uploading a secuare flash binary image onto the NAND + if options.update_srs: + dispatchflag = ctypes.c_uint32(0x00000001 | 0x00000004 | 0x40) + else: + dispatchflag = ctypes.c_uint32(0x00000001 | 0x00000004) + else: + # Uploading a component with a side car file. + if options.update_srs: + dispatchflag = ctypes.c_uint32(0x00000001 | 0x00000004 | 0x00000010 | 0x40) + else: + dispatchflag = ctypes.c_uint32(0x00000001 | 0x00000004 | 0x00000010) + + if multiupload: + # For second upload to append if the component is > 32MB in size + if options.update_srs: + dispatchflag = ctypes.c_uint32(0x00000001 | 0x00000004 | 0x00000010 | 0x00000020 | 0x40) + else: + dispatchflag = ctypes.c_uint32(0x00000001 | 0x00000004 | 0x00000010 | 0x00000020) + + self.rdmc.ui.printer("Uploading component " + filename + "\n") + + ret = dll.uploadComponent( + ctypes.create_string_buffer(compsigpath.encode("utf-8")), + ctypes.create_string_buffer(componentpath.encode("utf-8")), + ctypes.create_string_buffer(ilo_upload_filename.encode("utf-8")), + dispatchflag, + ) + + upload_failed = False + if ret != 0: + LOGGER.error("Component {} upload failed".format(filename)) + self.rdmc.ui.error("Component " + filename + " upload failed.\n") + upload_failed = True + else: + LOGGER.info("Component {} uploaded successfully".format(filename)) + self.rdmc.ui.printer("Component " + filename + " uploaded successfully.\n") + self.rdmc.ui.printer("[200] The operation completed successfully.\n") + if not self.wait_for_state_change(): + # Failed to upload the component. + raise UploadError("Error while processing the component.") + + multiupload = True + + if new_chif_needed: + dll.ChifTerminate() + dll.ChifClose(fhandle) + fhandle = None + BlobStore2.unloadchifhandle(dll) + # Restore old chif channel + dll = dll_bk + self.rdmc.app.current_client.connection._conn.channel.dll = dll_bk + LOGGER.info("Restored the old chif channel\n") + + except Exception as excep: + LOGGER.error("Exception occured, {}".format(excep)) + raise excep + + if upload_failed: + return ReturnCodes.FAILED_TO_UPLOAD_COMPONENT + else: + return ReturnCodes.SUCCESS + + def create_new_chif_for_upload(self, user, passwrd): + dll = BlobStore2.gethprestchifhandle() + dll.ChifInitialize(None) + # Enable Security Flag in Chif + dll.ChifEnableSecurity() + fhandle = c_void_p() + dll.ChifCreate.argtypes = [c_void_p] + dll.ChifCreate.restype = c_uint32 + status = dll.ChifCreate(byref(fhandle)) + if status != BlobReturnCodes.SUCCESS: + if status == BlobReturnCodes.CHIFERR_NoDriver: + raise HpIloNoChifDriverError( + "Error %s - No Chif Driver occurred while trying to create a channel." % status + ) + else: + raise HpIloInitialError("Error %s occurred while trying to create a channel." % status) + dll.initiate_credentials.argtypes = [c_char_p, c_char_p] + dll.initiate_credentials.restype = POINTER(c_ubyte) + usernew = create_string_buffer(user.encode("utf-8")) + passnew = create_string_buffer(passwrd.encode("utf-8")) + dll.initiate_credentials(usernew, passnew) + status = dll.ChifPing(fhandle) + if status != BlobReturnCodes.SUCCESS: + raise HpIloInitialError("Error %s occurred while trying to create a channel." % status) + dll.ChifSetRecvTimeout(fhandle, 60000) + credreturn = dll.ChifVerifyCredentials() + if not credreturn == BlobReturnCodes.SUCCESS: + if credreturn == BlobReturnCodes.CHIFERR_AccessDenied: + raise HpIloChifAccessDeniedError( + "Error %s - Chif Access Denied occurred while trying " + "to open a channel to iLO. Verify iLO Credetials passed." % credreturn + ) + else: + raise HpIloInitialError("Error %s occurred while trying " "to open a channel to iLO" % credreturn) + return dll, fhandle + + def uploadcommandvalidation(self, options): + """upload command method validation function + + :param options: command line options + :type options: list. + """ + self.rdmc.login_select_validation(self, options) + + def definearguments(self, customparser): + """Define command line argument for the upload command + + :param customparser: command line input + :type customparser: parser. + """ + if not customparser: + return + + self.cmdbase.add_login_arguments_group(customparser) + + customparser.add_argument( + "-j", + "--json", + dest="json", + action="store_true", + help="Optionally include this flag if you wish to change the" + " displayed output to JSON format. Preserving the JSON data" + " structure makes the information easier to parse.", + default=False, + ) + customparser.add_argument( + "--component", + dest="component", + help="""Component or binary file path to upload to the update service.""", + default="", + required=True, + ) + customparser.add_argument( + "--compsig", + dest="componentsig", + help="Component signature file path needed by iLO to authenticate the " + "component file. If not provided will try to find the " + "signature file from component file path.", + default="", + ) + customparser.add_argument( + "--forceupload", + dest="forceupload", + action="store_true", + help="Add this flag to force upload components with the same name already on the repository.", + default=False, + ) + customparser.add_argument( + "--update_repository", + dest="update_repository", + action="store_false", + help="Add this flag to skip uploading component/binary to the iLO Repository. If this " + "flag is included with --update_srs, it will be ignored. Adding component to the " + "repository is required to update the system reovery set.", + default=True, + ) + customparser.add_argument( + "--update_target", + dest="update_target", + action="store_true", + help="Add this flag if you wish to flash the component/binary.", + default=False, + ) + customparser.add_argument( + "--update_srs", + dest="update_srs", + action="store_true", + help="Add this flag to update the System Recovery Set with the uploaded component. " + "NOTE: This requires an account login with the system recovery set privilege.", + default=False, + ) + customparser.add_argument( + "--tpmover", + dest="tover", + action="store_true", + help="If set then the TPMOverrideFlag is passed in on the associated flash operations", + default=False, + ) + diff --git a/ilorest/extensions/iLO_REPOSITORY_COMMANDS/__init__.py b/ilorest/extensions/iLO_REPOSITORY_COMMANDS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ilorest/hpe.ico b/ilorest/hpe.ico new file mode 100644 index 0000000..4b53f1f Binary files /dev/null and b/ilorest/hpe.ico differ diff --git a/ilorest/iLORest.log b/ilorest/iLORest.log new file mode 100644 index 0000000..11dbd54 --- /dev/null +++ b/ilorest/iLORest.log @@ -0,0 +1,40 @@ +2023-12-01 15:34:58,559 INFO : Initializing no proxy. +2023-12-01 15:34:58,559 DEBUG : HTTP REQUEST: GET + PATH: /redfish/v1 + HEADERS: {'OData-Version': '4.0', 'Accept-Encoding': 'gzip'} + BODY: None +2023-12-01 15:34:58,559 INFO : Exception: TypeError(PoolKey.__new__() got an unexpected keyword argument 'key_log_dir') +2023-12-01 15:49:17,422 INFO : Initializing no proxy. +2023-12-01 15:49:17,423 DEBUG : HTTP REQUEST: GET + PATH: /redfish/v1 + HEADERS: {'OData-Version': '4.0', 'Accept-Encoding': 'gzip'} + BODY: None +2023-12-01 15:49:52,868 INFO : Exception: TypeError(PoolKey.__new__() got an unexpected keyword argument 'key_log_dir') +2023-12-01 15:55:39,458 INFO : Initializing no proxy. +2023-12-01 15:58:40,164 INFO : Initializing no proxy. +2023-12-01 15:58:40,164 DEBUG : HTTP REQUEST: GET + PATH: /redfish/v1 + HEADERS: {'OData-Version': '4.0', 'Accept-Encoding': 'gzip'} + BODY: None +2023-12-01 15:58:40,165 DEBUG : Starting new HTTPS connection (1): 10.132.144.205:443 +2023-12-01 15:58:40,200 DEBUG : Incremented Retry for (url='/redfish/v1'): Retry(total=9, connect=50, read=50, redirect=50, status=None) +2023-12-01 15:58:40,201 WARNING : Retrying (Retry(total=9, connect=50, read=50, redirect=50, status=None)) after connection broken by 'SSLError(SSLError(109, '[CONF: MODULE_INITIALIZATION_ERROR] module initialization error (_ssl.c:4004)'))': /redfish/v1 +2023-12-01 15:58:40,201 DEBUG : Starting new HTTPS connection (2): 10.132.144.205:443 +2023-12-01 15:59:01,338 INFO : Initializing no proxy. +2023-12-01 15:59:01,339 DEBUG : HTTP REQUEST: GET + PATH: /redfish/v1 + HEADERS: {'OData-Version': '4.0', 'Accept-Encoding': 'gzip'} + BODY: None +2023-12-01 15:59:01,339 DEBUG : Starting new HTTPS connection (1): 10.132.144.205:443 +2023-12-01 15:59:01,358 DEBUG : Incremented Retry for (url='/redfish/v1'): Retry(total=9, connect=50, read=50, redirect=50, status=None) +2023-12-01 15:59:01,359 WARNING : Retrying (Retry(total=9, connect=50, read=50, redirect=50, status=None)) after connection broken by 'SSLError(SSLError(109, '[CONF: MODULE_INITIALIZATION_ERROR] module initialization error (_ssl.c:4004)'))': /redfish/v1 +2023-12-01 15:59:01,359 DEBUG : Starting new HTTPS connection (2): 10.132.144.205:443 +2023-12-01 16:13:44,849 INFO : Initializing no proxy. +2023-12-01 16:13:44,849 DEBUG : HTTP REQUEST: GET + PATH: /redfish/v1 + HEADERS: {'OData-Version': '4.0', 'Accept-Encoding': 'gzip'} + BODY: None +2023-12-01 16:13:44,850 DEBUG : Starting new HTTPS connection (1): 10.132.144.205:443 +2023-12-01 16:13:44,863 DEBUG : Incremented Retry for (url='/redfish/v1'): Retry(total=9, connect=50, read=50, redirect=50, status=None) +2023-12-01 16:13:44,863 WARNING : Retrying (Retry(total=9, connect=50, read=50, redirect=50, status=None)) after connection broken by 'SSLError(SSLError(109, '[CONF: MODULE_INITIALIZATION_ERROR] module initialization error (_ssl.c:4004)'))': /redfish/v1 +2023-12-01 16:13:44,863 DEBUG : Starting new HTTPS connection (2): 10.132.144.205:443 diff --git a/ilorest/ilorest.sh b/ilorest/ilorest.sh new file mode 100644 index 0000000..35c52e1 --- /dev/null +++ b/ilorest/ilorest.sh @@ -0,0 +1,3 @@ +#!/bin/sh +SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +python3 $SCRIPT_PATH/rdmc.py $@ diff --git a/ilorest/rdmc.py b/ilorest/rdmc.py new file mode 100644 index 0000000..711f6b4 --- /dev/null +++ b/ilorest/rdmc.py @@ -0,0 +1,1210 @@ +### +# Copyright 2016-2021 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 -*- +""" +This is the main module for Redfish Utility which handles all of the CLI and UI interfaces +""" + +# ---------Imports--------- +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import collections +import copy +import ctypes +import errno +import glob +import importlib +import logging +import os +import shlex +import ssl +import sys +import traceback +from argparse import RawTextHelpFormatter +from builtins import open, str, super + +import six +from prompt_toolkit import PromptSession +from prompt_toolkit.auto_suggest import AutoSuggestFromHistory +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.shortcuts import CompleteStyle +from six.moves import input + +import redfish.hpilo +import redfish.rest.v1 +import redfish.ris +from argparse import ArgumentParser +import warnings + +try: + import cliutils + import versioning + import extensions + from config.rdmc_config import RdmcConfig + from rdmc_helper import HARDCODEDLIST + from rdmc_base_classes import RdmcCommandBase, RdmcOptionParser +except ModuleNotFoundError: + from ilorest import cliutils + from ilorest import versioning + from ilorest import extensions + from ilorest.config.rdmc_config import RdmcConfig + from ilorest.rdmc_helper import HARDCODEDLIST + from ilorest.rdmc_base_classes import RdmcCommandBase, RdmcOptionParser + +try: + from rdmc_helper import ( + LERR, + LOGGER, + LOUT, + UI, + AlreadyCloudConnectedError, + BirthcertParseError, + BootOrderMissingEntriesError, + CloudConnectFailedError, + CloudConnectTimeoutError, + CommandNotEnabledError, + ConfigurationFileError, + DeviceDiscoveryInProgress, + DownloadError, + Encryption, + FailureDuringCommitError, + FirmwareUpdateError, + IloLicenseError, + IncompatableServerTypeError, + IncompatibleiLOVersionError, + InfoMissingEntriesError, + InvalidCListFileError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + InvalidFileFormattingError, + InvalidFileInputError, + InvalidKeyError, + InvalidMSCfileInputError, + InvalidOrNothingChangedSettingsError, + InvalidPasswordLengthError, + MultipleServerConfigError, + NicMissingOrConfigurationError, + NoChangesFoundOrMadeError, + NoContentsFoundForOperationError, + NoCurrentSessionEstablished, + NoDifferencesFoundError, + PartitionMoutingError, + PathUnavailableError, + ProxyConfigFailedError, + RdmcError, + ResourceExists, + ReturnCodes, + StandardBlobErrorHandler, + TabAndHistoryCompletionClass, + TaskQueueError, + TfaEnablePreRequisiteError, + TimeOutError, + UnableToDecodeError, + UnabletoFindDriveError, + UploadError, + UsernamePasswordRequiredError, + iLORisCorruptionError, + ResourceNotReadyError, + InstallsetError, + ) +except ModuleNotFoundError: + from ilorest.rdmc_helper import ( + ReturnCodes, + RdmcError, + ConfigurationFileError, + CommandNotEnabledError, + InvalidCommandLineError, + InvalidCommandLineErrorOPTS, + UI, + LOGGER, + LERR, + LOUT, + InvalidFileFormattingError, + NoChangesFoundOrMadeError, + InvalidFileInputError, + NoContentsFoundForOperationError, + InfoMissingEntriesError, + MultipleServerConfigError, + InvalidOrNothingChangedSettingsError, + NoDifferencesFoundError, + InvalidMSCfileInputError, + InvalidPasswordLengthError, + FirmwareUpdateError, + DeviceDiscoveryInProgress, + BootOrderMissingEntriesError, + NicMissingOrConfigurationError, + StandardBlobErrorHandler, + NoCurrentSessionEstablished, + InvalidCListFileError, + FailureDuringCommitError, + IncompatibleiLOVersionError, + PartitionMoutingError, + TimeOutError, + DownloadError, + UploadError, + BirthcertParseError, + ResourceExists, + IncompatableServerTypeError, + IloLicenseError, + InvalidKeyError, + UnableToDecodeError, + UnabletoFindDriveError, + Encryption, + PathUnavailableError, + TaskQueueError, + UsernamePasswordRequiredError, + TabAndHistoryCompletionClass, + iLORisCorruptionError, + CloudConnectTimeoutError, + CloudConnectFailedError, + ProxyConfigFailedError, + AlreadyCloudConnectedError, + ResourceNotReadyError, + InstallsetError, + ) + +warnings.filterwarnings("ignore", category=DeprecationWarning) + +if os.name != "nt": + try: + import setproctitle + except ImportError: + pass + +# always flush stdout and stderr + +try: + CLI = cliutils.CLI() +except cliutils.ResourceAllocationError: + RdmcError("Unable to allocate more resources.") + RdmcError("ILOREST return code: %s\n" % ReturnCodes.RESOURCE_ALLOCATION_ISSUES_ERROR) + sys.exit(ReturnCodes.RESOURCE_ALLOCATION_ISSUES_ERROR) + +try: + # enable fips mode if our special functions are available in _ssl and OS is + # in FIPS mode + FIPSSTR = "" + # if Encryption.check_fips_mode_os(): + # LOGGER.info("FIPS mode is already enabled in openssl 3.0!") + if Encryption.check_fips_mode_os() and not Encryption.check_fips_mode_ssl(): + ssl.FIPS_mode_set(int(1)) + if ssl.FIPS_mode(): + FIPSSTR = "FIPS mode enabled using openssl version %s.\n" % ssl.OPENSSL_VERSION + LOGGER.info("FIPS mode enabled!") + else: + LOGGER.info("FIPS mode can not be enabled!") +except AttributeError: + pass + + +class RdmcCommand(RdmcCommandBase): + """Constructor""" + + def __init__(self, name, usage, summary, aliases, argparser, Args=None): + super().__init__(name, usage, summary, aliases, argparser) + self._commands = collections.OrderedDict() + self.ui = UI(1) + self.commands_dict = dict() + self.interactive = False + self._progname = "%s : %s" % (versioning.__shortname__, versioning.__longname__) + self.opts = None + self.encoding = None + self.config = RdmcConfig() + self.app = redfish.ris.RmcApp(showwarnings=True) + self.retcode = 0 + self.candidates = dict() + self.comm_map = dict() # point command id names or alias to handle + self.commlist = list() + self._redobj = None + self.log_dir = None + self.loaded_commands = [] + + # import all extensions dynamically + for name in extensions.classNames: + pkgName, cName = name.rsplit(".", 1) + pkgName = "extensions" + pkgName + try: + if "__pycache__" not in pkgName and "Command" in cName: + try: + self.commands_dict[cName] = getattr(importlib.import_module(pkgName, __package__), cName)() + except: + self.commands_dict[cName] = getattr( + importlib.import_module("ilorest." + pkgName, __package__), + cName, + )() + sName = pkgName.split(".")[1] + self.add_command(cName, section=sName) + except cliutils.ResourceAllocationError as excp: + self.ui.error(excp) + retcode = ReturnCodes.RESOURCE_ALLOCATION_ISSUES_ERROR + self.ui.error("Unable to allocate more resources.") + self.ui.printer(("ILOREST return code: %s\n" % retcode)) + sys.exit(retcode) + except Exception: + self.ui.error(("loading command: %s" % cName), None) + + # command mapping + commands_to_remove = [] + for command in self.commands_dict: + try: + self.comm_map[self.commands_dict[command].ident.get("name")] = command + for alias in self.commands_dict[command].ident.get("aliases"): + self.comm_map[alias] = command + except Exception as excp: + self.ui.command_not_enabled( + ("Command '%s' unable to be " "initialized...Removing" % command), + excp, + ) + commands_to_remove.append(command) + + # removing commands marked for deletion + for cmd in commands_to_remove: + del self.commands_dict[cmd] + del commands_to_remove + + # ---------End of imports--------- + + def add_command(self, command_name, section=None): + """Handles to addition of new commands + + :param command_name: command name + :type command_name: str. + :param section: section for the new command + :type section: str. + """ + if section not in self._commands: + self._commands[section] = list() + + self._commands[section].append(command_name) + + def get_commands(self): + """Retrieves dictionary of commands""" + return self._commands + + def search_commands(self, cmdname): + """Function to see if command exist in added commands + + :param cmdname: command to be searched + :type cmdname: str. + """ + + try: + tmp = self.comm_map.get(cmdname) + if not tmp: + tmp = cmdname + return self.commands_dict[tmp] + except KeyError: + raise cliutils.CommandNotFoundException(cmdname) + + def load_command(self, cmd): + """Fully Loads command and returns the class instance + + :param cmd: command identifier + :type opts: class + :returns: defined class instance + + """ + try: + cmd.cmdbase = RdmcCommandBase( + cmd.ident["name"], + cmd.ident["usage"], + cmd.ident["summary"], + cmd.ident["aliases"], + ) + cmd.parser = ArgumentParser( + prog=cmd.ident["name"], + usage=cmd.ident["usage"], + description=cmd.ident["description"], + formatter_class=RawTextHelpFormatter, + ) + cmd.rdmc = self + cmd.definearguments(cmd.parser) + for auxcmd in cmd.ident["auxcommands"]: + auxcmd = self.search_commands(auxcmd) + if auxcmd not in self.loaded_commands: + self.loaded_commands.append(auxcmd) + cmd.auxcommands[auxcmd.ident["name"]] = self.load_command(auxcmd) + else: + cmd.auxcommands[auxcmd.ident["name"]] = self.commands_dict[self.comm_map[auxcmd.ident["name"]]] + return cmd + except Exception as excp: + raise RdmcError("Unable to load command {}: {}".format(cmd.ident["name"], excp)) + + def _run_command(self, opts, args, help_disp): + """Calls the commands run function + + :param opts: command options + :type opts: options. + :param args: list of the entered arguments + :type args: list. + """ + cmd = self.search_commands(args[0]) + + self.load_command(cmd) + + if opts.debug: + LOGGER.setLevel(logging.DEBUG) + if not opts.nostdoutlog: + LOUT.setLevel(logging.DEBUG) + + for a in args: + if a == "-j" or a == "--json": + opts.nologo = True + break + if not opts.nologo and not self.interactive: + CLI.version(self._progname, versioning.__version__, versioning.__extracontent__) + if len(args) > 1: + return cmd.run(args[1:], help_disp) + + return cmd.run([], help_disp=help_disp) + + def run(self, line, help_disp=False): + """Main rdmc command worker function + + :param line: entered command line + :type line: list. + """ + if os.name == "nt": + if not ctypes.windll.shell32.IsUserAnAdmin() != 0: + self.app.typepath.adminpriv = False + elif not os.getuid() == 0: + self.app.typepath.adminpriv = False + + if "--version" in line or "-V" in line: + CLI.printer("%s %s\n" % (versioning.__longname__, versioning.__version__)) + sys.exit(self.retcode) + + help_disp = False + all_opts = True + help_indx = None + help_list = ["-h", "--help"] + for indx, elem in enumerate(line): + if elem in help_list: + help_indx = indx + if "-" in elem: + continue + else: + all_opts = False + if all_opts and (("-h" in line) or ("--help" in line)): + line = ["-h"] + elif help_indx: + # for comm in self.commands_dict: + # if self.commands_dict[comm].ident['name'] == line[0]: + # self.ui.printer(self.commands_dict[comm].ident['usage']) + # return ReturnCodes.SUCCESS + del line[help_indx] + help_disp = True + + if line and line[0] in ["-h", "--help"]: + cmddict = self.get_commands() + sorted_keys = sorted(list(cmddict.keys())) + + for key in sorted_keys: + if key[0] == "_": + continue + else: + self.parser.epilog = self.parser.epilog + "\n\n" + key + "\n" + for cmd in cmddict[key]: + c_help = "%-25s - %s\n" % ( + self.commands_dict[cmd].ident["name"], + self.commands_dict[cmd].ident["summary"], + ) + self.parser.epilog = self.parser.epilog + c_help + + (self.opts, nargv) = self.parser.parse_known_args(line) + + if self.opts.redirect: + try: + sys.stdout = open("console.log", "w") + except: + print("Unable to re-direct output for STDOUT.\n") + else: + print("Start of stdout file.\n\n") + try: + sys.stderr = open("console_err.log", "w") + except IOError: + print("Unable to re-direct output for STDERR.\n") + else: + print("Start of stderr file.\n\n") + + self.app.verbose = self.ui.verbosity = self.opts.verbose + + try: + # Test encoding functions are there + Encryption.encode_credentials("test") + self.app.set_encode_funct(Encryption.encode_credentials) + self.app.set_decode_funct(Encryption.decode_credentials) + self.encoding = True + except redfish.hpilo.risblobstore2.ChifDllMissingError: + self.encoding = False + + if self.opts.config is not None and len(self.opts.config) > 0: + if not os.path.isfile(self.opts.config): + self.retcode = ReturnCodes.CONFIGURATION_FILE_ERROR + sys.exit(self.retcode) + + self.config.configfile = self.opts.config + else: + # Default locations: Windows: executed directory Linux: /etc/ilorest/redfish.conf + self.config.configfile = ( + os.path.join(os.path.dirname(sys.executable), "redfish.conf") + if os.name == "nt" + else "/etc/ilorest/redfish.conf" + ) + + if not os.path.isfile(self.config.configfile): + LOGGER.warning("Config file '%s' not found\n\n", self.config.configfile) + + self.config.load() + + cachedir = None + if not self.opts.nocache: + self.config.cachedir = os.path.join(self.opts.config_dir, "cache") + cachedir = self.config.cachedir + + if cachedir: + self.app.cachedir = cachedir + try: + os.makedirs(cachedir) + except OSError as ex: + if ex.errno == errno.EEXIST: + pass + else: + raise + + if self.opts.logdir and self.opts.debug: + logdir = self.opts.logdir + else: + logdir = self.config.logdir + + if logdir and self.opts.debug: + try: + os.makedirs(logdir) + except OSError as ex: + if ex.errno == errno.EEXIST: + pass + else: + raise + self.log_dir = logdir + if self.opts.debug: + logfile = os.path.join(logdir, versioning.__shortname__ + ".log") + + # Create a file logger since we got a logdir + lfile = logging.FileHandler(filename=logfile) + formatter = logging.Formatter("%(asctime)s %(levelname)s\t: %(message)s") + + lfile.setFormatter(formatter) + lfile.setLevel(logging.DEBUG) + LOGGER.addHandler(lfile) + self.app.LOGGER = LOGGER + + if ("login" in line or any(x.startswith("--url") for x in line) or not line) and not ( + any(x.startswith(("-h", "--h")) for x in nargv) or "help" in line + ): + if not any(x.startswith("--sessionid") for x in line): + self.app.logout() + else: + creds, enc = self._pull_creds(nargv) + self.app.restore(creds=creds, enc=enc) + self.opts.is_redfish = self.app.typepath.updatedefinesflag(redfishflag=self.opts.is_redfish) + + if nargv: + try: + self.retcode = self._run_command(self.opts, nargv, help_disp) + if self.app.cache: + if ("logout" not in line) and ("--logout" not in line): + self.app.save() + self.app.redfishinst = None + else: + self.app.logout() + except AttributeError: + self.retcode = 0 + pass + except Exception as excp: + self.handle_exceptions(excp) + + return self.retcode + else: + self.cmdloop(self.opts) + + if self.app.cache: + self.app.save() + else: + self.app.logout() + + def cmdloop(self, opts): + """Interactive mode worker function + + :param opts: command options + :type opts: options. + """ + self.interactive = True + + if not opts.nologo: + sys.stdout.write(FIPSSTR) + CLI.version(self._progname, versioning.__version__, versioning.__extracontent__) + + if not self.app.typepath.adminpriv: + self.ui.user_not_admin() + + if opts.debug: + LOGGER.setLevel(logging.DEBUG) + LERR.setLevel(logging.DEBUG) + + for command, values in self.commands_dict.items(): + self.commlist.append(values.ident["name"]) + + for item in self.commlist: + if item == "help": + self.candidates[item] = self.commlist + else: + self.candidates[item] = [] + + self._redobj = TabAndHistoryCompletionClass(dict(self.candidates)) + + def bottom_toolbar(): + return HTML("Restful Interface Tool") + + try: + session = PromptSession( + completer=self._redobj, + auto_suggest=AutoSuggestFromHistory(), + complete_style=CompleteStyle.READLINE_LIKE, + complete_while_typing=True, + ) + session.output.disable_bracketed_paste() + except Exception as e: + print(e) + LOGGER.info("Console error: Tab complete is unavailable.") + session = None + if self.opts.notab: + LOGGER.info("No Tab Support: Tab complete is disabled.") + session = None + + while True: + try: + prompt_string = str(versioning.__shortname__) + " > " + if session: + if self.opts.toolbar: + line = session.prompt(prompt_string, bottom_toolbar=bottom_toolbar) + else: + line = session.prompt(prompt_string) + else: + line = input(prompt_string) + + except (EOFError, KeyboardInterrupt): + line = "quit\n" + + if not len(line): + continue + elif line.endswith(os.linesep): + line.rstrip(os.linesep) + + shlex.escape = "" + nargv = shlex.shlex(line, posix=True) + nargv.escape = "" + nargv.whitespace_split = True + nargv = list(nargv) + try: + if not ( + any(x.startswith("-h") for x in nargv) or any(x.startswith("--h") for x in nargv) or "help" in line + ): + if "login " in line or line == "login" or any(x.startswith("--url") for x in nargv): + self.app.logout() + self.retcode = self._run_command(opts, nargv, help_disp=False) + self.check_for_tab_lists(nargv) + except Exception as excp: + self.handle_exceptions(excp) + + if self.opts.verbose: + sys.stdout.write("iLOrest return code: %s\n" % self.retcode) + + return self.retcode + + def handle_exceptions(self, excp): + """Main exception handler for both shell and interactive modes + + :param excp: captured exception to be handled + :type excp: exception. + """ + # pylint: disable=redefined-argument-from-local + try: + if excp: + errorstr = "Exception: {0}".format(excp.__class__.__name__) + if excp.args: + errorstr = errorstr + "({0})".format(excp.args[0]) if hasattr(excp, "args") else errorstr + LOGGER.info(errorstr) + raise + # ****** RDMC ERRORS ****** + except ConfigurationFileError as excp: + self.retcode = ReturnCodes.CONFIGURATION_FILE_ERROR + self.ui.error(excp) + sys.exit(excp.errcode) + except InstallsetError as excp: + self.retcode = ReturnCodes.INSTALLSET_ERROR + self.ui.error(excp) + except InvalidCommandLineError as excp: + self.retcode = ReturnCodes.INVALID_COMMAND_LINE_ERROR + self.ui.invalid_commmand_line(excp) + except NoCurrentSessionEstablished as excp: + self.retcode = ReturnCodes.NO_CURRENT_SESSION_ESTABLISHED + self.ui.error(excp) + except TfaEnablePreRequisiteError as excp: + self.retcode = ReturnCodes.TFA_ENABLED_ERROR + self.ui.error(excp) + except iLORisCorruptionError as excp: + self.retcode = ReturnCodes.ILO_RIS_CORRUPTION_ERROR + self.ui.error(excp) + except ResourceNotReadyError as excp: + self.retcode = ReturnCodes.RESOURCE_NOT_READY_ERROR + self.ui.error(excp) + except CloudConnectTimeoutError as excp: + self.retcode = ReturnCodes.CLOUD_CONNECT_TIMEOUT + self.ui.error(excp) + except CloudConnectFailedError as excp: + self.retcode = ReturnCodes.CLOUD_CONNECT_FAILED + self.ui.error(excp) + except AlreadyCloudConnectedError as excp: + self.retcode = ReturnCodes.CLOUD_ALREADY_CONNECTED + self.ui.error(excp) + except ProxyConfigFailedError as excp: + self.retcode = ReturnCodes.PROXY_CONFIG_FAILED + self.ui.error(excp) + except UsernamePasswordRequiredError as excp: + self.retcode = ReturnCodes.USERNAME_PASSWORD_REQUIRED_ERROR + self.ui.error(excp) + except InvalidPasswordLengthError as excp: + self.retcode = ReturnCodes.INVALID_PASSWORD_LENGTH_ERROR + self.ui.error(excp) + except NoChangesFoundOrMadeError as excp: + self.retcode = ReturnCodes.NO_CHANGES_MADE_OR_FOUND + self.ui.invalid_commmand_line(excp) + except StandardBlobErrorHandler as excp: + self.retcode = ReturnCodes.GENERAL_ERROR + self.ui.standard_blob_error(excp) + except InvalidFileInputError as excp: + self.retcode = ReturnCodes.INVALID_FILE_INPUT_ERROR + self.ui.invalid_commmand_line(excp) + except InvalidCommandLineErrorOPTS as excp: + self.retcode = ReturnCodes.INVALID_COMMAND_LINE_ERROR + self.ui.invalid_commmand_line(excp) + except InvalidFileFormattingError as excp: + self.retcode = ReturnCodes.INVALID_FILE_FORMATTING_ERROR + self.ui.invalid_file_formatting(excp) + except NoContentsFoundForOperationError as excp: + self.retcode = ReturnCodes.NO_CONTENTS_FOUND_FOR_OPERATION + self.ui.no_contents_found_for_operation(excp) + except InfoMissingEntriesError as excp: + self.retcode = ReturnCodes.NO_VALID_INFO_ERROR + self.ui.error(excp) + except ( + InvalidOrNothingChangedSettingsError, + redfish.ris.rmc_helper.IncorrectPropValue, + ) as excp: + self.retcode = ReturnCodes.SAME_SETTINGS_ERROR + self.ui.error(excp) + except NoDifferencesFoundError as excp: + self.retcode = ReturnCodes.NO_CHANGES_MADE_OR_FOUND + self.ui.no_differences_found(excp) + except MultipleServerConfigError as excp: + self.retcode = ReturnCodes.MULTIPLE_SERVER_CONFIG_FAIL + self.ui.multiple_server_config_fail(excp) + except InvalidMSCfileInputError as excp: + self.retcode = ReturnCodes.MULTIPLE_SERVER_INPUT_FILE_ERROR + self.ui.multiple_server_config_input_file(excp) + except FirmwareUpdateError as excp: + self.retcode = ReturnCodes.FIRMWARE_UPDATE_ERROR + self.ui.error(excp) + except FailureDuringCommitError as excp: + self.retcode = ReturnCodes.FAILURE_DURING_COMMIT_OPERATION + self.ui.error(excp) + except BootOrderMissingEntriesError as excp: + self.retcode = ReturnCodes.BOOT_ORDER_ENTRY_ERROR + self.ui.error(excp) + except NicMissingOrConfigurationError as excp: + self.retcode = ReturnCodes.NIC_MISSING_OR_INVALID_ERROR + self.ui.error(excp) + except ( + IncompatibleiLOVersionError, + redfish.ris.rmc_helper.IncompatibleiLOVersionError, + ) as excp: + self.retcode = ReturnCodes.INCOMPATIBLE_ILO_VERSION_ERROR + self.ui.printer(excp) + except IncompatableServerTypeError as excp: + self.retcode = ReturnCodes.INCOMPATIBLE_SERVER_TYPE + self.ui.printer(excp) + except IloLicenseError as excp: + self.ui.printer(excp) + self.retcode = ReturnCodes.ILO_LICENSE_ERROR + except InvalidCListFileError as excp: + self.retcode = ReturnCodes.INVALID_CLIST_FILE_ERROR + self.ui.error(excp) + except PartitionMoutingError as excp: + self.retcode = ReturnCodes.UNABLE_TO_MOUNT_BB_ERROR + self.ui.error(excp) + except TimeOutError as excp: + self.retcode = ReturnCodes.UPDATE_SERVICE_BUSY + self.ui.error(excp) + except DownloadError as excp: + self.retcode = ReturnCodes.FAILED_TO_DOWNLOAD_COMPONENT + self.ui.error(excp) + except UploadError as excp: + self.retcode = ReturnCodes.FAILED_TO_UPLOAD_COMPONENT + self.ui.error(excp) + except BirthcertParseError as excp: + self.retcode = ReturnCodes.BIRTHCERT_PARSE_ERROR + self.ui.error(excp) + except ResourceExists as excp: + self.retcode = ReturnCodes.RESOURCE_EXISTS_ERROR + self.ui.error(excp) + except InvalidKeyError as excp: + self.retcode = ReturnCodes.ENCRYPTION_ERROR + self.ui.error("Invalid key has been entered for encryption/decryption.\n") + except UnableToDecodeError as excp: + self.retcode = ReturnCodes.ENCRYPTION_ERROR + self.ui.error(excp) + except UnabletoFindDriveError as excp: + self.retcode = ReturnCodes.DRIVE_MISSING_ERROR + self.ui.error(excp) + self.ui.printer("Error occurred while reading device labels.\n") + except PathUnavailableError as excp: + self.retcode = ReturnCodes.PATH_UNAVAILABLE_ERROR + if excp: + self.ui.error(excp) + else: + self.ui.printer("Requested path is unavailable.") + except TaskQueueError as excp: + self.retcode = ReturnCodes.TASKQUEUE_ERROR + self.ui.error(excp) + except DeviceDiscoveryInProgress as excp: + self.retcode = ReturnCodes.DEVICE_DISCOVERY_IN_PROGRESS + self.ui.error(excp) + # ****** CLI ERRORS ****** + except (CommandNotEnabledError, cliutils.CommandNotFoundException) as excp: + self.retcode = ReturnCodes.UI_CLI_COMMAND_NOT_FOUND_EXCEPTION + self.ui.command_not_found(excp) + # try: + # self.commands_dict['HelpCommand'].run('-h') + # except KeyError: + # pass + + # ****** RMC/RIS ERRORS ****** + except redfish.ris.UndefinedClientError: + self.retcode = ReturnCodes.RIS_UNDEFINED_CLIENT_ERROR + self.ui.error("Please login before making a selection.") + except ( + redfish.ris.InstanceNotFoundError, + redfish.ris.RisInstanceNotFoundError, + ) as excp: + self.retcode = ReturnCodes.RIS_INSTANCE_NOT_FOUND_ERROR + self.ui.printer(excp) + except redfish.ris.CurrentlyLoggedInError as excp: + self.retcode = ReturnCodes.RIS_CURRENTLY_LOGGED_IN_ERROR + self.ui.error(excp) + except redfish.ris.NothingSelectedError as excp: + self.retcode = ReturnCodes.RIS_NOTHING_SELECTED_ERROR + self.ui.nothing_selected() + except redfish.ris.NothingSelectedFilterError as excp: + self.retcode = ReturnCodes.RIS_NOTHING_SELECTED_FILTER_ERROR + self.ui.nothing_selected_filter() + except redfish.ris.NothingSelectedSetError as excp: + self.retcode = ReturnCodes.RIS_NOTHING_SELECTED_SET_ERROR + self.ui.nothing_selected_set() + except redfish.ris.InvalidSelectionError as excp: + self.retcode = ReturnCodes.RIS_INVALID_SELECTION_ERROR + self.ui.error(excp) + except redfish.ris.rmc_helper.UnableToObtainIloVersionError as excp: + self.retcode = ReturnCodes.INCOMPATIBLE_ILO_VERSION_ERROR + self.ui.error(excp) + except redfish.ris.IdTokenError as excp: + if hasattr(excp, "message"): + self.ui.printer(excp.message) + else: + self.ui.printer( + "Logged-in account does not have the privilege" + " required to fulfill the request or a required" + " token is missing." + "\nEX: biospassword flag if bios password present " + "or tpmenabled flag if TPM module present.\n" + ) + self.retcode = ReturnCodes.RIS_MISSING_ID_TOKEN + except redfish.ris.SessionExpired as excp: + self.retcode = ReturnCodes.RIS_SESSION_EXPIRED + self.app.logout() + self.ui.printer( + "Current session has expired or is invalid, " + "please login again with proper credentials to continue.\n" + ) + except redfish.ris.ValidationError as excp: + self.retcode = ReturnCodes.RIS_VALIDATION_ERROR + except redfish.ris.ValueChangedError as excp: + self.retcode = ReturnCodes.RIS_VALUE_CHANGED_ERROR + except redfish.ris.ris.SchemaValidationError as excp: + self.ui.printer("Error found in schema, try running with the " "--latestschema flag.\n") + self.retcode = ReturnCodes.RIS_SCHEMA_PARSE_ERROR + # ****** RMC/RIS ERRORS ****** + except redfish.rest.connections.RetriesExhaustedError as excp: + self.retcode = ReturnCodes.V1_RETRIES_EXHAUSTED_ERROR + self.ui.retries_exhausted_attemps() + except redfish.rest.connections.VnicNotEnabledError as excp: + self.retcode = ReturnCodes.VNIC_NOT_ENABLED_ERROR + self.ui.retries_exhausted_vnic_not_enabled() + except redfish.rest.v1.InvalidCredentialsError as excp: + self.retcode = ReturnCodes.V1_INVALID_CREDENTIALS_ERROR + self.ui.invalid_credentials(excp) + except redfish.rest.v1.JsonDecodingError as excp: + self.retcode = ReturnCodes.JSON_DECODE_ERROR + self.ui.error(excp) + except redfish.rest.v1.ServerDownOrUnreachableError as excp: + self.retcode = ReturnCodes.V1_SERVER_DOWN_OR_UNREACHABLE_ERROR + self.ui.error(excp) + except redfish.rest.connections.ChifDriverMissingOrNotFound as excp: + self.retcode = ReturnCodes.V1_CHIF_DRIVER_MISSING_ERROR + self.ui.printer( + "Chif driver not found, please check that the iLO channel interface" " driver (Chif) is installed.\n" + ) + except redfish.rest.connections.SecurityStateError as excp: + self.retcode = ReturnCodes.V1_SECURITY_STATE_ERROR + self.ui.printer( + "High security mode [%s] or Host Authentication has been enabled. " + "Please provide valid credentials.\n" % str(excp) + ) + except redfish.rest.connections.OneTimePasscodeError: + self.retcode = ReturnCodes.TFA_OTP_EMAILED + self.ui.printer( + "One Time Passcode Sent to registered email.\n" + "Retry the login command by including -o/--otp tag along with the OTP received.\n" + ) + except redfish.rest.connections.UnauthorizedLoginAttemptError as excp: + self.retcode = ReturnCodes.TFA_WRONG_OTP + self.ui.error(excp) + except redfish.rest.connections.TokenExpiredError as excp: + self.retcode = ReturnCodes.TFA_OTP_TIMEDOUT + self.ui.error(excp) + except redfish.hpilo.risblobstore2.ChifDllMissingError as excp: + self.retcode = ReturnCodes.REST_ILOREST_CHIF_DLL_MISSING_ERROR + self.ui.printer("iLOrest Chif dll not found, please check that the " "chif dll is present.\n") + except redfish.hpilo.risblobstore2.UnexpectedResponseError as excp: + self.retcode = ReturnCodes.REST_ILOREST_UNEXPECTED_RESPONSE_ERROR + self.ui.printer("Unexpected data received from iLO.\n") + except redfish.hpilo.risblobstore2.HpIloError as excp: + self.retcode = ReturnCodes.REST_ILOREST_ILO_ERROR + self.ui.printer("iLO returned a failed error code.\n") + except redfish.hpilo.risblobstore2.Blob2CreateError as excp: + self.retcode = ReturnCodes.REST_ILOREST_CREATE_BLOB_ERROR + self.ui.printer("Blob create operation failed.\n") + except redfish.hpilo.risblobstore2.Blob2ReadError as excp: + self.retcode = ReturnCodes.REST_ILOREST_READ_BLOB_ERROR + self.ui.printer("Blob read operation failed.\n") + except redfish.hpilo.risblobstore2.Blob2WriteError as excp: + self.retcode = ReturnCodes.REST_ILOREST_WRITE_BLOB_ERROR + self.ui.printer("Blob write operation failed.\n") + except redfish.hpilo.risblobstore2.Blob2DeleteError as excp: + self.retcode = ReturnCodes.REST_ILOREST_BLOB_DELETE_ERROR + self.ui.printer("Blob delete operation failed.\n") + except redfish.hpilo.risblobstore2.Blob2OverrideError as excp: + self.retcode = ReturnCodes.REST_ILOREST_BLOB_OVERRIDE_ERROR + self.ui.error(excp) + self.ui.printer( + "\nAnother user access in progress. Pease ensure only one user is accessing at a time locally.\n" + ) + except redfish.hpilo.risblobstore2.BlobRetriesExhaustedError as excp: + self.retcode = ReturnCodes.REST_BLOB_RETRIES_EXHAUSETED_ERROR + self.ui.printer("\nBlob operation still fails after max retries.\n") + except redfish.hpilo.risblobstore2.Blob2FinalizeError as excp: + self.retcode = ReturnCodes.REST_ILOREST_BLOB_FINALIZE_ERROR + self.ui.printer("Blob finalize operation failed.") + except redfish.hpilo.risblobstore2.BlobNotFoundError as excp: + self.retcode = ReturnCodes.REST_ILOREST_BLOB_NOT_FOUND_ERROR + self.ui.printer("Blob not found with key and namespace provided.\n") + except redfish.ris.rmc_helper.InvalidPathError as excp: + self.retcode = ReturnCodes.RIS_REF_PATH_NOT_FOUND_ERROR + self.ui.printer("Reference path not found.") + except redfish.ris.rmc_helper.IloResponseError as excp: + self.retcode = ReturnCodes.RIS_ILO_RESPONSE_ERROR + except redfish.ris.rmc_helper.UserNotAdminError as excp: + self.ui.user_not_admin() + self.retcode = ReturnCodes.USER_NOT_ADMIN + except redfish.hpilo.rishpilo.HpIloInitialError as excp: + self.ui.error(excp) + self.retcode = ReturnCodes.RIS_ILO_INIT_ERROR + except redfish.hpilo.rishpilo.HpIloChifAccessDeniedError as excp: + self.ui.error(excp) + self.retcode = ReturnCodes.RIS_ILO_CHIF_ACCESS_DENIED_ERROR + except redfish.hpilo.rishpilo.HpIloPrepareAndCreateChannelError as excp: + self.ui.error(excp) + self.retcode = ReturnCodes.RIS_CREATE_AND_PREPARE_CHANNEL_ERROR + except redfish.hpilo.rishpilo.HpIloChifPacketExchangeError as excp: + self.ui.error(excp) + self.retcode = ReturnCodes.RIS_ILO_CHIF_PACKET_EXCHANGE_ERROR + except redfish.hpilo.rishpilo.HpIloNoDriverError as excp: + self.ui.error(excp) + self.retcode = ReturnCodes.RIS_ILO_CHIF_NO_DRIVER_ERROR + except redfish.hpilo.rishpilo.HpIloWriteError as excp: + self.ui.error(excp) + self.retcode = ReturnCodes.RESOURCE_ALLOCATION_ISSUES_ERROR + except redfish.hpilo.rishpilo.HpIloReadError as excp: + self.ui.error(excp) + self.retcode = ReturnCodes.RESOURCE_ALLOCATION_ISSUES_ERROR + # ****** RIS OBJECTS ERRORS ****** + except redfish.ris.ris.BiosUnregisteredError as excp: + self.retcode = ReturnCodes.RIS_RIS_BIOS_UNREGISTERED_ERROR + self.ui.bios_unregistered_error() + # ****** FILE/IO ERRORS ****** + except IOError: + self.retcode = ReturnCodes.INVALID_FILE_INPUT_ERROR + self.ui.printer( + "Error accessing the file path. Verify the file path is correct and " "you have proper permissions.\n" + ) + # ****** GENERAL ERRORS ****** + except SystemExit: + self.retcode = ReturnCodes.GENERAL_ERROR + raise + except Exception as excp: + self.retcode = ReturnCodes.GENERAL_ERROR + sys.stderr.write("ERROR: %s\n" % excp) + + if self.opts.debug: + traceback.print_exc(file=sys.stderr) + + def check_for_tab_lists(self, command=None): + """Function to generate available options for tab tab + :param command: command for auto tab completion + :type command: string. + """ + # TODO: don't always update, only when we have a different selector. + # Try to use args to get specific nested posibilities? + changes = dict() + + # select options + typeslist = list() + + try: + typeslist = sorted(set(self.app.types())) + changes["select"] = typeslist + except: + pass + + # get/set/info options + getlist = list() + + try: + if self.app.typepath.defs: + typestr = self.app.typepath.defs.typestring + templist = self.app.getprops() + dictcopy = copy.copy(templist[0]) + + for content in templist: + for k in list(content.keys()): + if k.lower() in HARDCODEDLIST or "@odata" in k.lower(): + del content[k] + if "Bios." in dictcopy[typestr]: + if hasattr( + templist[0], + "Attributes", + ): + templist = templist[0]["Attributes"] + else: + templist = templist[0] + else: + templist = templist[0] + for key, _ in templist.items(): + getlist.append(key) + + getlist.sort() + + # if select command, get possible values + infovals = dict() + + if "select" in command: + if typestr in dictcopy: + (_, attributeregistry) = self.app.get_selection(setenable=True) + schema, reg = self.app.get_model(dictcopy, attributeregistry) + + if reg: + if "Attributes" in reg: + reg = reg["Attributes"] + for item in getlist: + for attribute in reg: + if item == attribute: + infovals.update({item: reg[attribute]}) + break + + changes["nestedinfo"] = infovals + + elif schema: + changes["nestedinfo"] = schema + + changes["get"] = getlist + changes["nestedprop"] = dictcopy["Attributes"] if "Attributes" in dictcopy else dictcopy + changes["set"] = getlist + changes["info"] = getlist + changes["val"] = [] + + readonly_list = [] + + for info in schema: + for checkread in schema[info]: + if "readonly" in checkread and schema[info]["readonly"] is True: + readonly_list.append(info) + + set_list = [x for x in getlist if x not in readonly_list] + + changes["set"] = set_list + + except: + pass + + if changes: + self._redobj.updates_tab_completion_lists(changes) + + def _pull_creds(self, args): + """Pull creds from the arguments for blobstore""" + cred_args = {} + enc = False + arg_iter = iter(args) + try: + for arg in arg_iter: + if arg in ("--enc", "-e"): + enc = True + if arg in ("-u", "--user"): + cred_args["username"] = next(arg_iter) + elif arg in ("-p", "--password"): + cred_args["password"] = next(arg_iter) + except StopIteration: + return {}, False + return cred_args, enc + + def rdmc_parse_arglist(self, cmdinstance, line=None, default=False): + """ + parses line into arguments taking special consideration + of quote characters + :param cmdinstance: command instance to be referenced + :type cmdinstance: class object + :param line: string of arguments passed in + :type line: str. + :param default: Flag to determine if the parsed command requires the default workaround for + argparse in Python 2. Argparse incorrectly assumes a sub-command is always + required, so we have to include a default sub-command for no arguments. + :type default: bool + :returns: args list + """ + + def checkargs(argopts): + """Check for optional args""" + (_, args) = argopts + for arg in args: + if arg.startswith("-") or arg.startswith("--"): + try: + cmdinstance.parser.error( + "The option %s is not available for %s" % (arg, cmdinstance.ident["name"]) + ) + except SystemExit: + raise InvalidCommandLineErrorOPTS("") + return argopts + + if line is None: + return checkargs(cmdinstance.parser.parse_known_args(line)) + + arglist = [] + if isinstance(line, six.string_types): + arglist = shlex.split(line, posix=False) + + for ind, val in enumerate(arglist): + arglist[ind] = val.strip("\"'") + elif isinstance(line, list): + arglist = line + + exarglist = [] + if os.name == "nt": + # need to glob for windows + for arg in arglist: + try: + gob = glob.glob(arg) + + if gob and len(gob) > 0: + exarglist.extend(gob) + else: + exarglist.append(arg) + except: + if not arg: + continue + else: + exarglist.append(arg) + else: + for arg in arglist: + if arg: + exarglist.append(arg) + # insert the 'default' positional argument when the first argument is an optional + # chicken and egg problem. Would be nice to figure out if a help option has been + # referenced; however, I may not be able to parse the exarglist (line in array form) + # in the event it is empty (appears there is no way to test parse and subsequently catch + # the error for flow control) + + found_help = False + for _opt in exarglist: + if _opt in ["-h", "--h", "-help", "--help"]: + found_help = True + break + if not found_help and default: + exarglist.insert(0, "default") + return checkargs(cmdinstance.parser.parse_known_args(exarglist)) + + +def ilorestcommand(): + # Initialization of main command class + ARGUMENTS = sys.argv[1:] + + RDMC = RdmcCommand( + Args=ARGUMENTS, + name=versioning.__shortname__, + usage=versioning.__shortname__ + " [command]", + summary="HPE RESTful Interface Tool", + aliases=[versioning.__shortname__], + argparser=RdmcOptionParser(), + ) + + # Main execution function call wrapper + if "setproctitle" in sys.modules: + FOUND = False + VARIABLE = setproctitle.getproctitle() + + for items in VARIABLE.split(" "): + if FOUND: + VARIABLE = VARIABLE.replace(items, "xxxxxxxx") + break + + if items == "--password" or items == "-p": + FOUND = True + + setproctitle.setproctitle(VARIABLE) + + RDMC.retcode = RDMC.run(ARGUMENTS) + + if RDMC.opts: + if RDMC.opts.verbose: + RDMC.ui.printer(("iLORest return code: %s\n" % RDMC.retcode)) + else: + RDMC.ui.printer(("iLORest return code: %s\n" % RDMC.retcode)) + + # Return code + sys.exit(RDMC.retcode) + + +if __name__ == "__main__": + ilorestcommand() diff --git a/ilorest/rdmc_base_classes.py b/ilorest/rdmc_base_classes.py new file mode 100644 index 0000000..ae093a5 --- /dev/null +++ b/ilorest/rdmc_base_classes.py @@ -0,0 +1,519 @@ +### +# Copyright 2017 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 -*- +"""This is the helper module for RDMC""" + +# ---------Imports--------- +from __future__ import unicode_literals + +import os +import sys +from argparse import ( + SUPPRESS, + Action, + ArgumentParser, + RawDescriptionHelpFormatter, + _ArgumentGroup, +) +from builtins import bytes, int, object, str, super + +from redfish.ris import NothingSelectedError + +try: + import cliutils +except ImportError: + from ilorest import cliutils + +try: + import versioning +except ImportError: + from ilorest import versioning +try: + import rdmc_helper +except ImportError: + from ilorest import rdmc_helper + +try: + from rdmc_helper import InvalidCommandLineErrorOPTS +except ImportError: + from ilorest.rdmc_helper import InvalidCommandLineErrorOPTS + + +# from extensions.COMMANDS import LoginCommand, SelectCommand + +# ---------End of imports--------- + + +class _Verbosity(Action): + def __init__(self, option_strings, dest, nargs, **kwargs): + super(_Verbosity, self).__init__(option_strings, dest, nargs, **kwargs) + + def __call__(self, parser, namespace, values, option_strings): + try: + if values: + tmp = next(iter(values)) + if tmp.isdigit(): + namespace.verbose = int(tmp) + else: + namespace.verbose = len(tmp) + 1 + return + namespace.verbose = 1 + except: + raise InvalidCommandLineErrorOPTS("Invalid verbosity selection ('-v').") + + +class CommandBase(object): + """Abstract base class for all Command objects. + + This class is used to build complex command line programs + """ + + def __init__(self, name, usage, summary, aliases=None, argparser=None): + self.name = name + self.summary = summary + self.aliases = aliases + self.config_required = True # does the command access config data + + if argparser is None: + self.parser = ArgumentParser() + else: + self.parser = argparser + + self.parser.usage = usage + # TODO: See if we can remove this or stop it from opening a subprocess all the time + # self._cli = cliutils.CLI() + + def run(self, line, help_disp=False): + """Called to actually perform the work. + + Override this method in your derived class. This is where your program + actually does work. + """ + pass + + +class RdmcCommandBase(CommandBase): + """Base class for rdmc commands which includes some common helper + methods. + """ + + def __init__(self, name, usage, summary, aliases, argparser=None, **kwargs): + """Constructor""" + CommandBase.__init__( + self, name=name, usage=usage, summary=summary, aliases=aliases, argparser=argparser, **kwargs + ) + self.json = False + self.cache = False + self.nologo = False + self.toolbar = False + + def login_select_validation(self, cmdinstance, options, skipbuild=False): + """Combined validation function to login and select with other commands. Not for use with + login or select commands themselves. Make sure your command imports options from + add_login_arguments_group or there will be errors. + + :param cmdinstance: the command object instance + :type cmdinstance: list. + :param options: command line options + :type options: list. + :param skipbuild: flag to only login and skip monolith build + :type skipbuild: bool. + """ + + logobj = cmdinstance.rdmc.load_command(cmdinstance.rdmc.search_commands("LoginCommand")) + selobj = cmdinstance.rdmc.load_command(cmdinstance.rdmc.search_commands("SelectCommand")) + inputline = list() + client = None + loggedin = False + + if hasattr(options, "json") and cmdinstance.rdmc.config.format.lower() == "json": + options.json = True + + try: + client = cmdinstance.rdmc.app.current_client + except: + if options.user or options.password or options.url or options.force_vnic: + if options.url: + inputline.extend([options.url]) + if options.user: + if options.encode: + options.user = rdmc_helper.Encryption.decode_credentials(options.user) + if isinstance(options.user, bytes): + options.user = options.user.decode("utf-8") + inputline.extend(["-u", options.user]) + if options.password: + if options.encode: + options.password = rdmc_helper.Encryption.decode_credentials(options.password) + if isinstance(options.password, bytes): + options.password = options.password.decode("utf-8") + inputline.extend(["-p", options.password]) + if options.force_vnic: + inputline.extend(["--force_vnic"]) + if getattr(options, "https_cert", False): + inputline.extend(["--https", options.https_cert]) + if getattr(options, "user_certificate", False): + inputline.extend(["--usercert", options.user_certificate]) + if getattr(options, "user_root_ca_key", False): + inputline.extend(["--userkey", options.user_root_ca_key]) + if getattr(options, "user_root_ca_password", False): + inputline.extend(["--userpassphrase", options.user_root_ca_password]) + else: + if cmdinstance.rdmc.config.url: + inputline.extend([cmdinstance.rdmc.config.url]) + if cmdinstance.rdmc.config.username: + inputline.extend(["-u", cmdinstance.rdmc.config.username]) + if cmdinstance.rdmc.config.password: + inputline.extend(["-p", cmdinstance.rdmc.config.password]) + if cmdinstance.rdmc.config.ssl_cert: + inputline.extend(["--https", cmdinstance.rdmc.config.ssl_cert]) + if getattr(options, "user_certificate", False): + inputline.extend(["--usercert", options.user_certificate]) + if getattr(options, "user_root_ca_key", False): + inputline.extend(["--userkey", options.user_root_ca_key]) + if getattr(options, "user_root_ca_password", False): + inputline.extend(["--userpassphrase", options.user_root_ca_password]) + if options.includelogs: + inputline.extend(["--includelogs"]) + if options.path: + inputline.extend(["--path", options.path]) + + if getattr(options, "biospassword", False): + inputline.extend(["--biospassword", options.biospassword]) + if getattr(options, "sessionid", False): + inputline.extend(["--sessionid", options.sessionid]) + if hasattr(options, "selector") and options.selector: + if inputline: + inputline.extend(["--selector", options.selector]) + logobj.loginfunction(inputline) + loggedin = True + else: + if getattr(options, "ref", False): + inputline.extend(["--refresh"]) + + inputline.extend([options.selector]) + selobj.selectfunction(inputline) + loggedin = True + elif hasattr(options, "selector"): + try: + inputline = list() + selector = cmdinstance.rdmc.app.selector + + if hasattr(options, "ref") and options.ref: + inputline.extend(["--refresh"]) + + if selector: + inputline.extend([selector]) + selobj.selectfunction(inputline) + loggedin = True + except NothingSelectedError: + raise NothingSelectedError + if not loggedin and not client and not options.url and not cmdinstance.rdmc.app.typepath.url: + try: + if cmdinstance.rdmc.opts.verbose: + sys.stdout.write("Local login initiated...\n") + else: + raise Exception + except Exception: + rdmc_helper.LOGGER.info("Local login initiated...\n") + if not loggedin and not client: + logobj.loginfunction(inputline, skipbuild=skipbuild) + + def logout_routine(self, cmdinstance, options): + """Routine to logout of a server automatically at the completion of a command. + + :param commandinstance: the command object instance + :type commandinstance: list. + :param options: command line options + :type options: list. + """ + + logoutobj = cmdinstance.rdmc.load_command(cmdinstance.rdmc.search_commands("LogoutCommand")) + + if getattr(options, "logout", False): + logoutobj.run("") + + def add_login_arguments_group(self, parser): + """Adds login arguments to the passed parser + + :param parser: The parser to add the login option group to + :type parser: ArgumentParser + """ + group = parser.add_argument_group("LOGIN OPTIONS", "Options for logging in to a system.") + group.add_argument("--url", dest="url", help="Use the provided iLO URL to login.", default=None) + group.add_argument( + "--sessionid", + dest="sessionid", + help="Use the provided sessionid to login.", + default=None, + ) + group.add_argument( + "-u", + "--user", + dest="user", + help="""If you are not logged in yet, including this flag along with the +password and URL flags can be used to login to a server in the same command.""", + default=None, + ) + group.add_argument( + "-p", + "--password", + dest="password", + help="""Use the provided iLO password to log in.""", + default=None, + ) + group.add_argument( + "-o", + "--otp", + dest="login_otp", + help="""Use the provided iLO OTP to log in.""", + default=None, + ) + group.add_argument( + "--biospassword", + dest="biospassword", + help="""Select this flag to input a BIOS password. Include this +flag if second-level BIOS authentication is needed for the command to execute. +This option is only used on Gen 9 systems.""", + default=None, + ) + group.add_argument( + "--https", + dest="https_cert", + help="""Use the provided CA bundle or SSL certificate with your login to +connect securely to the system in remote mode. This flag has no effect in local mode.""", + default=None, + ) + group.add_argument( + "--usercert", + dest="user_certificate", + type=str, + help="""Specify a user certificate file path for certificate based authentication +with iLO.\n**NOTE**: Inclusion of this argument will force certficate based +authentication. A root user certificate authority key or bundle will be required.""", + default=None, + ) + group.add_argument( + "--userkey", + dest="user_root_ca_key", + type=str, + help="""Specify a user root ca key file path for certificate based certificate +authentication with iLO. **NOTE 1**: Inclusion of this argument will force certficate based +authentication. A root user certificate authority key or bundle will be required. +**NOTE 2**: Inclusion of this argument will force certificate based authentication. +A user certificate will be required. +**NOTE 3**: A user will be prompted for a password if the root certificate authority key +is encrypted and \'-certpass/--userrootcapassword\' is omitted.""", + default=None, + ) + group.add_argument( + "--userpassphrase", + dest="user_root_ca_password", + type=str, + help="""Optionally specify a user root ca key file password for encrypted +user root certificate authority keys. **NOTE 1**: Inclusion of this argument will force +certficate based authentication. A root user certificate authority key or +bundle will be required. **NOTE 2**: The user will be prompted for a password +if the user root certificate authority key requires a password""", + default=None, + ) + # group.add_argument( + # '--certbundle', + # dest='ca_cert_bundle', + # type=str, + # help="""Specify a file path for the certificate authority bundle location + # (local repository for certificate collection) **NOTE**: Providing a custom certificate + # or root CA key will override the use of certificate bundles""", + # default=None) + group.add_argument( + "-e", + "--enc", + dest="encode", + action="store_true", + help=SUPPRESS, + default=False, + ) + group.add_argument( + "--includelogs", + dest="includelogs", + action="store_true", + help="Optionally include logs in the data retrieval process.", + default=False, + ) + group.add_argument( + "--path", + dest="path", + help="""Optionally set a starting point for data collection during login. +If you do not specify a starting point, the default path will be /redfish/v1/. +Note: The path flag can only be specified at the time of login. +Warning: Only for advanced users, and generally not needed for normal operations.""", + default=None, + ) + group.add_argument( + "--force_vnic", + dest="force_vnic", + action="store_true", + help="Force login through iLO Virtual NIC. **NOTE** " "iLO 5 required", + default=False, + ) + group.add_argument( + "--logout", + dest="logout", + action="store_true", + help="Logout after the completion of the command.", + default=None, + ) + + +class RdmcOptionParser(ArgumentParser): + """Constructor""" + + def __init__(self): + super().__init__( + usage="%s [GLOBAL OPTIONS] [COMMAND] [COMMAND ARGUMENTS] " "[COMMAND OPTIONS]" % versioning.__shortname__, + description="iLOrest is a command-line or interactive interface that allows users " + "to manage Hewlett Packard Enterprise products that take advantage" + " of RESTful APIs.\n\nIn order to view or manage a system you must" + " first login. You can login using the login command or during " + "execution of any other command.\nFrom here you can run any other " + "commands. To learn more about specific commands, run iLOrest " + "COMMAND -h.", + epilog="Examples:\n\nThe following is the standard flow of command" + "s to view system data.\n\tThe first example is each command " + "run individually: \n\n\tilorest login\n\tilorest select Bios.\n\t" + "ilorest get\n\n\tThe second is the list of all of the commands " + "run at once. First locally, then remotely.\n\tilorest get " + "--select Bios.\n\tilorest get --select Bios. --url -u" + " -p ", + formatter_class=RawDescriptionHelpFormatter, + ) + globalgroup = _ArgumentGroup(self, "GLOBAL OPTIONS") + + self.add_argument( + "--config", + dest="config", + help="Use the provided configuration file instead of the default one.", + metavar="FILE", + ) + + config_dir_default = os.path.join(cliutils.get_user_config_dir(), ".%s" % versioning.__shortname__) + self.add_argument( + "--cache-dir", + dest="config_dir", + default=config_dir_default, + help="Use the provided directory as the location to cache data" + " (default location: %s)" % config_dir_default, + metavar="PATH", + ) + self.add_argument( + "-v", + "--verbose", + dest="verbose", + action="count", + help="Display verbose information (with increasing level). '-v': Level 1, " + "Logging, Stdout, Stderr. '-vv': Level 2, Extends Level 1 with slightly " + "elaborated iLO and HTTP response message. '-vvv': Level3, Extends Level 2 " + "with message id, validation class, message text with embedded args, and " + "possible resolution/mitigation for iLO responses. Includes HTTP responses. " + "**NOTE 1**: Some responses may only contain limited information from the source." + "**NOTE 2**: Default level is 0.", + default=0, + ) + self.add_argument( + "-d", + "--debug", + dest="debug", + action="store_true", + help="""Display debug information.""", + default=False, + ) + self.add_argument( + "--logdir", + dest="logdir", + default=None, + help="""Use the provided directory as the location for log file.""", + metavar="PATH", + ) + self.add_argument( + "--nostdoutlog", + dest="nostdoutlog", + action="store_true", + help="""Disable debug logs to stdout.""", + default=False, + ) + self.add_argument( + "--nocache", + dest="nocache", + action="store_true", + help="During execution the application will temporarily store data only in memory.", + default=False, + ) + self.add_argument( + "--nologo", + dest="nologo", + action="store_true", + help="""Include to block copyright and logo.""", + default=False, + ) + self.add_argument( + "--toolbar", + dest="toolbar", + action="store_true", + help="""Show toolbar at the bottom.""", + default=False, + ) + self.add_argument( + "--notab", + dest="notab", + action="store_true", + help="""Disable tab complete.""", + default=False, + ) + self.add_argument( + "--redfish", + dest="is_redfish", + action="store_true", + help="Use this flag if you wish to to enable " + "Redfish only compliance. It is enabled by default " + "in systems with iLO5 and above.", + default=False, + ) + self.add_argument( + "--latestschema", + dest="latestschema", + action="store_true", + help="Optionally use the latest schema instead of the one " + "requested by the file. Note: May cause errors in some data " + "retrieval due to difference in schema versions.", + default=False, + ) + self.add_argument( + "--useproxy", + dest="proxy", + default=None, + help="""Use the provided proxy for communication.""", + metavar="URL", + ) + self.add_argument( + "--redirectconsole", + dest="redirect", + help="Optionally include this flag to redirect stdout/stderr console.", + nargs="?", + default=None, + const=True, + metavar="REDIRECT CONSOLE", + ) + self.add_argument_group(globalgroup) diff --git a/ilorest/rdmc_helper.py b/ilorest/rdmc_helper.py new file mode 100644 index 0000000..c429eea --- /dev/null +++ b/ilorest/rdmc_helper.py @@ -0,0 +1,1047 @@ +### +# Copyright 2017 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 -*- +"""This is the helper module for RDMC""" + +# ---------Imports--------- +from __future__ import unicode_literals + +import json +import logging +import os +import sys +import time +from collections import OrderedDict +from ctypes import byref, c_char_p, create_string_buffer + +import pyaes +import six +from prompt_toolkit.completion import Completer, Completion + +import redfish.hpilo.risblobstore2 as risblobstore2 +import redfish.ris + +try: + import versioning +except ImportError: + from ilorest import versioning + +if os.name == "nt": + from six.moves import winreg + if six.PY2: + from win32con import HKEY_LOCAL_MACHINE + elif six.PY3: + from win32.lib.win32con import HKEY_LOCAL_MACHINE + +# ---------End of imports--------- + + +# ---------Debug logger--------- +# Using hard coded list until better solution is found +HARDCODEDLIST = [ + "name", + "modified", + "type", + "description", + "attributeregistry", + "links", + "settingsresult", + "actions", + "availableactions", + "id", + "extref", +] + + +class InfoFilter(logging.Filter): + def filter(self, rec): + return rec.levelno in (logging.DEBUG, logging.INFO, logging.WARN) + + +LOGGER = logging.getLogger() +# default logging level setting +LOGGER.setLevel(logging.ERROR) +# loggin format +LFMT = logging.Formatter("%(levelname)s\t: %(message)s") +# when success, all logs should be in stdout. When error, it should goto stderr +# Initialize StreamHandler for Stdout and Stderr logs +LOUT = logging.StreamHandler(sys.stdout) +LERR = logging.StreamHandler(sys.stderr) +# set formatter +LERR.setFormatter(LFMT) +# default stderr level setting +LERR.setLevel(logging.ERROR) +# add logger handle +LOGGER.addHandler(LERR) +LOUT.setFormatter(LFMT) +# default stdout level setting +LOUT.setLevel(logging.WARN) +LOUT.addFilter(InfoFilter()) +# add logger handle +LOGGER.addHandler(LOUT) + + +# ---------End of debug logger--------- + + +class ReturnCodes(object): + """Return code class to be used by all functions""" + + SUCCESS = 0 + + # ****** RDMC ERRORS ****** + CONFIGURATION_FILE_ERROR = 1 + COMMAND_NOT_ENABLED_ERROR = 2 + INVALID_COMMAND_LINE_ERROR = 3 + INVALID_FILE_FORMATTING_ERROR = 4 + USER_NOT_ADMIN = 5 + NO_CONTENTS_FOUND_FOR_OPERATION = 6 + INVALID_FILE_INPUT_ERROR = 7 + NO_CHANGES_MADE_OR_FOUND = 8 + NO_VALID_INFO_ERROR = 9 + + # ****** CLI ERRORS ****** + UI_CLI_ERROR_EXCEPTION = 10 + UI_CLI_WARN_EXCEPTION = 11 + UI_CLI_USAGE_EXCEPTION = 12 + UI_CLI_COMMAND_NOT_FOUND_EXCEPTION = 13 + INVALID_PASSWORD_LENGTH_ERROR = 14 + + # ****** RMC/RIS ERRORS ****** + RIS_UNDEFINED_CLIENT_ERROR = 21 + RIS_CURRENTLY_LOGGED_IN_ERROR = 22 + RIS_INSTANCE_NOT_FOUND_ERROR = 23 + RIS_NOTHING_SELECTED_ERROR = 24 + RIS_NOTHING_SELECTED_FILTER_ERROR = 25 + RIS_NOTHING_SELECTED_SET_ERROR = 26 + RIS_INVALID_SELECTION_ERROR = 27 + RIS_VALIDATION_ERROR = 28 + RIS_MISSING_ID_TOKEN = 29 + RIS_SESSION_EXPIRED = 30 + + # ****** REST V1 ERRORS ****** + V1_RETRIES_EXHAUSTED_ERROR = 31 + V1_INVALID_CREDENTIALS_ERROR = 32 + V1_SERVER_DOWN_OR_UNREACHABLE_ERROR = 33 + V1_CHIF_DRIVER_MISSING_ERROR = 34 + REST_ILOREST_CHIF_DLL_MISSING_ERROR = 35 + REST_ILOREST_UNEXPECTED_RESPONSE_ERROR = 36 + REST_ILOREST_ILO_ERROR = 37 + REST_ILOREST_CREATE_BLOB_ERROR = 38 + REST_ILOREST_READ_BLOB_ERROR = 39 + + # ****** RDMC ERRORS ****** + SAME_SETTINGS_ERROR = 40 + FIRMWARE_UPDATE_ERROR = 41 + BOOT_ORDER_ENTRY_ERROR = 42 + NIC_MISSING_OR_INVALID_ERROR = 43 + NO_CURRENT_SESSION_ESTABLISHED = 44 + FAILURE_DURING_COMMIT_OPERATION = 45 + USERNAME_PASSWORD_REQUIRED_ERROR = 46 + VNIC_NOT_ENABLED_ERROR = 47 + MULTIPLE_SERVER_CONFIG_FAIL = 51 + MULTIPLE_SERVER_INPUT_FILE_ERROR = 52 + LOAD_SKIP_SETTING_ERROR = 53 + INCOMPATIBLE_ILO_VERSION_ERROR = 54 + INVALID_CLIST_FILE_ERROR = 55 + UNABLE_TO_MOUNT_BB_ERROR = 56 + BIRTHCERT_PARSE_ERROR = 57 + INCOMPATIBLE_SERVER_TYPE = 58 + ILO_LICENSE_ERROR = 59 + RESOURCE_EXISTS_ERROR = 60 + + # ****** RMC/RIS ERRORS ****** + RIS_VALUE_CHANGED_ERROR = 61 + RIS_REF_PATH_NOT_FOUND_ERROR = 62 + RIS_ILO_RESPONSE_ERROR = 63 + RIS_ILO_INIT_ERROR = 64 + RIS_SCHEMA_PARSE_ERROR = 65 + RIS_ILO_CHIF_ACCESS_DENIED_ERROR = 66 + RIS_CREATE_AND_PREPARE_CHANNEL_ERROR = 67 + RIS_ILO_CHIF_PACKET_EXCHANGE_ERROR = 71 + RIS_ILO_CHIF_NO_DRIVER_ERROR = 69 + + # ****** REST V1 ERRORS ****** + REST_ILOREST_WRITE_BLOB_ERROR = 70 + REST_ILOREST_BLOB_DELETE_ERROR = 68 + REST_ILOREST_BLOB_FINALIZE_ERROR = 72 + REST_ILOREST_BLOB_NOT_FOUND_ERROR = 73 + JSON_DECODE_ERROR = 74 + V1_SECURITY_STATE_ERROR = 75 + REST_ILOREST_BLOB_OVERRIDE_ERROR = 76 + REST_BLOB_RETRIES_EXHAUSETED_ERROR = 77 + + # ****** RDMC ERRORS ****** + RESOURCE_ALLOCATION_ISSUES_ERROR = 80 + ENCRYPTION_ERROR = 81 + DRIVE_MISSING_ERROR = 82 + PATH_UNAVAILABLE_ERROR = 83 + ILO_RIS_CORRUPTION_ERROR = 84 + RESOURCE_NOT_READY_ERROR = 85 + + # ****** RIS ERRORS ****** + RIS_RIS_BIOS_UNREGISTERED_ERROR = 100 + + # ***** Upload/Download ERRORS ****** + FAILED_TO_DOWNLOAD_COMPONENT = 101 + UPDATE_SERVICE_BUSY = 102 + FAILED_TO_UPLOAD_COMPONENT = 103 + TASKQUEUE_ERROR = 104 + DEVICE_DISCOVERY_IN_PROGRESS = 105 + INSTALLSET_ERROR = 106 + + # **** ComputeOpsManagement Errors**** + CLOUD_CONNECT_TIMEOUT = 111 + CLOUD_CONNECT_FAILED = 112 + CLOUD_ALREADY_CONNECTED = 113 + PROXY_CONFIG_FAILED = 114 + + # **** scep error **** + SCEP_ENABLED_ERROR = 121 + + # *** authentication error **** + TFA_WRONG_OTP = 131 + TFA_OTP_TIMEDOUT = 132 + TFA_ENABLED_ERROR = 133 + TFA_OTP_EMAILED = 134 + + # ****** GENERAL ERRORS ****** + GENERAL_ERROR = 255 + + +class RdmcError(Exception): + """Baseclass for all rdmc exceptions""" + + errcode = 1 + + def __init__(self, message=None): + Exception.__init__(self, message) + + +class ConfigurationFileError(RdmcError): + """Raised when something is wrong in the config file""" + + errcode = 3 + + +class ProxyConfigFailedError(RdmcError): + """Raised when ComputeOpsManagement connection fails""" + + pass + + +class CloudConnectTimeoutError(RdmcError): + """Raised when ComputeOpsManagement connection times out""" + + pass + + +class CloudConnectFailedError(RdmcError): + """Raised when ComputeOpsManagement connection fails""" + + pass + + +class TfaEnablePreRequisiteError(RdmcError): + """Raised when pre-requisites not met while enabling TFA""" + + pass + + +class AlreadyCloudConnectedError(RdmcError): + """Raised when ComputeOpsManagement is already connected""" + + pass + + +class CommandNotEnabledError(RdmcError): + """Raised when user tries to invoke a command that isn't enabled""" + + pass + + +class iLORisCorruptionError(RdmcError): + """Raised when user tries to invoke a command that isn't enabled""" + + pass + +class ResourceNotReadyError(RdmcError): + """Raised when user tries to invoke a command that isn't enabled""" + + pass + + +class UsernamePasswordRequiredError(RdmcError): + """Raised when username and password are required for local chif login""" + + pass + + +class PathUnavailableError(Exception): + """Raised when the requested path is unavailable""" + + pass + + +class InvalidCommandLineError(RdmcError): + """Raised when user enter incorrect command line arguments""" + + pass + + +class NoCurrentSessionEstablished(RdmcError): + """Raised when user enter incorrect command line arguments""" + + pass + + +class NoChangesFoundOrMadeError(RdmcError): + """Raised when no changes were found or made on the commit function""" + + pass + + +class StandardBlobErrorHandler(RdmcError): + """Raised when error occured for blob operations""" + + pass + + +class InvalidCommandLineErrorOPTS(RdmcError): + """Raised when user enter incorrect command line arguments""" + + pass + + +class InvalidFileInputError(RdmcError): + """Raised when user enter an invalid file input""" + + pass + + +class InvalidFileFormattingError(RdmcError): + """Raised when user enter incorrect load file formatting""" + + pass + + +class WindowsUserNotAdmin(RdmcError): + """Raised when user is not running as admin""" + + pass + + +class NoContentsFoundForOperationError(RdmcError): + """Raised when no contents were found for the current operation""" + + pass + + +class InfoMissingEntriesError(RdmcError): + """Raised when no valid entries for info were found in the current + instance""" + + pass + + +class InvalidOrNothingChangedSettingsError(RdmcError): + """Raised when something is wrong with the settings""" + + pass + + +class InvalidPasswordLengthError(RdmcError): + """Raised when password length is invalid""" + + pass + + +class NoDifferencesFoundError(RdmcError): + """Raised when no differences are found in the current configuration""" + + pass + + +class NothingSelectedError(RdmcError): + """Raised when type selection is reference but none have been provided""" + + pass + + +class InvalidPropertyError(RdmcError): + """Raised when one or more properties or attributes are in conflict with current or + specified configuration""" + + pass + + +class MultipleServerConfigError(RdmcError): + """Raised when one or more servers failed to load given configuration""" + + pass + + +class InvalidMSCfileInputError(RdmcError): + """Raised when servers input file for load has incorrect parameters""" + + pass + + +class FirmwareUpdateError(RdmcError): + """Raised when there is an error while updating firmware""" + + pass + + +class FailureDuringCommitError(RdmcError): + """Raised when there is an error during commit""" + + pass + + +class BootOrderMissingEntriesError(RdmcError): + """Raised when no entries were found for bios tools""" + + pass + + +class NicMissingOrConfigurationError(RdmcError): + """ Raised when no entries are found for given NIC or all NICs are \ + configured or when wrong inputs are presented for NIC entries""" + + pass + + +class IncompatibleiLOVersionError(RdmcError): + """Raised when the iLO version is above or below the required \ + version""" + + pass + + +class IncompatableServerTypeError(RdmcError): + """Raised when the server type is incompatable with the requested\ + command""" + + pass + + +class IloLicenseError(RdmcError): + """Raised when the proper iLO license is not available for a command""" + + pass + + +class ScepenabledError(RdmcError): + """Raised when the generation csr or deletion of https cert is issues when scep is enabled""" + + pass + + +class ResourceExists(RdmcError): + """Raised when the account to be added already exists""" + + pass + + +class InvalidCListFileError(RdmcError): + """Raised when an error occurs while reading the cfilelist \ + within AHS logs""" + + pass + + +class PartitionMoutingError(RdmcError): + """Raised when there is error or iLO fails to respond to \ + partition mounting request""" + + pass + + +class DownloadError(RdmcError): + """Raised when the component fails to download""" + + pass + + +class UploadError(RdmcError): + """Raised when the component fails to download""" + + pass + + +class TimeOutError(RdmcError): + """Raised when the update service times out""" + + pass + + +class LibHPsrvMissingError(RdmcError): + """Raised when unable to obtain the libhpsrv handle""" + + pass + + +class BirthcertParseError(RdmcError): + """Raised when unable to parse the birthcert""" + + pass + + +class InvalidKeyError(RdmcError): + """Raised when an invalid encryption key is used""" + + pass + + +class UnableToDecodeError(RdmcError): + """Raised when the file is unable to be decoded using the given key""" + + pass + + +class UnabletoFindDriveError(RdmcError): + """Raised when there is an issue finding required label""" + + pass + + +class TaskQueueError(RdmcError): + """Raised when there is an issue with the current order of taskqueue""" + + pass + + +class DeviceDiscoveryInProgress(RdmcError): + """Raised when device discovery in progress""" + + pass + + +class InvalidSmartArrayConfigurationError(RdmcError): + """Raised for an invalid configuration of a smart array controller and/or disk configuration""" + + pass + + +class FallbackChifUse(RdmcError): + """Fallback Chif Use""" + + pass + + +class InstallsetError(RdmcError): + """Error while deleting Recovery installset""" + + pass + + +class UI(object): + """UI class handles all of our printing etc so we have + consistency across the project""" + + def __init__(self, verbosity=1): + self.verbosity = verbosity + + def printer(self, data, flush=True, excp=None, verbose_override=False): + """Print wrapper for stdout vs fileout""" + if self.verbosity >= 0 or verbose_override: + if excp: + sys.stderr.write(str(data)) + else: + try: + sys.stdout.write(str(data)) + if flush: + sys.stdout.flush() + except IOError: + pass + + def command_not_found(self, cmd): + """Called when command was not found""" + self.printer( + ("\nCommand '%s' not found. Use the help command to " "see a list of available commands\n" % cmd), + excp=True, + ) + return ReturnCodes.UI_CLI_COMMAND_NOT_FOUND_EXCEPTION + + def command_not_enabled(self, cmd, excp): + """Called when command has not been enabled""" + self.printer(("\nCommand '%s' has not been enabled: %s\n" % (cmd, excp)), excp=excp) + + def invalid_commmand_line(self, excp): + """Called when user entered invalid command line entries""" + self.printer(("Error: %s\n" % excp), excp=excp) + + def ilo_ris_corruption(self, excp): + """Called when user entered invalid command line entries""" + self.printer(("\nError: %s\n" % excp), excp=excp) + + def standard_blob_error(self, excp): + """Called when user error encountered with blob""" + self.printer(("\nError: Blob operation failed with error code %s\n" % excp), excp=excp) + + def invalid_file_formatting(self, excp): + """Called when file formatting is unrecognizable""" + self.printer(("\nError: %s\n" % excp), excp=excp) + + def user_not_admin(self): + """Called when file formatting in unrecognizable""" + self.printer( + ( + "\nBoth remote and local mode is accessible when %s " + "is run as administrator. Only remote mode is available for non-" + "admin user groups.\n" % versioning.__longname__ + ), + excp=True, + ) + + def no_contents_found_for_operation(self, excp): + """Called when no contents were found for the current operation""" + self.printer(("\nError: %s\n" % excp), excp=True) + + def nothing_selected(self): + """Called when nothing has been select yet""" + self.printer( + "\nNo type currently selected. Please use the" + " 'types' command to\nget a list of types, or input" + " your type by using the '--selector' flag.\n", + excp=True, + ) + + def nothing_selected_filter(self): + """Called when nothing has been select after a filter set""" + self.printer("\nNothing was found to match your provided filter.\n", excp=True) + + def nothing_selected_set(self): + """Called when nothing has been select yet""" + self.printer("\nNothing is selected or selection is read-only.\n", excp=True) + + def no_differences_found(self, excp): + """Called when no difference is found in the current configuration""" + self.printer(("Error: %s\n" % excp), excp=True) + + def multiple_server_config_fail(self, excp): + """Called when one or more servers failed to load given configuration""" + self.printer(("Error: %s\n" % excp), excp=True) + + def multiple_server_config_input_file(self, excp): + """Called when servers input file has incorrect information""" + self.printer(("Error: %s\n" % excp), excp=True) + + def invalid_credentials(self, timeout): + """Called user has entered invalid credentials + :param timeout: timeout given for failed login attempt + :type timeout: int. + """ + self.printer("Validating...", excp=True) + + for _ in range(0, (int(str(timeout)) + 10)): + time.sleep(1) + self.printer(".", excp=True) + + self.printer( + "\nError: Could not authenticate. Invalid " "credentials, or bad username/password.\n", + excp=True, + ) + + def bios_unregistered_error(self): + """Called when ilo/bios unregistered error occurs""" + self.printer( + "\nERROR 100: Bios provider is unregistered. Please" + " refer to the documentation for details on this issue.\n", + excp=True, + ) + + def error(self, msg, inner_except=None): + """Used for general error handling + :param inner_except: raised exception to be logged + :type inner_except: exception. + :param msg: warning message + :type msg: string. + """ + if inner_except is not None: + LOGGER.error(msg, exc_info=True) + else: + LOGGER.error(msg) + + def warn(self, msg, inner_except=None): + """Used for general warning handling + :param inner_except: raised exception to be logged + :type inner_except: exception. + :param msg: warning message + :type msg: string. + """ + if inner_except is not None: + LOGGER.warning(msg, exc_info=True) + else: + LOGGER.warning(msg) + + def retries_exhausted_attemps(self): + """Called when url retries have been exhausted""" + LOGGER.error("Could not reach URL. Retries have been exhausted.") + + def retries_exhausted_vnic_not_enabled(self): + """Called when there is no VNIC is Enabled""" + self.printer("\nError: Could not reach URL, VNIC is not enabled. \n", excp=True) + + def print_out_json(self, content): + """Print out json content to std.out with sorted keys + :param content: content to be printed out + :type content: str. + """ + # stringify + content = json.dumps(content, indent=2, cls=redfish.ris.JSONEncoder, sort_keys=True) + self.printer(content, verbose_override=True) + self.printer("\n") + + def print_out_json_ordered(self, content): + """Print out sorted json content to std.out + :param content: content to be printed out + :type content: str. + """ + content = OrderedDict(sorted(list(content.items()), key=lambda x: x[0])) + content = json.dumps(content, indent=2, cls=redfish.ris.JSONEncoder) + self.printer(content, verbose_override=True) + self.printer("\n") + + def print_out_human_readable(self, content): + """Print out human readable content to std.out + :param content: content to be printed out + :type content: str. + """ + self.pretty_human_readable(content, enterloop=True) + self.printer("\n") + + def pretty_human_readable(self, content, indent=0, start=0, enterloop=False): + """Convert content to human readable and print out to std.out + :param content: content to be printed out + :type content: str. + :param indent: indent string to be used as seperator + :type indent: str. + :param start: used to determine the indent level + :type start: int. + """ + space = "\n" + "\t" * indent + " " * start + if isinstance(content, list): + for item in content: + if item is None: + continue + + self.pretty_human_readable(item, indent, start) + + if content.index(item) != (len(content) - 1): + self.printer(space) + elif isinstance(content, dict): + for key, value in content.items(): + if space and not enterloop: + self.printer(space) + + enterloop = False + self.printer((str(key) + "=")) + self.pretty_human_readable(value, indent, (start + len(key) + 2)) + else: + content = content if isinstance(content, six.string_types) else str(content) + + content = '""' if not content else content + # Changed to support py3, verify if there is a unicode prit issue. + + self.printer(content) + + +class Encryption(object): + """Encryption/Decryption object""" + + @staticmethod + def check_fips_mode_os(): + """Function to check for the OS fips mode + :param key: string to encrypt with + :type key: str. + :returns: returns True if FIPS mode is active, False otherwise + """ + fips = False + if os.name == "nt": + reg = winreg.ConnectRegistry(None, HKEY_LOCAL_MACHINE) + try: + reg = winreg.OpenKey( + reg, + "System\\CurrentControlSet\\Control\\" "Lsa\\FipsAlgorithmPolicy", + ) + winreg.QueryInfoKey(reg) + value, _ = winreg.QueryValueEx(reg, "Enabled") + if value: + fips = True + except: + fips = False + else: + try: + fipsfile = open("/proc/sys/crypto/fips_enabled") + result = fipsfile.readline() + if int(result) > 0: + fipsfile = True + fipsfile.close() + except: + fips = False + return fips + + @staticmethod + def check_fips_mode_ssl(): + """Function to check for the SSL fips mode + Uses custom cpython ssl module API, if available. Otheriwse + probes using ctypes.cdll APIs. + :returns: returns True if FIPS mode is active, False otherwise + """ + import ssl + + if hasattr(ssl, "FIPS_mode"): + return ssl.FIPS_mode() + + from ctypes import cdll + + libcrypto = cdll.LoadLibrary(ssl._ssl.__file__) + return libcrypto.FIPS_mode() + + def encrypt_file(self, filetxt, key): + """encrypt a file given a key + :param filetxt: content to be encrypted + :type content: str. + :param key: string to encrypt with + :type key: str. + """ + try: + filetxt = filetxt.encode() + except (UnicodeDecodeError, AttributeError): + pass # must be encoded already + try: + key = key.encode() + except (UnicodeDecodeError, AttributeError): + pass # must be encoded already + if Encryption.check_fips_mode_os(): + raise CommandNotEnabledError("Encrypting of files is not available" " in FIPS mode.") + if len(key) not in [16, 24, 32]: + raise InvalidKeyError("") + else: + encryptedfile = pyaes.AESModeOfOperationCTR(key).encrypt(filetxt) + + return encryptedfile + + def decrypt_file(self, filetxt, key): + """decrypt a file given a key + :param filetxt: content to be decrypted + :type content: str. + :param key: string to decrypt with + :type key: str. + :returns: returns the decrypted file + """ + try: + filetxt = filetxt.encode() + except (UnicodeDecodeError, AttributeError): + pass # must be encoded already + try: + key = key.encode() + except (UnicodeDecodeError, AttributeError): + pass # must be encoded already + if len(key) not in [16, 24, 32]: + raise InvalidKeyError("") + else: + decryptedfile = pyaes.AESModeOfOperationCTR(key).decrypt(filetxt) + try: + json.loads(decryptedfile) + except: + raise UnableToDecodeError( + "Unable to decrypt the file, make " "sure the key is the same as used in encryption." + ) + + return decryptedfile + + @staticmethod + def decode_credentials(credential): + """decode an encoded credential + :param credential: credential to be decoded + :type credential: str. + :returns: returns the decoded credential + """ + + lib = risblobstore2.BlobStore2.gethprestchifhandle() + credbuff = create_string_buffer(credential.encode("utf-8")) + retbuff = create_string_buffer(128) + + lib.decode_credentials.argtypes = [c_char_p] + + lib.decode_credentials(credbuff, byref(retbuff)) + + risblobstore2.BlobStore2.unloadchifhandle(lib) + # try: + # if isinstance(retbuff.value, bytes): + # retbuff.value = retbuff.value.decode('utf-8', 'ignore') + # if not retbuff.value: + # raise UnableToDecodeError("") + # except: + # raise UnableToDecodeError("Unable to decode credential %s." % credential) + + return retbuff.value + + @staticmethod + def encode_credentials(credential): + """encode a credential + :param credential: credential to be encoded + :type credential: str. + :returns: returns the encoded credential + """ + + lib = risblobstore2.BlobStore2.gethprestchifhandle() + if isinstance(credential, bytes): + credential = credential.decode("utf-8") + credbuff = create_string_buffer(credential.encode("utf-8")) + + retbuff = create_string_buffer(128) + + lib.encode_credentials.argtypes = [c_char_p] + + lib.encode_credentials(credbuff, byref(retbuff)) + + risblobstore2.BlobStore2.unloadchifhandle(lib) + try: + if six.PY2: + enc_val = retbuff.value.encode("utf-8") + elif six.PY3: + enc_val = retbuff.value.decode("utf-8") # .encode('utf-8') + if not retbuff.value: + raise UnableToDecodeError("") + except Exception: + raise UnableToDecodeError("Unable to decode credential %s." % credential) + + return enc_val + + +class TabAndHistoryCompletionClass(Completer): + """Tab and History Class used by interactive mode""" + + def __init__(self, options): + self.options = options + self.toolbar_text = None + self.last_complete = None + + def get_completions(self, document, complete_event): + """Function to return the options for autocomplete""" + word = "" + self.toolbar_text = "" + lstoption = self.options + if document.text: + tokens = document.text.split() + # We aren't completing options yet + tokens = [token for token in tokens if not token.startswith("-")] + + self.last_complete = tokens[-1] + nestedtokens = self.last_complete.split("/") + + if not document.text.endswith(" "): + tokens.pop() + word = document.get_word_under_cursor() + else: + nestedtokens = [] + if word == "/": + word = "" + + if len(tokens) >= 1: + if tokens[0] == "select": + # only first type + if len(tokens) >= 2: + lstoption = [] + else: + lstoption = self.options.get(tokens[0], {}) + elif tokens[0] in ["get", "list", "info", "set"]: + # Match properties + nested_data = self.options.get("nestedprop", {}) + nested_info = self.options.get("nestedinfo", {}) + for token in nestedtokens: + try: + nested_data = nested_data[token] + if tokens[0] == "get" and isinstance(nested_data, dict): + for k in list(nested_data.keys()): + if ( + k.lower() in HARDCODEDLIST + or "@odata" in k.lower() + or "@redfish.allowablevalues" in k.lower() + ): + del nested_data[k] + if nested_info: + if "properties" in nested_info: + nested_info = nested_info["properties"] + if "AttributeName" not in nested_info[token]: + nested_info = ( + nested_info["properties"][token] + if "properties" in nested_info + else nested_info[token] + ) + else: + nested_info = nested_info[token] + except Exception: + break + nested_data = list(nested_data.keys()) if isinstance(nested_data, dict) else [] + lstoption = nested_data + + # Try to get info for help bar + help_text = nested_info.get("HelpText", "") + enum_tab = [] + if "Type" in nested_info and nested_info["Type"].lower() == "enumeration": + help_text += "\nPossible Values:\n" + for value in nested_info["Value"]: + enum_tab.append(value["ValueName"]) + help_text += six.u(str(value["ValueName"])) + " " + + if not help_text: + try: + nested_info = nested_info["properties"] + except KeyError: + pass + help_text = nested_info.get("description", "") + if "enum" in nested_info: + help_text += "\nPossible Values:\n" + for value in nested_info["enum"]: + enum_tab.append(value) + help_text += six.u(str(value)) + " " + if isinstance(help_text, str): + help_text = help_text.replace(". ", ".\n") + self.toolbar_text = help_text + if tokens[0] in ["set"]: + lstoption = self.options.get("set") + else: + lstoption = {} + else: + for token in tokens: + # just match commands + lstoption = self.options.get(token, {}) + + for opt in lstoption: + if opt == word: + self.last_complete = opt + if opt.startswith(word): + yield Completion(opt + "", start_position=-len(word)) + + def bottom_toolbar(self): + return self.toolbar_text if self.toolbar_text else None + + def updates_tab_completion_lists(self, options): + """Function to update tab completion lists + :param options: options list + :type options: list. + """ + # Loop through options passed and add them to them + # to the current tab options list + for key, value in options.items(): + self.options[key] = value diff --git a/ilorest/versioning.py b/ilorest/versioning.py new file mode 100644 index 0000000..3427443 --- /dev/null +++ b/ilorest/versioning.py @@ -0,0 +1,23 @@ +### +# Copyright 2017 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 -*- +""" Version strings for the utility """ + +__version__ = "4.7.0.0" +__shortname__ = "iLORest" +__longname__ = "RESTful Interface Tool" +__extracontent__ = "Copyright (c) 2014-2023 Hewlett Packard Enterprise Development LP\n" diff --git a/rdmc-pyinstaller-lin.spc b/rdmc-pyinstaller-lin.spc index 9ade6c1..535bd21 100644 --- a/rdmc-pyinstaller-lin.spc +++ b/rdmc-pyinstaller-lin.spc @@ -10,7 +10,7 @@ def hiddenImportGet(): classNames = [] _Commands = {} - extensionDir = os.path.dirname(os.getcwd()+ '/src') + extensionDir = os.path.dirname(os.getcwd()+ '/ilorest') replacement = '/' @@ -33,17 +33,17 @@ def hiddenImportGet(): def getData(): datalist = [] - extensionDir = os.path.dirname(os.getcwd()+ '/src/extensions/') + extensionDir = os.path.dirname(os.getcwd()+ '/ilorest/extensions/') for (cwd, dirs, _) in os.walk(extensionDir): for dir in dirs: - tempstr = cwd.split('/src/')[-1]+'/'+dir+'/' - datalist.append(('./src/' + tempstr + '*.pyc', tempstr)) + tempstr = cwd.split('/ilorest/')[-1]+'/'+dir+'/' + datalist.append(('./ilorest/' + tempstr + '*.pyc', tempstr)) return datalist compileall.compile_dir('.', force=True, quiet=True, legacy=True) -a = Analysis(['.//src//rdmc.py'], - pathex=[], +a = Analysis(['.//ilorest//rdmc.py'], + pathex=['./ilorest'], binaries=None, datas=getData(), hiddenimports=hiddenImportGet(), diff --git a/rdmc-pyinstaller-mac.spec b/rdmc-pyinstaller-mac.spec index d7f6e06..6f1cef4 100644 --- a/rdmc-pyinstaller-mac.spec +++ b/rdmc-pyinstaller-mac.spec @@ -10,7 +10,7 @@ def hiddenImportGet(): classNames = [] _Commands = {} - extensionDir = os.path.dirname(os.getcwd()+ '/src') + extensionDir = os.path.dirname(os.getcwd()+ '/ilorest/extensions/') replacement = '/' @@ -33,11 +33,11 @@ def hiddenImportGet(): def getData(): datalist = [] - extensionDir = os.path.dirname(os.getcwd()+ '/src/extensions/') + extensionDir = os.path.dirname(os.getcwd()+ '/ilorest/extensions/') for (cwd, dirs, _) in os.walk(extensionDir): for dir in dirs: - tempstr = cwd.split('/src/')[-1]+'/'+dir+'/' - datalist.append(('./src/' + tempstr + '*.pyc', tempstr)) + tempstr = cwd.split('/ilorest/')[-1]+'/'+dir+'/' + datalist.append(('./ilorest/' + tempstr + '*.pyc', tempstr)) datalist.append(('./packaging/jsonpath_rw', 'jsonpath_rw')) @@ -45,7 +45,7 @@ def getData(): compileall.compile_dir('.', force=True, quiet=True, legacy=True) -a = Analysis(['.//src//rdmc.py'], +a = Analysis(['.//ilorest//rdmc.py'], pathex=[], binaries=None, datas=getData(), diff --git a/rdmc-pyinstaller-windows.spec b/rdmc-pyinstaller-windows.spec index ff5ecfb..118379d 100644 --- a/rdmc-pyinstaller-windows.spec +++ b/rdmc-pyinstaller-windows.spec @@ -10,7 +10,7 @@ def hiddenImportGet(): classNames = [] _Commands = {} - extensionDir = os.path.dirname(os.getcwd()+ '\\src') + extensionDir = os.path.dirname(os.getcwd()+ '\\ilorest') if os.name != 'nt': replacement = '/' @@ -36,17 +36,17 @@ def hiddenImportGet(): def getData(): datalist = [] - extensionDir = os.path.dirname(os.getcwd()+ '\\src\\extensions\\') + extensionDir = os.path.dirname(os.getcwd()+ '\\ilorest\\extensions\\') for (cwd, dirs, _) in os.walk(extensionDir): for dir in dirs: - tempstr = cwd.split('\\src\\')[-1]+'\\'+dir+'\\' - datalist.append(('.\\src\\' + tempstr + '*.pyc', tempstr)) + tempstr = cwd.split('\\ilorest\\')[-1]+'\\'+dir+'\\' + datalist.append(('.\\ilorest\\' + tempstr + '*.pyc', tempstr)) return datalist compileall.compile_dir('.', force=True, quiet=True, legacy=True) -a = Analysis(['.\\src\\rdmc.py'], - pathex=['.\\src'], +a = Analysis(['.\\ilorest\\rdmc.py'], + pathex=['.\\ilorest'], binaries=None, datas=getData(), hiddenimports=hiddenImportGet(), diff --git a/rdmc.spec.in b/rdmc.spec.in index 8e68f98..4aa4319 100644 --- a/rdmc.spec.in +++ b/rdmc.spec.in @@ -34,10 +34,10 @@ Authors: %build mv rdmc-pyinstaller-lin.spc rdmc-pyinstaller-lin.spec /usr/local/python3.8/bin/pyinstaller rdmc-pyinstaller-lin.spec -cp dist/ilorest ilorest +#cp dist/ilorest ilorest %install -install -D -m 0755 ilorest $RPM_BUILD_ROOT%{_sbindir}/ilorest +install -D -m 0755 dist/ilorest $RPM_BUILD_ROOT%{_sbindir}/ilorest mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/ilorest install -D -m 0644 rdmc-linux.conf $RPM_BUILD_ROOT%{_sysconfdir}/ilorest/redfish.conf diff --git a/rdmc.spec10.in b/rdmc.spec10.in new file mode 100644 index 0000000..2aa36af --- /dev/null +++ b/rdmc.spec10.in @@ -0,0 +1,76 @@ +# +# spec file for package ilorest (Version %VERSION%) +# + +# norootforbuild + +Name: ilorest +License: Copyright 2016-2022 Hewlett Packard Enterprise Development LP +Group: System/Configuration/Packaging +AutoReqProv: on +Version: %VERSION% +Release: %RELEASE% + +Source0: ilorest-%{version}.tar.bz2 +Url: https://www.hpe.com/info/restfulapi +Vendor: Hewlett Packard Enterprise +Packager: Hewlett Packard Enterprise +Summary: RESTful Interface Tool + +BuildRoot: %{_tmppath}/%{name}-%{version}-build +#BuildRequires: which + +%description +Command line interface for managing HPE ProLiant Servers + +%global __os_install_post %{nil} + +Authors: +-------- + Hewlett Packard Enterprise + +%prep +%setup -n ilorest-%{version} + +%build +mv rdmc-pyinstaller-lin.spc rdmc-pyinstaller-lin.spec +/usr/local/python3.10/bin/pyinstaller rdmc-pyinstaller-lin.spec +cp dist/ilorest ilorest + +%install +install -D -m 0755 dist/ilorest $RPM_BUILD_ROOT%{_sbindir}/ilorest + +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/ilorest +install -D -m 0644 rdmc-linux.conf $RPM_BUILD_ROOT%{_sysconfdir}/ilorest/redfish.conf + +mkdir -p $RPM_BUILD_ROOT%{_libdir}/ +install -D -m 0666 ./externals/*.so $RPM_BUILD_ROOT%{_libdir}/ + +%post +#rm -f %{_sbindir}/ilorest + +%postun +#rm -f %{_sbindir}/ilorest + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-, root, root) +%{_sbindir}/%{name} +%attr(644, root, root) %{_libdir}/ilorest_chif.so +%config %{_sysconfdir}/%{name}/* +%dir %{_sysconfdir}/%{name} +%defattr(444, root, root) + +%changelog +* Fri Mar 26 2021 rajeevalochana.kallur@hpe.com +- Modified +* Thu Jan 19 2017 prithvi.subrahmanya.v@hpe.com +- Added post section. +* Mon Dec 07 2015 jack.g.garcia@hpe.com +- Changes for the new code. +* Fri Jun 19 2014 jorge.cisneros@hp.com +- Changes for the new code. +* Thu Jun 01 2014 james.ayvaz@hp.com +- initial version %VERSION% diff --git a/rdmc.spec11.in b/rdmc.spec11.in new file mode 100644 index 0000000..ae0255f --- /dev/null +++ b/rdmc.spec11.in @@ -0,0 +1,76 @@ +# +# spec file for package ilorest (Version %VERSION%) +# + +# norootforbuild + +Name: ilorest +License: Copyright 2016-2022 Hewlett Packard Enterprise Development LP +Group: System/Configuration/Packaging +AutoReqProv: on +Version: %VERSION% +Release: %RELEASE% + +Source0: ilorest-%{version}.tar.bz2 +Url: https://www.hpe.com/info/restfulapi +Vendor: Hewlett Packard Enterprise +Packager: Hewlett Packard Enterprise +Summary: RESTful Interface Tool + +BuildRoot: %{_tmppath}/%{name}-%{version}-build +#BuildRequires: which + +%description +Command line interface for managing HPE ProLiant Servers + +%global __os_install_post %{nil} + +Authors: +-------- + Hewlett Packard Enterprise + +%prep +%setup -n ilorest-%{version} + +%build +mv rdmc-pyinstaller-lin.spc rdmc-pyinstaller-lin.spec +/usr/local/python3.11/bin/pyinstaller rdmc-pyinstaller-lin.spec +cp dist/ilorest ilorest + +%install +install -D -m 0755 dist/ilorest $RPM_BUILD_ROOT%{_sbindir}/ilorest + +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/ilorest +install -D -m 0644 rdmc-linux.conf $RPM_BUILD_ROOT%{_sysconfdir}/ilorest/redfish.conf + +mkdir -p $RPM_BUILD_ROOT%{_libdir}/ +install -D -m 0666 ./externals/*.so $RPM_BUILD_ROOT%{_libdir}/ + +%post +#rm -f %{_sbindir}/ilorest + +%postun +#rm -f %{_sbindir}/ilorest + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-, root, root) +%{_sbindir}/%{name} +%attr(644, root, root) %{_libdir}/ilorest_chif.so +%config %{_sysconfdir}/%{name}/* +%dir %{_sysconfdir}/%{name} +%defattr(444, root, root) + +%changelog +* Fri Mar 26 2021 rajeevalochana.kallur@hpe.com +- Modified +* Thu Jan 19 2017 prithvi.subrahmanya.v@hpe.com +- Added post section. +* Mon Dec 07 2015 jack.g.garcia@hpe.com +- Changes for the new code. +* Fri Jun 19 2014 jorge.cisneros@hp.com +- Changes for the new code. +* Thu Jun 01 2014 james.ayvaz@hp.com +- initial version %VERSION% diff --git a/requirements.txt b/requirements.txt index c1700ec..483e277 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,12 @@ jsonpath-rw >= 1.4.0 setproctitle >= 1.1.8; platform_system == "Linux" jsondiff >= 1.2.0 tabulate >= 0.8.7 -prompt_toolkit >= 3.0.10 +prompt_toolkit certifi >= 2020.12.5 -pywin32 >= 300; platform_system == "Windows" +pywin32; platform_system == "Windows" +pypiwin32; python_version <= '2.7.19' wcwidth >= 0.2.5 +enum; python_version <= '2.7.19' +pre-commit +future +futures; python_version <= '2.7.19' diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..b68802f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,27 @@ +[isort] +profile = black +[pycodestyle] +ignore = E203 +max_line_length = 120 +[pylint] +max-line-length = 120 +[flake8] +max-line-length = 120 +extend-ignore = E203,E722,C901,E731,W605, F841 +exclude = + .git, + packaging, + __pycache__, + build, + dist, + docs, + ilorest/redfish + tests, + ilorest/extensions/_hidden_commands/AutomaticTestingCommand.py, +max-complexity = 20 + +[bdist_wheel] +universal=1 + +[metadata] +license_files = LICENSE diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..13b57f0 --- /dev/null +++ b/setup.py @@ -0,0 +1,52 @@ +from setuptools import find_packages, setup + +extras = {} + +setup(name='ilorest', + version='4.7.0.0', + description='HPE iLORest Tool', + author='Hewlett Packard Enterprise', + author_email='rajeevalochana.kallur@hpe.com', + extras_require=extras, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Topic :: Communications' + ], + keywords='Hewlett Packard Enterprise', + url='https://github.com/HewlettPackard/python-redfish-utility', + packages=find_packages('.'), + package_dir={'': '.'}, + entry_points={ + 'console_scripts': [ + 'ilorest = ilorest.rdmc:ilorestcommand', + ], + }, + install_requires=[ + 'urllib3 >= 1.26.2', + 'pyaes >= 1.6.1', + 'colorama >= 0.4.4', + 'jsonpointer >= 2.0', + 'six >= 1.15.0', + 'ply >= 3.11', + 'decorator >= 4.4.2', + 'jsonpatch >= 1.28', + 'jsonpath-rw >= 1.4.0', + 'setproctitle >= 1.1.8; platform_system == "Linux"', + 'jsondiff >= 1.2.0', + 'tabulate >= 0.8.7', + 'prompt_toolkit', + 'certifi >= 2020.12.5', + 'pywin32; platform_system == "Windows"', + 'wcwidth >= 0.2.5', + 'pyudev', + 'future', + 'enum; python_version <= "2.7.19"', + 'futures; python_version <= "2.7.19"', + 'python-ilorest-library >= "4.7.0.0"' + ]) diff --git a/win32/rdmc-pyinstaller.spec.in b/win32/rdmc-pyinstaller.spec.in index 0f1b456..754fb1f 100644 --- a/win32/rdmc-pyinstaller.spec.in +++ b/win32/rdmc-pyinstaller.spec.in @@ -10,7 +10,7 @@ def hiddenImportGet(): classNames = [] _Commands = {} - extensionDir = os.path.dirname(os.getcwd()+ '\\src') + extensionDir = os.path.dirname(os.getcwd()+ '\\ilorest') if os.name != 'nt': replacement = '/' @@ -39,15 +39,15 @@ def getData(): extensionDir = os.path.dirname(os.getcwd()+ '\\extensions\\') for (cwd, dirs, _) in os.walk(extensionDir): for dir in dirs: - tempstr = cwd.split('\\src\\')[-1]+'\\'+dir+'\\' - datalist.append(('.\\src\\' + tempstr + '*.pyc', tempstr)) + tempstr = cwd.split('\\ilorest\\')[-1]+'\\'+dir+'\\' + datalist.append(('.\\ilorest\\' + tempstr + '*.pyc', tempstr)) return datalist compileall.compile_dir('.', force=True, quiet=True, legacy=True) -a = Analysis(['.\\src\\rdmc.py'], - pathex=['.\\src'], +a = Analysis(['.\\ilorest\\rdmc.py'], + pathex=['.\\ilorest'], binaries=None, datas=getData(), hiddenimports=hiddenImportGet(),