From b8475d5427f1bc27411e04b1e82e8cc3a30959b4 Mon Sep 17 00:00:00 2001 From: Mike Raineri Date: Fri, 12 Jul 2019 10:27:12 -0400 Subject: [PATCH 1/3] Modified account management checker to leverage Tacklebox --- account_management/account_management.py | 303 ++++++++---------- account_management/test_account_management.py | 93 ------ account_management/toolspath.py | 8 +- requirements.txt | 1 - 4 files changed, 135 insertions(+), 270 deletions(-) delete mode 100644 account_management/test_account_management.py diff --git a/account_management/account_management.py b/account_management/account_management.py index 889b3d1..911645c 100644 --- a/account_management/account_management.py +++ b/account_management/account_management.py @@ -1,190 +1,149 @@ # Copyright Notice: -# Copyright 2017 Distributed Management Task Force, Inc. All rights reserved. +# Copyright 2017-2019 Distributed Management Task Force, Inc. All rights reserved. # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Usecase-Checkers/blob/master/LICENSE.md -import getopt -import logging -import re -import sys +""" +Account Management Usecase Test -# noinspection PyUnresolvedReferences -import toolspath -from redfishtool import AccountService -from redfishtool import ServiceRoot -from redfishtool import redfishtoolTransport -from usecase.results import Results -from usecase.validation import SchemaValidation +File : account_management.py +Brief : This file contains the definitions and functionalities for performing + the usecase test for account management +""" -def run_account_operation(rft, account): - """ - Send AccountService operation to remote host - """ - return account.runOperation(rft) - +import argparse +import sys -def setup_account_operation(args, rft, account): - """ - Setup the args for the operation in the rft and account_service instances - """ - rft.subcommand = args[0] - rft.subcommandArgv = args - rft.printVerbose(5, "account_management:setup_account_operation: args: {}".format(args)) - # if no args, this is an AccountService 'get' command - if len(args) < 2: - account.operation = "get" - account.args = None - else: - account.operation = args[1] - account.args = args[1:] # now args points to the 1st argument - account.argnum = len(account.args) - - -def setup_and_run_account_operation(args, rft, account): - """ - Setup the operation, run it, print the results and return the results - """ - setup_account_operation(args, rft, account) - rc, r, j, d = run_account_operation(rft, account) - rft.printVerbose(1, "account_management:setup_and_run_account_operation: " + - "rc = {}, response = {}, json_data = {}, data = {}" - .format(rc, r, j, d)) - return rc, r, j, d +import redfish +import redfish_utilities +import toolspath +from usecase.results import Results -def validate_account_command(rft, account, validator, args): - """ - Perform the account operation and validate the returned payload against the schema +def verify_user( context, user_name, role = None ): """ - rc, r, j, d = setup_and_run_account_operation(args, rft, account) - msg = None - if rc != 0: - msg = "Error issuing '{}' command, return code = {}".format(args[1], rc) - if rc == 0 and j is True and d is not None: - schema = validator.get_json_schema(d) - rc, msg = validator.validate_json(d, schema) - return rc, msg + Checks that a given user is in the user list with a certain role + Args: + context: The Redfish client object with an open session + user_name: The name of the user to check + role: The role for the user -def get_service_root(rft, root): + Returns: + True if a match is found, false otherwise """ - Get Service Root information - """ - rc, r, j, d = root.getServiceRoot(rft) - rft.printVerbose(1, "account_management:get_service_root: rc = {}, response = {}, json_data = {}, data = {}" - .format(rc, r, j, d)) - return d - - -def display_usage(pgm_name): - print("Usage: {} [-v] [-d ] [-u ] [-p ] -r [-S ]".format(pgm_name)) - print(" [-i ] [-m :] [ [ ...]]") - + user_list = redfish_utilities.get_users( context ) + for user in user_list: + if user["UserName"] == user_name: + if role is None or user["RoleId"] == role: + return True + break -def log_results(results): - """ - Log the results of the account management validation run - """ - results.write_results() + return False +if __name__ == "__main__": -def main(argv): - """ - main - """ - rft = redfishtoolTransport.RfTransport() - account = AccountService.RfAccountServiceMain() - root = ServiceRoot.RfServiceRoot() - output_dir = None - - try: - opts, args = getopt.gnu_getopt(argv[1:], "vu:p:r:d:i:m:S:", ["verbose", "user=", "password=", "rhost=", - "directory=", "id=", "match=", "Secure="]) - except getopt.GetoptError: - rft.printErr("Error parsing options") - display_usage(argv[0]) - sys.exit(1) - - for index, (opt, arg) in enumerate(opts): - if opt in ("-v", "--verbose"): - rft.verbose = min((rft.verbose + 1), 5) - elif opt in ("-d", "--directory"): - output_dir = arg - elif opt in ("-r", "--rhost"): - rft.rhost = arg - elif opt in ("-u", "--user"): - rft.user = arg - elif opt in ("-p", "--password"): - rft.password = arg - # mask password in 'opts', which will be logged in Results - opts[index] = (opt, "********") - elif opt in ("-i", "--id"): - rft.IdLevel2 = rft.matchLevel2Value = arg - rft.gotIdLevel2Optn = rft.gotMatchLevel2Optn = True - rft.IdLevel2OptnCount += 1 - rft.matchLevel2Prop = "Id" - elif opt in ("-m", "--match"): - # arg is of the form: ":" - match_prop_pattern = "^(.+):(.+)$" - match_prop_match = re.search(match_prop_pattern, arg) - if match_prop_match: - rft.matchLevel2Prop = match_prop_match.group(1) - rft.matchLevel2Value = match_prop_match.group(2) - rft.IdLevel2OptnCount += 1 - rft.gotMatchLevel2Optn = True + # Get the input arguments + argget = argparse.ArgumentParser( description = "Usecase checker for account management" ) + argget.add_argument( "--user", "-u", type = str, required = True, help = "The user name for authentication" ) + argget.add_argument( "--password", "-p", type = str, required = True, help = "The password for authentication" ) + argget.add_argument( "--rhost", "-r", type = str, required = True, help = "The address of the Redfish service" ) + argget.add_argument( "--Secure", "-S", type = str, default = "Always", help = "When to use HTTPS (Always, IfSendingCredentials, IfLoginOrAuthenticatedApi, Never)" ) + argget.add_argument( "--directory", "-d", type = str, default = None, help = "Output directory for results.json" ) + args = argget.parse_args() + + test_username = "alice73t" + test_password = "hUPgd9Z4" + + # Set up the Redfish object + base_url = "https://" + args.rhost + if args.Secure == "Never": + base_url = "http://" + args.rhost + with redfish.redfish_client( base_url = base_url, username = args.user, password = args.password ) as redfish_obj: + # Create the results object + service_root = redfish_obj.get( "/redfish/v1/" ) + results = Results( "Account Management", service_root.dict ) + if args.directory is not None: + results.set_output_dir( args.directory ) + + # Get the list of current users + user_list = redfish_utilities.get_users( redfish_obj ) + user_count = len( user_list ) + if user_count == 0: + results.update_test_results( "User Count", 1, "No users were found" ) + else: + results.update_test_results( "User Count", 0, None ) + + # Create a new user + user_added = False + try: + print( "Creating new user '{}'".format( test_username ) ) + redfish_utilities.add_user( redfish_obj, test_username, test_password, "Administrator" ) + redfish_utilities.modify_user( redfish_obj, test_username, new_enabled = True ) + results.update_test_results( "Add User", 0, None ) + user_added = True + except: + results.update_test_results( "Add User", 1, "Failed to add user '{}'".format( test_username ) ) + + # Only run the remaining tests if the user was added successfully + if user_added: + # Get the list of current users to verify the new user was added + if verify_user( redfish_obj, test_username, "Administrator" ): + results.update_test_results( "Add User", 0, None ) else: - rft.printErr("Invalid level2 --match= option format: {}".format(arg)) - rft.printErr(" Expect --match=: Ex -m ProcessorType:CPU, --match=ProcessorType:CPU", - noprog=True) - sys.exit(1) - elif opt in ("-S", "--Secure"): # Specify when to use HTTPS - rft.secure = arg - if rft.secure not in rft.secureValidValues: - rft.printErr("Invalid --Secure option: {}".format(rft.secure)) - rft.printErr(" Valid values: {}".format(rft.secureValidValues), noprog=True) - sys.exit(1) - - # check for invalid Level-2 collection member reference options - if rft.IdLevel2OptnCount > 1: - rft.printErr("Syntax error: invalid mix of options -i and -m used to specify a 2nd-level collection member.") - rft.printErr(" Valid combinations: -i | -m ", noprog=True) - display_usage(argv[0]) - sys.exit(1) - - if not args: - # if no args, use the default scenario (add, get, modify, delete user) - user = "alice73t" - scenario_list = [["AccountService", "adduser", user, "hUPgd9Z4"], - ["AccountService", "useradmin", user, "disable"], - ["AccountService", "deleteuser", user], - ["AccountService", "Accounts"]] - else: - scenario_list = [["AccountService"] + args] - - # Set up logging - log_level = logging.WARNING - if 0 < rft.verbose < 3: - log_level = logging.INFO - elif rft.verbose >= 3: - log_level = logging.DEBUG - logging.basicConfig(stream=sys.stderr, level=log_level) - - service_root = get_service_root(rft, root) - results = Results("Account Management Checker", service_root) - if output_dir is not None: - results.set_output_dir(output_dir) - args_list = [argv[0]] + [v for opt in opts for v in opt] + args - results.add_cmd_line_args(args_list) - auth = (rft.user, rft.password) - nossl = True if rft.secure == "Never" else False - validator = SchemaValidation(rft.rhost, service_root, results, auth=auth, verify=False, nossl=nossl) - for scenario in scenario_list: - rc, msg = validate_account_command(rft, account, validator, scenario) - results.update_test_results(scenario[1], rc, msg) - - log_results(results) - exit(results.get_return_code()) - + results.update_test_results( "Add User", 1, "Failed to find user '{}' with the role 'Administrator'".format( test_username ) ) + + # Log in with the new user + print( "Logging in as '{}'".format( test_username ) ) + test_obj = redfish.redfish_client( base_url = base_url, username = test_username, password = test_password ) + try: + test_obj.login( auth = "session" ) + test_list = redfish_utilities.get_users( test_obj ) + results.update_test_results( "Credential Check", 0, None ) + except: + results.update_test_results( "Credential Check", 1, "Failed to login with user '{}'".format( test_username ) ) + finally: + test_obj.logout() + + # Log in with the new user, but with bad credentials + print( "Logging in as '{}', but with the wrong password".format( test_username ) ) + test_obj = redfish.redfish_client( base_url = base_url, username = test_username, password = test_password + "ExtraStuff" ) + try: + test_obj.login( auth = "session" ) + test_list = redfish_utilities.get_users( test_obj ) + results.update_test_results( "Credential Check", 1, "Login with user '{}' when using invalid credentials".format( test_username ) ) + except: + results.update_test_results( "Credential Check", 0, None ) + finally: + test_obj.logout() + + # Change the role of the user + test_roles = [ "ReadOnly", "Operator", "Administrator" ] + for role in test_roles: + try: + print( "Setting user '{}' to role '{}'".format( test_username, role ) ) + redfish_utilities.modify_user( redfish_obj, test_username, new_role = role ) + results.update_test_results( "Change Role", 0, None ) + if verify_user( redfish_obj, test_username, role ): + results.update_test_results( "Change Role", 0, None ) + else: + results.update_test_results( "Change Role", 1, "Failed to find user '{}' with the role '{}'".format( test_username, role ) ) + except: + results.update_test_results( "Change Role", 1, "Failed to set user '{}' to '{}'".format( test_username, role ) ) + + # Delete the user + try: + print( "Deleting user '{}'".format( test_username ) ) + redfish_utilities.delete_user( redfish_obj, test_username ) + results.update_test_results( "Delete User", 0, None ) + if verify_user( redfish_obj, test_username ): + results.update_test_results( "Delete User", 1, "User '{}' is still in the user list".format( test_username ) ) + else: + results.update_test_results( "Delete User", 0, None ) + except: + results.update_test_results( "Delete User", 0, "Failed to delete user '{}'".format( test_username ) ) + + # Save the results + results.write_results() -if __name__ == "__main__": - main(sys.argv) + sys.exit( results.get_return_code() ) diff --git a/account_management/test_account_management.py b/account_management/test_account_management.py deleted file mode 100644 index 0d07845..0000000 --- a/account_management/test_account_management.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright Notice: -# Copyright 2017 Distributed Management Task Force, Inc. All rights reserved. -# License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Usecase-Checkers/blob/master/LICENSE.md -# -# Unit tests for account_management.py -# - -from unittest import TestCase -from unittest import mock - -import account_management - - -class AccountServiceTest(TestCase): - - @mock.patch('requests.models.Response', autospec=True) - @mock.patch('account_management.redfishtoolTransport.RfTransport', autospec=True) - @mock.patch('account_management.AccountService.RfAccountServiceMain', autospec=True) - @mock.patch('account_management.SchemaValidation', autospec=True) - def setUp(self, mock_validator, mock_account_service, mock_transport, mock_response): - self.validator = mock_validator - self.account = mock_account_service - self.rft = mock_transport - mock_transport.IdOptnCount = 1 - - def run_validator_side_effect(json, schema): - return 0, None - - def run_good_operation_side_effect(rft): - mock_response.status_code = 200 - data = {"Members": [{"Id": 1, "UserName": "Administrator"}]} - return 0, mock_response, True, data - - def run_bad_operation_side_effect(rft): - mock_response.status_code = 405 - return 5, mock_response, False, None - - self.validator.validate_json.side_effect = run_validator_side_effect - self.good_side_effect = run_good_operation_side_effect - self.bad_side_effect = run_bad_operation_side_effect - self.account.runOperation.side_effect = self.good_side_effect - - def run_good_setup_and_operation(self, args): - self.account.runOperation.side_effect = self.good_side_effect - account_management.setup_account_operation(args, self.rft, self.account) - rc, r, j, d = account_management.run_account_operation(self.rft, self.account) - self.assertEqual(self.account.runOperation.call_count, 1, - "account.runOperation call_count should be 1") - self.assertEqual(rc, 0, "return code (rc) should be 0") - self.assertEqual(r.status_code, 200, "response status_code should be 200") - self.assertEqual(j, True, "json_data should be True") - self.assertTrue("Members" in d, "data_type should contain 'Members' element") - - def run_bad_setup_and_operation(self, args): - self.account.runOperation.side_effect = self.bad_side_effect - account_management.setup_account_operation(args, self.rft, self.account) - rc, r, j, d = account_management.run_account_operation(self.rft, self.account) - self.assertEqual(self.account.runOperation.call_count, 1, - "account.runOperation call_count should be 1") - self.assertEqual(rc, 5, "return code (rc) should be 5") - self.assertEqual(r.status_code, 405, "response status_code should be 405") - self.assertEqual(j, False, "json_data should be False") - self.assertEqual(d, None, "data_type should be None") - - """ - Tests for functions setup_operation() and run_systems_operation() - """ - def test_run_good_adduser(self): - self.run_good_setup_and_operation(["AccountService", "adduser", "alice", "ksbt6529", "Admin"]) - - def test_run_bad_adduser(self): - self.run_bad_setup_and_operation(["AccountService", "adduser", "alice", "ksbt6529", "Admin"]) - - def test_run_good_deleteuser(self): - self.run_good_setup_and_operation(["AccountService", "deleteuser", "alice"]) - - def test_run_bad_deleteuser(self): - self.run_bad_setup_and_operation(["AccountService", "deleteuser", "alice"]) - - def test_run_good_useradmin(self): - self.run_good_setup_and_operation(["AccountService", "useradmin", "alice", "disable"]) - - def test_run_bad_useradmin(self): - self.run_bad_setup_and_operation(["AccountService", "useradmin", "alice", "disable"]) - - """ - Tests for function validate_reset_command() - """ - def test_validate_account_command(self): - args = ["AccountService", "adduser", "alice", "ksbt6529", "Admin"] - account_management.validate_account_command(self.rft, self.account, self.validator, args) - self.assertEqual(self.account.runOperation.call_count, 1, - "account.runOperation call_count should be 1") diff --git a/account_management/toolspath.py b/account_management/toolspath.py index bb72c61..9e27662 100644 --- a/account_management/toolspath.py +++ b/account_management/toolspath.py @@ -1,11 +1,11 @@ # Copyright Notice: -# Copyright 2017 Distributed Management Task Force, Inc. All rights reserved. +# Copyright 2017-2019 Distributed Management Task Force, Inc. All rights reserved. # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Usecase-Checkers/blob/master/LICENSE.md import os import sys -cur_dir = os.path.dirname(__file__) -path_dir = os.path.abspath(os.path.join(cur_dir, os.path.pardir)) +cur_dir = os.path.dirname( __file__ ) +path_dir = os.path.abspath( os.path.join( cur_dir, os.path.pardir ) ) if path_dir not in sys.path: - sys.path.insert(0, path_dir) + sys.path.insert( 0, path_dir ) diff --git a/requirements.txt b/requirements.txt index a601997..6698ff9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ jsonschema -redfishtool redfish>=2.1.0 redfish_utilities>=1.0.0 From 5bffc7754f12d58882552af996f8f4a3817ed399 Mon Sep 17 00:00:00 2001 From: Mike Raineri Date: Fri, 12 Jul 2019 10:27:34 -0400 Subject: [PATCH 2/3] Readme updates --- README.md | 67 +++++++++++++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 2cc727f..43b80f3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - Copyright 2017-2019 Distributed Management Task Force, Inc. All rights reserved. # Redfish-Usecase-Checkers @@ -14,72 +13,68 @@ For example: ## Prerequisites -Install `jsonschema`, `redfishtool`, `redfish`, and `redfish_utilities`: +Install `jsonschema`, `redfish`, and `redfish_utilities`: ``` pip install jsonschema -pip install redfishtool pip install redfish pip install redfish_utilities ``` -## Example Usage +## Test Details and Examples Each tool may be ran with -h, for verbose help on parameters. -### One time boot checker examples +### One Time Boot Checker -Sets all systems found at `127.0.0.1:8000` the boot override set to either PXE or USB, and resets the system to see the boot override is performed. +This checker logs into a specified service and traverses the `Systems` collection. +It will perform the following operations on all systems: +* Reads the `Boot` object +* Sets the `BootSourceOverrideTarget` property to either `Pxe` or `Usb`, depending on what's allowed +* Performs a reset of the system +* Monitors the `BootSourceOverrideTarget` property after the reset to ensure it changes back to `None` +Example: ``` -$ python3 one_time_boot_check.py -r 127.0.0.1:8000 -u -p +$ python3 one_time_boot_check.py -r 127.0.0.1:8000 -u -p -S Always ``` -### Power/thermal info checker examples +### Power/Thermal Info Checker -Finds all chassis instances at `127.0.0.1:8000` and collects their respective power and thermal information. +This checker logs into a specified service and traverses the `Chassis` collection. +For each chassis found, it will ensure that it can collect at least one sensor reading from the `Power` and `Thermal` resources. +Example: ``` -$ python3 power_thermal_test.py -r 127.0.0.1:8000 -u -p +$ python3 power_thermal_test.py -r 127.0.0.1:8000 -u -p -S Always ``` -### Power control checker examples +### Power Control Checker -Performs all possible resets of systems found at `127.0.0.1:8000`: +This checker logs into a specified service and traverses the `Systems` collection. +It will perform the following operations on all systems: +* Reads the allowable `ResetType` parameter values +* Performs a reset using each of the allowable `ResetType` values +Example: ``` -$ python3 power_control.py -r 127.0.0.1:8000 -u -p +$ python3 power_control.py -r 127.0.0.1:8000 -u -p -S Always ``` -### Account management checker examples - +### Account Management Checker -Issue command to add user `alice` on host `127.0.0.1` with security enabled: -``` -$ python3 account_management.py -r 127.0.0.1 -u root -p -S Always adduser alice -``` - -Issue command to fetch the account for user `alice`: -``` -$ python3 account_management.py -r 127.0.0.1 -u root -p -S Always Accounts -mUserName:alice -``` - -Issue command to disable the account for user `alice`: -``` -$ python3 account_management.py -r 127.0.0.1 -u root -p -S Always useradmin alice disable -``` - -Issue command to delete the account for user `alice`: -``` -$ python3 account_management.py -r 127.0.0.1 -u root -p -S Always deleteuser alice -``` +This checker logs into a specified service and performs the following operations: +* Creates a new user +* Logs into the service with the new user +* Modifies the new user with different roles +* Deletes the new user -Issue command to set username for account with Id=3 to `bob`: +Example: ``` -$ python3 account_management.py -r 127.0.0.1 -u root -p -S Always setusername 3 bob +$ python3 account_management.py --r 127.0.0.1:8000 -u -p -S Always ``` From be6679fa3a15ec2eb99f14659b9734cd45e89563 Mon Sep 17 00:00:00 2001 From: Mike Raineri Date: Fri, 12 Jul 2019 10:31:39 -0400 Subject: [PATCH 3/3] Error message fix --- account_management/account_management.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_management/account_management.py b/account_management/account_management.py index 911645c..cdfaa77 100644 --- a/account_management/account_management.py +++ b/account_management/account_management.py @@ -141,7 +141,7 @@ def verify_user( context, user_name, role = None ): else: results.update_test_results( "Delete User", 0, None ) except: - results.update_test_results( "Delete User", 0, "Failed to delete user '{}'".format( test_username ) ) + results.update_test_results( "Delete User", 1, "Failed to delete user '{}'".format( test_username ) ) # Save the results results.write_results()