From cfcf667ebe60fa8213067fdc4ac3bfc9d21146cd Mon Sep 17 00:00:00 2001 From: Ryan Causey Date: Wed, 7 Oct 2020 22:19:45 -0700 Subject: [PATCH] INTERNAL: Remove dead code --- .../stack/argument_processors/firmware.py | 511 ----------- .../stack/commands/add/firmware/__init__.py | 82 -- .../commands/add/firmware/imp/__init__.py | 42 - .../commands/add/firmware/imp/plugin_basic.py | 97 -- .../commands/add/firmware/make/__init__.py | 29 - .../add/firmware/make/plugin_basic.py | 38 - .../commands/add/firmware/model/__init__.py | 39 - .../add/firmware/model/plugin_basic.py | 85 -- .../commands/add/firmware/plugin_basic.py | 290 ------ .../add/firmware/version_regex/__init__.py | 46 - .../firmware/version_regex/plugin_basic.py | 130 --- .../commands/add/host/firmware/__init__.py | 17 - .../add/host/firmware/mapping/__init__.py | 49 - .../add/host/firmware/mapping/plugin_basic.py | 88 -- .../stack/commands/list/firmware/__init__.py | 48 - .../commands/list/firmware/imp/__init__.py | 34 - .../list/firmware/imp/plugin_basic.py | 25 - .../commands/list/firmware/make/__init__.py | 43 - .../list/firmware/make/plugin_basic.py | 42 - .../commands/list/firmware/model/__init__.py | 59 -- .../list/firmware/model/plugin_basic.py | 122 --- .../commands/list/firmware/plugin_basic.py | 46 - .../list/firmware/version_regex/__init__.py | 34 - .../firmware/version_regex/plugin_basic.py | 29 - .../commands/list/host/firmware/__init__.py | 105 --- .../list/host/firmware/imp_dell_x1052.py | 75 -- .../list/host/firmware/imp_mellanox.py | 66 -- .../list/host/firmware/mapping/__init__.py | 85 -- .../host/firmware/mapping/plugin_basic.py | 132 --- .../list/host/firmware/plugin_basic.py | 47 - .../list/host/firmware/plugin_version.py | 98 -- .../commands/remove/firmware/__init__.py | 57 -- .../commands/remove/firmware/imp/__init__.py | 29 - .../remove/firmware/imp/plugin_basic.py | 42 - .../commands/remove/firmware/make/__init__.py | 29 - .../remove/firmware/make/plugin_basic.py | 59 -- .../firmware/make/version_regex/__init__.py | 29 - .../make/version_regex/plugin_basic.py | 33 - .../remove/firmware/model/__init__.py | 34 - .../remove/firmware/model/plugin_basic.py | 76 -- .../firmware/model/version_regex/__init__.py | 33 - .../model/version_regex/plugin_basic.py | 42 - .../commands/remove/firmware/plugin_basic.py | 85 -- .../remove/firmware/version_regex/__init__.py | 29 - .../firmware/version_regex/plugin_basic.py | 30 - .../commands/remove/host/firmware/__init__.py | 17 - .../remove/host/firmware/mapping/__init__.py | 63 -- .../host/firmware/mapping/plugin_basic.py | 145 --- .../stack/commands/set/firmware/__init__.py | 17 - .../commands/set/firmware/make/__init__.py | 0 .../firmware/make/version_regex/__init__.py | 33 - .../make/version_regex/plugin_basic.py | 42 - .../commands/set/firmware/model/__init__.py | 0 .../set/firmware/model/imp/__init__.py | 37 - .../set/firmware/model/imp/plugin_basic.py | 51 -- .../firmware/model/version_regex/__init__.py | 37 - .../model/version_regex/plugin_basic.py | 50 - .../stack/commands/sync/firmware/__init__.py | 43 - .../commands/sync/firmware/plugin_basic.py | 92 -- .../commands/sync/host/firmware/__init__.py | 238 ----- .../sync/host/firmware/imp_dell_x1052.py | 120 --- .../sync/host/firmware/imp_mellanox.py | 147 --- .../sync/host/firmware/plugin_basic.py | 58 -- common/src/stack/pylib/stack/firmware.py | 137 --- .../list/mock_list_firmware_run_plugins.py | 34 - .../files/sync/mock_firmware_run_plugins.py | 34 - .../tests/list/test_list_host_firmware.py | 60 -- .../list/test_list_host_firmware_mapping.py | 107 --- .../tests/sync/test_sync_firmware.py | 60 -- .../tests/sync/test_sync_host_firmware.py | 50 - .../argument_processors/test_firmware.py | 691 -------------- ...stack_commands_add_firmware_imp__init__.py | 30 - ..._commands_add_firmware_imp_plugin_basic.py | 230 ----- ...tack_commands_add_firmware_make__init__.py | 29 - ...commands_add_firmware_make_plugin_basic.py | 81 -- ...ack_commands_add_firmware_model__init__.py | 30 - ...ommands_add_firmware_model_plugin_basic.py | 202 ----- ...and_stack_commands_add_firmware__init__.py | 30 - ...tack_commands_add_firmware_plugin_basic.py | 852 ------------------ ...ands_add_firmware_version_regex__init__.py | 30 - ...add_firmware_version_regex_plugin_basic.py | 298 ------ ...mands_add_host_firmware_mapping__init__.py | 30 - ..._add_host_firmware_mapping_plugin_basic.py | 161 ---- ...tack_commands_list_firmware_imp__init__.py | 49 - ...commands_list_firmware_imp_plugin_basic.py | 41 - ...ack_commands_list_firmware_make__init__.py | 65 -- ...ommands_list_firmware_make_plugin_basic.py | 53 -- ...ck_commands_list_firmware_model__init__.py | 62 -- ...mmands_list_firmware_model_plugin_basic.py | 229 ----- ...nd_stack_commands_list_firmware__init__.py | 69 -- ...ack_commands_list_firmware_plugin_basic.py | 59 -- ...nds_list_firmware_version_regex__init__.py | 49 - ...ist_firmware_version_regex_plugin_basic.py | 41 - ...list_host_firmware_mapping_plugin_basic.py | 79 -- ...ck_commands_remove_firmware_imp__init__.py | 29 - ...mmands_remove_firmware_imp_plugin_basic.py | 67 -- ...k_commands_remove_firmware_make__init__.py | 29 - ...mands_remove_firmware_make_plugin_basic.py | 86 -- ...ove_firmware_make_version_regex__init__.py | 29 - ...irmware_make_version_regex_plugin_basic.py | 67 -- ..._commands_remove_firmware_model__init__.py | 30 - ...ands_remove_firmware_model_plugin_basic.py | 106 --- ...ve_firmware_model_version_regex__init__.py | 30 - ...rmware_model_version_regex_plugin_basic.py | 75 -- ..._stack_commands_remove_firmware__init__.py | 30 - ...k_commands_remove_firmware_plugin_basic.py | 185 ---- ...s_remove_firmware_version_regex__init__.py | 29 - ...ove_firmware_version_regex_plugin_basic.py | 57 -- ...ds_remove_host_firmware_mapping__init__.py | 30 - ...move_host_firmware_mapping_plugin_basic.py | 351 -------- ...set_firmware_make_version_regex__init__.py | 30 - ...irmware_make_version_regex_plugin_basic.py | 77 -- ...commands_set_firmware_model_imp__init__.py | 30 - ...nds_set_firmware_model_imp_plugin_basic.py | 77 -- ...et_firmware_model_version_regex__init__.py | 30 - ...rmware_model_version_regex_plugin_basic.py | 77 -- .../pylib/stack/test_pylib_stack_firmware.py | 238 ----- 117 files changed, 10191 deletions(-) delete mode 100644 common/src/stack/command/stack/argument_processors/firmware.py delete mode 100644 common/src/stack/command/stack/commands/add/firmware/__init__.py delete mode 100644 common/src/stack/command/stack/commands/add/firmware/imp/__init__.py delete mode 100644 common/src/stack/command/stack/commands/add/firmware/imp/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/add/firmware/make/__init__.py delete mode 100644 common/src/stack/command/stack/commands/add/firmware/make/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/add/firmware/model/__init__.py delete mode 100644 common/src/stack/command/stack/commands/add/firmware/model/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/add/firmware/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/add/firmware/version_regex/__init__.py delete mode 100644 common/src/stack/command/stack/commands/add/firmware/version_regex/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/add/host/firmware/__init__.py delete mode 100644 common/src/stack/command/stack/commands/add/host/firmware/mapping/__init__.py delete mode 100644 common/src/stack/command/stack/commands/add/host/firmware/mapping/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/list/firmware/__init__.py delete mode 100644 common/src/stack/command/stack/commands/list/firmware/imp/__init__.py delete mode 100644 common/src/stack/command/stack/commands/list/firmware/imp/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/list/firmware/make/__init__.py delete mode 100644 common/src/stack/command/stack/commands/list/firmware/make/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/list/firmware/model/__init__.py delete mode 100644 common/src/stack/command/stack/commands/list/firmware/model/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/list/firmware/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/list/firmware/version_regex/__init__.py delete mode 100644 common/src/stack/command/stack/commands/list/firmware/version_regex/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/list/host/firmware/__init__.py delete mode 100644 common/src/stack/command/stack/commands/list/host/firmware/imp_dell_x1052.py delete mode 100644 common/src/stack/command/stack/commands/list/host/firmware/imp_mellanox.py delete mode 100644 common/src/stack/command/stack/commands/list/host/firmware/mapping/__init__.py delete mode 100644 common/src/stack/command/stack/commands/list/host/firmware/mapping/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/list/host/firmware/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/list/host/firmware/plugin_version.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/__init__.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/imp/__init__.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/imp/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/make/__init__.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/make/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/make/version_regex/__init__.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/make/version_regex/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/model/__init__.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/model/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/model/version_regex/__init__.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/model/version_regex/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/version_regex/__init__.py delete mode 100644 common/src/stack/command/stack/commands/remove/firmware/version_regex/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/remove/host/firmware/__init__.py delete mode 100644 common/src/stack/command/stack/commands/remove/host/firmware/mapping/__init__.py delete mode 100644 common/src/stack/command/stack/commands/remove/host/firmware/mapping/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/set/firmware/__init__.py delete mode 100644 common/src/stack/command/stack/commands/set/firmware/make/__init__.py delete mode 100644 common/src/stack/command/stack/commands/set/firmware/make/version_regex/__init__.py delete mode 100644 common/src/stack/command/stack/commands/set/firmware/make/version_regex/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/set/firmware/model/__init__.py delete mode 100644 common/src/stack/command/stack/commands/set/firmware/model/imp/__init__.py delete mode 100644 common/src/stack/command/stack/commands/set/firmware/model/imp/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/set/firmware/model/version_regex/__init__.py delete mode 100644 common/src/stack/command/stack/commands/set/firmware/model/version_regex/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/sync/firmware/__init__.py delete mode 100644 common/src/stack/command/stack/commands/sync/firmware/plugin_basic.py delete mode 100644 common/src/stack/command/stack/commands/sync/host/firmware/__init__.py delete mode 100644 common/src/stack/command/stack/commands/sync/host/firmware/imp_dell_x1052.py delete mode 100644 common/src/stack/command/stack/commands/sync/host/firmware/imp_mellanox.py delete mode 100644 common/src/stack/command/stack/commands/sync/host/firmware/plugin_basic.py delete mode 100644 common/src/stack/pylib/stack/firmware.py delete mode 100644 test-framework/test-suites/integration/files/list/mock_list_firmware_run_plugins.py delete mode 100644 test-framework/test-suites/integration/files/sync/mock_firmware_run_plugins.py delete mode 100644 test-framework/test-suites/integration/tests/list/test_list_host_firmware.py delete mode 100644 test-framework/test-suites/integration/tests/list/test_list_host_firmware_mapping.py delete mode 100644 test-framework/test-suites/integration/tests/sync/test_sync_firmware.py delete mode 100644 test-framework/test-suites/integration/tests/sync/test_sync_host_firmware.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/argument_processors/test_firmware.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/imp/test_command_stack_commands_add_firmware_imp__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/imp/test_command_stack_commands_add_firmware_imp_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/make/test_command_stack_commands_add_firmware_make__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/make/test_command_stack_commands_add_firmware_make_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/model/test_command_stack_commands_add_firmware_model__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/model/test_command_stack_commands_add_firmware_model_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/test_command_stack_commands_add_firmware__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/test_command_stack_commands_add_firmware_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/version_regex/test_command_stack_commands_add_firmware_version_regex__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/version_regex/test_command_stack_commands_add_firmware_version_regex_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/host/firmware/mapping/test_command_stack_commands_add_host_firmware_mapping__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/add/host/firmware/mapping/test_command_stack_commands_add_host_firmware_mapping_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/imp/test_command_stack_commands_list_firmware_imp__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/imp/test_command_stack_commands_list_firmware_imp_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/make/test_command_stack_commands_list_firmware_make__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/make/test_command_stack_commands_list_firmware_make_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/model/test_command_stack_commands_list_firmware_model__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/model/test_command_stack_commands_list_firmware_model_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/test_command_stack_commands_list_firmware__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/test_command_stack_commands_list_firmware_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/version_regex/test_command_stack_commands_list_firmware_version_regex__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/version_regex/test_command_stack_commands_list_firmware_version_regex_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/list/host/firmware/mapping/test_command_stack_commands_list_host_firmware_mapping_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/imp/test_command_stack_commands_remove_firmware_imp__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/imp/test_command_stack_commands_remove_firmware_imp_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/test_command_stack_commands_remove_firmware_make__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/test_command_stack_commands_remove_firmware_make_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/version_regex/test_command_stack_commands_remove_firmware_make_version_regex__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/version_regex/test_command_stack_commands_remove_firmware_make_version_regex_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/test_command_stack_commands_remove_firmware_model__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/test_command_stack_commands_remove_firmware_model_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/version_regex/test_command_stack_commands_remove_firmware_model_version_regex__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/version_regex/test_command_stack_commands_remove_firmware_model_version_regex_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/test_command_stack_commands_remove_firmware__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/test_command_stack_commands_remove_firmware_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/version_regex/test_command_stack_commands_remove_firmware_version_regex__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/version_regex/test_command_stack_commands_remove_firmware_version_regex_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/host/firmware/mapping/test_command_stack_commands_remove_host_firmware_mapping__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/remove/host/firmware/mapping/test_command_stack_commands_remove_host_firmware_mapping_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/make/version_regex/test_command_stack_commands_set_firmware_make_version_regex__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/make/version_regex/test_command_stack_commands_set_firmware_make_version_regex_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/imp/test_command_stack_commands_set_firmware_model_imp__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/imp/test_command_stack_commands_set_firmware_model_imp_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/version_regex/test_command_stack_commands_set_firmware_model_version_regex__init__.py delete mode 100644 test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/version_regex/test_command_stack_commands_set_firmware_model_version_regex_plugin_basic.py delete mode 100644 test-framework/test-suites/unit/tests/pylib/stack/test_pylib_stack_firmware.py diff --git a/common/src/stack/command/stack/argument_processors/firmware.py b/common/src/stack/command/stack/argument_processors/firmware.py deleted file mode 100644 index 7c9068d89..000000000 --- a/common/src/stack/command/stack/argument_processors/firmware.py +++ /dev/null @@ -1,511 +0,0 @@ -from pathlib import Path -from collections import namedtuple, OrderedDict -from stack.exception import CommandError - -class FirmwareArgProcessor: - """A mixin to process firmware command arguments.""" - - def get_make_id(self, make): - """Get the ID of the make with the provided name. - - This will raise a CommandError if the make with the provided name doesn't exist. - """ - row = self.db.select("id FROM firmware_make WHERE name=%s", make) - if not row: - raise CommandError(cmd = self, msg = f"Firmware make {make} doesn't exist.") - - return row[0][0] - - def make_exists(self, make): - """Returns whether the given make exists in the database.""" - return self.db.count("(id) FROM firmware_make WHERE name=%s", make) - - def ensure_make_exists(self, make): - """Ensures that the provided make exists. - - If the make does not exist in the database, a CommandError is raised. - - If an empty string is provided, a CommandError is raised. - """ - if not make: - raise CommandError(cmd = self, msg = "A make is required.") - - if not self.make_exists(make = make): - raise CommandError(cmd = self, msg = f"The make {make} does not exist.") - - def ensure_unique_makes(self, makes): - """Ensures that none of the names in the list of makes provided already exist in the database. - - If an empty iterable is provided, a CommandError is raised. - - If any make name is blank, a CommandError is raised. - """ - makes = tuple(makes) - if not makes: - raise CommandError(cmd = self, msg = "Makes are required.") - - # No empty strings allowed. - if not all(makes): - raise CommandError(cmd = self, msg = "A make cannot be an empty string.") - - # ensure the make names don't already exist - existing_makes = [ - make - for make, exists in ( - (make, self.make_exists(make)) for make in makes - ) - if exists - ] - if existing_makes: - raise CommandError(cmd = self, msg = f"The following firmware makes already exist: {existing_makes}.") - - def ensure_makes_exist(self, makes): - """Ensures that all of the names in the list of makes provided already exist in the datbase. - - If an empty iterable is provided, a CommandError is raised. - - If any make name is blank, a CommandError is raised. - """ - makes = tuple(makes) - if not makes: - raise CommandError(cmd = self, msg = "Makes are required.") - - # No empty strings allowed. - if not all(makes): - raise CommandError(cmd = self, msg = "A make cannot be an empty string.") - - # ensure the make names already exist - missing_makes = [ - make - for make, exists in ( - (make, self.make_exists(make)) for make in makes - ) - if not exists - ] - if missing_makes: - raise CommandError(cmd = self, msg = f"The following firmware makes don't exist: {missing_makes}.") - - def get_model_id(self, make, model): - """Get the ID of the model with the provided name related to the provided make. - - This will raise a CommandError if the make + model combo doesn't exist. - """ - row = self.db.select( - """ - firmware_model.id - FROM firmware_model - INNER JOIN firmware_make - ON firmware_model.make_id=firmware_make.id - WHERE firmware_make.name=%s AND firmware_model.name=%s - """, - (make, model), - ) - if not row: - raise CommandError(cmd = self, msg = f"Firmware model {model} doesn't exist for make {make}.") - - return row[0][0] - - def model_exists(self, make, model): - """Returns whether the given model exists for the given make.""" - return self.db.count( - """ - (firmware_model.id) - FROM firmware_model - INNER JOIN firmware_make - ON firmware_model.make_id=firmware_make.id - WHERE firmware_make.name=%s AND firmware_model.name=%s - """, - (make, model), - ) - - def ensure_model_exists(self, make, model): - """Ensures that a given model exists for a given make. - - If the make does not exist in the database, a CommandError is raised. - - If an empty string is provided for either make or model, a CommandError is raised. - """ - self.ensure_make_exists(make = make) - - if not model: - raise CommandError(cmd = self, msg = "A model is required.") - - if not self.model_exists(make = make, model = model): - raise CommandError(cmd = self, msg = f"Make {make} does not exist for model {model}.") - - def ensure_unique_models(self, make, models): - """Ensures that none of the given model names already exist in the database for the given make. - - If an empty iterable is provided, a CommandError is raised. - - If any model name is blank, a CommandError is raised. - """ - # We don't require that the make exists because a set of models for a non-existent make would be - # entirely new in that they are going to be added under a make that doesn't already exist. - models = tuple(models) - if not models: - raise CommandError(cmd = self, msg = "Models are required.") - - # No empty strings allowed. - if not all(models): - raise CommandError(cmd = self, msg = "A model cannot be an empty string.") - - # ensure the model name doesn't already exist for the given make - existing_makes_models = [ - (make, model) - for make, model, exists in ( - (make, model, self.model_exists(make, model)) for model in models - ) - if exists - ] - if existing_makes_models: - raise CommandError(cmd = self, msg = f"The following make and model combinations already exist {existing_makes_models}.") - - def ensure_models_exist(self, make, models): - """Ensures that all of the given model names already exist in the database for the given make. - - If the make does not exist, a CommandError is raised. - - If an empty iterable is provided, a CommandError is raised. - - If an empty string is provided for either make or one of the models, a CommandError is raised. - - If any model name is blank, a CommandError is raised. - """ - self.ensure_make_exists(make = make) - - models = tuple(models) - if not models: - raise CommandError(cmd = self, msg = "Models are required.") - - # No empty strings allowed. - if not all(models): - raise CommandError(cmd = self, msg = "A model cannot be an empty string.") - - # ensure the models exist - missing_models = [ - model - for model, exists in ( - (model, self.model_exists(make, model)) for model in models - ) - if not exists - ] - if missing_models: - raise CommandError( - cmd = self, - msg = f"The following firmware models don't exist for make {make}: {missing_models}." - ) - - def firmware_exists(self, make, model, version): - """Returns whether the given firmware version exists for the make + model combo.""" - return self.db.count( - """ - (firmware.id) - FROM firmware - INNER JOIN firmware_model - ON firmware.model_id=firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id=firmware_make.id - WHERE firmware_make.name=%s AND firmware_model.name=%s AND firmware.version=%s - """, - (make, model, version) - ) - - def ensure_firmware_exists(self, make, model, version): - """Ensure that the provided firmware version for the make and model exists. - - If the make does not exist, a CommandError is raised. - - If the model does not exist, a CommandError is raised. - - If an empty string is provided for make, model, or version, a CommandError is raised. - """ - self.ensure_model_exists(make = make, model = model) - - if not version: - raise CommandError(cmd = self, msg = "A version is required.") - - if not self.firmware_exists(make = make, model = model, version = version): - raise CommandError( - cmd = self, - msg = f"The firmware version {version} does not exist for make {make} and model {model}.", - ) - - def ensure_firmwares_exist(self, make, model, versions): - """Ensures that all the firmware versions provided exist for the given make and model. - - If the make does not exist, a CommandError is raised. - - If the model does not exist, a CommandError is raised. - - If an empty iterable is provided, a CommandError is raised. - - If an empty string is provided for make, model, or version, a CommandError is raised. - """ - self.ensure_model_exists(make = make, model = model) - - versions = tuple(versions) - if not versions: - raise CommandError(cmd = self, msg = "Versions are required.") - - # No empty strings allowed. - if not all(versions): - raise CommandError(cmd = self, msg = "A version cannot be an empty string.") - - # ensure the versions exist in the DB - missing_versions = [ - version - for version, exists in ( - (version, self.firmware_exists(make, model, version)) for version in versions - ) - if not exists - ] - if missing_versions: - raise CommandError( - cmd = self, - msg = f"The following firmware versions don't exist for make {make} and model {model}: {missing_versions}." - ) - - def get_firmware_id(self, make, model, version): - """Get the ID of the firmware version with the provided version for the provided make and model. - - This will raise a CommandError if the firmware version matching the provided information doesn't exist. - """ - row = self.db.select( - """ - firmware.id - FROM firmware - INNER JOIN firmware_model - ON firmware.model_id=firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id=firmware_make.id - WHERE firmware_make.name=%s AND firmware_model.name=%s AND firmware.version=%s - """, - (make, model, version) - ) - if not row: - raise CommandError(cmd = self, msg = f"Firmware version {version} doesn't exist for make {make} and model {model}.") - - return row[0][0] - - def get_common_frontend_ip(self, hostname): - """Attempts to get an IP of one of the front end interfaces that shares a network with the provided host. - - If the frontend and the host have no common networks, a CommandError is raised. - If none of the interfaces on the common network have IP addresses, a CommandError is raised. - """ - host_interface_frontend = self.call(command = "list.host.interface", args = ["a:frontend"]) - # try to get the set of all common networks between the front end and the target host - host_networks = set(host["network"] for host in self.call(command = "list.host.interface", args = [hostname])) - frontend_networks = set(frontend_interface["network"] for frontend_interface in host_interface_frontend) - common_networks = list(host_networks & frontend_networks) - - if not common_networks: - raise CommandError( - cmd = self, - msg = ( - f"{hostname} does not share a network with the frontend, and thus cannot fetch firmware" - f" from it. Please configure {hostname} to share a common network with the frontend." - ) - ) - - # try to get the IP addresses of frontend interfaces on the common networks - ip_addr = [ - frontend_interface["ip"] for frontend_interface in host_interface_frontend - if frontend_interface["network"] in common_networks and frontend_interface["ip"] - ] - - if not ip_addr: - raise CommandError( - cmd = self, - msg = ( - f"None of the network interfaces on the frontend attached to the following common networks" - f"have an IP address. Please configure at least one interface to have an IP address on one" - f"of the following networks: {common_networks}" - ) - ) - # pick the first one and use it - return ip_addr[0] - - def get_firmware_url(self, hostname, firmware_file): - """Attempts to get a url to allow a backend to download the provided firmware file from the front end. - - If the frontend and the backend have no common networks, a CommandError is raised. - If none of the interfaces on the common network have IP addresses, a CommandError is raised. - If the file doesn't exist on disk, a CommandError is raised. - """ - try: - firmware_file = Path(firmware_file).resolve(strict = True) - except FileNotFoundError as exception: - raise CommandError( - cmd = self, - msg = f"Cannot resolve frontend URL for firmware file that does not exist: {exception}", - ) - - ip_addr = self.get_common_frontend_ip(hostname = hostname) - # remove the /export/stack prefix from the file path, as /install points to /export/stack - firmware_file = Path().joinpath(*(part for part in firmware_file.parts if part not in ("/", "export", "stack"))) - - return f"http://{ip_addr}/install/{firmware_file}" - - def imp_exists(self, imp): - """Returns whether the given implementation name exists in the database.""" - return self.db.count("(id) FROM firmware_imp WHERE name=%s", imp) - - def ensure_imp_exists(self, imp): - """Ensures that the provided implementation exists in the database. - - If the provided name is an empty string, a CommandError is raised. - - If the imp name does not exist in the database, a CommandError is raised. - """ - if not imp: - raise CommandError(cmd = self, msg = "Imp is required.") - - if not self.imp_exists(imp = imp): - raise CommandError(cmd = self, msg = f"Imp {imp} does not exist in the database.") - - def ensure_imps_exist(self, imps): - """Ensures that all of the given imp names already exist in the database. - - If an empty iterable is provided, a CommandError is raised. - - If an empty string is provided for any imp name, a CommandError is raised. - """ - imps = tuple(imps) - if not imps: - raise CommandError(cmd = self, msg = "Imps are required.") - - # No empty strings allowed. - if not all(imps): - raise CommandError(cmd = self, msg = "A imp cannot be an empty string.") - - # ensure the imps exist - missing_imps = [ - imp - for imp, exists in ( - (imp, self.imp_exists(imp)) for imp in imps - ) - if not exists - ] - if missing_imps: - raise CommandError( - cmd = self, - msg = f"The following firmware implementations don't exist in the database: {missing_imps}." - ) - - def get_imp_id(self, imp): - """Get the ID of the implementation with the provided name. - - This will raise a CommandError if the implementation with the provided name doesn't exist. - """ - row = self.db.select('id FROM firmware_imp WHERE name=%s', imp) - if not row: - raise CommandError(cmd = self, msg = f"Firmware implementation {imp} doesn't exist in the database.") - - return row[0][0] - - def version_regex_exists(self, name): - """Returns whether the given version_regex name exists in the database.""" - return self.db.count("(id) FROM firmware_version_regex WHERE name=%s", name) - - def ensure_version_regex_exists(self, name): - """Ensures that a version_regex with the provided name exists in the database. - - If the name is an empty string, a CommandError is raised. - - If the version_regex does not exist, a CommandError is raised. - """ - if not name: - raise CommandError(cmd = self, msg = "A version regex name is required.") - - if not self.version_regex_exists(name = name): - raise CommandError(cmd = self, msg = f"A version regex named {name} does not exist.") - - def ensure_version_regexes_exist(self, names): - """Ensures that all of the given version_regex names already exist in the database. - - If an empty iterable is provided, a CommandError is raised. - - If an empty string is provided for any version_regex name, a CommandError is raised. - """ - names = tuple(names) - if not names: - raise CommandError(cmd = self, msg = "Version regex names are required.") - - # No empty strings allowed. - if not all(names): - raise CommandError(cmd = self, msg = "A version regex name cannot be an empty string.") - - # ensure the version_regexes exist - missing_version_regexes = [ - name - for name, exists in ( - (name, self.version_regex_exists(name)) for name in names - ) - if not exists - ] - if missing_version_regexes: - raise CommandError( - cmd = self, - msg = f"The following firmware version regexes don't exist in the database: {missing_version_regexes}." - ) - - def get_version_regex_id(self, name): - """Get the ID of the version_regex with the provided name. - - This will raise a CommandError if the version_regex with the provided name doesn't exist. - """ - row = self.db.select("id FROM firmware_version_regex WHERE name=%s", name) - if not row: - raise CommandError(cmd = self, msg = f"Firmware version_regex {name} doesn't exist in the database.") - - return row[0][0] - - def try_get_version_regex(self, make, model): - """Attempts to return the most relevant version regex for the make and model combination provided. - - If found, a namedtuple is returned with the members 'regex', 'name', and 'description'. The - regex member is the actual regular expression to use for validation while name and description - are the user friendly name and an optional description of the regex, respectively. - - A version regex set on the model will be preferred to one set on the make. - - If neither the make nor the model have a version regex set, None will be returned. - """ - regex = None - row = self.db.select( - """ - make_regex.regex, make_regex.name, make_regex.description, model_regex.regex, model_regex.name, model_regex.description - FROM firmware_make - INNER JOIN firmware_model - ON firmware_model.make_id = firmware_make.id - LEFT JOIN firmware_version_regex as make_regex - ON firmware_make.version_regex_id = make_regex.id - LEFT JOIN firmware_version_regex as model_regex - ON firmware_model.version_regex_id = model_regex.id - WHERE firmware_make.name = %s AND firmware_model.name = %s - """, - (make, model) - ) - if row: - make_regex, make_regex_name, make_regex_description = row[0][0:3] - model_regex, model_regex_name, model_regex_description = row[0][3:6] - RegexInfo = namedtuple("RegexInfo", ("regex", "name", "description")) - # prefer the model regex - if model_regex: - regex = RegexInfo( - regex = model_regex, - name = model_regex_name, - description = model_regex_description - ) - # else use the make regex - elif make_regex: - regex = RegexInfo( - regex = make_regex, - name = make_regex_name, - description = make_regex_description - ) - # Otherwise we return nothing. - - return regex diff --git a/common/src/stack/command/stack/commands/add/firmware/__init__.py b/common/src/stack/command/stack/commands/add/firmware/__init__.py deleted file mode 100644 index 2357b8805..000000000 --- a/common/src/stack/command/stack/commands/add/firmware/__init__.py +++ /dev/null @@ -1,82 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from stack.argument_processors.firmware import FirmwareArgProcessor -from stack.argument_processors.host import HostArgProcessor -import stack.commands - -class command(stack.commands.add.command, FirmwareArgProcessor): - pass - -class Command(command, HostArgProcessor): - """ - Adds a firmware image to stacki. - - - The firmware version being added. This must be unique for make + model. - - - - The URL or local file path pointing to where to retrieve the firmware image. - - - - The firmware make to add this firmware image to. If the make does not already exist, it will be created. - - - - The firmware model to add this firmware image to. If the model does not already exist, it will be created. - - - - The optional firmware implementation to run for the model this firmware is for. If the make + model provided does not already - exist, this parameter is required. If the implementation does not already exist, it will be created. - - - - The optional host names to associate with this firmware. This can either be a single host name, a comma separate list of hosts, - or one of the common host name specifiers like 'a:switch'. - - - - The optional hash to use when verifying the integrity of the fetched image. - - - - The optional hash algorithm to use to verify the integrity of the fetched image. If not specified this defaults to MD5. - - - - Fetches the firmware file from the source (a local file on the front end in /export/some/path), associates it with the - Mellanox make and 7800 model, and sets the version to 3.6.5002, and adds it to be tracked in the stacki database. - - - - This performs the same steps as the previous example except the image is fetched via HTTP. - - - - This performs the same steps as the previous example except the firmware gets associated with the hosts named switch-0-1 and switch-0-2. - - - - This performs the same steps as the previous example except the firmware gets associated with all hosts that are of the appliance switch type. - - - - Assuming that the make, model, and implementation do no already exist, this adds a new firmware version 1.2.3.4 - associated with new_make and new_model that will use new_imp to read and write to mapped hosts. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/add/firmware/imp/__init__.py b/common/src/stack/command/stack/commands/add/firmware/imp/__init__.py deleted file mode 100644 index 207b8f9a0..000000000 --- a/common/src/stack/command/stack/commands/add/firmware/imp/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.add.firmware.command): - """ - Adds a firmware implementation to the stacki database. - - - The name of the implementation file to run, without the "imp_" prepended or the extension appended. - - - - Zero or more models that this implementation applies to. Multiple models should be specified as a comma separated list. - If this is specified, make is also required. - - - - The optional make of the models this implementation applies to. If this is specified, models are also required. - - - - Adds the firmware implementation named mellanox_6xxx_7xxx and sets it to be the one run for make mellanox and models m7800 and m6036. - - - - This simply adds the implementation named mellanox_6xxx_7xxx to be tracked by stacki. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/add/firmware/imp/plugin_basic.py b/common/src/stack/command/stack/commands/add/firmware/imp/plugin_basic.py deleted file mode 100644 index 7847ea990..000000000 --- a/common/src/stack/command/stack/commands/add/firmware/imp/plugin_basic.py +++ /dev/null @@ -1,97 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from contextlib import ExitStack -from pathlib import Path -import inspect -from stack.util import lowered -import stack.commands -import stack.commands.list.host.firmware -import stack.commands.sync.host.firmware -from stack.exception import CommandError, ArgRequired, ArgUnique, ArgError, ParamRequired, ParamError - -class Plugin(stack.commands.Plugin): - """Attempts to add an implementation to the database and optionally associate it with models.""" - - def provides(self): - return "basic" - - def validate_args(self, args): - """Validate that there is only one implementation name passed.""" - # Require a implementation name - if not args: - raise ArgRequired(cmd = self.owner, arg = "name") - # Should only be one - if len(args) != 1: - raise ArgUnique(cmd = self.owner, arg = "name") - - def validate_imp(self, imp): - """Validate that the implementation doesn't already exist in the database, and that the implementation files are on disk.""" - # Should not already exist - if self.owner.imp_exists(imp = imp): - raise ArgError( - cmd = self.owner, - arg = "name", - msg = f"An implementation named {imp} already exists in the database.", - ) - - # Should exist on disk - list_firmware_imp = Path( - inspect.getsourcefile(stack.commands.list.host.firmware), - ).parent.resolve() / f"imp_{imp}.py" - sync_firmware_imp = Path( - inspect.getsourcefile(stack.commands.sync.host.firmware), - ).parent.resolve() / f"imp_{imp}.py" - if not list_firmware_imp.exists() or not sync_firmware_imp.exists(): - raise ArgError( - cmd = self.owner, - arg = "name", - msg = ( - f"Could not find an implementation named imp_{imp}.py. Please ensure an" - " implementation file is placed into each of the following locations:\n" - f"{list_firmware_imp}\n{sync_firmware_imp}" - ), - ) - - def run(self, args): - params, args = args - self.validate_args(args = args) - imp = args[0].lower() - self.validate_imp(imp = imp) - - make, models, = lowered( - self.owner.fillParams( - names = [("make", ""), ("models", "")], - params = params, - ) - ) - # If either make or model are set, both arguments are required and must be valid. - if make or models: - # process a comma separated list of models - models = [model.strip() for model in models.split(",") if model.strip()] - self.owner.ensure_models_exist(make = make, models = models) - - with ExitStack() as cleanup: - # Add the implementation - self.owner.db.execute("INSERT INTO firmware_imp (name) VALUES (%s)", (imp,)) - cleanup.callback(self.owner.call, command = "remove.firmware.imp", args = [imp]) - - # If the make and model are specified associate the imp with the make + model - if make and models: - self.owner.call( - command = "set.firmware.model.imp", - args = [*models, f"make={make}", f"imp={imp}"], - ) - # else no association, just put it in the database. - - # everything worked, dismiss cleanup - cleanup.pop_all() diff --git a/common/src/stack/command/stack/commands/add/firmware/make/__init__.py b/common/src/stack/command/stack/commands/add/firmware/make/__init__.py deleted file mode 100644 index 814068e9c..000000000 --- a/common/src/stack/command/stack/commands/add/firmware/make/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.add.firmware.command): - """ - Adds firmware makes to the stacki database. - - - One or more make names to add. Make names are required to be unique, and any duplicates will be ignored. - - - - Adds two makes with the names 'mellanox' and 'dell' to the set of available firmware makes. - - """ - - def run(self, params, args): - self.runPlugins(args = args) diff --git a/common/src/stack/command/stack/commands/add/firmware/make/plugin_basic.py b/common/src/stack/command/stack/commands/add/firmware/make/plugin_basic.py deleted file mode 100644 index 05683e24b..000000000 --- a/common/src/stack/command/stack/commands/add/firmware/make/plugin_basic.py +++ /dev/null @@ -1,38 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import unique_everseen, lowered -from stack.exception import ArgRequired, ArgError, CommandError - -class Plugin(stack.commands.Plugin): - """Attempts to add all provided make names to the database.""" - - def provides(self): - return "basic" - - def run(self, args): - # get rid of any duplicate names - makes = tuple(unique_everseen(lowered(args))) - # ensure the make names don't already exist - self.owner.ensure_unique_makes(makes = makes) - - self.owner.db.execute( - """ - INSERT INTO firmware_make ( - name - ) - VALUES (%s) - """, - [(make,) for make in makes], - many = True, - ) diff --git a/common/src/stack/command/stack/commands/add/firmware/model/__init__.py b/common/src/stack/command/stack/commands/add/firmware/model/__init__.py deleted file mode 100644 index 63626919d..000000000 --- a/common/src/stack/command/stack/commands/add/firmware/model/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.add.firmware.command): - """ - Adds firmware models to the stacki database. - - - One or more model names to add. Model names are required to be unique, and any duplicates will be ignored. - - - - The make of the models being added. If this does not correspond to an already existing make, one will be added. - - - - The implementation name to run for the models being added. This should be the name of the implementation file minus the 'imp_' prefix and file extension. - If this does not correspond to an already existing implementation, one will be added. - - - - Adds two models with the names 'awesome_9001' and 'mediocre_5200' to the set of available firmware models under the 'boss hardware corp' make. - This also sets the implementation to run for those models as the one named 'imp_boss_hardware_corp.py'. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/add/firmware/model/plugin_basic.py b/common/src/stack/command/stack/commands/add/firmware/model/plugin_basic.py deleted file mode 100644 index 4abdccab9..000000000 --- a/common/src/stack/command/stack/commands/add/firmware/model/plugin_basic.py +++ /dev/null @@ -1,85 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from contextlib import ExitStack -import stack.commands -from stack.util import unique_everseen, lowered -from stack.exception import ArgRequired, CommandError, ParamRequired - -class Plugin(stack.commands.Plugin): - """Attempts to add all provided model names to the database associated with the given make and implementation.""" - - def provides(self): - return "basic" - - def create_missing_make(self, make, cleanup): - """Create the make if it does not already exist.""" - if not self.owner.make_exists(make = make): - self.owner.call(command = "add.firmware.make", args = [make]) - cleanup.callback(self.owner.call, command = "remove.firmware.make", args = [make]) - - def create_missing_imp(self, imp, cleanup): - """Create the implementation if it does not already exist.""" - if not self.owner.imp_exists(imp = imp): - self.owner.call(command = "add.firmware.imp", args = [imp]) - cleanup.callback(self.owner.call, command = "remove.firmware.imp", args = [imp]) - - def run(self, args): - params, args = args - make, imp, = lowered( - self.owner.fillParams( - names = [ - ("make", ""), - ("imp", ""), - ], - params = params, - ), - ) - - # require a make - if not make: - raise ParamRequired(cmd = self.owner, param = "make") - # require an implementation - if not imp: - raise ParamRequired(cmd = self.owner, param = "imp") - - # get rid of any duplicate names - models = tuple(unique_everseen(lowered(args))) - # ensure the model name doesn't already exist for the given make - self.owner.ensure_unique_models(make = make, models = models) - - with ExitStack() as cleanup: - # create the make if it doesn't already exist - self.create_missing_make(make = make, cleanup = cleanup) - # create the implementation if it doesn't already exist - self.create_missing_imp(imp = imp, cleanup = cleanup) - - # get the ID of the make to associate with - make_id = self.owner.get_make_id(make = make) - # get the ID of the imp to associate with - imp_id = self.owner.get_imp_id(imp = imp) - - self.owner.db.execute( - """ - INSERT INTO firmware_model ( - name, - make_id, - imp_id - ) - VALUES (%s, %s, %s) - """, - [(model, make_id, imp_id) for model in models], - many = True, - ) - - # everything was successful, dismiss cleanup. - cleanup.pop_all() diff --git a/common/src/stack/command/stack/commands/add/firmware/plugin_basic.py b/common/src/stack/command/stack/commands/add/firmware/plugin_basic.py deleted file mode 100644 index 742b86355..000000000 --- a/common/src/stack/command/stack/commands/add/firmware/plugin_basic.py +++ /dev/null @@ -1,290 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.exception import ArgRequired, ArgUnique, ArgError, ParamRequired, ParamError -import stack.firmware -from stack.util import unique_everseen, lowered -from pathlib import Path -from contextlib import ExitStack -import re - -class Plugin(stack.commands.Plugin): - """Attempts to add firmware to be tracked by stacki.""" - - def provides(self): - return "basic" - - def validate_args(self, args): - """Validate that a version number is provided and that there is only one.""" - # Require a version name - if not args: - raise ArgRequired(cmd = self.owner, arg = "version") - # should only be one version name - if len(args) != 1: - raise ArgUnique(cmd = self.owner, arg = "version") - - def validate_required_params(self, source, make, model): - """Validate that the required parameters are provided.""" - # require a source - if not source: - raise ParamRequired(cmd = self.owner, param = "source") - # require both make and model - if not make: - raise ParamRequired(cmd = self.owner, param = "make") - if not model: - raise ParamRequired(cmd = self.owner, param = "model") - - def validate_imp(self, make, model, imp): - """Validate whether an imp is required due to the model not already existing, - and validate that if the model exists that any requested implementation matches. - """ - # require an implementation if the model does not exist. - if not imp and not self.owner.model_exists(make = make, model = model): - # Get the list of valid makes + models and add them to the error message in an attempt to be helpful - makes_and_models = "\n".join( - f"{make_model['make']} + {make_model['model']}" for make_model in - self.owner.call(command = "list.firmware.model") - ) - raise ParamError( - cmd = self.owner, - param = "imp", - msg = ( - f"is required because make and model combination {make} + {model} doesn't exist." - f" Did you mean to use one of the below makes and/or models?\n{makes_and_models}" - ), - ) - # Raise an error if the implementation was provided for an already existing model, and it does not match. - if imp and self.owner.model_exists(make = make, model = model): - existing_imp = self.owner.call( - command = "list.firmware.model", - args = [model, f"make={make}"] - )[0]["implementation"] - - if imp != existing_imp: - raise ParamError( - cmd = self.owner, - param = "imp", - msg = ( - f"The provided imp {imp} doesn't match the existing imp {existing_imp} for the already existing" - f" make + model {make} + {model}. Did you not mean to specify an imp? The parameter is not required" - " for existing models.\nIf you want to change what implementation is used, run" - " 'stack set firmware model imp'." - ), - ) - - def validate_hash_alg_supported(self, hash_alg): - """Validates that the requested hash algorithm is supported.""" - try: - stack.firmware.ensure_hash_alg_supported(hash_alg = hash_alg) - except stack.firmware.FirmwareError as exception: - raise ParamError( - cmd = self.owner, - param = "hash_alg", - msg = f"{exception}", - ) from exception - - def validate_version(self, version, make, model): - """Attempts to validate the version number against a version_regex if one is set for the - make or model provided as well as ensuring the firmware version doesn't already exist. - """ - # Attempt to get the version regex entry. - regex = self.owner.try_get_version_regex(make = make, model = model) - if regex and not re.search(pattern = regex.regex, string = version, flags = re.IGNORECASE): - raise ArgError( - cmd = self.owner, - arg = "version", - msg = ( - f"The format of the version number {version} does not validate based on the regex {regex.regex}" - f" named {regex.name}{f' with description {regex.description}' if regex.description else ''}." - ), - ) - - if self.owner.firmware_exists(make = make, model = model, version = version): - raise ArgError( - cmd = self.owner, - arg = "version", - msg = f"The firmware version {version} for make {make} and model {model} already exists.", - ) - - def validate_inputs(self, source, make, model, version, imp, hash_alg): - """Validate the input params and version arg.""" - # Make sure all require parameters are present. - self.validate_required_params(source = source, make = make, model = model) - # Check if an implementation is required, and whether one was provided. - self.validate_imp(make = make, model = model, imp = imp) - # Validate that the hash algorithm is supported - self.validate_hash_alg_supported(hash_alg = hash_alg) - # validate the version matches the version_regex if one is set and that it doesn't already exist. - self.validate_version(version = version, make = make, model = model) - - def create_missing_imp(self, imp, cleanup): - """Adds an implementation to the database if provided and it doesn't already exist.""" - # create the implementation if provided one and it doesn't already exist - if imp and not self.owner.imp_exists(imp = imp): - self.owner.call(command = "add.firmware.imp", args = [imp]) - cleanup.callback(self.owner.call, command = "remove.firmware.imp", args = [imp]) - - def create_missing_make(self, make, cleanup): - """Adds a make to the database if it doesn't already exist.""" - # create the make if it doesn't already exist - if not self.owner.make_exists(make = make): - self.owner.call(command = "add.firmware.make", args = [make]) - cleanup.callback(self.owner.call, command = "remove.firmware.make", args = [make]) - - def create_missing_model(self, make, model, imp, cleanup): - """Adds a model to the database if it doesn't already exist.""" - # create the model if it doesn't already exist - if not self.owner.model_exists(make = make, model = model): - self.owner.call(command = "add.firmware.model", args = [model, f"make={make}", f"imp={imp}"]) - cleanup.callback(self.owner.call, command = "remove.firmware.model", args = [model, f"make={make}"]) - - def create_missing_related_entries(self, make, model, imp, cleanup): - """Create any missing related entries as necessary.""" - self.create_missing_imp(imp = imp, cleanup = cleanup) - self.create_missing_make(make = make, cleanup = cleanup) - self.create_missing_model(make = make, model = model, imp = imp, cleanup = cleanup) - - def file_cleanup(self, file_path): - """Remove the file if it exists. - - Needed because "stack remove firmware" also removes the file - and that might have been run as part of the exit stack unwinding. - """ - if file_path.exists(): - file_path.unlink() - - def fetch_firmware(self, source, make, model, cleanup): - """Try to fetch the firmware from the source and return the path to the file.""" - try: - file_path = stack.firmware.fetch_firmware( - source = source, - make = make, - model = model, - ) - - cleanup.callback(self.file_cleanup, file_path = file_path) - return file_path - - except stack.firmware.FirmwareError as exception: - raise ParamError( - cmd = self.owner, - param = "source", - msg = f"{exception}", - ) from exception - - def calculate_hash(self, file_path, hash_alg, hash_value): - """Calculate the file hash and verify it against the provided hash, if present.""" - try: - return stack.firmware.calculate_hash(file_path = file_path, hash_alg = hash_alg, hash_value = hash_value) - except stack.firmware.FirmwareError as exception: - raise ParamError( - cmd = self.owner, - param = "hash", - msg = f"{exception}", - ) from exception - - def add_firmware(self, source, make, model, version, imp, hash_value, hash_alg, cleanup): - """Adds a firmware file to be managed by stacki. - - This adds the file locally on disk as well as inserts metadata into the database. - """ - # fetch the firmware from the source and copy the firmware into a stacki managed file - file_path = self.fetch_firmware( - source = source, - make = make, - model = model, - cleanup = cleanup, - ) - # calculate the file hash and compare it with the user provided value if present. - file_hash = self.calculate_hash( - file_path = file_path, - hash_alg = hash_alg, - hash_value = hash_value, - ) - # add imp, make, and model database entries if needed. - self.create_missing_related_entries(make = make, model = model, imp = imp, cleanup = cleanup) - - # get the ID of the model to associate with - model_id = self.owner.get_model_id(make, model) - # insert into DB associated with make + model - self.owner.db.execute( - """ - INSERT INTO firmware ( - model_id, - source, - version, - hash_alg, - hash, - file - ) - VALUES (%s, %s, %s, %s, %s, %s) - """, - (model_id, source, version, hash_alg, file_hash, str(file_path)), - ) - cleanup.callback(self.owner.call, command = "remove.firmware", args = [version, f"make={make}", f"model={model}"]) - - def run(self, args): - params, args = args - self.validate_args(args = args) - version = args[0].lower() - - *params_to_lower, hash_value, source = self.owner.fillParams( - names = [ - ("make", ""), - ("model", ""), - ("imp", ""), - ("hosts", ""), - ("hash_alg", "md5"), - ("hash", ""), - ("source", ""), - ], - params = params, - ) - # Lowercase all params that can be. - make, model, imp, hosts, hash_alg = lowered(params_to_lower) - self.validate_inputs( - source = source, - make = make, - model = model, - version = version, - imp = imp, - hash_alg = hash_alg, - ) - - # Convert hosts to a list if set - if hosts: - # Make sure the host names are unique. - hosts = tuple(unique_everseen(host.strip() for host in hosts.split(",") if host.strip())) - # Validate the hosts exist. - hosts = self.owner.getHosts(args = hosts) - - # we use ExitStack to hold our cleanup operations and roll back should something fail. - with ExitStack() as cleanup: - # Get the firmware file on disk in the right location and add the metadata to the database. - self.add_firmware( - source = source, - make = make, - model = model, - version = version, - imp = imp, - hash_value = hash_value, - hash_alg = hash_alg, - cleanup = cleanup, - ) - - # if hosts are provided, set the firmware relation - if hosts: - self.owner.call(command = "add.host.firmware.mapping", args = [*hosts, f"version={version}", f"make={make}", f"model={model}"]) - - # everything went down without a problem, dismiss the cleanup - cleanup.pop_all() diff --git a/common/src/stack/command/stack/commands/add/firmware/version_regex/__init__.py b/common/src/stack/command/stack/commands/add/firmware/version_regex/__init__.py deleted file mode 100644 index 765dfc940..000000000 --- a/common/src/stack/command/stack/commands/add/firmware/version_regex/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.add.firmware.command): - """ - Adds a firmware version regex to the stacki database for use in parsing and validating firmware version numbers. - - - A valid Python regex to use to use against the version number returned from the target hardware. - - - - The human readable name for this regex. - - - - An optional description for this regex. This is useful to describe what the regex does for future travellers. - - - - The make that this regex should apply to. - - - - The optional models for the given make that this regex applies to. Multiple models should be specified as a comma separated list. - - - - Adds a regex with the name mellanox_version and the description provided that looks for three number groups separated by dots to the Stacki database. - It also associates the regex with the m7800 and m6036 models for the mellanox make. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/add/firmware/version_regex/plugin_basic.py b/common/src/stack/command/stack/commands/add/firmware/version_regex/plugin_basic.py deleted file mode 100644 index abf17375c..000000000 --- a/common/src/stack/command/stack/commands/add/firmware/version_regex/plugin_basic.py +++ /dev/null @@ -1,130 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import re -from contextlib import ExitStack -from stack.util import unique_everseen -import stack.commands -from stack.exception import ArgError, ArgRequired, ArgUnique, ParamRequired, ParamError - -class Plugin(stack.commands.Plugin): - """Attempts to add a version_regex to the database and associate it with the specified makes and/or models.""" - - def provides(self): - return "basic" - - def validate_args(self, args): - """Validate that the arguments to this plugin are as expected.""" - # Require a version regex - if not args: - raise ArgRequired(cmd = self.owner, arg = "regex") - - # Should only be one - if len(args) != 1: - raise ArgUnique(cmd = self.owner, arg = "regex") - - def validate_regex(self, regex): - """Make sure the provided regex is a valid Python regex.""" - # Don't allow empty string, which will compile into a regex fine. - if not regex: - raise ArgError( - cmd = self.owner, - arg = "regex", - msg = f"Regex cannot be an empty string." - ) - - # Require the version_regex to be a valid regex - try: - re.compile(regex) - except re.error as exception: - raise ArgError( - cmd = self.owner, - arg = "regex", - msg = f"Invalid regex supplied: {exception}." - ) - - def validate_name(self, name): - """Validate the name is provided and is unique.""" - # A name is required - if not name: - raise ParamRequired(cmd = self.owner, param = "name") - - # The name must not already exist - if self.owner.version_regex_exists(name = name): - raise ParamError( - cmd = self.owner, - param = "name", - msg = f"A version_regex with the name {name} already exists in the database." - ) - - def run(self, args): - params, args = args - self.validate_args(args = args) - version_regex = args[0] - self.validate_regex(regex = version_regex) - - name, description, make, models = self.owner.fillParams( - names = [ - ("name", ""), - ("description", ""), - ("make", ""), - ("models", ""), - ], - params = params, - ) - name = name.lower() - self.validate_name(name = name) - make = make.lower() - models = models.lower() - # Process models if specified - if models: - models = tuple( - unique_everseen( - (model.strip() for model in models.split(',') if model.strip()) - ) - ) - # The make and models must exist - self.owner.ensure_models_exist(make = make, models = models) - else: - # Only need to check that the make exists. - self.owner.ensure_make_exists(make = make) - - with ExitStack() as cleanup: - # add the regex - self.owner.db.execute( - """ - INSERT INTO firmware_version_regex ( - regex, - name, - description - ) - VALUES (%s, %s, %s) - """, - (version_regex, name, description), - ) - cleanup.callback(self.owner.call, command = "remove.firmware.version_regex", args = [name]) - - # If models are specified, associate it with the relevant models for the given make - if models: - self.owner.call( - command = "set.firmware.model.version_regex", - args = [*models, f"make={make}", f"version_regex={name}"], - ) - # else associate it with just the make - else: - self.owner.call( - command = "set.firmware.make.version_regex", - args = [make, f"version_regex={name}"], - ) - - # everything worked, dismiss cleanup - cleanup.pop_all() diff --git a/common/src/stack/command/stack/commands/add/host/firmware/__init__.py b/common/src/stack/command/stack/commands/add/host/firmware/__init__.py deleted file mode 100644 index ab26ebde2..000000000 --- a/common/src/stack/command/stack/commands/add/host/firmware/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from stack.argument_processors.firmware import FirmwareArgProcessor -import stack.commands - -class command(stack.commands.add.host.command, FirmwareArgProcessor): - pass diff --git a/common/src/stack/command/stack/commands/add/host/firmware/mapping/__init__.py b/common/src/stack/command/stack/commands/add/host/firmware/mapping/__init__.py deleted file mode 100644 index af4d99e89..000000000 --- a/common/src/stack/command/stack/commands/add/host/firmware/mapping/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.add.host.firmware.command): - """ - Maps firmware files to hosts so 'stack sync host firmware' can find them. - - - One or more hostnames to associate with a firmware version. - - - - The firmware version to map to the provided hosts. - - - - The make of the firmware version. - - - - The model for the given make of the firmware version. - - - - Maps firmware version 3.6.2010 for make mellanox and model m7800 to the host with the name switch-11-3. - - - - This is the same as the previous example except the firmware will be mapped to both switch-11-3 and switch-10-15. - - - - This is the same as the previous example except the firmware will be mapped to all hosts that are switch appliances. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/add/host/firmware/mapping/plugin_basic.py b/common/src/stack/command/stack/commands/add/host/firmware/mapping/plugin_basic.py deleted file mode 100644 index 44b31913a..000000000 --- a/common/src/stack/command/stack/commands/add/host/firmware/mapping/plugin_basic.py +++ /dev/null @@ -1,88 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import unique_everseen, lowered -from stack.exception import CommandError - -class Plugin(stack.commands.Plugin): - """Attempts to map firmware versions to hosts.""" - - def provides(self): - return "basic" - - def ensure_unique_mappings(self, hosts, make, model, version): - """Ensure the proposed mappings are unique.""" - # The mappings must not already exist - existing_mappings = [ - f"{host} mapped to firmware {version} for make {make} and model {model}" for host, make, model, version in ( - row for row in self.owner.db.select( - """ - nodes.Name, firmware_make.name, firmware_model.name, firmware.version - FROM firmware_mapping - INNER JOIN nodes - ON firmware_mapping.node_id = nodes.ID - INNER JOIN firmware - ON firmware_mapping.firmware_id = firmware.id - INNER JOIN firmware_model - ON firmware.model_id = firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id = firmware_model.id - WHERE nodes.Name IN %s AND firmware_make.name = %s AND firmware_model.name = %s AND firmware.version = %s - """, - (hosts, make, model, version) - ) - ) - ] - if existing_mappings: - existing_mappings = "\n".join(existing_mappings) - raise CommandError(cmd = self.owner, msg = f"The following firmware mappings already exist:\n{existing_mappings}") - - def run(self, args): - params, args = args - args = tuple(unique_everseen(lowered(args))) - hosts = self.owner.getHosts(args = args) - - version, make, model, = lowered( - self.owner.fillParams( - names = [ - ("version", ""), - ("make", ""), - ("model", ""), - ], - params = params, - ) - ) - # Make, model, and version are required. This checks them all. - self.owner.ensure_firmware_exists(make = make, model = model, version = version) - # Make sure the proposed mappings are unique. - self.ensure_unique_mappings(hosts = hosts, make = make, model = model, version = version) - - # Get the ID's of all the hosts - node_ids = ( - row[0] for row in self.owner.db.select("ID FROM nodes WHERE Name in %s", (hosts,)) - ) - # Get the firmware version ID - firmware_id = self.owner.get_firmware_id(make = make, model = model, version = version) - - # Add the mapping entries. - self.owner.db.execute( - """ - INSERT INTO firmware_mapping ( - node_id, - firmware_id - ) - VALUES (%s, %s) - """, - [(node_id, firmware_id) for node_id in node_ids], - many = True, - ) diff --git a/common/src/stack/command/stack/commands/list/firmware/__init__.py b/common/src/stack/command/stack/commands/list/firmware/__init__.py deleted file mode 100644 index 47c5f220a..000000000 --- a/common/src/stack/command/stack/commands/list/firmware/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from stack.argument_processors.firmware import FirmwareArgProcessor -import stack.commands - -class command(stack.commands.list.command, FirmwareArgProcessor): - pass - -class Command(command): - """ - Lists all firmware images tracked by stacki. - - - Set this to list more detailed firmware image information - - - - Lists all firmware files tracked in the stacki database. - - """ - - def run(self, params, args): - expanded, = self.fillParams( - names = [("expanded", False)], - params = params, - ) - expanded = self.str2bool(expanded) - - header = [] - values = [] - for provides, results in self.runPlugins(args = expanded): - header.extend(results["keys"]) - values.extend(results["values"]) - - self.beginOutput() - for owner, vals in values: - self.addOutput(owner = owner, vals = vals) - self.endOutput(header = header) diff --git a/common/src/stack/command/stack/commands/list/firmware/imp/__init__.py b/common/src/stack/command/stack/commands/list/firmware/imp/__init__.py deleted file mode 100644 index cd4cfa6e8..000000000 --- a/common/src/stack/command/stack/commands/list/firmware/imp/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.list.firmware.command): - """ - Lists all firmware implementations tracked by stacki. - - - Lists all firmware implementations tracked in the stacki database. - - """ - - def run(self, params, args): - header = [] - values = [] - for provides, results in self.runPlugins(): - header.extend(results["keys"]) - values.extend(results["values"]) - - self.beginOutput() - for owner, vals in values: - self.addOutput(owner = owner, vals = vals) - self.endOutput(header = header) diff --git a/common/src/stack/command/stack/commands/list/firmware/imp/plugin_basic.py b/common/src/stack/command/stack/commands/list/firmware/imp/plugin_basic.py deleted file mode 100644 index af1f081e5..000000000 --- a/common/src/stack/command/stack/commands/list/firmware/imp/plugin_basic.py +++ /dev/null @@ -1,25 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Plugin(stack.commands.Plugin): - """Returns the names of all implementations in the database.""" - - def provides(self): - return "basic" - - def run(self, args): - return { - "keys": ["imp"], - "values": [(row[0], []) for row in self.owner.db.select("name FROM firmware_imp")] - } diff --git a/common/src/stack/command/stack/commands/list/firmware/make/__init__.py b/common/src/stack/command/stack/commands/list/firmware/make/__init__.py deleted file mode 100644 index dacd50480..000000000 --- a/common/src/stack/command/stack/commands/list/firmware/make/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.list.firmware.command): - """ - Lists all firmware makes tracked by stacki. - - - Set this to list more detailed firmware make information - - - - Lists all firmware makes tracked in the stacki database. - - """ - - def run(self, params, args): - expanded, = self.fillParams( - names = [("expanded", False)], - params = params, - ) - expanded = self.str2bool(expanded) - header = [] - values = [] - for provides, results in self.runPlugins(args = expanded): - header.extend(results["keys"]) - values.extend(results["values"]) - - self.beginOutput() - for owner, vals in values: - self.addOutput(owner = owner, vals = vals) - self.endOutput(header = header) diff --git a/common/src/stack/command/stack/commands/list/firmware/make/plugin_basic.py b/common/src/stack/command/stack/commands/list/firmware/make/plugin_basic.py deleted file mode 100644 index 6d3e97311..000000000 --- a/common/src/stack/command/stack/commands/list/firmware/make/plugin_basic.py +++ /dev/null @@ -1,42 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Plugin(stack.commands.Plugin): - """Returns the names of all makes in the database.""" - - def provides(self): - return "basic" - - def run(self, args): - # If expanded is true, also list any user defined implementations and version regexes - if args: - return { - "keys": ["make", "version_regex_name"], - "values": [ - (row[0], row[1:]) for row in self.owner.db.select( - """ - firmware_make.name, firmware_version_regex.name - FROM firmware_make - LEFT JOIN firmware_version_regex - ON firmware_make.version_regex_id = firmware_version_regex.id - """ - ) - ] - } - - # Otherwise just return the names of the makes. - return { - "keys": ["make"], - "values": [(row[0], []) for row in self.owner.db.select("name FROM firmware_make")] - } diff --git a/common/src/stack/command/stack/commands/list/firmware/model/__init__.py b/common/src/stack/command/stack/commands/list/firmware/model/__init__.py deleted file mode 100644 index 27127a904..000000000 --- a/common/src/stack/command/stack/commands/list/firmware/model/__init__.py +++ /dev/null @@ -1,59 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.list.firmware.command): - """ - Lists firmware models tracked by stacki. - - - Zero or more models to list information for. If no models are specified, all models are listed. - - - - The optional make of the models to list. This is required if models are specified as arguments. - Setting this with no models specified will list all models for the given make. - - - - Set this to list more detailed firmware model information. - - - - Lists all firmware models tracked in the stacki database. - - - - Lists information for all firmware models under the mellanox make. - - - - Lists information for the firmware models m7800 and m6036 under the mellanox make. - - - - Lists additional information for all firmware models tracked in the database. - - """ - - def run(self, params, args): - header = [] - values = [] - for provides, results in self.runPlugins(args = (params, args)): - header.extend(results["keys"]) - values.extend(results["values"]) - - self.beginOutput() - for owner, vals in values: - self.addOutput(owner = owner, vals = vals) - self.endOutput(header = header) diff --git a/common/src/stack/command/stack/commands/list/firmware/model/plugin_basic.py b/common/src/stack/command/stack/commands/list/firmware/model/plugin_basic.py deleted file mode 100644 index 8a9bff15e..000000000 --- a/common/src/stack/command/stack/commands/list/firmware/model/plugin_basic.py +++ /dev/null @@ -1,122 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import unique_everseen, lowered - -class Plugin(stack.commands.Plugin): - """Returns the make name and model name of all models in the database.""" - - def provides(self): - return "basic" - - def validate_make(self, make): - """If a make is provided, ensure it exists.""" - if make: - self.owner.ensure_make_exists(make = make) - - def validate_models(self, make, models): - """If models are provided, ensure both the make and the models exist.""" - if models: - # This will also fail if the make is not provided or doesn't exist. - self.owner.ensure_models_exist(make = make, models = models) - - def get_expanded_results(self, make, models): - """Return the expanded results set from the database. - - If make is provided, this will filter results to the ones that match the make. - - If both make and models are provided, this will filter the results to the ones that match - on make and in models. - """ - query = """ - firmware_make.name, firmware_model.name, firmware_imp.name, firmware_version_regex.name - FROM firmware_make - INNER JOIN firmware_model - ON firmware_model.make_id=firmware_make.id - INNER JOIN firmware_imp - ON firmware_model.imp_id=firmware_imp.id - LEFT JOIN firmware_version_regex - ON firmware_model.version_regex_id=firmware_version_regex.id - """ - query_params = [] - # Filter on make and models if both are provided. - if make and models: - query += " WHERE firmware_model.name IN %s AND firmware_make.name=%s" - query_params.extend((models, make)) - # Filter on only make if models aren't provided but a make is. - elif make: - query += " WHERE firmware_make.name=%s" - query_params.append(make) - # Otherwise return everything - - return { - "keys": ["make", "model", "implementation", "version_regex_name"], - "values": [ - (row[0], row[1:]) - for row in self.owner.db.select(query, query_params) - ] - } - - def get_results(self, make, models): - """Return the simple results set from the database. - - If make is provided, this will filter results to the ones that match the make. - - If both make and models are provided, this will filter the results to the ones that match - on make and in models. - """ - query = """ - firmware_make.name, firmware_model.name - FROM firmware_make - INNER JOIN firmware_model - ON firmware_model.make_id=firmware_make.id - """ - query_params = [] - # Filter on make and models if both are provided. - if make and models: - query += " WHERE firmware_model.name IN %s AND firmware_make.name=%s" - query_params.extend((models, make)) - # Filter on only make if models aren't provided but a make is. - elif make: - query += " WHERE firmware_make.name=%s" - query_params.append(make) - # Otherwise return everything - - return { - "keys": ["make", "model"], - "values": [ - (row[0], row[1:]) - for row in self.owner.db.select(query, query_params) - ] - } - - def run(self, args): - params, args = args - models = tuple(unique_everseen(lowered(args))) - - expanded, make = lowered( - self.owner.fillParams( - names = [("expanded", "false"), ("make", "")], - params = params, - ) - ) - expanded = self.owner.str2bool(expanded) - self.validate_make(make = make) - self.validate_models(make = make, models = models) - - # If expanded is true, also list the implementation and any version regex associated with the model. - if expanded: - return self.get_expanded_results(make = make, models = models) - - # Otherwise just return the names of the makes and models. - return self.get_results(make = make, models = models) diff --git a/common/src/stack/command/stack/commands/list/firmware/plugin_basic.py b/common/src/stack/command/stack/commands/list/firmware/plugin_basic.py deleted file mode 100644 index 0f2a4bf43..000000000 --- a/common/src/stack/command/stack/commands/list/firmware/plugin_basic.py +++ /dev/null @@ -1,46 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Plugin(stack.commands.Plugin): - """Returns information about all firmware versions tracked in the database. - - If expanded is set to True, additional information is returned. - """ - - def provides(self): - return "basic" - - def run(self, args): - expanded = args - keys = ["make", "model", "version"] - if expanded: - keys.extend(["source", "hash", "hash_alg"]) - - values = [ - (row[0], row[1:]) - for row in self.owner.db.select( - f""" - firmware_make.name, firmware_model.name, firmware.version{ - ", firmware.source, firmware.hash, firmware.hash_alg" if expanded else "" - } - FROM firmware - INNER JOIN firmware_model - ON firmware.model_id=firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id=firmware_make.id - """, - ) - ] - - return {"keys": keys, "values": values} diff --git a/common/src/stack/command/stack/commands/list/firmware/version_regex/__init__.py b/common/src/stack/command/stack/commands/list/firmware/version_regex/__init__.py deleted file mode 100644 index fdd3d8dc4..000000000 --- a/common/src/stack/command/stack/commands/list/firmware/version_regex/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.list.firmware.command): - """ - Lists all firmware version regexes tracked by stacki. - - - Lists all firmware version regexes tracked in the stacki database. - - """ - - def run(self, params, args): - header = [] - values = [] - for provides, results in self.runPlugins(): - header.extend(results["keys"]) - values.extend(results["values"]) - - self.beginOutput() - for owner, vals in values: - self.addOutput(owner = owner, vals = vals) - self.endOutput(header = header) diff --git a/common/src/stack/command/stack/commands/list/firmware/version_regex/plugin_basic.py b/common/src/stack/command/stack/commands/list/firmware/version_regex/plugin_basic.py deleted file mode 100644 index c5f630ff8..000000000 --- a/common/src/stack/command/stack/commands/list/firmware/version_regex/plugin_basic.py +++ /dev/null @@ -1,29 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Plugin(stack.commands.Plugin): - """Returns the names, regex, and description of all version regexes in the database.""" - - def provides(self): - return "basic" - - def run(self, args): - return { - "keys": ["name", "regex", "description"], - "values": [ - (row[0], row[1:]) for row in self.owner.db.select( - "name, regex, description FROM firmware_version_regex" - ) - ], - } diff --git a/common/src/stack/command/stack/commands/list/host/firmware/__init__.py b/common/src/stack/command/stack/commands/list/host/firmware/__init__.py deleted file mode 100644 index 1d1f95c22..000000000 --- a/common/src/stack/command/stack/commands/list/host/firmware/__init__.py +++ /dev/null @@ -1,105 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import os -import re -from collections import namedtuple - -from stack.argument_processors.firmware import FirmwareArgProcessor -from stack.argument_processors.host import HostArgProcessor -import stack.commands - -class command( - FirmwareArgProcessor, - HostArgProcessor, - stack.commands.list.command, -): - pass - - -class Command(command): - """ - List the hosts, and their corresponding available and installed firmwares. - - - Zero, one or more host names. If no host names are supplied, info about - all the known hosts is listed. - - - - List info for backend-0-0. - - - - List info for all known hosts. - - - """ - - def run(self, params, args): - # Resolve any provided hostnames - hosts = self.getHostnames(names = args) - - header = ["host", "make", "model",] - # build a dictionary keyed by (host + make + model) so that the plugins and implementations - # can return the data mapped appropriately. We do this by getting all the firmware mappings - # and looking at the make and model of firmwares mapped to hosts. - CommonKey = namedtuple("CommonKey", ("host", "make", "model")) - CommonData = namedtuple("CommonData", ("firmware_version", "firmware_imp")) - values = { - CommonKey(*row[0:3]): CommonData(*row[3:]) for row in self.db.select( - """ - nodes.Name, firmware_make.name, firmware_model.name, firmware.version, firmware_imp.name - FROM firmware_mapping - INNER JOIN nodes - ON firmware_mapping.node_id = nodes.ID - INNER JOIN firmware - ON firmware_mapping.firmware_id = firmware.id - INNER JOIN firmware_model - ON firmware.model_id = firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id = firmware_make.id - INNER JOIN firmware_imp - ON firmware_model.imp_id = firmware_imp.id - WHERE nodes.Name IN %s - """, - (hosts,) - ) - } - results = { - key: [] for key in values - } - - # loop through all the plugin results and extend header and values as necessary. - CommonResult = namedtuple("CommonResult", ("header", "values")) - for provides, result in self.runPlugins((CommonResult, values)): - header.extend(result.header) - for host_make_model, items in result.values.items(): - results[host_make_model].extend(items) - - # add empty entries for hosts with no firmware mappings. - results.update( - { - # pad out with None for each extra column header added by the plugins - CommonKey(host, None, None): [None for i in range(len(header) - 3)] for host in hosts - if host not in (host_make_model.host for host_make_model in values) - } - ) - - # output the results - self.beginOutput() - for host_make_model, result in results.items(): - self.addOutput( - host_make_model.host, - [host_make_model.make, host_make_model.model, *result] - ) - self.endOutput(header = header) diff --git a/common/src/stack/command/stack/commands/list/host/firmware/imp_dell_x1052.py b/common/src/stack/command/stack/commands/list/host/firmware/imp_dell_x1052.py deleted file mode 100644 index 4022cd5bb..000000000 --- a/common/src/stack/command/stack/commands/list/host/firmware/imp_dell_x1052.py +++ /dev/null @@ -1,75 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ - -from concurrent.futures import ThreadPoolExecutor -import stack.commands -from stack.exception import CommandError -from stack.switch.x1052 import SwitchDellX1052 - -class Implementation(stack.commands.Implementation): - - def list_firmware(self, host, switch_attrs): - kwargs = { - "username" : switch_attrs.get("switch_username"), - "password" : switch_attrs.get("switch_password"), - } - - kwargs = {key: value for key, value in kwargs.items() if value is not None} - - # the context manager ensures we disconnect from the switch - with SwitchDellX1052(switch_ip_address = host, switchname = host, **kwargs) as x1052_switch: - x1052_switch.connect() - return x1052_switch.get_versions() - - def run(self, args): - errors = [] - # Since we can get both the boot and the software version based on the hostname, collapse the - # "software" and the "boot" models together into one. - x1052_attrs_per_host = { - host_make_model.host: {} for host_make_model in args - } - for host_make_model, attrs in args.items(): - x1052_attrs_per_host[host_make_model.host].update(attrs) - - # now run each image listing in parallel - with ThreadPoolExecutor(thread_name_prefix = "dell_firmware_listing") as executor: - futures_by_host = { - host: executor.submit( - self.list_firmware, - host = host, - switch_attrs = attrs - ) - for host, attrs in x1052_attrs_per_host.items() - } - # now collect the results, adding any errors into the errors list - results_by_host = {} - for host, future in futures_by_host.items(): - if future.exception() is not None: - errors.append(future.exception()) - else: - results_by_host[host] = future.result() - - # if there were any errors, aggregate all the errors into one - error_messages = [] - for error in errors: - # if this looks like a stacki exception type, grab the message from it. - if hasattr(error, "message") and callable(getattr(error, "message")): - error_messages.append(error.message()) - else: - error_messages.append(f'{error}') - - if error_messages: - error_message = '\n'.join(error_messages) - raise CommandError( - cmd = self.owner, - msg = f"Errors occurred during Dell firmware listing:\n{error_message}" - ) - - # Split results back out into host_make_model. - return { - host_make_model: results_by_host[host_make_model.host].boot if "boot" in host_make_model.model else results_by_host[host_make_model.host].software - for host_make_model in args - } diff --git a/common/src/stack/command/stack/commands/list/host/firmware/imp_mellanox.py b/common/src/stack/command/stack/commands/list/host/firmware/imp_mellanox.py deleted file mode 100644 index 60710043a..000000000 --- a/common/src/stack/command/stack/commands/list/host/firmware/imp_mellanox.py +++ /dev/null @@ -1,66 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ - -from concurrent.futures import ThreadPoolExecutor -import stack.commands -from stack.exception import CommandError -from stack.switch.m7800 import SwitchMellanoxM7800 - -class Implementation(stack.commands.Implementation): - - def list_firmware(self, host, switch_attrs): - kwargs = { - 'username' : switch_attrs.get('switch_username', None), - 'password' : switch_attrs.get('switch_password', None), - } - - kwargs = {key: value for key, value in kwargs.items() if value is not None} - - # the context manager ensures we disconnect from the switch - with SwitchMellanoxM7800(host, **kwargs) as m7800_switch: - m7800_switch.connect() - image_listing = m7800_switch.show_images() - installed_image = image_listing.installed_images[image_listing.last_boot_partition] - - return installed_image - - def run(self, args): - errors = [] - # now run each image listing in parallel - with ThreadPoolExecutor(thread_name_prefix = 'mellanox_firmware_listing') as executor: - futures_by_host_make_model = { - host_make_model: executor.submit( - self.list_firmware, - host = host_make_model.host, - switch_attrs = attrs - ) - for host_make_model, attrs in args.items() - } - # now collect the results, adding any errors into the errors list - results_by_host_make_model = {} - for host_make_model, future in futures_by_host_make_model.items(): - if future.exception() is not None: - errors.append(future.exception()) - else: - results_by_host_make_model[host_make_model] = future.result() - - # if there were any errors, aggregate all the errors into one - error_messages = [] - for error in errors: - # if this looks like a stacki exception type, grab the message from it. - if hasattr(error, 'message') and callable(getattr(error, 'message')): - error_messages.append(error.message()) - else: - error_messages.append(f'{error}') - - if error_messages: - error_message = '\n'.join(error_messages) - raise CommandError( - cmd = self.owner, - msg = f"Errors occurred during Mellanox firmware listing:\n{error_message}" - ) - - return results_by_host_make_model diff --git a/common/src/stack/command/stack/commands/list/host/firmware/mapping/__init__.py b/common/src/stack/command/stack/commands/list/host/firmware/mapping/__init__.py deleted file mode 100644 index 7881757c4..000000000 --- a/common/src/stack/command/stack/commands/list/host/firmware/mapping/__init__.py +++ /dev/null @@ -1,85 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.list.host.firmware.command): - """ - Lists the firmware mappings that exist in the stacki database. - - - Zero or more hostnames to filter the results by. - - - - Zero or more firmware versions to filter by. Multiple versions should be specified as a comma separated list. - If one or more versions are specified, the make and model parameters are required. - - - - The optional make of the firmware to filter by. - - - - The optional model of the firmware to filter by. - If this is specified, make is required. - - - - The value to sort the results by. Should be one of 'host', 'make', 'model', or 'version'. - - - - Lists all of the firmware mappings for all hosts. - - - - Lists all of the firmware mappings for the host named switch-13-11. - - - - Lists all of the firmware mappings for firmware versions 3.6.5002 and 3.6.8010 for make mellanox and model m7800 that are - mapped to the hosts named switch-13-11 and switch-13-12. - - - - Lists all of the firmware mappings for firmware versions 3.6.5002 and 3.6.8010 for make mellanox and model m7800 for all hosts. - - - - Lists all of the firmware mappings for make mellanox and model m7800 for all hosts that are switch appliances. - - - - Lists all of the firmware mappings for make mellanox and model m7800 for all hosts. - - - - Lists all of the firmware mappings for make mellanox for the host with the name switch-13-11. - - - - Lists all of the firmware mappings for make mellanox for all hosts. - - """ - - def run(self, params, args): - header = [] - values = [] - for provides, results in self.runPlugins(args = (params, args)): - header.extend(results['keys']) - values.extend(results['values']) - - self.beginOutput() - for owner, vals in values: - self.addOutput(owner = owner, vals = vals) - self.endOutput(header = header) diff --git a/common/src/stack/command/stack/commands/list/host/firmware/mapping/plugin_basic.py b/common/src/stack/command/stack/commands/list/host/firmware/mapping/plugin_basic.py deleted file mode 100644 index 3d30d946c..000000000 --- a/common/src/stack/command/stack/commands/list/host/firmware/mapping/plugin_basic.py +++ /dev/null @@ -1,132 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import unique_everseen -from stack.exception import ParamError - -class Plugin(stack.commands.Plugin): - """Lists firmware mappings and filters the results on any provided arguments.""" - - def provides(self): - return "basic" - - def get_firmware_mappings(self, hosts, versions, make, model, sort): - """Gets the mappings using the provided arguments as a filter.""" - where_clause = [] - query_args = [] - - if hosts: - where_clause.append("nodes.Name IN %s") - query_args.append(hosts) - - if make: - where_clause.append("firmware_make.name=%s") - query_args.append(make) - - if model: - where_clause.append("firmware_model.name=%s") - query_args.append(model) - - if versions: - where_clause.append("firmware.version IN %s") - query_args.append(versions) - - if where_clause: - where_clause = f"WHERE {' AND '.join(where_clause)}" - else: - where_clause = "" - - return [ - (row[0], row[1:]) for row in self.owner.db.select( - f""" - nodes.Name, firmware.version, firmware_make.name, firmware_model.name - FROM firmware_mapping - INNER JOIN nodes - ON firmware_mapping.node_id = nodes.ID - INNER JOIN firmware - ON firmware_mapping.firmware_id = firmware.id - INNER JOIN firmware_model - ON firmware.model_id = firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id = firmware_make.id - {where_clause} - ORDER BY {sort} - """, - query_args, - ) - ] - - def run(self, args): - params, hosts = args - make, model, versions, sort, = self.owner.fillParams( - names = [ - ("make", ""), - ("model", ""), - ("versions", ""), - ("sort", "host"), - ], - params = params, - ) - - sort_map = { - "host": "nodes.Name", - "make": "firmware_make.name", - "model": "firmware_model.name", - "version": "firmware.version", - } - # sort must be one of the allowed values - try: - # also convert to the column name for use in ORDER BY - sort = sort_map[sort] - except KeyError: - raise ParamError( - cmd = self.owner, - param = "sort", - msg = f"Sort must be one of: {list(sort_map.keys())}", - ) - # process hosts if present - if hosts: - # hosts must exist - hosts = self.owner.getHosts(args = hosts) - - # Process versions if present. This will check the make and model as well since those are - # now required. - if versions: - # turn a comma separated string into a list of versions and - # get rid of any duplicate names - versions = tuple( - unique_everseen( - (version.strip() for version in versions.split(",") if version.strip()) - ) - ) - self.owner.ensure_firmwares_exist(make = make, model = model, versions = versions) - # Process model if present. This will check the make as well since that is now required. - elif model: - self.owner.ensure_model_exists(make = make, model = model) - # Process make if present. - elif make: - self.owner.ensure_make_exists(make = make) - - results = self.get_firmware_mappings( - hosts = hosts, - make = make, - model = model, - versions = versions, - sort = sort, - ) - - # return the results - return { - "keys": ["host", "version", "make", "model"], - "values": results - } diff --git a/common/src/stack/command/stack/commands/list/host/firmware/plugin_basic.py b/common/src/stack/command/stack/commands/list/host/firmware/plugin_basic.py deleted file mode 100644 index 88fb580dd..000000000 --- a/common/src/stack/command/stack/commands/list/host/firmware/plugin_basic.py +++ /dev/null @@ -1,47 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ - -import re -import stack.commands - -class Plugin(stack.commands.Plugin): - - def provides(self): - return 'basic' - - def run(self, args): - # Unpack args. - CommonResult, database_values_dict = args - - # This plugin gets the desired firmware version, so add that column to the header. - header = ["desired_firmware_version"] - results = {host_make_model: [] for host_make_model in database_values_dict} - - # For each host_make_model + firmware_version, firmware_imp combination, get the desired firmware version and validate it. - for host_make_model, firmware_info in database_values_dict.items(): - # Ensure that if a version regex exists, that we check the version in the database matches. - # Since they are optional, it could have been set after the fact and a bad version number was - # snuck in. - regex_obj = self.owner.try_get_version_regex( - make = host_make_model.make, - model = host_make_model.model, - ) - if regex_obj and not re.search(regex_obj.regex, firmware_info.firmware_version, re.IGNORECASE): - results[host_make_model].append( - f"{firmware_info.firmware_version} (Invalid format per the version_regex named {regex_obj.name} and will be ignored by sync unless forced)" - ) - else: - results[host_make_model].append(firmware_info.firmware_version) - - # add empty values for host + make + model combos that have no results and - # join together mutiple version results into one string for host + make + model combos with multiple results. - for host_make_model, value in results.items(): - if not value: - results[host_make_model].append(None) - elif len(value) > 1: - results[host_make_model] = [f"(ambiguous, sync will ignore unless forced) {', '.join(value)}"] - - return CommonResult(header, results) diff --git a/common/src/stack/command/stack/commands/list/host/firmware/plugin_version.py b/common/src/stack/command/stack/commands/list/host/firmware/plugin_version.py deleted file mode 100644 index 4ec3eb525..000000000 --- a/common/src/stack/command/stack/commands/list/host/firmware/plugin_version.py +++ /dev/null @@ -1,98 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ - -import re -import stack.commands -from stack.exception import CommandError -from stack.util import flatten - -class Plugin(stack.commands.Plugin): - - def provides(self): - return 'version' - - def requires(self): - return ['basic'] - - def check_errors(self, results): - """Checks for any errors in the results of run_implementations_parallel. If there are errors, - this will aggregate them all into one CommandError and raise it. - """ - # drop any results that didn't have any errors and aggregate the rest into one exception - error_messages = [] - for error in ( - value.exception for value in results.values() - if value is not None and value.exception is not None - ): - # if this looks like a stacki exception type, grab the message from it. - if hasattr(error, 'message') and callable(getattr(error, 'message')): - error_messages.append(error.message()) - else: - error_messages.append(f'{error}') - - if error_messages: - error_message = '\n'.join(error_messages) - raise CommandError( - cmd = self.owner, - msg = f"Errors occurred while listing firmware:\n{error_message}" - ) - - def run(self, args): - # Unpack args. - CommonResult, database_values_dict = args - # get all host attrs up front - host_attrs = self.owner.getHostAttrDict( - host = [host_make_model.host for host_make_model in database_values_dict], - ) - - # Re-map the required information by implementation name so that run_implementations_parallel can - # run each implementation in parallel, and each implementation has all the info it needs. - mapped_by_imp_name = {} - for host_make_model, firmware_info in database_values_dict.items(): - if firmware_info.firmware_imp in mapped_by_imp_name: - mapped_by_imp_name[firmware_info.firmware_imp][host_make_model] = host_attrs[host_make_model.host] - else: - mapped_by_imp_name[firmware_info.firmware_imp] = {host_make_model: host_attrs[host_make_model.host]} - - # run the implementations in parallel. - results_by_imp = self.owner.run_implementations_parallel( - implementation_mapping = mapped_by_imp_name, - display_progress = True, - ) - # Check for any errors. This will raise an exception if any implementations raised an exception. - self.check_errors(results = results_by_imp) - # rebuild the results as (host, make, model) mapped to version - results_by_host_make_model = { - host_make_model: version for host_make_model, version in flatten( - results.result.items() for results in results_by_imp.values() - if results is not None and results.result is not None - ) - } - - # Use the version_regex (if set) to parse out and validate the version numbers returned by the implementations - for host_make_model, version in results_by_host_make_model.items(): - regex_obj = self.owner.try_get_version_regex( - make = host_make_model.make, - model = host_make_model.model, - ) - if regex_obj: - match = re.search(regex_obj.regex, version, re.IGNORECASE) - if not match: - results_by_host_make_model[host_make_model] = ( - f"{version} (Doesn't validate using the version_regex named {regex_obj.name} and will be ignored by sync)" - ) - else: - results_by_host_make_model[host_make_model] = match.group() - - # Do a final pass to turn the results into a list as the top level command expects. - # Also set None for host + make + model combos that had no results. - for host_make_model in database_values_dict: - if host_make_model not in results_by_host_make_model: - results_by_host_make_model[host_make_model] = [None] - else: - results_by_host_make_model[host_make_model] = [results_by_host_make_model[host_make_model]] - - return CommonResult(header = ['current_firmware_version'], values = results_by_host_make_model) diff --git a/common/src/stack/command/stack/commands/remove/firmware/__init__.py b/common/src/stack/command/stack/commands/remove/firmware/__init__.py deleted file mode 100644 index 6cc650c80..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/__init__.py +++ /dev/null @@ -1,57 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from stack.argument_processors.firmware import FirmwareArgProcessor -import stack.commands - -class command(stack.commands.remove.command, FirmwareArgProcessor): - pass - -class Command(command): - """ - Removes firmware images from stacki. - - - Zero or more firmware versions to be removed. If no versions are specified, all will be removed. - If one or more versions are specified, the make and model parameters are required. - - - - The optional make of the firmware to remove. - If this is specified but no versions or model are specified, this will remove all firmware versions for the make. - - - - The optional model of the firmware to remove. - If this is specified, make is required. - If no versions are specified but make and model are specified, all firmware versions for that make and model will be removed. - - - - Removes all firmware. - - - - Removes the firmware with version 3.6.5002 for the mellanox m7800 make and model. - - - - Removes all firmware for the mellanox make. - - - - Removes all firmware for the mellanox make and m7800 model. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/remove/firmware/imp/__init__.py b/common/src/stack/command/stack/commands/remove/firmware/imp/__init__.py deleted file mode 100644 index 3ecb5886e..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/imp/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.remove.firmware.command): - """ - Removes firmware implementations from the stacki database. - - - One or more implementations to remove. - - - - Removes the mellanox_m6xxx_7xxx and intel_special implementations from the database. - - """ - - def run(self, params, args): - self.runPlugins(args = args) diff --git a/common/src/stack/command/stack/commands/remove/firmware/imp/plugin_basic.py b/common/src/stack/command/stack/commands/remove/firmware/imp/plugin_basic.py deleted file mode 100644 index 75aaf5974..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/imp/plugin_basic.py +++ /dev/null @@ -1,42 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from pymysql import IntegrityError -import stack.commands -from stack.util import unique_everseen, lowered -from stack.exception import ArgRequired, CommandError - -class Plugin(stack.commands.Plugin): - """Attempts to remove implementations.""" - - def provides(self): - return "basic" - - def run(self, args): - # remove any duplicates - args = tuple(unique_everseen(lowered(args))) - # The imps must exist - self.owner.ensure_imps_exist(imps = args) - - # remove the implementations - try: - self.owner.db.execute("DELETE FROM firmware_imp WHERE name IN %s", (args,)) - except IntegrityError: - raise CommandError( - cmd = self.owner, - msg = ( - "Failed to remove all implementations because some are still in use." - " Please run 'stack list firmware model expanded=true' to list the" - " models still using the implementation and 'stack remove firmware model'" - " to remove them." - ) - ) diff --git a/common/src/stack/command/stack/commands/remove/firmware/make/__init__.py b/common/src/stack/command/stack/commands/remove/firmware/make/__init__.py deleted file mode 100644 index e51433375..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/make/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.remove.firmware.command): - """ - Removes firmware makes from the stacki database. - - - One or more make names to remove. This will also remove any associated models and firmware associated with those models. - - - - Removes two makes with the names 'mellanox' and 'dell'. This also removes any associated models and firmware associated with the models removed. - - """ - - def run(self, params, args): - self.runPlugins(args = args) diff --git a/common/src/stack/command/stack/commands/remove/firmware/make/plugin_basic.py b/common/src/stack/command/stack/commands/remove/firmware/make/plugin_basic.py deleted file mode 100644 index 95068ac9d..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/make/plugin_basic.py +++ /dev/null @@ -1,59 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import unique_everseen, lowered -from stack.exception import ArgRequired, ArgError, CommandError - -class Plugin(stack.commands.Plugin): - """Attempts to remove all provided makes and any associated models and firmware from the database.""" - - def provides(self): - return "basic" - - def remove_related_models(self, makes): - """Remove any models related to the provided makes.""" - # remove associated models - for make in makes: - # get all the models associated with this make - models_to_remove = [ - row[0] for row in - self.owner.db.select( - """ - firmware_model.name - FROM firmware_model - INNER JOIN firmware_make - ON firmware_model.make_id=firmware_make.id - WHERE firmware_make.name=%s - """, - make, - ) - ] - - # and remove them if we found any - if models_to_remove: - self.owner.call(command = "remove.firmware.model", args = [*models_to_remove, f"make={make}"]) - - def run(self, args): - # get rid of any duplicate names - makes = tuple(unique_everseen(lowered(args))) - # ensure the make names already exist - self.owner.ensure_makes_exist(makes = makes) - - # Remove any related models. - self.remove_related_models(makes = makes) - - # now delete the makes - self.owner.db.execute( - "DELETE FROM firmware_make WHERE name IN %s", - (makes,) - ) diff --git a/common/src/stack/command/stack/commands/remove/firmware/make/version_regex/__init__.py b/common/src/stack/command/stack/commands/remove/firmware/make/version_regex/__init__.py deleted file mode 100644 index ad7b1aef7..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/make/version_regex/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.remove.firmware.command): - """ - Disassociates firmware version_regexes from one or more makes. - - - One or more makes to disassociate from a version_regex. - - - - Disassociates the mellanox and intel makes from any version_regexes that were set for them. - - """ - - def run(self, params, args): - self.runPlugins(args = args) diff --git a/common/src/stack/command/stack/commands/remove/firmware/make/version_regex/plugin_basic.py b/common/src/stack/command/stack/commands/remove/firmware/make/version_regex/plugin_basic.py deleted file mode 100644 index 99c07a235..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/make/version_regex/plugin_basic.py +++ /dev/null @@ -1,33 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import lowered, unique_everseen -from stack.exception import ArgRequired - -class Plugin(stack.commands.Plugin): - """Attempts to disassociate version_regexes from makes.""" - - def provides(self): - return "basic" - - def run(self, args): - # Require make names - if not args: - raise ArgRequired(cmd = self.owner, arg = "makes") - # lowercase all args and remove any duplicates - args = tuple(unique_everseen(lowered(args))) - # The makes must exist - self.owner.ensure_makes_exist(makes = args) - - # disassociate the makes from version_regexes - self.owner.db.execute("UPDATE firmware_make SET version_regex_id=NULL WHERE name IN %s", (args,)) diff --git a/common/src/stack/command/stack/commands/remove/firmware/model/__init__.py b/common/src/stack/command/stack/commands/remove/firmware/model/__init__.py deleted file mode 100644 index 39c6a1957..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/model/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.remove.firmware.command): - """ - Removes a firmware model from the stacki database. - - - One or more model names to remove. Any firmware associated with the models will also be removed. - - - - The maker of the models being removed. This must correspond to an already existing make. - - - - Removes two models with the names 'awesome_9001' and 'mediocre_5200' from the set of available firmware models under the 'boss hardware corp' make. - This also removes any firmware associated with those models. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/remove/firmware/model/plugin_basic.py b/common/src/stack/command/stack/commands/remove/firmware/model/plugin_basic.py deleted file mode 100644 index 26609b162..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/model/plugin_basic.py +++ /dev/null @@ -1,76 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import unique_everseen, lowered -from stack.exception import ArgRequired, ArgError, ParamError, ParamRequired, CommandError -from pathlib import Path - -class Plugin(stack.commands.Plugin): - """Attempts to remove all provided models from the system.""" - - def provides(self): - return "basic" - - def remove_related_firmware(self, make, models): - """Remove any firmware related to the provided make + model combinations.""" - for model in models: - # get all the firmware associated with this make and model - firmware_to_remove = [ - row[0] for row in - self.owner.db.select( - """ - firmware.version - FROM firmware - INNER JOIN firmware_model - ON firmware.model_id=firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id=firmware_make.id - WHERE firmware_make.name=%s AND firmware_model.name=%s - """, - (make, model), - ) - ] - # and remove them if we found any - if firmware_to_remove: - self.owner.call( - command = "remove.firmware", - args = [*firmware_to_remove, f"make={make}", f"model={model}"], - ) - - def run(self, args): - params, args = args - make, = lowered( - self.owner.fillParams( - names = [("make", "")], - params = params, - ) - ) - - # get rid of any duplicate names - models = tuple(unique_everseen(lowered(args))) - # ensure the make and models exist - self.owner.ensure_models_exist(make = make, models = models) - - # remove associated firmware - self.remove_related_firmware(make = make, models = models) - - # now delete the models - self.owner.db.execute( - """ - DELETE firmware_model FROM firmware_model - INNER JOIN firmware_make - ON firmware_model.make_id=firmware_make.id - WHERE firmware_model.name IN %s AND firmware_make.name=%s - """, - (models, make), - ) diff --git a/common/src/stack/command/stack/commands/remove/firmware/model/version_regex/__init__.py b/common/src/stack/command/stack/commands/remove/firmware/model/version_regex/__init__.py deleted file mode 100644 index 679a9b24c..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/model/version_regex/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.remove.firmware.command): - """ - Disassociates firmware version_regexes from one or more models. - - - One or more models to disassociate from a version_regex. - - - - The make of the provided models. - - - - Disassociates the m7800 and m6036 models for the mellanox make from any version_regexes that were set for them. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/remove/firmware/model/version_regex/plugin_basic.py b/common/src/stack/command/stack/commands/remove/firmware/model/version_regex/plugin_basic.py deleted file mode 100644 index af6199444..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/model/version_regex/plugin_basic.py +++ /dev/null @@ -1,42 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import lowered, unique_everseen -from stack.exception import ArgRequired, ParamRequired, ParamError - -class Plugin(stack.commands.Plugin): - """Attempts to disassociate version_regexes from models.""" - - def provides(self): - return "basic" - - def run(self, args): - params, args = args - # Lowercase all args and remove any duplicates. - models = tuple(unique_everseen(lowered(args))) - - make, = lowered( - self.owner.fillParams(names = [("make", "")], params = params) - ) - # The make and models must exist. - self.owner.ensure_models_exist(models = models, make = make) - - # disassociate the models from version_regexes - self.owner.db.execute( - """ - UPDATE firmware_model - INNER JOIN firmware_make ON firmware_make.id = firmware_model.make_id - SET firmware_model.version_regex_id=NULL WHERE firmware_model.name IN %s AND firmware_make.name=%s - """, - (models, make), - ) diff --git a/common/src/stack/command/stack/commands/remove/firmware/plugin_basic.py b/common/src/stack/command/stack/commands/remove/firmware/plugin_basic.py deleted file mode 100644 index e26c8d9d3..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/plugin_basic.py +++ /dev/null @@ -1,85 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import unique_everseen, lowered -from pathlib import Path -from contextlib import suppress - -class Plugin(stack.commands.Plugin): - """Attempts to remove all provided firmware versions for the given make and model from the database and the file system.""" - - def provides(self): - return "basic" - - def validate_inputs(self, make, model, versions): - """Validate that the provided inputs are valid.""" - # process make if present - if make: - self.owner.ensure_make_exists(make = make) - # process model if present - if model: - self.owner.ensure_model_exists(make = make, model = model) - # Process versions if present - if versions: - self.owner.ensure_firmwares_exist(make = make, model = model, versions = versions) - - def build_query(self, make, model, versions): - """Build the select query based on the arguments.""" - query = """ - firmware.id, firmware.file - FROM firmware - INNER JOIN firmware_model - ON firmware.model_id=firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id=firmware_make.id - WHERE {} - """ - query_params = [] - # If versions are specified, get the specific versions to remove - if versions: - query = query.format("firmware.version IN %s AND firmware_make.name=%s AND firmware_model.name=%s") - query_params.extend((versions, make, model)) - # Else if make and model are specified, remove all firmwares for that make and model - elif make and model: - query = query.format("firmware_make.name=%s AND firmware_model.name=%s") - query_params.extend((make, model)) - # Else if make is specified, remove all firmwares for that make - elif make: - query = query.format("firmware_make.name=%s") - query_params.extend((make,)) - # otherwise remove all firmware - else: - query = "firmware.id, firmware.file FROM firmware" - - return query, query_params - - def run(self, args): - params, args = args - versions = tuple(unique_everseen(lowered(args))) - make, model = lowered( - self.owner.fillParams( - names = [("make", ""), ("model", "")], - params = params, - ) - ) - self.validate_inputs(make = make, model = model, versions = versions) - - # Get the query and query_params based on the arguments and parameters. - query, query_params = self.build_query(make = make, model = model, versions = versions) - # remove the file and then the db entry for each firmware to remove - for firmware_id, file_path in ((row[0], row[1]) for row in self.owner.db.select(query, query_params)): - # If the file doesn't exist, that's ok since we were trying to delete it anyways. - with suppress(FileNotFoundError): - Path(file_path).resolve(strict = True).unlink() - - self.owner.db.execute("DELETE FROM firmware WHERE id=%s", firmware_id) diff --git a/common/src/stack/command/stack/commands/remove/firmware/version_regex/__init__.py b/common/src/stack/command/stack/commands/remove/firmware/version_regex/__init__.py deleted file mode 100644 index 0328583d6..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/version_regex/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.remove.firmware.command): - """ - Removes firmware version_regexes from the stacki database. - - - One or more version_regexes to remove. - - - - Removes the mellanox_version and intel_version version_regexes from the database. - - """ - - def run(self, params, args): - self.runPlugins(args = args) diff --git a/common/src/stack/command/stack/commands/remove/firmware/version_regex/plugin_basic.py b/common/src/stack/command/stack/commands/remove/firmware/version_regex/plugin_basic.py deleted file mode 100644 index 59ccc6931..000000000 --- a/common/src/stack/command/stack/commands/remove/firmware/version_regex/plugin_basic.py +++ /dev/null @@ -1,30 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import lowered, unique_everseen -from stack.exception import ArgRequired - -class Plugin(stack.commands.Plugin): - """Attempts to remove version_regexes.""" - - def provides(self): - return "basic" - - def run(self, args): - # lowercase all args and remove any duplicates - names = tuple(unique_everseen(lowered(args))) - # The version_regexes must exist - self.owner.ensure_version_regexes_exist(names = names) - - # remove the version_regexes - self.owner.db.execute("DELETE FROM firmware_version_regex WHERE name IN %s", (names,)) diff --git a/common/src/stack/command/stack/commands/remove/host/firmware/__init__.py b/common/src/stack/command/stack/commands/remove/host/firmware/__init__.py deleted file mode 100644 index 6defb7b20..000000000 --- a/common/src/stack/command/stack/commands/remove/host/firmware/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from stack.argument_processors.firmware import FirmwareArgProcessor -import stack.commands - -class command(stack.commands.remove.host.command, FirmwareArgProcessor): - pass diff --git a/common/src/stack/command/stack/commands/remove/host/firmware/mapping/__init__.py b/common/src/stack/command/stack/commands/remove/host/firmware/mapping/__init__.py deleted file mode 100644 index 5c862171b..000000000 --- a/common/src/stack/command/stack/commands/remove/host/firmware/mapping/__init__.py +++ /dev/null @@ -1,63 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.remove.host.firmware.command): - """ - Removes firmware mappings. - - - Zero or more hosts to have their mapped firmware versions removed. - If no hosts are specified, all mappings for all hosts are removed. - - - - Zero or more firmware versions to be unmapped. Multiple versions should be specified as a comma separated list. - If no versions are specified, all will be removed. - If one or more versions are specified, the make and model parameters are required. - - - - The optional make of the firmware to unmap. - If this is specified but no versions or model are specified, this will remove all firmware mappings that match the make. - - - - The optional model of the firmware to unmap. - If this is specified, make is required. - If no versions are specified but make and model are specified, all firmwares for that make and model will be removed. - - - - Removes all firmware mappings for all hosts. - - - - Unmaps the firmware versions 3.6.5002 and 3.6.8010 for the mellanox m7800 make and model from the hosts named switch-13-11 and switch-13-12. - - - - Unmaps all firmware for the mellanox make and m7800 model from all hosts. - - - - Unmaps all of the firmware for the mellanox make from the host named switch-13-11. - - - - Unmaps all of the firmware for the mellanox make from all hosts. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/remove/host/firmware/mapping/plugin_basic.py b/common/src/stack/command/stack/commands/remove/host/firmware/mapping/plugin_basic.py deleted file mode 100644 index 33a66430f..000000000 --- a/common/src/stack/command/stack/commands/remove/host/firmware/mapping/plugin_basic.py +++ /dev/null @@ -1,145 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import unique_everseen, lowered -from stack.exception import ArgError, ParamError, ParamRequired, CommandError - -class Plugin(stack.commands.Plugin): - """Attempts to remove firmware mappings based on the provided arguments.""" - - def provides(self): - return "basic" - - def validate_make(self, make): - """If the make is provided, ensure it exists.""" - if make: - # ensure the make exists - self.owner.ensure_make_exists(make = make) - - def validate_model(self, make, model): - """If the model is provided, ensure the make and model exist.""" - if model: - # ensure the model exists - self.owner.ensure_model_exists(make = make, model = model) - - def get_firmware_mappings_to_remove(self, hosts, versions, make, model): - """Gets the mappings to remove using the provided arguments as a filter.""" - # If specific hosts are specified, build the query based on host. - if hosts: - query = """ - firmware_mapping.id - FROM firmware_mapping - INNER JOIN nodes - ON firmware_mapping.node_id = nodes.ID - INNER JOIN firmware - ON firmware_mapping.firmware_id = firmware.id - INNER JOIN firmware_model - ON firmware.model_id = firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id = firmware_make.id - WHERE nodes.Name IN %s {} - """ - query_params = [hosts] - # If versions and hosts are specified, get the specific mappings to remove for the hosts - if versions: - query = query.format( - "AND firmware.version IN %s AND firmware_make.name=%s AND firmware_model.name=%s", - ) - query_params.extend((versions, make, model)) - # Else if make, model, and hosts are specified, remove all mappings for that make and model for the specified hosts - elif make and model: - query = query.format("AND firmware_make.name=%s AND firmware_model.name=%s") - query_params.extend((make, model)) - # Else if make and hosts are specified, remove all mappings for that make for the specified hosts. - elif make: - query = query.format("AND firmware_make.name=%s") - query_params.extend((make,)) - # Else only hosts are specified, so remove all firmware mappings from the specified hosts - else: - query = query.format("") - # If no hosts are specified, build the query based solely on the other params. - else: - query = """ - firmware_mapping.id - FROM firmware_mapping - INNER JOIN firmware - ON firmware_mapping.firmware_id = firmware.id - INNER JOIN firmware_model - ON firmware.model_id = firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id = firmware_make.id - {} - """ - query_params = [] - # If versions are specified, get all mappings for the specified firmware versions. - if versions: - query = query.format("WHERE firmware.version IN %s AND firmware_make.name=%s AND firmware_model.name=%s") - query_params.extend((versions, make, model)) - # Else if make and model are specified, remove all mappings for that make and model for all hosts - elif make and model: - query = query.format("WHERE firmware_make.name=%s AND firmware_model.name=%s") - query_params.extend((make, model)) - # Else if make is specified, remove all mappings for that make for all hosts. - elif make: - query = query.format("WHERE firmware_make.name=%s") - query_params.extend((make,)) - # otherwise remove all mappings - else: - query = "firmware_mapping.id FROM firmware_mapping" - - return [row[0] for row in self.owner.db.select(query, query_params)] - - def run(self, args): - params, args = args - hosts = tuple(unique_everseen(lowered(args))) - # process hosts if present - if hosts: - # hosts must exist - hosts = self.owner.getHosts(args = hosts) - - make, model, versions = lowered( - self.owner.fillParams( - names = [ - ("make", ""), - ("model", ""), - ("versions", ""), - ], - params = params, - ), - ) - # process make if present - self.validate_make(make = make) - # process model if present - self.validate_model(make = make, model = model) - # Process versions if present - if versions: - # turn a comma separated string into a list of versions and - # get rid of any duplicate names - versions = tuple( - unique_everseen( - (version.strip() for version in versions.split(",") if version.strip()) - ) - ) - # ensure the versions exist - self.owner.ensure_firmwares_exist(make = make, model = model, versions = versions) - - mappings_to_remove = self.get_firmware_mappings_to_remove( - hosts = hosts, - versions = versions, - make = make, - model = model, - ) - - # remove the mappings - if mappings_to_remove: - self.owner.db.execute("DELETE FROM firmware_mapping WHERE id IN %s", (mappings_to_remove,)) diff --git a/common/src/stack/command/stack/commands/set/firmware/__init__.py b/common/src/stack/command/stack/commands/set/firmware/__init__.py deleted file mode 100644 index 1f55f2747..000000000 --- a/common/src/stack/command/stack/commands/set/firmware/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from stack.argument_processors.firmware import FirmwareArgProcessor -import stack.commands - -class command(stack.commands.set.command, FirmwareArgProcessor): - pass diff --git a/common/src/stack/command/stack/commands/set/firmware/make/__init__.py b/common/src/stack/command/stack/commands/set/firmware/make/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/common/src/stack/command/stack/commands/set/firmware/make/version_regex/__init__.py b/common/src/stack/command/stack/commands/set/firmware/make/version_regex/__init__.py deleted file mode 100644 index 4955b0450..000000000 --- a/common/src/stack/command/stack/commands/set/firmware/make/version_regex/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.set.firmware.command): - """ - Associates a firmware version_regex with one or more makes. - - - One or more makes to associate the version_regex with. - - - - The name of the version_regex to associate with the provided makes. - - - - Sets the firmware make mellanox to use the mellanox_version regex when parsing version numbers. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/set/firmware/make/version_regex/plugin_basic.py b/common/src/stack/command/stack/commands/set/firmware/make/version_regex/plugin_basic.py deleted file mode 100644 index 621f9373e..000000000 --- a/common/src/stack/command/stack/commands/set/firmware/make/version_regex/plugin_basic.py +++ /dev/null @@ -1,42 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import lowered, unique_everseen -from stack.exception import ArgRequired, ParamRequired, ParamError - -class Plugin(stack.commands.Plugin): - """Attempts to associate a version_regex with makes.""" - - def provides(self): - return "basic" - - def run(self, args): - params, args = args - makes = tuple(unique_everseen(lowered(args))) - - version_regex, = lowered( - self.owner.fillParams(names = [("version_regex", "")], params = params), - ) - - # The makes must exist - self.owner.ensure_makes_exist(makes = makes) - # The version_regex must exist - self.owner.ensure_version_regex_exists(name = version_regex) - - # get the version_regex ID - version_regex_id = self.owner.get_version_regex_id(name = version_regex) - # associate the makes with the version_regex - self.owner.db.execute( - "UPDATE firmware_make SET version_regex_id=%s WHERE name in %s", - (version_regex_id, makes), - ) diff --git a/common/src/stack/command/stack/commands/set/firmware/model/__init__.py b/common/src/stack/command/stack/commands/set/firmware/model/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/common/src/stack/command/stack/commands/set/firmware/model/imp/__init__.py b/common/src/stack/command/stack/commands/set/firmware/model/imp/__init__.py deleted file mode 100644 index 730d40eb6..000000000 --- a/common/src/stack/command/stack/commands/set/firmware/model/imp/__init__.py +++ /dev/null @@ -1,37 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.set.firmware.command): - """ - Associates a firmware implementation with one or more models. - - - One or more models to associate the implementation with. - - - - The name of the implementation to associate with the provided models. - - - - The make of the models. - - - - Sets the mellanox_6xxx_7xxx implementation as the one to run for the models m7800 and m6036 for make mellanox. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/set/firmware/model/imp/plugin_basic.py b/common/src/stack/command/stack/commands/set/firmware/model/imp/plugin_basic.py deleted file mode 100644 index ad3f9bdf2..000000000 --- a/common/src/stack/command/stack/commands/set/firmware/model/imp/plugin_basic.py +++ /dev/null @@ -1,51 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import unique_everseen, lowered -from stack.exception import ArgRequired, ParamRequired, ParamError - -class Plugin(stack.commands.Plugin): - """Attempts to associate an implementation with models.""" - - def provides(self): - return "basic" - - def run(self, args): - params, args = args - models = tuple(unique_everseen(lowered(args))) - - imp, make, = lowered( - self.owner.fillParams( - names = [ - ("imp", ""), - ("make", ""), - ], - params = params, - ), - ) - self.owner.ensure_models_exist(make = make, models = models) - self.owner.ensure_imp_exists(imp = imp) - - # get the implementation ID - imp_id = self.owner.get_imp_id(imp = imp) - # associate the models with the imp - self.owner.db.execute( - """ - UPDATE firmware_model - INNER JOIN firmware_make - ON firmware_model.make_id = firmware_make.id - SET firmware_model.imp_id=%s - WHERE firmware_make.name = %s AND firmware_model.name IN %s - """, - (imp_id, make, models), - ) diff --git a/common/src/stack/command/stack/commands/set/firmware/model/version_regex/__init__.py b/common/src/stack/command/stack/commands/set/firmware/model/version_regex/__init__.py deleted file mode 100644 index 14d26727f..000000000 --- a/common/src/stack/command/stack/commands/set/firmware/model/version_regex/__init__.py +++ /dev/null @@ -1,37 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands - -class Command(stack.commands.set.firmware.command): - """ - Associates a firmware version_regex with one or more models - - - One or more models to associate the version_regex with. - - - - The make of the provided models. - - - - The name of the version_regex to associate with the provided models. - - - - Sets the firmware models m7800 and m6036 for make mellanox to use the mellanox_version regex when parsing version numbers. - - """ - - def run(self, params, args): - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/set/firmware/model/version_regex/plugin_basic.py b/common/src/stack/command/stack/commands/set/firmware/model/version_regex/plugin_basic.py deleted file mode 100644 index ebe0f8da1..000000000 --- a/common/src/stack/command/stack/commands/set/firmware/model/version_regex/plugin_basic.py +++ /dev/null @@ -1,50 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2019 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.util import lowered, unique_everseen -from stack.exception import ArgRequired, ParamRequired, ParamError - -class Plugin(stack.commands.Plugin): - """Attempts to associate a version_regex with models.""" - - def provides(self): - return "basic" - - def run(self, args): - params, args = args - # Lowercase and make all args unique. - models = tuple(unique_everseen(lowered(args))) - - make, version_regex, = lowered( - self.owner.fillParams( - names = [ - ("make", ""), - ("version_regex", ""), - ], - params = params, - ), - ) - self.owner.ensure_models_exist(make = make, models = models) - self.owner.ensure_version_regex_exists(name = version_regex) - - # get the version_regex ID - version_regex_id = self.owner.get_version_regex_id(name = version_regex) - # associate the models with the version_regex - self.owner.db.execute( - """ - UPDATE firmware_model - INNER JOIN firmware_make ON firmware_make.id = firmware_model.make_id - SET firmware_model.version_regex_id=%s WHERE firmware_model.name IN %s AND firmware_make.name=%s - """, - (version_regex_id, models, make), - ) diff --git a/common/src/stack/command/stack/commands/sync/firmware/__init__.py b/common/src/stack/command/stack/commands/sync/firmware/__init__.py deleted file mode 100644 index ed2b0d41c..000000000 --- a/common/src/stack/command/stack/commands/sync/firmware/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# @copyright@ -# Copyright (c) 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ - -from stack.argument_processors.firmware import FirmwareArgProcessor -import stack.commands - -class command(stack.commands.sync.command, FirmwareArgProcessor): - pass - - -class Command(command): - """ - Syncs the firmware files on the frontend with what the expected firmware is in the stacki database - - - Zero or more firmware versions to sync. If none are specified, all firmware files tracked by stacki will be synced. - - - - The make of the firmware versions to be synced. This is required if version arguments are specified. - - - - The model of the firmware versions to be synced. This is required if version arguments are specified. - - - - Makes sure the firmware file with version 3.6.8010 for Mellanox m7800 devices exists on the filesystem - and has the correct hash. It will be re-fetched from the source if necessary. - - - - Syncs all known firmware files, checking that they exist on the filesystem and have the correct hash. - They will be re-fetched from the source if necessary. - - """ - - def run(self, params, args): - self.notify('Sync Firmware') - self.runPlugins(args = (params, args)) diff --git a/common/src/stack/command/stack/commands/sync/firmware/plugin_basic.py b/common/src/stack/command/stack/commands/sync/firmware/plugin_basic.py deleted file mode 100644 index ed44d0174..000000000 --- a/common/src/stack/command/stack/commands/sync/firmware/plugin_basic.py +++ /dev/null @@ -1,92 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from pathlib import Path -import stack.commands -import stack.firmware -from stack.util import unique_everseen -from stack.exception import ArgError, ParamRequired, CommandError - -class Plugin(stack.commands.Plugin): - """Attempts to sync firmware files on the filesystem with what is in the stacki database""" - - def provides(self): - return 'basic' - - def run(self, args): - params, args = args - make, model = self.owner.fillParams( - names = [ - ('make', None), - ('model', None) - ], - params = params - ) - # get rid of any duplicate names - versions = tuple(unique_everseen(args)) - # set up for a potential where clause - where_clause = "" - query_args = tuple() - - if versions: - # ensure the versions exist in the DB - self.owner.ensure_firmwares_exist(make = make, model = model, versions = versions) - # limit query to the selected versions - where_clause = "WHERE firmware.version IN %s AND firmware_make.name=%s AND firmware_model.name=%s" - query_args = (versions, make, model) - - for row in self.owner.db.select( - f''' - firmware.version, firmware.source, firmware.hash_alg, firmware.hash, firmware.file, firmware_make.name, firmware_model.name - FROM firmware - INNER JOIN firmware_model - ON firmware.model_id=firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id=firmware_make.id - {where_clause} - ''', - query_args, - ): - version, source, hash_alg, hash_value, local_file, make, model = row - local_file = Path(local_file) - # check that the local file exists, and fetch it if not - if not local_file.exists(): - try: - local_file = stack.firmware.fetch_firmware( - source = source, - make = make, - model = model, - filename = local_file.name - ) - except stack.firmware.FirmwareError as exception: - raise CommandError( - cmd = self.owner, - msg = f'Error while fetching version {version} for make {make} and model {model}: {exception}' - ) - # verify the hash - try: - stack.firmware.calculate_hash(file_path = local_file, hash_alg = hash_alg, hash_value = hash_value) - except stack.firmware.FirmwareError as exception: - raise CommandError( - cmd = self.owner, - msg = f'Error during file verification: {exception}' - ) - - # prune any files that shouldn't be there - files_expected = [ - Path(file_path).resolve() - for file_path in (row[0] for row in self.owner.db.select('firmware.file FROM firmware')) - ] - for file_path in stack.firmware.BASE_PATH.glob('**/*'): - file_path = file_path.resolve(strict = True) - if file_path.is_file() and file_path not in files_expected: - file_path.unlink() diff --git a/common/src/stack/command/stack/commands/sync/host/firmware/__init__.py b/common/src/stack/command/stack/commands/sync/host/firmware/__init__.py deleted file mode 100644 index da3b016f3..000000000 --- a/common/src/stack/command/stack/commands/sync/host/firmware/__init__.py +++ /dev/null @@ -1,238 +0,0 @@ -# @copyright@ -# Copyright (c) 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ - -import re -from pathlib import Path -from collections import namedtuple -from dataclasses import make_dataclass -import itertools -from stack.argument_processors.firmware import FirmwareArgProcessor -import stack.commands -import stack.firmware - -class Command(stack.commands.sync.host.command, FirmwareArgProcessor): - """ - Syncs firmware to hosts that are compatible with the firmware - - - Zero or more hosts to sync. If none are specified, all hosts will have their firmware synced. - - - - Force the firmware update process to run for hosts that are already in sync. - - - - If a compatible firmware version is tracked by stacki, the firmware will be synced to switch-18-11. - - - - For each host, if a compatible firmware version is tracked by stacki, it will be synced to the host. - - """ - - def _get_invalid_mappings(self, results, force, host_current_firmwares): - """Processes the result set and excludes any invalid mappings. - - This is where, if force is not set, multiple firmware versions for the same host + make + model - are rejected and already up to date hosts are skipped. This will also perform the version regex - validation if one is found. - """ - invalid_mappings = set() - for host_make_model, firmware_info in results.items(): - # don't allow multiple firmware versions for a host + make + model combo unless force is specified. - if not force and len(firmware_info.firmware_files) > 1: - invalid_mappings.add(host_make_model) - self.notify( - f"The host + make + model combination of {host_make_model.host} {host_make_model.make} {host_make_model.model}" - f" has multiple firmware versions mapped to it and is being skipped. Check your configuration or use force=true to override." - ) - continue - - # Ensure that if a version regex exists, that we check the version in the database matches. - # Since they are optional, it could have been set after the fact and a bad version number was - # snuck in. - regex_obj = self.try_get_version_regex( - make = host_make_model.make, - model = host_make_model.model, - ) - for firmware_file in firmware_info.firmware_files: - # force does not override the regex check because version_regexes are optional. - if regex_obj and not re.search(regex_obj.regex, firmware_file.version, re.IGNORECASE): - invalid_mappings.add(host_make_model) - self.notify( - f"Skipping The host + make + model combination of {host_make_model.host} {host_make_model.make} {host_make_model.model}" - f" because the firmware version {firmware_file.version} does not validate using the version_regex named {regex_obj.name}." - f" Check that your version_regex is correct using 'stack list firmware version_regex'." - ) - break - # don't upgrade to the same version for a host + make + model combo unless force is specified. - if not force and firmware_file.version == host_current_firmwares[host_make_model]: - invalid_mappings.add(host_make_model) - self.notify( - f"Skipping The host + make + model combination of {host_make_model.host} {host_make_model.make} {host_make_model.model}" - f" because the current version {host_current_firmwares[host_make_model]} matches the desired version {firmware_file.version}" - f" Use force=true to override." - ) - break - - return invalid_mappings - - def _sync_required_firmware_files(self, results): - """Ensures the required firmware files are on disk based on the resulting firmware information to sync.""" - # build a dictionary of make + model mapped to versions - keyfunc = lambda items: (items[0].make, items[0].model) - firmware_files_to_sync = { - make_model: [ - firmware_file.version - for _, firmware_info in values - for firmware_file in firmware_info.firmware_files - ] - for make_model, values in itertools.groupby( - sorted(results.items(), key = keyfunc), - key = keyfunc, - ) - } - # sync all firmware we need first to ensure the local filesystem - # is consistent with what the DB - for make_model, versions in firmware_files_to_sync.items(): - self.call(command = 'sync.firmware', args = [*versions, f"make={make_model[0]}", f"model={make_model[1]}"]) - - def _get_host_firmwares(self, common_key, hosts, host_attrs, host_current_firmwares, force): - """Get all the firmware information from the database needed to perform the sync operation.""" - results = {} - # The firmware information for a given host + make + model combination. - FirmwareInfo = make_dataclass( - "FirmwareInfo", ( - # Can potentially support multiple firmware files - "firmware_files", - "current_version", - "imp", - "host_attrs", - "force", - "frontend_ip", - ) - ) - # The information for the firmware file to be applied. - FirmwareFile = make_dataclass( - "FirmwareFile", ( - "file", - "version", - "url", - ) - ) - for row in self.db.select( - """ - nodes.Name, firmware_make.name, firmware_model.name, firmware.version, firmware.file, firmware_imp.name - FROM firmware_mapping - INNER JOIN nodes - ON firmware_mapping.node_id = nodes.ID - INNER JOIN firmware - ON firmware_mapping.firmware_id = firmware.id - INNER JOIN firmware_model - ON firmware.model_id = firmware_model.id - INNER JOIN firmware_make - ON firmware_model.make_id = firmware_make.id - INNER JOIN firmware_imp - ON firmware_model.imp_id = firmware_imp.id - WHERE nodes.Name IN %s - """, - (hosts,) - ): - host_make_model = common_key(*row[:3]) - version, firmware_file, imp = row[3:] - path = Path(firmware_file).resolve() - # If the key does not already exist in the map, add it. Otherwise, update the existing information. - if host_make_model in results: - results[host_make_model].firmware_files.append( - FirmwareFile( - file = path, - version = version, - # This will be resolved later after pruning invalid mappings. - url = None, - ) - ) - else: - results[host_make_model] = FirmwareInfo( - current_version = host_current_firmwares[host_make_model], - imp = imp, - host_attrs = host_attrs[host_make_model.host], - force = force, - frontend_ip = self.get_common_frontend_ip(hostname = host_make_model.host), - firmware_files = [ - FirmwareFile( - file = path, - version = version, - # This will be resolved later after pruning invalid mappings. - url = None, - ) - ] - ) - - # Perform validation now that we have all the info from the DB - invalid_mappings = self._get_invalid_mappings( - results = results, - force = force, - host_current_firmwares = host_current_firmwares, - ) - # Drop the invalid mappings. - for host_make_model in invalid_mappings: - results.pop(host_make_model) - - # Notify about the hosts being skipped because of no mapped firmware. - hosts_with_mapped_firmware = [host_make_model.host for host_make_model in results] - for host in hosts: - if host not in hosts_with_mapped_firmware: - self.notify( - f"Skipping {host} because no firmware is mapped to it." - ) - - # Make sure the files we need exist on disk based on the results. - # We do this after the results are pruned because we don't want to run a bunch of - # potentially long file fetch operations on files we aren't going to use. - self._sync_required_firmware_files(results) - - # Finally, post process the remaining mappings to insert data we skipped adding earlier. - # We had to wait because `get_firmware_url` requires that the path resolves to an existing - # file on disk, which wasn't guaranteed until we called `_sync_required_firmware_files`. - for host_make_model, firmware_info in results.items(): - for firmware_file in firmware_info.firmware_files: - firmware_file.url = self.get_firmware_url( - hostname = host_make_model.host, - firmware_file = firmware_file.file, - ) - - return results - - def run(self, params, args): - self.notify('Sync Host Firmware') - hosts = self.getHostnames(names = args) - force, = self.fillParams( - names = [('force', False)], - params = params - ) - force = self.str2bool(force) - - host_attrs = self.getHostAttrDict(host = hosts) - # grab all the current firmware versions - CommonKey = namedtuple("CommonKey", ("host", "make", "model")) - current_firmware_versions = { - CommonKey(result["host"], result["make"], result["model"]): result["current_firmware_version"] - for result in self.call(command = 'list.host.firmware', args = hosts) - } - - # get the firmware info for all hosts - results = self._get_host_firmwares( - common_key = CommonKey, - hosts = hosts, - host_attrs = host_attrs, - host_current_firmwares = current_firmware_versions, - force = force, - ) - - # only run plugins if we have hosts to sync. - if results: - self.runPlugins(args = results) diff --git a/common/src/stack/command/stack/commands/sync/host/firmware/imp_dell_x1052.py b/common/src/stack/command/stack/commands/sync/host/firmware/imp_dell_x1052.py deleted file mode 100644 index ccfde322b..000000000 --- a/common/src/stack/command/stack/commands/sync/host/firmware/imp_dell_x1052.py +++ /dev/null @@ -1,120 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from concurrent.futures import ThreadPoolExecutor -import syslog -import stack.commands -from stack.exception import CommandError -from stack.switch.x1052 import SwitchDellX1052 -from stack.switch import SwitchException -from stack.expectmore import ExpectMoreException - -class Implementation(stack.commands.Implementation): - - def update_firmware(self, switch_name, tftp_ip, software_file_path = None, boot_file_path = None, **kwargs): - """Updates the software and/or boot firmware on the provided switch.""" - # No need to continue if we weren't provided any software to upgrade - if software_file_path is None and boot_file_path is None: - return - - try: - x1052_switch = SwitchDellX1052(switch_ip_address = switch_name, switchname = switch_name, **kwargs) - x1052_switch.set_tftp_ip(ip = tftp_ip) - x1052_switch.connect() - # Upload the new software to the switch - if software_file_path is not None: - x1052_switch.upload_software(software_file = software_file_path) - - # Upload the new boot firmware to the switch - if boot_file_path is not None: - x1052_switch.upload_boot(boot_file = boot_file_path) - - # Reboot the switch to apply the updates - x1052_switch.reload() - # Turn some potentially verbose and detailed error messages into something more end user friendly - # while keeping the dirty details available in the logs. - except (SwitchException, ExpectMoreException) as exception: - stack.commands.Log( - message = f"Error during firmware update on {switch_name}: {exception}", - level = syslog.LOG_ERR - ) - raise CommandError( - cmd = self.owner, - msg = f"Failed to update firmware on {switch_name}." - ) - - def run(self, args): - """Runs the firmware update for each provided Dell x1052 switch in parallel.""" - # Since we can update both the boot and the software version based on the hostname, collapse the - # "software" and the "boot" models together into one. - software_key = "software_file_path" - boot_key = "boot_file_path" - x1052_firmware_args_per_host = { - switch_name_make_model.host: {software_key: None, boot_key: None} - for switch_name_make_model in args - } - for switch_name_make_model, firmware_info in args.items(): - # There is no currently known valid multi-file use case for Dell for a given model grouping (x1052-software and/or x1052-boot). - # We don't care if the user tried to force multiple firmware files down our throats. - if len(firmware_info.firmware_files) > 1: - raise CommandError( - self.owner, - msg = ( - "Firmware update for Dell switches cannot operate on multiple firmware files at once." - " Please fix your configuration and try again." - ) - ) - - # Generate a set of switch kwargs - kwargs = { - "username": firmware_info.host_attrs.get("switch_username"), - "password": firmware_info.host_attrs.get("switch_password"), - } - kwargs = {key: value for key, value in kwargs.items() if value is not None} - - # Update the software or boot dictionary key based on the model for this firmware file. - firmware_file = firmware_info.firmware_files.pop() - if "boot" in switch_name_make_model.model: - update_key = boot_key - else: - update_key = software_key - - x1052_firmware_args_per_host[switch_name_make_model.host].update( - {update_key: firmware_file.file, "tftp_ip": firmware_info.frontend_ip, **kwargs} - ) - - errors = [] - # now run each switch upgrade in parallel - with ThreadPoolExecutor(thread_name_prefix = "dell_firmware_update") as executor: - futures = [ - executor.submit(self.update_firmware, switch_name = switch_name, **switch_args) - for switch_name, switch_args in x1052_firmware_args_per_host.items() - ] - # Collect any errors, we don't expect there to be any return values. - for future in futures: - errors.append(future.exception()) - - # drop any Nones returned because of no exceptions and aggregate all remaining errors into one - error_messages = [] - for error in [error for error in errors if error is not None]: - # if this looks like a stacki exception type, grab the message from it. - if hasattr(error, 'message') and callable(getattr(error, 'message')): - error_messages.append(error.message()) - else: - error_messages.append(f'{error}') - - if error_messages: - error_message = '\n'.join(error_messages) - raise CommandError( - cmd = self.owner, - msg = f"Errors occurred during Dell firmware upgrade:\n{error_message}" - ) diff --git a/common/src/stack/command/stack/commands/sync/host/firmware/imp_mellanox.py b/common/src/stack/command/stack/commands/sync/host/firmware/imp_mellanox.py deleted file mode 100644 index 01e603b3b..000000000 --- a/common/src/stack/command/stack/commands/sync/host/firmware/imp_mellanox.py +++ /dev/null @@ -1,147 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -from threading import Timer -from contextlib import suppress -from concurrent.futures import ThreadPoolExecutor -import syslog -import stack.commands -from stack.exception import CommandError -from stack.switch.m7800 import SwitchMellanoxM7800 -from stack.switch import SwitchException -from stack.expectmore import ExpectMoreException - -class Implementation(stack.commands.Implementation): - - def update_firmware(self, switch_name, firmware_url, downgrade, **kwargs): - try: - # connect to the switch and run the firmware upgrade procedure - m7800_switch = SwitchMellanoxM7800(switch_name, **kwargs) - m7800_switch.connect() - # delete all stored images on the switch before sending ours over - for image in m7800_switch.show_images().images_fetched_and_available: - m7800_switch.image_delete(image = image.filename) - - m7800_switch.image_fetch(url = firmware_url) - # install the firmware we just sent to the switch - m7800_switch.install_firmware( - # grab the filename from the switch on purpose in case it does something funky with it - image = m7800_switch.show_images().images_fetched_and_available[0].filename - ) - # set the switch to boot from our installed image - m7800_switch.image_boot_next() - # perform extra downgrade steps if necessary - if downgrade: - # need to force a boot, even if the old code parsing the new configuration fails. - m7800_switch.disable_fallback_reboot() - m7800_switch.write_configuration() - m7800_switch.reload() - # now wait for the switch to come back. - reconnected = False - # timeout after 30 minutes. We use a no-op lambda because we just want to know when the timer expired. - timer = Timer(1800, lambda: ()) - timer.start() - while timer.is_alive(): - # swallow the expected exceptions while trying to connect to a switch that isn't ready yet. - with suppress(SwitchException, ExpectMoreException): - # use the switch as a context manager so every time the connect or factory reset fails, - # we disconnect from the switch. - with m7800_switch: - m7800_switch.connect() - # now factory reset the switch, which will reboot it again. - # The successful connect above doesn't seem to guarantee that we can fire a - # factory reset command, so we try in this loop. - m7800_switch.factory_reset() - timer.cancel() - reconnected = True - - if not reconnected: - raise CommandError( - cmd = self.owner, - msg = f'Unable to reconnect {switch_name} to switch while performing downgrade procedure.' - ) - else: - m7800_switch.reload() - # Turn some potentially verbose and detailed error messages into something more end user friendly - # while keeping the dirty details available in the logs. - except (SwitchException, ExpectMoreException) as exception: - stack.commands.Log( - message = f'Error during firmware update on {switch_name}: {exception}', - level = syslog.LOG_ERR - ) - raise CommandError( - cmd = self.owner, - msg = f'Failed to update firmware on {switch_name}.' - ) - - def run(self, args): - switch_upgrade_args = {} - # for each switch, build a set of args - for switch_name_make_model, firmware_info in args.items(): - # There is no currently known valid multi-file use case for mellanox. - # We don't care if the user tried to force multiple firmware files down our throats. - if len(firmware_info.firmware_files) > 1: - raise CommandError( - self.owner, - msg = ( - "Firmware update for mellanox switches cannot operate on multiple firmware files at once." - " Please fix your configuration and try again." - ) - ) - firmware_file = firmware_info.firmware_files.pop() - - kwargs = { - 'username': firmware_info.host_attrs.get('switch_username', None), - 'password': firmware_info.host_attrs.get('switch_password', None) - } - - kwargs = {key: value for key, value in kwargs.items() if value is not None} - - notice = f'Syncing firmware {firmware_file.version} for {switch_name_make_model.host}.' - # check for downgrade as we have to do extra steps - downgrade = firmware_info.current_version > firmware_file.version - if downgrade: - notice += f' This is a downgrade from {firmware_info.current_version} and will perform a factory reset.' - self.owner.notify(notice) - # build the args - switch_upgrade_args[switch_name_make_model.host] = { - 'firmware_url': firmware_file.url, - 'downgrade': downgrade, - **kwargs, - } - - errors = [] - # now run each switch upgrade in parallel - with ThreadPoolExecutor(thread_name_prefix = 'mellanox_firmware_update') as executor: - futures = [ - executor.submit(self.update_firmware, switch_name = switch_name, **switch_args) - for switch_name, switch_args in switch_upgrade_args.items() - ] - # Collect any errors, we don't expect there to be any return values. - for future in futures: - errors.append(future.exception()) - - # drop any Nones returned because of no exceptions and aggregate all remaining errors into one - error_messages = [] - for error in [error for error in errors if error is not None]: - # if this looks like a stacki exception type, grab the message from it. - if hasattr(error, 'message') and callable(getattr(error, 'message')): - error_messages.append(error.message()) - else: - error_messages.append(f'{error}') - - if error_messages: - error_message = '\n'.join(error_messages) - raise CommandError( - cmd = self.owner, - msg = f"Errors occurred during Mellanox firmware upgrade:\n{error_message}" - ) diff --git a/common/src/stack/command/stack/commands/sync/host/firmware/plugin_basic.py b/common/src/stack/command/stack/commands/sync/host/firmware/plugin_basic.py deleted file mode 100644 index fb6cb273c..000000000 --- a/common/src/stack/command/stack/commands/sync/host/firmware/plugin_basic.py +++ /dev/null @@ -1,58 +0,0 @@ -# @copyright@ -# Copyright (c) 2006 - 2018 Teradata -# All rights reserved. Stacki(r) v5.x stacki.com -# https://github.com/Teradata/stacki/blob/master/LICENSE.txt -# @copyright@ -# -# @rocks@ -# Copyright (c) 2000 - 2010 The Regents of the University of California -# All rights reserved. Rocks(r) v5.4 www.rocksclusters.org -# https://github.com/Teradata/stacki/blob/master/LICENSE-ROCKS.txt -# @rocks@ - -import stack.commands -from stack.exception import CommandError -import stack.firmware - -class Plugin(stack.commands.Plugin): - """Attempts to sync firmware to hosts""" - - def provides(self): - return "basic" - - def run(self, args): - mapped_by_imp_name = {} - # Remap by implementation name in preparation for running implementations in parallel. - for host_make_model, firmware_info in args.items(): - # update if the imp already exists in the map, otherwise add a new key. - if firmware_info.imp in mapped_by_imp_name: - mapped_by_imp_name[firmware_info.imp].update( - {host_make_model: firmware_info} - ) - else: - mapped_by_imp_name[firmware_info.imp] = {host_make_model: firmware_info} - - - # we don't expect return values, but the implementations might raise exceptions, so gather them here - results = self.owner.run_implementations_parallel( - implementation_mapping = mapped_by_imp_name, - display_progress = True, - ) - # drop any results that didn't have any errors and aggregate the rest into one exception - error_messages = [] - for error in ( - value.exception for value in results.values() - if value is not None and value.exception is not None - ): - # if this looks like a stacki exception type, grab the message from it. - if hasattr(error, 'message') and callable(getattr(error, 'message')): - error_messages.append(error.message()) - else: - error_messages.append(f'{error}') - - if error_messages: - error_message = '\n'.join(error_messages) - raise CommandError( - cmd = self.owner, - msg = f"Errors occurred during firmware sync:\n{error_message}" - ) diff --git a/common/src/stack/pylib/stack/firmware.py b/common/src/stack/pylib/stack/firmware.py deleted file mode 100644 index 5579be207..000000000 --- a/common/src/stack/pylib/stack/firmware.py +++ /dev/null @@ -1,137 +0,0 @@ -import hashlib -from pathlib import Path -import uuid -from urllib.parse import urlparse -from enum import Enum, auto, unique -import stack.download - -# Base path to store managed firmware files under -BASE_PATH = Path("/export/stack/firmware/") - -@unique -class SUPPORTED_SCHEMES(Enum): - """Supported schemes to fetch firmware from a source to be managed by stacki""" - file = auto() - http = auto() - https = auto() - - @classmethod - def pretty_string(cls): - """Return a nice human readable list of names.""" - return ", ".join(cls.__members__.keys()) - - def __str__(self): - """Return the human readable name of the enum when printing or stringifying.""" - return f"{self.name}" - - -# Require the supported hash algorithms to be the always present ones -SUPPORTED_HASH_ALGS = hashlib.algorithms_guaranteed - -class FirmwareError(Exception): - """The exception type raised by the firmware utilities in this module.""" - pass - -def ensure_hash_alg_supported(hash_alg): - """Ensures that the provided hash algorithm is supported. - - If it is not supported, a FirmwareError is raised. - """ - if hash_alg not in SUPPORTED_HASH_ALGS: - raise FirmwareError( - f"hash_alg must be one of the following: {SUPPORTED_HASH_ALGS}" - ) - -def calculate_hash(file_path, hash_alg, hash_value = "", digest_length = 256): - """Calculates the hash of the provided file using the provided algorithm and returns it as a hex string. - - hash_alg is required to be one of the SUPPORTED_HASH_ALGS and a FirmwareError will be raised if it is not. - - If a hash value is provided, this checks the calculated hash against the provided hash. The hash_value should - be a string of the form provided by hash.hexdigest(). A FirmwareError is raised if the hashes do not match. - - For some algorithms a length is required (shake_128 and shake_256 at the time of this writing). The digest_length - parameter allows the overriding of the default length used. This parameter is ignored if the algorithm does not - allow specifying the digest length. - """ - ensure_hash_alg_supported(hash_alg = hash_alg) - - hasher = hashlib.new(name = hash_alg, data = Path(file_path).read_bytes()) - # Handle case where the hash algorithm requires a digest length. - try: - calculated_hash = hasher.hexdigest() - except TypeError: - calculated_hash = hasher.hexdigest(digest_length) - - # check the hash if one was provided to check against - if hash_value and hash_value != calculated_hash: - raise FirmwareError( - f"Calculated hash {calculated_hash} does not match expected hash {hash_value}. Algorithm was {hash_alg}." - ) - - return calculated_hash - -def fetch_firmware(source, make, model, filename = None, **kwargs): - """Fetches the firmware file from the provided source and copies it into a stacki managed file. - - source should be the URL from which to pull the firmware image from. If this is not one of the - supported schemes, a FirmwareError is raised. - - make and model must be set to the make and model the firmware image applies to. - - filename is the optional file name to write the image out to locally. This does not change the - destination folder, only the file name. - - The remaining kwargs will be captured and passed through as necessary to the underlying mechanism - used to fetch the file from the source. For example, fetching via HTTP might need a username and - a password for authentication. - - A FirmwareError is raised if fetching the file from the source fails. - """ - # parse the URL to figure out how we're going to fetch it - url = urlparse(url = source) - - # build file path to write out to - dest_dir = BASE_PATH / make / model - dest_dir = dest_dir.resolve() - dest_dir.mkdir(parents = True, exist_ok = True) - # set a random file name if the name is not set - final_file = dest_dir / (uuid.uuid4().hex if filename is None else filename) - - try: - scheme = SUPPORTED_SCHEMES[url.scheme] - except KeyError as exception: - # Assume if there is no scheme but there is a path, - # assume that we were passed a local file path. - if not url.scheme and url.path: - scheme = SUPPORTED_SCHEMES.file - else: - raise FirmwareError( - f"Scheme {url.scheme} is not supported. The source must use one of the following supported" - f" schemes: {SUPPORTED_SCHEMES.pretty_string()}." - ) from exception - - if scheme == SUPPORTED_SCHEMES.file: - # grab the source file and copy it into the destination file - try: - source_file = Path(url.path).resolve(strict = True) - except FileNotFoundError as exception: - raise FirmwareError(f"{exception}") from exception - - final_file.write_bytes(source_file.read_bytes()) - - elif scheme in (SUPPORTED_SCHEMES.http, SUPPORTED_SCHEMES.https): - try: - stack.download.fetch(url = source, file_path = final_file, verbose = True, **kwargs) - except stack.download.FetchError as exception: - raise FirmwareError(f"{exception}") from exception - - # add more supported schemes here - # elif scheme == SUPPORTED_SCHEMES.foo: - else: - # Case where we forgot to add a elif case for a new scheme that was added. - raise RuntimeError( - f"Someone wrote a bug! Code needs to be added to handle the {scheme} scheme." - ) - - return final_file diff --git a/test-framework/test-suites/integration/files/list/mock_list_firmware_run_plugins.py b/test-framework/test-suites/integration/files/list/mock_list_firmware_run_plugins.py deleted file mode 100644 index 52f31e317..000000000 --- a/test-framework/test-suites/integration/files/list/mock_list_firmware_run_plugins.py +++ /dev/null @@ -1,34 +0,0 @@ -from collections import namedtuple -from unittest.mock import patch -import stack.commands.sync.host.firmware -import stack.commands.list.host.firmware.imp_mellanox -import stack.commands.list.host.firmware.imp_dell_x1052 - -# Make the mellanox implementation for list host firmware return a low version -# number that will be a candidate for upgrading. -mock_mellanox_list_firmware = patch.object( - target = stack.commands.list.host.firmware.imp_mellanox.Implementation, - attribute = "list_firmware", - autospec = True, -).start() -mock_mellanox_list_firmware.return_value = "0.0.0" - -# Make the dell implementation for list host firmware return a low version -# number that will be a candidate for upgrading. -mock_dell_list_firmware = patch.object( - target = stack.commands.list.host.firmware.imp_dell_x1052.Implementation, - attribute = "list_firmware", - autospec = True, -).start() -mock_dell_list_firmware.return_value = namedtuple("Versions", ("software", "boot", "hardware"))( - software = "0.0.0.0", - boot = "0.0.0.0", - hardware = "0.0.0.0", -) - -# Mock runPlugins calls -patch.object( - target = stack.commands.sync.host.firmware.Command, - attribute = "runPlugins", - autospec = True, -).start() diff --git a/test-framework/test-suites/integration/files/sync/mock_firmware_run_plugins.py b/test-framework/test-suites/integration/files/sync/mock_firmware_run_plugins.py deleted file mode 100644 index 52f31e317..000000000 --- a/test-framework/test-suites/integration/files/sync/mock_firmware_run_plugins.py +++ /dev/null @@ -1,34 +0,0 @@ -from collections import namedtuple -from unittest.mock import patch -import stack.commands.sync.host.firmware -import stack.commands.list.host.firmware.imp_mellanox -import stack.commands.list.host.firmware.imp_dell_x1052 - -# Make the mellanox implementation for list host firmware return a low version -# number that will be a candidate for upgrading. -mock_mellanox_list_firmware = patch.object( - target = stack.commands.list.host.firmware.imp_mellanox.Implementation, - attribute = "list_firmware", - autospec = True, -).start() -mock_mellanox_list_firmware.return_value = "0.0.0" - -# Make the dell implementation for list host firmware return a low version -# number that will be a candidate for upgrading. -mock_dell_list_firmware = patch.object( - target = stack.commands.list.host.firmware.imp_dell_x1052.Implementation, - attribute = "list_firmware", - autospec = True, -).start() -mock_dell_list_firmware.return_value = namedtuple("Versions", ("software", "boot", "hardware"))( - software = "0.0.0.0", - boot = "0.0.0.0", - hardware = "0.0.0.0", -) - -# Mock runPlugins calls -patch.object( - target = stack.commands.sync.host.firmware.Command, - attribute = "runPlugins", - autospec = True, -).start() diff --git a/test-framework/test-suites/integration/tests/list/test_list_host_firmware.py b/test-framework/test-suites/integration/tests/list/test_list_host_firmware.py deleted file mode 100644 index 4a6610033..000000000 --- a/test-framework/test-suites/integration/tests/list/test_list_host_firmware.py +++ /dev/null @@ -1,60 +0,0 @@ -import json -import pytest - -@pytest.mark.parametrize( - "hosts, expected_results", - ( - ( - "", - [ - {"host": "backend-0-0", "make": "mellanox", "model": "m7800", "desired_firmware_version": "1.2.3", "current_firmware_version": "0.0.0"}, - {"host": "backend-0-1", "make": "dell", "model": "x1052-software", "desired_firmware_version": "1.2.3.4", "current_firmware_version": "0.0.0.0"}, - {"host": "frontend-0-0", "make": None, "model": None, "desired_firmware_version": None, "current_firmware_version": None}, - ], - ), - ( - "backend-0-0", - [{"host": "backend-0-0", "make": "mellanox", "model": "m7800", "desired_firmware_version": "1.2.3", "current_firmware_version": "0.0.0"}], - ), - ( - "backend-0-1", - [{"host": "backend-0-1", "make": "dell", "model": "x1052-software", "desired_firmware_version": "1.2.3.4", "current_firmware_version": "0.0.0.0"}], - ), - ), -) -def test_list_host_firmware( - host, - add_host_with_net, - fake_local_firmware_file, - revert_firmware, - inject_code, - test_file, - hosts, - expected_results, -): - """Test that list host firmware filters correctly based on provided arguments.""" - # Add a backend-0-1 - add_host_with_net( - hostname = "backend-0-1", - rack = 0, - rank = 1, - appliance = "backend", - interface = "eth0", - ip = "192.168.1.1", - network = "fake_net", - address = "192.168.1.0", - pxe = True, - ) - # Add a piece of mellanox firmware to backend-0-0. - result = host.run(f"stack add firmware 1.2.3 make=mellanox model=m7800 source={fake_local_firmware_file} hosts=backend-0-0") - assert result.rc == 0 - # Add a piece of dell firmware to backend-0-1 - result = host.run(f"stack add firmware 1.2.3.4 make=dell model=x1052-software source={fake_local_firmware_file} hosts=backend-0-1") - assert result.rc == 0 - - # Now list the firmware. We need to mock out the running of plugins so it doesn't - # actually try to talk to hardware that doesn't exist. - with inject_code(test_file("list/mock_list_firmware_run_plugins.py")): - result = host.run(f"stack list host firmware {hosts} output-format=json") - assert result.rc == 0 - assert json.loads(result.stdout) == expected_results diff --git a/test-framework/test-suites/integration/tests/list/test_list_host_firmware_mapping.py b/test-framework/test-suites/integration/tests/list/test_list_host_firmware_mapping.py deleted file mode 100644 index 0a5f301c1..000000000 --- a/test-framework/test-suites/integration/tests/list/test_list_host_firmware_mapping.py +++ /dev/null @@ -1,107 +0,0 @@ -import json -import pytest - -@pytest.mark.parametrize( - "hosts, expected_results", - ( - ( - "", - [ - {"host": "backend-0-0", "version": "1.2.3", "make": "mellanox", "model": "m7800"}, - {"host": "backend-0-1", "version": "1.2.3.4", "make": "dell", "model": "x1052-software"}, - ], - ), - ("backend-0-0", [{"host": "backend-0-0", "version": "1.2.3", "make": "mellanox", "model": "m7800"}]), - ("backend-0-1", [{"host": "backend-0-1", "version": "1.2.3.4", "make": "dell", "model": "x1052-software"}]), - ), -) -def test_list_host_firmware_mapping_host_filter( - host, - add_host_with_net, - fake_local_firmware_file, - revert_firmware, - hosts, - expected_results, -): - """Test that list host firmware mapping filters correctly based on provided arguments.""" - # Add a backend-0-1 - add_host_with_net( - hostname = "backend-0-1", - rack = 0, - rank = 1, - appliance = "backend", - interface = "eth0", - ip = "192.168.1.1", - network = "fake_net", - address = "192.168.1.0", - pxe = True, - ) - # Add a piece of mellanox firmware to backend-0-0. - result = host.run(f"stack add firmware 1.2.3 make=mellanox model=m7800 source={fake_local_firmware_file} hosts=backend-0-0") - assert result.rc == 0 - # Add a piece of dell firmware to backend-0-1 - result = host.run(f"stack add firmware 1.2.3.4 make=dell model=x1052-software source={fake_local_firmware_file} hosts=backend-0-1") - assert result.rc == 0 - - # List the firmware mappings - result = host.run(f"stack list host firmware mapping {hosts} output-format=json") - assert result.rc == 0 - assert expected_results == json.loads(result.stdout) - -@pytest.mark.parametrize( - "make, model, versions, expected_results", - ( - ( - "", - "", - "", - [ - {"host": "backend-0-0", "version": "1.2.3", "make": "mellanox", "model": "m7800"}, - {"host": "backend-0-1", "version": "1.2.3.4", "make": "dell", "model": "x1052-software"}, - ], - ), - ("mellanox", "", "", [{"host": "backend-0-0", "version": "1.2.3", "make": "mellanox", "model": "m7800"}]), - ("mellanox", "m7800", "", [{"host": "backend-0-0", "version": "1.2.3", "make": "mellanox", "model": "m7800"}]), - ("mellanox", "m7800", "1.2.3", [{"host": "backend-0-0", "version": "1.2.3", "make": "mellanox", "model": "m7800"}]), - ("dell", "", "", [{"host": "backend-0-1", "version": "1.2.3.4", "make": "dell", "model": "x1052-software"}]), - ("dell", "x1052-software", "", [{"host": "backend-0-1", "version": "1.2.3.4", "make": "dell", "model": "x1052-software"}]), - ("dell", "x1052-software", "1.2.3.4", [{"host": "backend-0-1", "version": "1.2.3.4", "make": "dell", "model": "x1052-software"}]), - ), -) -def test_list_host_firmware_mapping_non_host_filter( - host, - add_host_with_net, - fake_local_firmware_file, - revert_firmware, - make, - model, - versions, - expected_results, -): - """Test that list host firmware mapping filters correctly based on provided arguments.""" - # Add a backend-0-1 - add_host_with_net( - hostname = "backend-0-1", - rack = 0, - rank = 1, - appliance = "backend", - interface = "eth0", - ip = "192.168.1.1", - network = "fake_net", - address = "192.168.1.0", - pxe = True, - ) - # Add a piece of mellanox firmware to backend-0-0. - result = host.run(f"stack add firmware 1.2.3 make=mellanox model=m7800 source={fake_local_firmware_file} hosts=backend-0-0") - assert result.rc == 0 - # Add a piece of dell firmware to backend-0-1 - result = host.run(f"stack add firmware 1.2.3.4 make=dell model=x1052-software source={fake_local_firmware_file} hosts=backend-0-1") - assert result.rc == 0 - - # List the firmware mappings - result = host.run( - f"stack list host firmware mapping {f'make={make}' if make else ''} {f'model={model}' if model else ''} " - f"{f'versions={versions}' if versions else ''} output-format=json" - ) - assert result.rc == 0 - assert expected_results == json.loads(result.stdout) diff --git a/test-framework/test-suites/integration/tests/sync/test_sync_firmware.py b/test-framework/test-suites/integration/tests/sync/test_sync_firmware.py deleted file mode 100644 index a58118ebd..000000000 --- a/test-framework/test-suites/integration/tests/sync/test_sync_firmware.py +++ /dev/null @@ -1,60 +0,0 @@ -import stack.firmware - -def test_sync_firmware_resync(host, fake_local_firmware_file, revert_firmware): - """Add some firmware, nuke the firmware dir, and then sync firmware and make sure it shows back up.""" - # Add a fake piece of firmware. - result = host.run(f"stack add firmware 1.2.3 make=mellanox model=m7800 source={fake_local_firmware_file}") - assert result.rc == 0 - - # Now find the file on disk and nuke it. The files are given random UUID4 names, - # so we have to glob for the file. - firmware_file_on_disk = [ - firmware_file for firmware_file in stack.firmware.BASE_PATH.glob(f"**/*") - if firmware_file.is_file() - ] - assert len(firmware_file_on_disk) == 1 - firmware_file_on_disk = firmware_file_on_disk[0] - firmware_file_on_disk.unlink() - assert not firmware_file_on_disk.exists() - - # Now run the sync command. - result = host.run("stack sync firmware") - assert result.rc == 0 - - # Make sure the file exists again. - firmware_file_on_disk = [ - firmware_file for firmware_file in stack.firmware.BASE_PATH.glob(f"**/*") - if firmware_file.is_file() - ] - assert len(firmware_file_on_disk) == 1 - assert firmware_file_on_disk[0].exists() - -def test_sync_firmware_selective_resync(host, fake_local_firmware_file, revert_firmware): - """Add two firmware files, nuke em both, selectively sync one and ensure that's the only one that shows back up.""" - result = host.run(f"stack add firmware 1.2.3 make=mellanox model=m7800 source={fake_local_firmware_file}") - assert result.rc == 0 - result = host.run(f"stack add firmware 2.3.4 make=mellanox model=m7800 source={fake_local_firmware_file}") - assert result.rc == 0 - - # Now find the files on disk and nuke em. The files are given random UUID4 names, - # so we have to glob for the file. - firmware_files_on_disk = [ - firmware_file for firmware_file in stack.firmware.BASE_PATH.glob(f"**/*") - if firmware_file.is_file() - ] - assert len(firmware_files_on_disk) == 2 - for firmware_file in firmware_files_on_disk: - firmware_file.unlink() - assert not firmware_file.exists() - - # Now run the sync command. - result = host.run(f"stack sync firmware 2.3.4 make=mellanox model=m7800") - assert result.rc == 0 - - # Make sure only one file exists - firmware_files_on_disk = [ - firmware_file for firmware_file in stack.firmware.BASE_PATH.glob(f"**/*") - if firmware_file.is_file() - ] - assert len(firmware_files_on_disk) == 1 - assert firmware_files_on_disk[0].exists() diff --git a/test-framework/test-suites/integration/tests/sync/test_sync_host_firmware.py b/test-framework/test-suites/integration/tests/sync/test_sync_host_firmware.py deleted file mode 100644 index 84b80b5ae..000000000 --- a/test-framework/test-suites/integration/tests/sync/test_sync_host_firmware.py +++ /dev/null @@ -1,50 +0,0 @@ -import stack.firmware - -def test_sync_only_needed_files(host, add_host_with_net, fake_local_firmware_file, inject_code, test_file, revert_firmware): - """Test that sync host firmware only tries to sync firmware it needs, not all in the database. - - This adds multiple firmware versions for the same make + model combo on purpose to exercise clustered syncing of - firmware files. This also requires passing the force flag to `sync host firmware`. - """ - # Add multiple piecies of firmware for mellanox, and associate it with the host we are going to sync. - result = host.run(f"stack add firmware 1.2.3 make=mellanox model=m7800 source={fake_local_firmware_file} hosts=backend-0-0") - assert result.rc == 0 - result = host.run(f"stack add firmware 2.3.4 make=mellanox model=m7800 source={fake_local_firmware_file} hosts=backend-0-0") - assert result.rc == 0 - # Add multiple firmwares for dell, and associate it with the host we are going to sync. - result = host.run(f"stack add firmware 1.2.3.4 make=dell model=x1052-software source={fake_local_firmware_file} hosts=backend-0-0") - assert result.rc == 0 - result = host.run(f"stack add firmware 2.3.4.5 make=dell model=x1052-software source={fake_local_firmware_file} hosts=backend-0-0") - assert result.rc == 0 - result = host.run(f"stack add firmware 1.2.3.4 make=dell model=x1052-boot source={fake_local_firmware_file} hosts=backend-0-0") - assert result.rc == 0 - result = host.run(f"stack add firmware 2.3.4.5 make=dell model=x1052-boot source={fake_local_firmware_file} hosts=backend-0-0") - assert result.rc == 0 - # Add a piece of firmware we don't care about. Don't associate it with any hosts. - result = host.run(f"stack add firmware 1.2.3 make=mellanox model=m6036 source={fake_local_firmware_file}") - assert result.rc == 0 - # Now find the files on disk and nuke em. The files are given random UUID4 names, - # so we have to glob for the file. - firmware_files_on_disk = [ - firmware_file for firmware_file in stack.firmware.BASE_PATH.glob(f"**/*") - if firmware_file.is_file() - ] - assert len(firmware_files_on_disk) == 7 - for firmware_file in firmware_files_on_disk: - firmware_file.unlink() - assert not firmware_file.exists() - - # Now sync the firmware to the host. We need to mock out the running of plugins so it doesn't - # actually try to sync to hardware that doesn't exist. - with inject_code(test_file("sync/mock_firmware_run_plugins.py")): - result = host.run("stack sync host firmware force=true") - assert result.rc == 0 - - # Now it should have added back only the firmware for the switch-0-0 host that was to be synced. - firmware_files_on_disk = [ - firmware_file for firmware_file in stack.firmware.BASE_PATH.glob(f"**/*") - if firmware_file.is_file() - ] - assert len(firmware_files_on_disk) == 6 - for firmware_file in firmware_files_on_disk: - assert firmware_file.exists() diff --git a/test-framework/test-suites/unit/tests/command/stack/argument_processors/test_firmware.py b/test-framework/test-suites/unit/tests/command/stack/argument_processors/test_firmware.py deleted file mode 100644 index fe9adf07b..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/argument_processors/test_firmware.py +++ /dev/null @@ -1,691 +0,0 @@ -from unittest.mock import MagicMock, ANY, patch, call -import pytest -from stack.argument_processors.firmware import FirmwareArgProcessor -import stack.commands -from stack.exception import CommandError - -class TestFirmwareArgProcessor: - """A test case for the firmware argument processor.""" - - @pytest.fixture - def argument_processor(self): - test_argument_processor = FirmwareArgProcessor() - test_argument_processor.db = MagicMock(spec_set = ["select", "count"]) - return test_argument_processor - - def test_get_make_id(self, argument_processor): - """Test that get make ID works as expected in the normal case.""" - argument_processor.db.select.return_value = [[1]] - mock_make = "foo" - - # Expect our result to be returned. - assert argument_processor.db.select.return_value[0][0] == argument_processor.get_make_id( - make = mock_make, - ) - # Ensure that select was called appropriately. - argument_processor.db.select.assert_called_once_with(ANY, mock_make) - - def test_get_make_id_error(self, argument_processor): - """Test that get make ID fails if no make exists in the DB with that name.""" - argument_processor.db.select.return_value = [] - - with pytest.raises(CommandError): - argument_processor.get_make_id(make = "foo") - - @pytest.mark.parametrize("return_value", (0, 1)) - def test_make_exists(self, argument_processor, return_value): - """Test that make exists returns the correct results.""" - argument_processor.db.count.return_value = return_value - - assert return_value == argument_processor.make_exists(make = "foo") - argument_processor.db.count.assert_called_once_with(ANY, "foo") - - @patch.object(target = FirmwareArgProcessor, attribute = "make_exists", autospec = True) - def test_ensure_make_exists(self, mock_make_exists, argument_processor): - """Test that ensure make exists works when the input is valid and the make exists in the database.""" - mock_make_exists.return_value = True - mock_make = "foo" - - argument_processor.ensure_make_exists(make = mock_make) - - mock_make_exists.assert_called_once_with(argument_processor, make = mock_make) - - @pytest.mark.parametrize("test_input, return_value", (("", True), ("foo", False))) - @patch.object(target = FirmwareArgProcessor, attribute = "make_exists", autospec = True) - def test_ensure_make_exists_errors(self, mock_make_exists, test_input, return_value, argument_processor): - """Test that ensure make exists fails when the input is bad or the make doesn't exist in the database.""" - mock_make_exists.return_value = return_value - - with pytest.raises(CommandError): - argument_processor.ensure_make_exists(make = test_input) - - @patch.object(target = FirmwareArgProcessor, attribute = "make_exists", autospec = True) - def test_ensure_unique_makes(self, mock_make_exists, argument_processor): - """Test that ensure_unique_makes works as expected in the case where the makes don't already exist.""" - mock_make_exists.return_value = False - mock_makes = ("foo", "bar", "baz") - - argument_processor.ensure_unique_makes(makes = mock_makes) - - assert [call(argument_processor, mock_make) for mock_make in mock_makes] == mock_make_exists.mock_calls - - @pytest.mark.parametrize("test_input, return_value", (([], False), (["", "", ""], False), (["foo", "bar", "baz"], True))) - @patch.object(target = FirmwareArgProcessor, attribute = "make_exists", autospec = True) - def test_ensure_unique_makes_error(self, mock_make_exists, test_input, return_value, argument_processor): - """Test that ensure_unique_makes raises an error if the input is invalid or the makes already exist.""" - mock_make_exists.return_value = return_value - - with pytest.raises(CommandError): - argument_processor.ensure_unique_makes(makes = test_input) - - @patch.object(target = FirmwareArgProcessor, attribute = "make_exists", autospec = True) - def test_ensure_makes_exist(self, mock_make_exists, argument_processor): - """Test that ensure_makes_exist works as expected in the case where the makes already exist.""" - mock_make_exists.return_value = True - mock_makes = ("foo", "bar", "baz") - - argument_processor.ensure_makes_exist(makes = mock_makes) - - assert [call(argument_processor, mock_make) for mock_make in mock_makes] == mock_make_exists.mock_calls - - @pytest.mark.parametrize("test_input, return_value", (([], True), (["", "", ""], True), (["foo", "bar", "baz"], False))) - @patch.object(target = FirmwareArgProcessor, attribute = "make_exists", autospec = True) - def test_ensure_makes_exist_error(self, mock_make_exists, test_input, return_value, argument_processor): - """Test that ensure_makes_exist raises an error if the input is invalid or one or more makes don't already exist.""" - mock_make_exists.return_value = return_value - - with pytest.raises(CommandError): - argument_processor.ensure_makes_exist(makes = test_input) - - def test_get_model_id(self, argument_processor): - """Test that get model ID works as expected when the make + model exist in the database.""" - argument_processor.db.select.return_value = [[1]] - mock_make = "foo" - mock_model = "bar" - - assert argument_processor.db.select.return_value[0][0] == argument_processor.get_model_id( - make = mock_make, - model = mock_model, - ) - argument_processor.db.select.assert_called_once_with(ANY, (mock_make, mock_model)) - - def test_get_model_id_error(self, argument_processor): - """Test that get model ID fails as expected when the make + model do not exist in the database.""" - argument_processor.db.select.return_value = [] - mock_make = "foo" - mock_model = "bar" - - with pytest.raises(CommandError): - argument_processor.get_model_id(make = mock_make, model = mock_model) - - @pytest.mark.parametrize("return_value", (0, 1)) - def test_model_exists(self, return_value, argument_processor): - """Test that model exists returns the correct results.""" - mock_make = "foo" - mock_model = "bar" - argument_processor.db.count.return_value = return_value - - assert return_value == argument_processor.model_exists(make = mock_make, model = mock_model) - argument_processor.db.count.assert_called_once_with(ANY, (mock_make, mock_model)) - - @patch.object(target = FirmwareArgProcessor, attribute = "model_exists", autospec = True) - @patch.object(target = FirmwareArgProcessor, attribute = "ensure_make_exists", autospec = True) - def test_ensure_model_exists(self, mock_ensure_make_exists, mock_model_exists, argument_processor): - """Test that ensure_model_exists works when the make + model both exist.""" - mock_make = "bar" - mock_model = "foo" - - argument_processor.ensure_model_exists(model = mock_model, make = mock_make) - - mock_ensure_make_exists.assert_called_once_with(argument_processor, make = mock_make) - mock_model_exists.assert_called_once_with(argument_processor, make = mock_make, model = mock_model) - - @pytest.mark.parametrize( - "mock_model, side_effect, return_value", - ( - ("", None, True), - ("foo", CommandError(cmd = None, msg = "Test error"), True), - ("foo", None, False), - ) - ) - @patch.object(target = FirmwareArgProcessor, attribute = "model_exists", autospec = True) - @patch.object(target = FirmwareArgProcessor, attribute = "ensure_make_exists", autospec = True) - def test_ensure_model_exists_errors( - self, - mock_ensure_make_exists, - mock_model_exists, - mock_model, - side_effect, - return_value, - argument_processor, - ): - """Test that ensure_model_exists fails if the input is invalid or when the make + model don't both exist.""" - mock_make = "bar" - mock_ensure_make_exists.side_effect = side_effect - mock_model_exists.return_value = return_value - - with pytest.raises(CommandError): - argument_processor.ensure_model_exists(model = mock_model, make = mock_make) - - @patch.object(target = FirmwareArgProcessor, attribute = "model_exists", autospec = True) - def test_ensure_unique_models(self, mock_model_exists, argument_processor): - """Test that ensure_unique_models works as expected when the make + model combinations do not already exist.""" - mock_model_exists.return_value = False - mock_make = "foo" - mock_models = ("bar", "baz", "bag") - - argument_processor.ensure_unique_models(make = mock_make, models = mock_models) - - assert [ - call(argument_processor, mock_make, mock_model) - for mock_model in mock_models - ] == mock_model_exists.mock_calls - - @pytest.mark.parametrize("test_input, return_value", (([], False), (["", "", ""], False), (["foo", "bar", "baz"], True))) - @patch.object(target = FirmwareArgProcessor, attribute = "model_exists", autospec = True) - def test_ensure_unique_models_error(self, mock_model_exists, test_input, return_value, argument_processor): - """Test that ensure_unique_models fails as expected when the inputs are invalid or the make + model combinations already exist.""" - mock_model_exists.return_value = return_value - mock_make = "foo" - - with pytest.raises(CommandError): - argument_processor.ensure_unique_models(make = mock_make, models = test_input) - - @patch.object(target = FirmwareArgProcessor, attribute = "model_exists", autospec = True) - @patch.object(target = FirmwareArgProcessor, attribute = "ensure_make_exists", autospec = True) - def test_ensure_models_exist(self, mock_ensure_make_exists, mock_model_exists, argument_processor): - """Test that ensure_models_exist works as expected when the make + model combinations already exist.""" - mock_model_exists.return_value = True - mock_make = "foo" - mock_models = ("bar", "baz", "bag") - - argument_processor.ensure_models_exist(make = mock_make, models = mock_models) - - mock_ensure_make_exists.assert_called_once_with(argument_processor, make = mock_make) - assert [ - call(argument_processor, mock_make, mock_model) - for mock_model in mock_models - ] == mock_model_exists.mock_calls - - @pytest.mark.parametrize( - "mock_models, side_effect, return_value", - ( - ([], None, True), - (["", "", ""], None, True), - (["bar", "baz", "bag"], CommandError(cmd = None, msg = "Test error"), True), - (["bar", "baz", "bag"], None, False), - ) - ) - @patch.object(target = FirmwareArgProcessor, attribute = "model_exists", autospec = True) - @patch.object(target = FirmwareArgProcessor, attribute = "ensure_make_exists", autospec = True) - def test_ensure_models_exist_error( - self, - mock_ensure_make_exists, - mock_model_exists, - mock_models, - side_effect, - return_value, - argument_processor, - ): - """Test that ensure_models_exist fails as expected when the make + model combinations do not already exist.""" - mock_ensure_make_exists.side_effect = side_effect - mock_model_exists.return_value = return_value - mock_make = "foo" - - with pytest.raises(CommandError): - argument_processor.ensure_models_exist(make = mock_make, models = mock_models) - - @pytest.mark.parametrize("return_value", (0, 1)) - def test_firmware_exists(self, return_value, argument_processor): - """Test that firmware exists returns the correct results.""" - mock_make = "foo" - mock_model = "bar" - mock_version = "baz" - argument_processor.db.count.return_value = return_value - - assert return_value == argument_processor.firmware_exists( - make = mock_make, - model = mock_model, - version = mock_version, - ) - argument_processor.db.count.assert_called_once_with(ANY, (mock_make, mock_model, mock_version)) - - @patch.object(target = FirmwareArgProcessor, attribute = "firmware_exists", autospec = True) - @patch.object(target = FirmwareArgProcessor, attribute = "ensure_model_exists", autospec = True) - def test_ensure_firmware_exists(self, mock_ensure_model_exists, mock_firmware_exists, argument_processor): - """Test that ensure_firmware_exists works when the firmware + make + model all exist in the database.""" - mock_firmware_exists.return_value = True - mock_make = "foo" - mock_model = "bar" - mock_version = "baz" - - argument_processor.ensure_firmware_exists(make = mock_make, model = mock_model, version = mock_version) - - mock_ensure_model_exists.assert_called_once_with( - argument_processor, - make = mock_make, - model = mock_model, - ) - mock_firmware_exists.assert_called_once_with( - argument_processor, - make = mock_make, - model = mock_model, - version = mock_version, - ) - - @pytest.mark.parametrize( - "mock_version, side_effect, return_value", - ( - ("", None, True), - ("foo", CommandError(cmd = None, msg = "Test message"), True), - ("foo", None, False), - ) - ) - @patch.object(target = FirmwareArgProcessor, attribute = "firmware_exists", autospec = True) - @patch.object(target = FirmwareArgProcessor, attribute = "ensure_model_exists", autospec = True) - def test_ensure_firmware_exists_errors( - self, - mock_ensure_model_exists, - mock_firmware_exists, - mock_version, - side_effect, - return_value, - argument_processor, - ): - """Test that ensure_firmware_exists fails when given invalid input or the firmware + make + model do not all exist in the database.""" - mock_firmware_exists.return_value = True - mock_make = "foo" - mock_model = "bar" - mock_ensure_model_exists.side_effect = side_effect - mock_firmware_exists.return_value = return_value - - with pytest.raises(CommandError): - argument_processor.ensure_firmware_exists(make = mock_make, model = mock_model, version = mock_version) - - @patch.object(target = FirmwareArgProcessor, attribute = "firmware_exists", autospec = True) - @patch.object(target = FirmwareArgProcessor, attribute = "ensure_model_exists", autospec = True) - def test_ensure_firmwares_exist(self, mock_ensure_model_exists, mock_firmware_exists, argument_processor): - """Test that ensure_firmwares_exist works as expected when the firmware files exist for the given make + model.""" - mock_firmware_exists.return_value = True - mock_make = "foo" - mock_model = "bar" - mock_versions = ("baz", "bag", "boo") - - argument_processor.ensure_firmwares_exist( - make = mock_make, - model = mock_model, - versions = mock_versions, - ) - - mock_ensure_model_exists.assert_called_once_with(argument_processor, make = mock_make, model = mock_model) - assert [ - call(argument_processor, mock_make, mock_model, mock_version) - for mock_version in mock_versions - ] == mock_firmware_exists.mock_calls - - @pytest.mark.parametrize( - "mock_versions, side_effect, return_value", - ( - ([], None, True), - (["", "", ""], None, True), - (["bar", "baz", "bag"], CommandError(cmd = None, msg = "Test error"), True), - (["bar", "baz", "bag"], None, False), - ) - ) - @patch.object(target = FirmwareArgProcessor, attribute = "firmware_exists", autospec = True) - @patch.object(target = FirmwareArgProcessor, attribute = "ensure_model_exists", autospec = True) - def test_ensure_firmwares_exist_errors( - self, - mock_ensure_model_exists, - mock_firmware_exists, - mock_versions, - side_effect, - return_value, - argument_processor, - ): - """Test that ensure_firmwares_exist fails as expected when the firmware files don't exist for the given make + model.""" - mock_ensure_model_exists.side_effect = side_effect - mock_firmware_exists.return_value = return_value - mock_make = "foo" - mock_model = "bar" - - with pytest.raises(CommandError): - argument_processor.ensure_firmwares_exist( - make = mock_make, - model = mock_model, - versions = mock_versions, - ) - - def test_get_firmware_id(self, argument_processor): - """Test that get_firmware_id works as expected when the firmware exists.""" - argument_processor.db.select.return_value = [[1]] - mock_make = "foo" - mock_model = "bar" - mock_version = "baz" - - assert argument_processor.db.select.return_value[0][0] == argument_processor.get_firmware_id( - make = mock_make, - model = mock_model, - version = mock_version, - ) - argument_processor.db.select.assert_called_once_with(ANY, (mock_make, mock_model, mock_version)) - - def test_get_firmware_id_error(self, argument_processor): - """Test that get_firmware_id works as expected.""" - argument_processor.db.select.return_value = [] - mock_make = "foo" - mock_model = "bar" - mock_version = "baz" - - with pytest.raises(CommandError): - argument_processor.get_firmware_id( - make = mock_make, - model = mock_model, - version = mock_version, - ) - - # Use create = True here because the self.call method comes from the Command class, - # which the class under test is expected to be mixed in with. - @patch.object(target = FirmwareArgProcessor, attribute = "call", create = True) - def test_get_common_frontend_ip(self, mock_call, argument_processor): - """Test that get_common_frontend_ip gets the expected common frontend IP when the frontend and the target host share a network.""" - mock_hostname = "sd-stacki-mock-backend" - # Set up the call("list.host.interface") returns, ensuring each separate return value contains one shared network. - mock_call_return_values = ( - # Mock front end interfaces - [{"network": "foo", "ip": "1.2.3.4"}, {"network": "bar", "ip": "2.3.4.5"}], - # Mock other host interfaces - [{"network": "baz", "ip": "3.4.5.6"}, {"network": "foo", "ip": "1.2.3.10"}], - ) - mock_call.side_effect = mock_call_return_values - - result = argument_processor.get_common_frontend_ip(hostname = mock_hostname) - - # Make sure the right IP was returned - assert mock_call_return_values[0][0]["ip"] == result - # Make sure the list host interface calls happened - assert mock_call.mock_calls == [ - call(command = "list.host.interface", args = ["a:frontend"]), - call(command = "list.host.interface", args = [mock_hostname]), - ] - - # Use create = True here because the self.call method comes from the Command class, - # which the class under test is expected to be mixed in with. - @patch.object(target = FirmwareArgProcessor, attribute = "call", create = True) - def test_get_common_frontend_ip_no_common_network(self, mock_call, argument_processor): - """Test that get_common_frontend_ip fails when there is no common network.""" - mock_hostname = "sd-stacki-mock-backend" - # Set up the call("list.host.interface") returns, ensuring each separate return value contains no shared networks. - mock_call_return_values = ( - # Mock front end interfaces - [{"network": "foo", "ip": "1.2.3.4"}, {"network": "bar", "ip": "2.3.4.5"}], - # Mock other host interfaces - [{"network": "baz", "ip": "3.4.5.6"}, {"network": "bag", "ip": "1.2.3.10"}], - ) - mock_call.side_effect = mock_call_return_values - - with pytest.raises(CommandError): - argument_processor.get_common_frontend_ip(hostname = mock_hostname) - - # Use create = True here because the self.call method comes from the Command class, - # which the class under test is expected to be mixed in with. - @patch.object(target = FirmwareArgProcessor, attribute = "call", create = True) - def test_get_common_frontend_ip_no_interface_ip(self, mock_call, argument_processor): - """Test that get_common_frontend_ip fails when there is no IP on the interface on the common network.""" - mock_hostname = "sd-stacki-mock-backend" - # Set up the call("list.host.interface") returns, ensuring each separate return value contains one shared network. - mock_call_return_values = ( - # Mock front end interfaces - [{"network": "foo", "ip": ""}, {"network": "bar", "ip": "2.3.4.5"}], - # Mock other host interfaces - [{"network": "baz", "ip": "3.4.5.6"}, {"network": "foo", "ip": "1.2.3.10"}], - ) - mock_call.side_effect = mock_call_return_values - - with pytest.raises(CommandError): - argument_processor.get_common_frontend_ip(hostname = mock_hostname) - - @patch.object(target = FirmwareArgProcessor, attribute = "get_common_frontend_ip", autospec = True) - @patch(target = "stack.argument_processors.firmware.Path", autospec = True) - def test_get_firmware_url(self, mock_path, mock_get_common_frontend_ip, argument_processor): - """Test that get_firmware_url returns the correct URL.""" - mock_get_common_frontend_ip.return_value = "1.2.3.4" - # need to mock out Path.parts. - mock_path_parts = ( - "/", - "export", - "stack", - "firmware", - "make", - "model", - "file", - ) - mock_path.return_value.resolve.return_value.parts = mock_path_parts - expected_path_parts = mock_path_parts[3:] - expected_path = "/".join(expected_path_parts) - mock_path.return_value.joinpath.return_value.__str__.return_value = expected_path - expected_url = f"http://{mock_get_common_frontend_ip.return_value}/install/{expected_path}" - mock_firmware_file = "foo" - mock_hostname = "bar" - - result = argument_processor.get_firmware_url( - hostname = mock_hostname, - firmware_file = mock_firmware_file, - ) - - # Make sure the returned URL is correct. - assert expected_url == result - # Check that the path was resolved. - assert all( - call in mock_path.mock_calls - for call in call(mock_firmware_file).resolve(strict = True).call_list() - ) - # Make sure the IP was retrieved - mock_get_common_frontend_ip.assert_called_once_with(argument_processor, hostname = mock_hostname) - # Make sure the path was rebuilt excluding the right elements - mock_path.return_value.joinpath.assert_called_once_with(*expected_path_parts) - - @patch.object(target = FirmwareArgProcessor, attribute = "get_common_frontend_ip", autospec = True) - @patch(target = "stack.argument_processors.firmware.Path", autospec = True) - def test_get_firmware_url_file_does_not_exist(self, mock_path, mock_get_common_frontend_ip, argument_processor): - """Test that get_firmware_url raises a CommandError if the firmware file does not exist.""" - mock_path.return_value.resolve.side_effect = FileNotFoundError("Test error") - - with pytest.raises(CommandError): - argument_processor.get_firmware_url( - hostname = "foo", - firmware_file = "bar", - ) - - @patch.object(target = FirmwareArgProcessor, attribute = "get_common_frontend_ip", autospec = True) - @patch(target = "stack.argument_processors.firmware.Path", autospec = True) - def test_get_firmware_url_no_common_ip(self, mock_path, mock_get_common_frontend_ip, argument_processor): - """Test that get_firmware_url passes through the CommandError if get_common_frontend_ip fails.""" - mock_get_common_frontend_ip.side_effect = CommandError(cmd = "", msg = "Test error") - - with pytest.raises(CommandError): - argument_processor.get_firmware_url( - hostname = "foo", - firmware_file = "bar", - ) - - @pytest.mark.parametrize("return_value", (0, 1)) - def test_imp_exists(self, return_value, argument_processor): - """Test that imp_exists works as expected.""" - mock_imp = "foo" - argument_processor.db.count.return_value = return_value - - assert return_value == argument_processor.imp_exists(imp = mock_imp) - argument_processor.db.count.assert_called_once_with(ANY, mock_imp) - - @patch.object(target = FirmwareArgProcessor, attribute = "imp_exists", autospec = True) - def test_ensure_imp_exists(self, mock_imp_exists, argument_processor): - """Test that ensure_imp_exists works when the input is valid and the imp exists in the database.""" - mock_imp = "foo" - mock_imp_exists.return_value = True - - argument_processor.ensure_imp_exists(imp = mock_imp) - - mock_imp_exists.assert_called_once_with(argument_processor, imp = mock_imp) - - @pytest.mark.parametrize("mock_imp, return_value", (("", True), ("foo", False))) - @patch.object(target = FirmwareArgProcessor, attribute = "imp_exists", autospec = True) - def test_ensure_imp_exists_errors(self, mock_imp_exists, mock_imp, return_value, argument_processor): - """Test that ensure_imp_exists fails when the input is invalid or the imp does not exist in the database.""" - mock_imp_exists.return_value = return_value - - with pytest.raises(CommandError): - argument_processor.ensure_imp_exists(imp = mock_imp) - - @patch.object(target = FirmwareArgProcessor, attribute = "imp_exists", autospec = True) - def test_ensure_imps_exist(self, mock_imp_exists, argument_processor): - """Test that ensure_imps_exist works as expected when all implementations exist in the database.""" - mock_imp_exists.return_value = True - mock_imps = ("foo", "bar", "baz") - - argument_processor.ensure_imps_exist(imps = mock_imps) - - assert [call(argument_processor, mock_imp) for mock_imp in mock_imps] == mock_imp_exists.mock_calls - - @pytest.mark.parametrize( - "mock_imps, return_value", - ( - ([], True), - (["", "", ""], True), - (["bar", "baz", "bag"], False), - ) - ) - @patch.object(target = FirmwareArgProcessor, attribute = "imp_exists", autospec = True) - def test_ensure_imps_exist_error( - self, - mock_imp_exists, - mock_imps, - return_value, - argument_processor, - ): - """Test that ensure_imps_exist fails when at least one implementation doesn't exist in the database.""" - mock_imp_exists.return_value = return_value - - with pytest.raises(CommandError): - argument_processor.ensure_imps_exist(imps = mock_imps) - - def test_get_imp_id(self, argument_processor): - """Test that get_imp_id works as expected when the implementation exists in the database.""" - argument_processor.db.select.return_value = [[1]] - mock_imp = "foo" - - assert argument_processor.db.select.return_value[0][0] == argument_processor.get_imp_id( - imp = mock_imp, - ) - argument_processor.db.select.assert_called_once_with(ANY, mock_imp) - - def test_get_imp_id_error(self, argument_processor): - """Test that get_imp_id fails as expected when the implementation does not exist in the database.""" - argument_processor.db.select.return_value = [] - mock_imp = "foo" - - with pytest.raises(CommandError): - argument_processor.get_imp_id(imp = mock_imp) - - @pytest.mark.parametrize("return_value", (0, 1)) - def test_version_regex_exists(self, return_value, argument_processor): - """Test that version_regex_exists works as expected.""" - argument_processor.db.count.return_value = return_value - mock_name = "foo" - - assert return_value == argument_processor.version_regex_exists(name = mock_name) - argument_processor.db.count.assert_called_once_with(ANY, mock_name) - - @patch.object(target = FirmwareArgProcessor, attribute = "version_regex_exists", autospec = True) - def test_ensure_version_regex_exists(self, mock_version_regex_exists, argument_processor): - """Test that ensure_version_regex_exists works in the case where the input is valid and the version regex exists in the database.""" - mock_version_regex_exists.return_value = True - mock_name = "foo" - - argument_processor.ensure_version_regex_exists(name = mock_name) - - mock_version_regex_exists.assert_called_once_with(argument_processor, name = mock_name) - - @pytest.mark.parametrize("mock_name, return_value", (("", True), ("foo", False))) - @patch.object(target = FirmwareArgProcessor, attribute = "version_regex_exists", autospec = True) - def test_ensure_version_regex_exists_errors(self, mock_version_regex_exists, mock_name, return_value, argument_processor): - """Test that ensure_version_regex_exists works in the case where the input is valid and the version regex exists in the database.""" - mock_version_regex_exists.return_value = return_value - - with pytest.raises(CommandError): - argument_processor.ensure_version_regex_exists(name = mock_name) - - @patch.object(target = FirmwareArgProcessor, attribute = "version_regex_exists", autospec = True) - def test_ensure_version_regexes_exist(self, mock_version_regex_exists, argument_processor): - """Test that ensure_version_regexes_exist works in the case where all the version regexes exist in the database.""" - mock_version_regex_exists.return_value = True - mock_names = ("foo", "bar", "baz") - - argument_processor.ensure_version_regexes_exist(names = mock_names) - - assert [call(argument_processor, mock_name) for mock_name in mock_names] == mock_version_regex_exists.mock_calls - - @pytest.mark.parametrize( - "mock_names, return_value", - ( - ([], True), - (["", "", ""], True), - (["bar", "baz", "bag"], False), - ) - ) - @patch.object(target = FirmwareArgProcessor, attribute = "version_regex_exists", autospec = True) - def test_ensure_version_regexes_exist_error(self, mock_version_regex_exists, mock_names, return_value, argument_processor): - """Test that ensure_version_regexes_exist fails in the case where at least one of the version regexes does not exist in the database.""" - mock_version_regex_exists.return_value = return_value - - with pytest.raises(CommandError): - argument_processor.ensure_version_regexes_exist(names = mock_names) - - def test_get_version_regex_id(self, argument_processor): - """Test that get_version_regex_id works as expected when the version_regex exists in the database.""" - argument_processor.db.select.return_value = [[1]] - mock_name = "foo" - - assert argument_processor.db.select.return_value[0][0] == argument_processor.get_version_regex_id( - name = mock_name, - ) - argument_processor.db.select.assert_called_once_with(ANY, mock_name) - - def test_get_version_regex_id_error(self, argument_processor): - """Test that get_version_regex_id fails as expected when the version_regex does not exist in the database.""" - argument_processor.db.select.return_value = [] - mock_name = "foo" - - with pytest.raises(CommandError): - argument_processor.get_version_regex_id(name = mock_name) - - @pytest.mark.parametrize( - "test_input, expected", - ( - ( - [[None, None, None, "mock_model_regex", "mock_model_regex_name", "mock_model_regex_description"]], - ("mock_model_regex", "mock_model_regex_name", "mock_model_regex_description"), - ), - ( - [["mock_make_regex", "mock_make_regex_name", "mock_make_regex_description", None, None, None,]], - ("mock_make_regex", "mock_make_regex_name", "mock_make_regex_description"), - ), - ( - [["mock_make_regex", "mock_make_regex_name", "mock_make_regex_description", "mock_model_regex", "mock_model_regex_name", "mock_model_regex_description"]], - ("mock_model_regex", "mock_model_regex_name", "mock_model_regex_description"), - ), - ([], None), - ) - ) - def test_try_get_version_regex(self, test_input, expected, argument_processor): - """Test that try_get_version_regex works as expected, preferring a model regex over a make one.""" - argument_processor.db.select.return_value = test_input - mock_make = "foo" - mock_model = "bar" - - # Make sure the expected result is returned. - assert expected == argument_processor.try_get_version_regex( - make = mock_make, - model = mock_model, - ) - argument_processor.db.select.assert_called_once_with(ANY, (mock_make, mock_model)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/imp/test_command_stack_commands_add_firmware_imp__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/imp/test_command_stack_commands_add_firmware_imp__init__.py deleted file mode 100644 index 3f86337a8..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/imp/test_command_stack_commands_add_firmware_imp__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.add.firmware.imp import Command - -class TestAddFirmwareImpCommand: - """A test case to hold the tests for the add firmware imp stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args and params.""" - mock_params = {"foo": "bar", "baz": "bag"} - mock_args = ["foo", "bar", "baz"] - - command.run(params = mock_params, args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/imp/test_command_stack_commands_add_firmware_imp_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/imp/test_command_stack_commands_add_firmware_imp_plugin_basic.py deleted file mode 100644 index 0bc49e3f9..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/imp/test_command_stack_commands_add_firmware_imp_plugin_basic.py +++ /dev/null @@ -1,230 +0,0 @@ -from unittest.mock import create_autospec, patch, ANY -import pytest -import stack.commands.list.host.firmware -import stack.commands.sync.host.firmware -from stack.commands import DatabaseConnection -from stack.commands.add.firmware import Command -from stack.commands.add.firmware.imp.plugin_basic import Plugin -from stack.exception import ArgError, ParamError, CommandError - -class TestAddImpBasicPlugin: - """A test case for the add firmware imp basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Make sure the plugin returns the correct provides information.""" - assert basic_plugin.provides() == "basic" - - def test_validate_args(self, basic_plugin): - """Test that validate_args works if the args list has one element.""" - basic_plugin.validate_args(args = ["foo"]) - - @pytest.mark.parametrize("test_input", ([], ["foo", "bar"])) - def test_validate_args_failure(self, test_input, basic_plugin): - """Test that validate_args fails with bad input.""" - with pytest.raises(ArgError): - basic_plugin.validate_args(args = test_input) - - @patch(target = "stack.commands.add.firmware.imp.plugin_basic.Path", autospec = True) - @patch(target = "inspect.getsourcefile", autospec = True) - def test_validate_imp(self, mock_getsourcefile, mock_path, basic_plugin): - """Test that validate_imp works when the imp doesn't already exist in the database and the files are found on disk.""" - basic_plugin.owner.imp_exists.return_value = False - mock_path.return_value.parent.resolve.return_value.__truediv__.return_value.exists.return_value = True - mock_imp = "foo" - - basic_plugin.validate_imp(imp = mock_imp) - - # Make sure the database was checked for duplicates. - basic_plugin.owner.imp_exists.assert_called_once_with(imp = mock_imp) - # Make sure the files were checked for existence on disk. - mock_getsourcefile.assert_any_call(stack.commands.list.host.firmware) - mock_getsourcefile.assert_any_call(stack.commands.sync.host.firmware) - mock_path.assert_any_call(mock_getsourcefile.return_value) - mock_path.return_value.parent.resolve.return_value.__truediv__.return_value.exists.assert_called_with() - - @patch(target = "stack.commands.add.firmware.imp.plugin_basic.Path", autospec = True) - @patch(target = "inspect.getsourcefile", autospec = True) - def test_validate_imp_already_exists(self, mock_getsourcefile, mock_path, basic_plugin): - """Test that validate_imp fails when the implementation already exists in the database.""" - basic_plugin.owner.imp_exists.return_value = True - mock_path.return_value.parent.resolve.return_value.__truediv__.return_value.exists.return_value = True - mock_imp = "foo" - - with pytest.raises(ArgError): - basic_plugin.validate_imp(imp = mock_imp) - - @pytest.mark.parametrize("return_values", ((True, False), (False, True), (False, False))) - @patch(target = "stack.commands.add.firmware.imp.plugin_basic.Path", autospec = True) - @patch(target = "inspect.getsourcefile", autospec = True) - def test_validate_imp_missing_files(self, mock_getsourcefile, mock_path, return_values, basic_plugin): - """Test that validate_imp fails when the implementation files are missing from the filesystem.""" - basic_plugin.owner.imp_exists.return_value = False - mock_path.return_value.parent.resolve.return_value.__truediv__.return_value.exists.side_effect = return_values - mock_imp = "foo" - - with pytest.raises(ArgError): - basic_plugin.validate_imp(imp = mock_imp) - - @patch(target = "stack.commands.add.firmware.imp.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.add.firmware.imp.plugin_basic.ExitStack", autospec = True) - @patch.object(target = Plugin, attribute = "validate_imp", autospec = True) - @patch.object(target = Plugin, attribute = "validate_args", autospec = True) - def test_run( - self, - mock_validate_args, - mock_validate_imp, - mock_exit_stack, - mock_lowered, - basic_plugin, - ): - """Test that run modifies the database as expected when all the params and args are valid.""" - expected_imp = "foo" - mock_args = [expected_imp] - expected_models = ["baz", "fizz", "buzz"] - mock_params = {"make": "bar", "models": ", ".join(expected_models)} - mock_lowered.return_value = (param for param in mock_params.values()) - - basic_plugin.run(args = (mock_params, mock_args)) - - # Make sure the arguments and parameters were validated. - mock_validate_args.assert_called_once_with(basic_plugin, args = mock_args) - mock_validate_imp.assert_called_once_with(basic_plugin, imp = expected_imp) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [("make", ""), ("models", "")], - params = mock_params, - ) - basic_plugin.owner.ensure_models_exist.assert_called_once_with( - make = mock_params["make"], - models = expected_models, - ) - # Expect the database to be updated - basic_plugin.owner.db.execute.assert_called_once_with(ANY, (expected_imp,)) - basic_plugin.owner.call.assert_called_once_with( - command = "set.firmware.model.imp", - args = [*expected_models, f"make={mock_params['make']}", f"imp={expected_imp}"], - ) - # Make sure cleanup was setup and then dismissed. - mock_exit_stack.return_value.__enter__.return_value.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware.imp", - args = [expected_imp], - ) - mock_exit_stack.return_value.__enter__.return_value.pop_all.assert_called_once_with() - - @patch(target = "stack.commands.add.firmware.imp.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.add.firmware.imp.plugin_basic.ExitStack", autospec = True) - @patch.object(target = Plugin, attribute = "validate_imp", autospec = True) - @patch.object(target = Plugin, attribute = "validate_args", autospec = True) - def test_run_no_make_or_models( - self, - mock_validate_args, - mock_validate_imp, - mock_exit_stack, - mock_lowered, - basic_plugin, - ): - """Test that run modifies the database as expected when no make or models are provided.""" - expected_imp = "foo" - mock_args = [expected_imp] - mock_params = {} - mock_lowered.return_value = ("", "") - - basic_plugin.run(args = (mock_params, mock_args)) - - # Make sure the arguments and parameters were validated. - mock_validate_args.assert_called_once_with(basic_plugin, args = mock_args) - mock_validate_imp.assert_called_once_with(basic_plugin, imp = expected_imp) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [("make", ""), ("models", "")], - params = mock_params, - ) - basic_plugin.owner.ensure_models_exist.assert_not_called() - # Expect the database to be updated - basic_plugin.owner.db.execute.assert_called_once_with(ANY, (expected_imp,)) - basic_plugin.owner.call.assert_not_called() - # Make sure cleanup was setup and then dismissed. - mock_exit_stack.return_value.__enter__.return_value.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware.imp", - args = [expected_imp], - ) - mock_exit_stack.return_value.__enter__.return_value.pop_all.assert_called_once_with() - - @pytest.mark.parametrize("failure_mock", ("ensure_models_exist", "validate_imp", "validate_args")) - @patch(target = "stack.commands.add.firmware.imp.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.add.firmware.imp.plugin_basic.ExitStack", autospec = True) - @patch.object(target = Plugin, attribute = "validate_imp", autospec = True) - @patch.object(target = Plugin, attribute = "validate_args", autospec = True) - def test_run_errors( - self, - mock_validate_args, - mock_validate_imp, - mock_exit_stack, - mock_lowered, - failure_mock, - basic_plugin, - ): - """Test that run fails if any of the params or args are invalid and does not touch the database.""" - mock_args = ["foo"] - mock_params = {"make": "bar", "models": "baz, fizz, buzz"} - mock_lowered.return_value = (param for param in mock_params.values()) - mock_validation_functions = { - "ensure_models_exist": basic_plugin.owner.ensure_models_exist, - "validate_imp": mock_validate_imp, - "validate_args": mock_validate_args, - } - # Set the error on the correct mock validation function. - mock_validation_functions[failure_mock].side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = (mock_params, mock_args)) - - # Expect the database to not be updated - basic_plugin.owner.db.execute.assert_not_called() - - @patch(target = "stack.commands.add.firmware.imp.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.add.firmware.imp.plugin_basic.ExitStack", autospec = True) - @patch.object(target = Plugin, attribute = "validate_imp", autospec = True) - @patch.object(target = Plugin, attribute = "validate_args", autospec = True) - def test_run_cleanup( - self, - mock_validate_args, - mock_validate_imp, - mock_exit_stack, - mock_lowered, - basic_plugin, - ): - """Test that run cleans up if setting the implementation relation fails.""" - mock_args = ["foo"] - mock_params = {"make": "bar", "models": "baz, fizz, buzz"} - mock_lowered.return_value = (param for param in mock_params.values()) - # Set the call to fail. - basic_plugin.owner.call.side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = (mock_params, mock_args)) - - # Expect the cleanup to be set up, but not dismissed due to the error. - mock_exit_stack.return_value.__enter__.return_value.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware.imp", - args = [mock_args[0].lower()], - ) - mock_exit_stack.return_value.__enter__.return_value.pop_all.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/make/test_command_stack_commands_add_firmware_make__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/make/test_command_stack_commands_add_firmware_make__init__.py deleted file mode 100644 index 69bf524b9..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/make/test_command_stack_commands_add_firmware_make__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.add.firmware.make import Command - -class TestAddFirmwareMakeCommand: - """A test case to hold the tests for the add firmware make stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - - command.run(args = mock_args, params = "unused") - - mock_runPlugins.assert_called_once_with(command, args = mock_args) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/make/test_command_stack_commands_add_firmware_make_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/make/test_command_stack_commands_add_firmware_make_plugin_basic.py deleted file mode 100644 index 1d5f37cc8..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/make/test_command_stack_commands_add_firmware_make_plugin_basic.py +++ /dev/null @@ -1,81 +0,0 @@ -from unittest.mock import create_autospec, patch, ANY -import pytest -from stack.commands import DatabaseConnection -from stack.commands.add.firmware import Command -from stack.commands.add.firmware.make.plugin_basic import Plugin -from stack.exception import CommandError - -class TestAddMakeBasicPlugin: - """A test case for the add firmware make basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Make sure the plugin returns the correct provides information.""" - assert basic_plugin.provides() == "basic" - - @patch(target = "stack.commands.add.firmware.make.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.add.firmware.make.plugin_basic.unique_everseen", autospec = True) - def test_run( - self, - mock_unique_everseen, - mock_lowered, - basic_plugin, - ): - """Test that run modifies the database as expected when all the args are valid.""" - expected_makes = ("foo", "bar", "baz") - mock_args = [*expected_makes] - mock_unique_everseen.return_value = (arg for arg in mock_args) - - basic_plugin.run(args = mock_args) - - # Make sure the arguments were validated. - mock_lowered.assert_called_once_with(mock_args) - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.ensure_unique_makes.assert_called_once_with( - makes = expected_makes, - ) - # Expect the database to be updated - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - [(expected_make,) for expected_make in expected_makes], - many = True, - ) - - @patch(target = "stack.commands.add.firmware.make.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.add.firmware.make.plugin_basic.unique_everseen", autospec = True) - def test_run_error( - self, - mock_unique_everseen, - mock_lowered, - basic_plugin, - ): - """Test that run fails when argument validation fails and does not touch the database.""" - expected_makes = ("foo", "bar", "baz") - mock_args = [*expected_makes] - mock_unique_everseen.return_value = (arg for arg in mock_args) - basic_plugin.owner.ensure_unique_makes.side_effect = CommandError( - cmd = basic_plugin.owner, - msg = "Test error", - ) - - with pytest.raises(CommandError): - basic_plugin.run(args = mock_args) - - # Expect the database to be untouched - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/model/test_command_stack_commands_add_firmware_model__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/model/test_command_stack_commands_add_firmware_model__init__.py deleted file mode 100644 index 64b535cd9..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/model/test_command_stack_commands_add_firmware_model__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.add.firmware.model import Command - -class TestAddFirmwareModelCommand: - """A test case to hold the tests for the add firmware model stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"fizz": "buzz"} - - command.run(args = mock_args, params = mock_params) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/model/test_command_stack_commands_add_firmware_model_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/model/test_command_stack_commands_add_firmware_model_plugin_basic.py deleted file mode 100644 index fb26ef919..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/model/test_command_stack_commands_add_firmware_model_plugin_basic.py +++ /dev/null @@ -1,202 +0,0 @@ -from unittest.mock import create_autospec, patch, ANY, call -import pytest -from contextlib import ExitStack -from stack.commands import DatabaseConnection -from stack.commands.add.firmware import Command -from stack.commands.add.firmware.model.plugin_basic import Plugin -from stack.exception import CommandError - -class TestAddFirmwareModelBasicPlugin: - """A test case for the add firmware model basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure the plugin returns the correct provides information.""" - assert basic_plugin.provides() == "basic" - - def test_create_missing_make(self, basic_plugin): - """Test that create missing make works as expected when the make doesn't exist.""" - basic_plugin.owner.make_exists.return_value = False - mock_exit_stack = create_autospec( - spec = ExitStack, - spec_set = True, - ) - mock_make = "foo" - - with mock_exit_stack() as mock_cleanup: - basic_plugin.create_missing_make(make = mock_make, cleanup = mock_cleanup) - - basic_plugin.owner.make_exists.assert_called_once_with(make = mock_make) - basic_plugin.owner.call.assert_called_once_with(command = "add.firmware.make", args = [mock_make]) - mock_exit_stack.return_value.__enter__.return_value.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware.make", - args = [mock_make], - ) - - def test_create_missing_make_make_exists(self, basic_plugin): - """Test that create missing make works as expected when the make exists.""" - basic_plugin.owner.make_exists.return_value = True - mock_exit_stack = create_autospec( - spec = ExitStack, - spec_set = True, - ) - mock_make = "foo" - - with mock_exit_stack() as mock_cleanup: - basic_plugin.create_missing_make(make = mock_make, cleanup = mock_cleanup) - - basic_plugin.owner.make_exists.assert_called_once_with(make = mock_make) - basic_plugin.owner.call.assert_not_called() - mock_exit_stack.return_value.__enter__.return_value.assert_not_called() - - def test_create_missing_imp(self, basic_plugin): - """Test that create missing imp works as expected when the imp doesn't exist.""" - basic_plugin.owner.imp_exists.return_value = False - mock_exit_stack = create_autospec( - spec = ExitStack, - spec_set = True, - ) - mock_imp = "foo" - - with mock_exit_stack() as mock_cleanup: - basic_plugin.create_missing_imp(imp = mock_imp, cleanup = mock_cleanup) - - basic_plugin.owner.imp_exists.assert_called_once_with(imp = mock_imp) - basic_plugin.owner.call.assert_called_once_with(command = "add.firmware.imp", args = [mock_imp]) - mock_exit_stack.return_value.__enter__.return_value.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware.imp", - args = [mock_imp], - ) - - def test_create_missing_imp_make_exists(self, basic_plugin): - """Test that create missing imp works as expected when the imp exists.""" - basic_plugin.owner.imp_exists.return_value = True - mock_exit_stack = create_autospec( - spec = ExitStack, - spec_set = True, - ) - mock_imp = "foo" - - with mock_exit_stack() as mock_cleanup: - basic_plugin.create_missing_imp(imp = mock_imp, cleanup = mock_cleanup) - - basic_plugin.owner.imp_exists.assert_called_once_with(imp = mock_imp) - basic_plugin.owner.call.assert_not_called() - mock_exit_stack.return_value.__enter__.return_value.assert_not_called() - - @patch.object(target = Plugin, attribute = "create_missing_imp", autospec = True) - @patch.object(target = Plugin, attribute = "create_missing_make", autospec = True) - @patch(target = "stack.commands.add.firmware.model.plugin_basic.ExitStack", autospec = True) - @patch(target = "stack.commands.add.firmware.model.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.add.firmware.model.plugin_basic.unique_everseen", autospec = True) - def test_run( - self, - mock_unique_everseen, - mock_lowered, - mock_exit_stack, - mock_create_missing_make, - mock_create_missing_imp, - basic_plugin, - ): - """Test that run modifies the database as expected when all the args are valid.""" - expected_models = ("foo", "bar", "baz") - mock_args = [*expected_models] - mock_params = {"make": "fizz", "imp": "buzz"} - mock_unique_everseen.return_value = (arg for arg in mock_args) - mock_lowered.return_value = mock_params.values() - - basic_plugin.run(args = (mock_params, mock_args)) - - # model sure the arguments were validated. - assert [call(basic_plugin.owner.fillParams.return_value), call(mock_args)] == mock_lowered.mock_calls - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.ensure_unique_models.assert_called_once_with( - models = expected_models, - make = mock_params["make"], - ) - # Expect the make and imp to be created if needed - mock_create_missing_make.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - cleanup = mock_exit_stack.return_value.__enter__.return_value, - ) - mock_create_missing_imp.assert_called_once_with( - basic_plugin, - mock_params["imp"], - cleanup = mock_exit_stack.return_value.__enter__.return_value, - ) - # Expect the IDs of the imp and make to be fetched. - basic_plugin.owner.get_make_id.assert_called_once_with(make = mock_params["make"]) - basic_plugin.owner.get_imp_id.assert_called_once_with(imp = mock_params["imp"]) - # Expect the database to be updated - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - [ - ( - model, - basic_plugin.owner.get_make_id.return_value, - basic_plugin.owner.get_imp_id.return_value, - ) - for model in expected_models - ], - many = True, - ) - # Expect cleanup to be dismissed. - mock_exit_stack.return_value.__enter__.return_value.pop_all.assert_called_once_with() - - @pytest.mark.parametrize( - "mock_make, mock_imp, side_effect", - ( - ("", "bar", None), - ("foo", "", None), - ("foo", "bar", CommandError(cmd = None, msg = "Test message")), - ) - ) - @patch.object(target = Plugin, attribute = "create_missing_imp", autospec = True) - @patch.object(target = Plugin, attribute = "create_missing_make", autospec = True) - @patch(target = "stack.commands.add.firmware.model.plugin_basic.ExitStack", autospec = True) - @patch(target = "stack.commands.add.firmware.model.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.add.firmware.model.plugin_basic.unique_everseen", autospec = True) - def test_run_errors( - self, - mock_unique_everseen, - mock_lowered, - mock_exit_stack, - mock_create_missing_make, - mock_create_missing_imp, - mock_make, - mock_imp, - side_effect, - basic_plugin, - ): - """Test that run fails when the arguments or parameters are invalid and does not touch the database.""" - expected_models = ("foo", "bar", "baz") - mock_args = [*expected_models] - mock_params = {"make": mock_make, "imp": mock_imp} - mock_unique_everseen.return_value = (arg for arg in mock_args) - mock_lowered.return_value = mock_params.values() - basic_plugin.owner.ensure_unique_models.side_effect = side_effect - - with pytest.raises(CommandError): - basic_plugin.run(args = (mock_params, mock_args)) - - # Expect the database to not be touched - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/test_command_stack_commands_add_firmware__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/test_command_stack_commands_add_firmware__init__.py deleted file mode 100644 index efbf7b1c5..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/test_command_stack_commands_add_firmware__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.add.firmware import Command - -class TestAddFirmwareCommand: - """A test case to hold the tests for the add firmware stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"fizz": "buzz"} - - command.run(args = mock_args, params = mock_params) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/test_command_stack_commands_add_firmware_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/test_command_stack_commands_add_firmware_plugin_basic.py deleted file mode 100644 index 03c606d42..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/test_command_stack_commands_add_firmware_plugin_basic.py +++ /dev/null @@ -1,852 +0,0 @@ -from unittest.mock import create_autospec, patch, ANY, call -from collections import namedtuple -from contextlib import ExitStack -from pathlib import Path -import re -import pytest -from stack.commands import DatabaseConnection -from stack.commands.add.firmware import Command -from stack.commands.add.firmware.plugin_basic import Plugin -from stack.exception import CommandError, ArgError, ParamRequired, ParamError -import stack.firmware - -class TestAddFirmwareBasicPlugin: - """A test case for the add firmware basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure the plugin returns the correct provides information.""" - assert basic_plugin.provides() == "basic" - - def test_validate_args(self, basic_plugin): - """Test that validate args works when only one argument is passed.""" - basic_plugin.validate_args(args = ["foo"]) - - @pytest.mark.parametrize("args", (["foo", "bar"], [])) - def test_validate_args_errors(self, args, basic_plugin): - """Test that validate args fails when invalid arguments are passed.""" - with pytest.raises(ArgError): - basic_plugin.validate_args(args = args) - - def test_validate_required_params(self, basic_plugin): - """Test that validate_required_params works if all required params are provided.""" - basic_plugin.validate_required_params(make = "foo", model = "bar", source = "baz") - - @pytest.mark.parametrize( - "make, model, source", - ( - ("foo", "bar", ""), - ("foo", "", "baz"), - ("", "bar", "baz"), - ) - ) - def test_validate_required_params_errors(self, make, model, source, basic_plugin): - """Test that validate_required_params fails when required params are missing.""" - with pytest.raises(ParamRequired): - basic_plugin.validate_required_params(make = make, model = model, source = source) - - @pytest.mark.parametrize( - "imp, model_exists_return, call_return", - ( - ("", True, [{"implementation": ""}]), - ("foo", False, [{"implementation": ""}]), - ("foo", True, [{"implementation": "foo"}]), - ) - ) - def test_validate_imp(self, imp, model_exists_return, call_return, basic_plugin): - """Test that validate_imp works with the expected valid combinations of parameters.""" - mock_make = "fizz" - mock_model = "buzz" - basic_plugin.owner.model_exists.return_value = model_exists_return - basic_plugin.owner.call.return_value = call_return - - basic_plugin.validate_imp(make = mock_make, model = mock_model, imp = imp) - - # Ensure the expected calls were made when checking if the parameters were valid. - basic_plugin.owner.model_exists.assert_any_call(make = mock_make, model = mock_model) - if call_return[0]["implementation"]: - basic_plugin.owner.call.assert_called_once_with( - command = "list.firmware.model", - args = [mock_model, f"make={mock_make}"] - ) - - @pytest.mark.parametrize( - "imp, model_exists_return, call_return", - ( - ("", False, [{"make": "blah", "model": "blah"}]), - ("foo", True, [{"implementation": "bar"}]), - ) - ) - def test_validate_imp_errors(self, imp, model_exists_return, call_return, basic_plugin): - """Test that validate_imp fails with the invalid combinations of parameters.""" - mock_make = "fizz" - mock_model = "buzz" - basic_plugin.owner.model_exists.return_value = model_exists_return - basic_plugin.owner.call.return_value = call_return - - with pytest.raises(ParamError): - basic_plugin.validate_imp(make = mock_make, model = mock_model, imp = imp) - - @patch(target = "stack.firmware.ensure_hash_alg_supported", autospec = True) - def test_validate_hash_alg_supported(self, mock_ensure_hash_alg_supported, basic_plugin): - """Test that validate_hash_alg_supported works when the algorithm is supported.""" - mock_alg = "md5" - - basic_plugin.validate_hash_alg_supported(hash_alg = mock_alg) - - # Make sure it was validated - mock_ensure_hash_alg_supported.assert_called_once_with(hash_alg = mock_alg) - - @patch(target = "stack.firmware.ensure_hash_alg_supported", autospec = True) - def test_validate_hash_alg_supported_error(self, mock_ensure_hash_alg_supported, basic_plugin): - """Test that validate_hash_alg_supported fails and re-raises the right exception when ensure_hash_alg_supported fails.""" - mock_alg = "md5" - mock_ensure_hash_alg_supported.side_effect = stack.firmware.FirmwareError("Test error") - - with pytest.raises(ParamError): - basic_plugin.validate_hash_alg_supported(hash_alg = mock_alg) - - MockRegexReturn = namedtuple("MockRegexReturn", ("name", "regex", "description")) - - @pytest.mark.parametrize( - "regex", - (tuple(), MockRegexReturn(name = "test", regex = "foo", description = "test")) - ) - @patch(target = "re.search", autospec = True) - def test_validate_version(self, mock_search, regex, basic_plugin): - """Test that validate version regex works as expected when the version is valid.""" - mock_version = "fizz" - mock_make = "buzz" - mock_model = "bam!" - basic_plugin.owner.try_get_version_regex.return_value = regex - basic_plugin.owner.firmware_exists.return_value = False - mock_search.return_value = True - - basic_plugin.validate_version(version = mock_version, make = mock_make, model = mock_model) - - # Make sure the version was validated - basic_plugin.owner.firmware_exists.assert_called_once_with( - version = mock_version, - make = mock_make, - model = mock_model, - ) - basic_plugin.owner.try_get_version_regex.assert_called_once_with( - make = mock_make, - model = mock_model, - ) - if regex: - mock_search.assert_called_once_with( - pattern = regex.regex, - string = mock_version, - flags = re.IGNORECASE, - ) - - @pytest.mark.parametrize( - "regex, search_return, firmware_return", - ( - (MockRegexReturn(name = "test", regex = "foo", description = "test"), False, False), - (MockRegexReturn(name = "test", regex = "foo", description = "test"), False, True), - (tuple(), False, True), - ) - ) - @patch(target = "re.search", autospec = True) - def test_validate_version_errors(self, mock_search, regex, search_return, firmware_return, basic_plugin): - """Test that validate version regex works as expected when the version is valid.""" - mock_version = "fizz" - mock_make = "buzz" - mock_model = "bam!" - basic_plugin.owner.try_get_version_regex.return_value = regex - basic_plugin.owner.firmware_exists.return_value = firmware_return - mock_search.return_value = search_return - - with pytest.raises(ArgError): - basic_plugin.validate_version(version = mock_version, make = mock_make, model = mock_model) - - @patch.object(target = Plugin, attribute = "validate_required_params", autospec = True) - @patch.object(target = Plugin, attribute = "validate_hash_alg_supported", autospec = True) - @patch.object(target = Plugin, attribute = "validate_imp", autospec = True) - @patch.object(target = Plugin, attribute = "validate_version", autospec = True) - def test_validate_inputs( - self, - mock_validate_version, - mock_validate_imp, - mock_validate_hash_alg_supported, - mock_validate_required_params, - basic_plugin, - ): - """Test that validate_inputs invokes the correct validation functions.""" - mock_source = "foo" - mock_make = "bar" - mock_model = "baz" - mock_version = "bag" - mock_imp = "fizz" - mock_hash_alg = "md5" - - basic_plugin.validate_inputs( - source = mock_source, - make = mock_make, - model = mock_model, - version = mock_version, - imp = mock_imp, - hash_alg = mock_hash_alg, - ) - - mock_validate_version.assert_called_once_with( - basic_plugin, - make = mock_make, - model = mock_model, - version = mock_version, - ) - mock_validate_imp.assert_called_once_with( - basic_plugin, - imp = mock_imp, - make = mock_make, - model = mock_model, - ) - mock_validate_hash_alg_supported.assert_called_once_with( - basic_plugin, - hash_alg = mock_hash_alg, - ) - mock_validate_required_params.assert_called_once_with( - basic_plugin, - source = mock_source, - make = mock_make, - model = mock_model, - ) - - @pytest.mark.parametrize( - "failure_mock", - ("validate_required_params", "validate_hash_alg_supported", "validate_imp", "validate_version"), - ) - @patch.object(target = Plugin, attribute = "validate_required_params", autospec = True) - @patch.object(target = Plugin, attribute = "validate_hash_alg_supported", autospec = True) - @patch.object(target = Plugin, attribute = "validate_imp", autospec = True) - @patch.object(target = Plugin, attribute = "validate_version", autospec = True) - def test_validate_inputs_errors( - self, - mock_validate_version, - mock_validate_imp, - mock_validate_hash_alg_supported, - mock_validate_required_params, - failure_mock, - basic_plugin, - ): - """Test that validate_inputs fails if any of the validation functions fail.""" - mock_source = "foo" - mock_make = "bar" - mock_model = "baz" - mock_version = "bag" - mock_imp = "fizz" - mock_hash_alg = "md5" - mock_validation_functions = { - "validate_required_params": mock_validate_required_params, - "validate_hash_alg_supported": mock_validate_hash_alg_supported, - "validate_imp": mock_validate_imp, - "validate_version": mock_validate_version, - } - mock_validation_functions[failure_mock].side_effect = CommandError( - cmd = basic_plugin.owner, - msg = "test error", - ) - - with pytest.raises(CommandError): - basic_plugin.validate_inputs( - source = mock_source, - make = mock_make, - model = mock_model, - version = mock_version, - imp = mock_imp, - hash_alg = mock_hash_alg, - ) - - def test_create_missing_imp(self, basic_plugin): - """Test that create missing imp calls the expected functions when the imp doesn't exist.""" - mock_imp = "foo" - basic_plugin.owner.imp_exists.return_value = False - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - - with mock_exit_stack() as mock_cleanup: - basic_plugin.create_missing_imp(imp = mock_imp, cleanup = mock_cleanup) - - # Make sure appropriate calls were made - basic_plugin.owner.call.assert_called_once_with( - command = "add.firmware.imp", - args = [mock_imp], - ) - mock_cleanup.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware.imp", - args = [mock_imp], - ) - - @pytest.mark.parametrize("imp, return_value", (("foo", True), ("", "unused"))) - def test_create_missing_imp_skipped(self, imp, return_value, basic_plugin): - """Test that create missing imp skips implementation creation based on provided arguments.""" - mock_imp = imp - basic_plugin.owner.imp_exists.return_value = return_value - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - - with mock_exit_stack() as mock_cleanup: - basic_plugin.create_missing_imp(imp = mock_imp, cleanup = mock_cleanup) - - # Make sure no implementations were attempted to be created - basic_plugin.owner.call.assert_not_called() - mock_cleanup.callback.assert_not_called() - - def test_create_missing_make(self, basic_plugin): - """Test that create missing make calls the expected functions when the make doesn't exist.""" - mock_make = "foo" - basic_plugin.owner.make_exists.return_value = False - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - - with mock_exit_stack() as mock_cleanup: - basic_plugin.create_missing_make(make = mock_make, cleanup = mock_cleanup) - - # Make sure appropriate calls were made - basic_plugin.owner.call.assert_called_once_with( - command = "add.firmware.make", - args = [mock_make], - ) - mock_cleanup.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware.make", - args = [mock_make], - ) - - def test_create_missing_make_skipped(self, basic_plugin): - """Test that create missing make skips implementation creation based on provided arguments.""" - mock_make = "foo" - basic_plugin.owner.imp_exists.return_value = True - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - - with mock_exit_stack() as mock_cleanup: - basic_plugin.create_missing_make(make = mock_make, cleanup = mock_cleanup) - - # Make sure no makes were attempted to be created - basic_plugin.owner.call.assert_not_called() - mock_cleanup.callback.assert_not_called() - - def test_create_missing_model(self, basic_plugin): - """Test that create missing model calls the expected functions when the make doesn't exist.""" - mock_make = "foo" - mock_model = "bar" - mock_imp = "baz" - basic_plugin.owner.model_exists.return_value = False - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - - with mock_exit_stack() as mock_cleanup: - basic_plugin.create_missing_model( - make = mock_make, - model = mock_model, - imp = mock_imp, - cleanup = mock_cleanup, - ) - - # Make sure appropriate calls were made - basic_plugin.owner.call.assert_called_once_with( - command = "add.firmware.model", - args = [mock_model, f"make={mock_make}", f"imp={mock_imp}"], - ) - mock_cleanup.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware.model", - args = [mock_model, f"make={mock_make}"], - ) - - def test_create_missing_model_skipped(self, basic_plugin): - """Test that create missing model skips implementation creation based on provided arguments.""" - mock_make = "foo" - mock_model = "bar" - mock_imp = "baz" - basic_plugin.owner.model_exists.return_value = True - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - - with mock_exit_stack() as mock_cleanup: - basic_plugin.create_missing_model( - make = mock_make, - model = mock_model, - imp = mock_imp, - cleanup = mock_cleanup, - ) - - # Make sure no makes were attempted to be created - basic_plugin.owner.call.assert_not_called() - mock_cleanup.callback.assert_not_called() - - @patch.object(target = Plugin, attribute = "create_missing_imp", autospec = True) - @patch.object(target = Plugin, attribute = "create_missing_make", autospec = True) - @patch.object(target = Plugin, attribute = "create_missing_model", autospec = True) - def test_create_missing_related_entries( - self, - mock_create_missing_model, - mock_create_missing_make, - mock_create_missing_imp, - basic_plugin, - ): - """Test that all missing related entries are attempted to be created.""" - mock_make = "foo" - mock_model = "bar" - mock_imp = "baz" - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - - with mock_exit_stack() as mock_cleanup: - basic_plugin.create_missing_related_entries( - make = mock_make, - model = mock_model, - imp = mock_imp, - cleanup = mock_cleanup, - ) - - mock_create_missing_model.assert_called_once_with( - basic_plugin, - make = mock_make, - model = mock_model, - imp = mock_imp, - cleanup = mock_cleanup, - ) - mock_create_missing_make.assert_called_once_with( - basic_plugin, - make = mock_make, - cleanup = mock_cleanup, - ) - mock_create_missing_imp.assert_called_once_with( - basic_plugin, - imp = mock_imp, - cleanup = mock_cleanup, - ) - - @pytest.mark.parametrize("failure_mock", ("create_missing_imp", "create_missing_make", "create_missing_model")) - @patch.object(target = Plugin, attribute = "create_missing_imp", autospec = True) - @patch.object(target = Plugin, attribute = "create_missing_make", autospec = True) - @patch.object(target = Plugin, attribute = "create_missing_model", autospec = True) - def test_create_missing_related_entries_errors( - self, - mock_create_missing_model, - mock_create_missing_make, - mock_create_missing_imp, - failure_mock, - basic_plugin, - ): - """Test that create_missing_related_entries fails when adding any related entry fails.""" - mock_make = "foo" - mock_model = "bar" - mock_imp = "baz" - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - mock_creation_functions = { - "create_missing_imp": mock_create_missing_imp, - "create_missing_make": mock_create_missing_make, - "create_missing_model": mock_create_missing_model, - } - mock_creation_functions[failure_mock].side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with mock_exit_stack() as mock_cleanup: - with pytest.raises(CommandError): - basic_plugin.create_missing_related_entries( - make = mock_make, - model = mock_model, - imp = mock_imp, - cleanup = mock_cleanup, - ) - - def test_file_cleanup(self, basic_plugin): - """Test that file path works as expected when the file still exists.""" - mock_path = create_autospec(spec = Path, spec_set = True, instance = True) - mock_path.exists.return_value = True - - basic_plugin.file_cleanup(file_path = mock_path) - - mock_path.exists.assert_called_once_with() - mock_path.unlink.assert_called_once_with() - - def test_file_cleanup_skip(self, basic_plugin): - """Test that file path skips unlinking when the file doesn't exist.""" - mock_path = create_autospec(spec = Path, spec_set = True, instance = True) - mock_path.exists.return_value = False - - basic_plugin.file_cleanup(file_path = mock_path) - - mock_path.exists.assert_called_once_with() - mock_path.unlink.assert_not_called() - - @patch(target = "stack.firmware.fetch_firmware", autospec = True) - @patch.object(target = Plugin, attribute = "file_cleanup", autospec = True) - def test_fetch_firmware(self, mock_file_cleanup, mock_fetch_firmware, basic_plugin): - """Test that fetch_firmware works as expected when the firmware can be fetched from the source.""" - mock_source = "/foo/bar" - mock_make = "foo" - mock_model = "bar" - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - - with mock_exit_stack() as mock_cleanup: - result = basic_plugin.fetch_firmware( - source = mock_source, - make = mock_make, - model = mock_model, - cleanup = mock_cleanup, - ) - - mock_fetch_firmware.assert_called_once_with( - source = mock_source, - make = mock_make, - model = mock_model, - ) - mock_cleanup.callback.assert_called_once_with( - basic_plugin.file_cleanup, - file_path = mock_fetch_firmware.return_value, - ) - assert mock_fetch_firmware.return_value == result - - @patch(target = "stack.firmware.fetch_firmware", autospec = True) - @patch.object(target = Plugin, attribute = "file_cleanup", autospec = True) - def test_fetch_firmware_error(self, mock_file_cleanup, mock_fetch_firmware, basic_plugin): - """Test that fetch_firmware raises the correct exception type when stack.firmware.fetch_firmware fails.""" - mock_source = "/foo/bar" - mock_make = "foo" - mock_model = "bar" - mock_fetch_firmware.side_effect = stack.firmware.FirmwareError("test error") - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - - with mock_exit_stack() as mock_cleanup: - with pytest.raises(ParamError): - basic_plugin.fetch_firmware( - source = mock_source, - make = mock_make, - model = mock_model, - cleanup = mock_cleanup, - ) - - mock_fetch_firmware.assert_called_once_with( - source = mock_source, - make = mock_make, - model = mock_model, - ) - mock_cleanup.callback.assert_not_called() - - @patch(target = "stack.firmware.calculate_hash", autospec = True) - def test_calculate_hash(self, mock_calculate_hash, basic_plugin): - """Test that calculate hash works as expected when the hash is valid.""" - mock_file_path = "/foo" - mock_hash_alg = "md5" - mock_hash_value = "bar" - - result = basic_plugin.calculate_hash( - file_path = mock_file_path, - hash_alg = mock_hash_alg, - hash_value = mock_hash_value, - ) - - assert mock_calculate_hash.return_value == result - mock_calculate_hash.assert_called_once_with( - file_path = mock_file_path, - hash_alg = mock_hash_alg, - hash_value = mock_hash_value, - ) - - @patch(target = "stack.firmware.calculate_hash", autospec = True) - def test_calculate_hash_error(self, mock_calculate_hash, basic_plugin): - """Test that calculate hash raises the correct exception type on a failure.""" - mock_file_path = "/foo" - mock_hash_alg = "md5" - mock_hash_value = "bar" - mock_calculate_hash.side_effect = stack.firmware.FirmwareError("Test error") - - with pytest.raises(ParamError): - basic_plugin.calculate_hash( - file_path = mock_file_path, - hash_alg = mock_hash_alg, - hash_value = mock_hash_value, - ) - - @patch.object(target = Plugin, attribute = "create_missing_related_entries", autospec = True) - @patch.object(target = Plugin, attribute = "calculate_hash", autospec = True) - @patch.object(target = Plugin, attribute = "fetch_firmware", autospec = True) - def test_add_firmware( - self, - mock_fetch_firmware, - mock_calculate_hash, - mock_create_missing_related_entries, - basic_plugin, - ): - """Test that adding firmware performs the expected actions.""" - mock_source = "foo" - mock_make = "bar" - mock_model = "baz" - mock_version = "bag" - mock_imp = "fizz" - mock_hash_value = "buzz" - mock_hash_alg = "md5" - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - - with mock_exit_stack() as mock_cleanup: - basic_plugin.add_firmware( - source = mock_source, - make = mock_make, - model = mock_model, - version = mock_version, - imp = mock_imp, - hash_value = mock_hash_value, - hash_alg = mock_hash_alg, - cleanup = mock_cleanup, - ) - - mock_fetch_firmware.assert_called_once_with( - basic_plugin, - source = mock_source, - make = mock_make, - model = mock_model, - cleanup = mock_cleanup, - ) - mock_calculate_hash.assert_called_once_with( - basic_plugin, - file_path = mock_fetch_firmware.return_value, - hash_value = mock_hash_value, - hash_alg = mock_hash_alg, - ) - mock_create_missing_related_entries.assert_called_once_with( - basic_plugin, - make = mock_make, - model = mock_model, - imp = mock_imp, - cleanup = mock_cleanup, - ) - basic_plugin.owner.get_model_id.assert_called_once_with( - make = mock_make, - model = mock_model, - ) - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - ( - basic_plugin.owner.get_model_id.return_value, - mock_source, - mock_version, - mock_hash_alg, - mock_calculate_hash.return_value, - str(mock_fetch_firmware.return_value), - ) - ) - mock_cleanup.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware", - args = [mock_version, f"make={mock_make}", f"model={mock_model}"], - ) - - @pytest.mark.parametrize( - "failure_mock", - ("create_missing_related_entries", "calculate_hash", "fetch_firmware"), - ) - @patch.object(target = Plugin, attribute = "create_missing_related_entries", autospec = True) - @patch.object(target = Plugin, attribute = "calculate_hash", autospec = True) - @patch.object(target = Plugin, attribute = "fetch_firmware", autospec = True) - def test_add_firmware_errors( - self, - mock_fetch_firmware, - mock_calculate_hash, - mock_create_missing_related_entries, - failure_mock, - basic_plugin, - ): - """Test that adding firmware fails when performing related actions fails.""" - mock_source = "foo" - mock_make = "bar" - mock_model = "baz" - mock_version = "bag" - mock_imp = "fizz" - mock_hash_value = "buzz" - mock_hash_alg = "md5" - mock_exit_stack = create_autospec(spec = ExitStack, spec_set = True) - mock_functions = { - "create_missing_related_entries": mock_create_missing_related_entries, - "calculate_hash": mock_calculate_hash, - "fetch_firmware": mock_fetch_firmware, - } - mock_functions[failure_mock].side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with mock_exit_stack() as mock_cleanup: - with pytest.raises(CommandError): - basic_plugin.add_firmware( - source = mock_source, - make = mock_make, - model = mock_model, - version = mock_version, - imp = mock_imp, - hash_value = mock_hash_value, - hash_alg = mock_hash_alg, - cleanup = mock_cleanup, - ) - - basic_plugin.owner.db.execute.assert_not_called() - - @patch.object(target = Plugin, attribute = "add_firmware", autospec = True) - @patch.object(target = Plugin, attribute = "validate_inputs", autospec = True) - @patch.object(target = Plugin, attribute = "validate_args", autospec = True) - @patch(target = "stack.commands.add.firmware.plugin_basic.ExitStack", autospec = True) - @patch(target = "stack.commands.add.firmware.plugin_basic.unique_everseen", autospec = True) - @patch(target = "stack.commands.add.firmware.plugin_basic.lowered", autospec = True) - def test_run( - self, - mock_lowered, - mock_unique_everseen, - mock_exit_stack, - mock_validate_args, - mock_validate_inputs, - mock_add_firmware, - basic_plugin, - ): - """Test that run performs all expected operations when all params and args are valid.""" - mock_params = { - "make": "foo", - "model": "bar", - "imp": "baz", - "hosts": "herp, derp, ferp", - "hash_alg": "md5", - "hash": "1234", - "source": "/fizz/buzz", - } - mock_args = ["MOCK_VERSION"] - expected_version = mock_args[0].lower() - expected_hosts = tuple(host.strip() for host in mock_params["hosts"].split(",") if host.strip()) - expected_lower_params = list(mock_params.values())[:5] - basic_plugin.owner.fillParams.return_value = mock_params.values() - mock_lowered.return_value = expected_lower_params - mock_unique_everseen.return_value = expected_hosts - basic_plugin.owner.getHosts.return_value = expected_hosts - - basic_plugin.run(args = (mock_params, mock_args)) - - mock_validate_args.assert_called_once_with(basic_plugin, args = mock_args) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [ - ("make", ""), - ("model", ""), - ("imp", ""), - ("hosts", ""), - ("hash_alg", "md5"), - ("hash", ""), - ("source", ""), - ], - params = mock_params, - ) - mock_lowered.assert_called_once_with(expected_lower_params) - mock_validate_inputs.assert_called_once_with( - basic_plugin, - source = mock_params["source"], - make = mock_params["make"], - model = mock_params["model"], - version = expected_version, - imp = mock_params["imp"], - hash_alg = mock_params["hash_alg"], - ) - mock_unique_everseen.assert_called_once() - args = mock_unique_everseen.call_args[0] - assert expected_hosts == tuple(args[0]) - basic_plugin.owner.getHosts.assert_called_once_with(args = expected_hosts) - mock_add_firmware.assert_called_once_with( - basic_plugin, - source = mock_params["source"], - make = mock_params["make"], - model = mock_params["model"], - version = expected_version, - imp = mock_params["imp"], - hash_value = mock_params["hash"], - hash_alg = mock_params["hash_alg"], - cleanup = mock_exit_stack.return_value.__enter__.return_value, - ) - basic_plugin.owner.call.assert_called_once_with( - command = "add.host.firmware.mapping", - args = [ - *expected_hosts, - f"version={expected_version}", - f"make={mock_params['make']}", - f"model={mock_params['model']}", - ], - ) - mock_exit_stack.return_value.__enter__.return_value.pop_all.assert_called_once_with() - - @patch.object(target = Plugin, attribute = "add_firmware", autospec = True) - @patch.object(target = Plugin, attribute = "validate_inputs", autospec = True) - @patch.object(target = Plugin, attribute = "validate_args", autospec = True) - @patch(target = "stack.commands.add.firmware.plugin_basic.ExitStack", autospec = True) - @patch(target = "stack.commands.add.firmware.plugin_basic.unique_everseen", autospec = True) - @patch(target = "stack.commands.add.firmware.plugin_basic.lowered", autospec = True) - def test_run_no_hosts( - self, - mock_lowered, - mock_unique_everseen, - mock_exit_stack, - mock_validate_args, - mock_validate_inputs, - mock_add_firmware, - basic_plugin, - ): - """Test that run performs all expected operations when all params and args are valid and no hosts are provided.""" - mock_params = { - "make": "foo", - "model": "bar", - "imp": "baz", - "hosts": "", - "hash_alg": "md5", - "hash": "1234", - "source": "/fizz/buzz", - } - mock_args = ["MOCK_VERSION"] - expected_version = mock_args[0].lower() - expected_lower_params = list(mock_params.values())[:5] - basic_plugin.owner.fillParams.return_value = mock_params.values() - mock_lowered.return_value = expected_lower_params - - basic_plugin.run(args = (mock_params, mock_args)) - - mock_validate_args.assert_called_once_with(basic_plugin, args = mock_args) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [ - ("make", ""), - ("model", ""), - ("imp", ""), - ("hosts", ""), - ("hash_alg", "md5"), - ("hash", ""), - ("source", ""), - ], - params = mock_params, - ) - mock_lowered.assert_called_once_with(expected_lower_params) - mock_validate_inputs.assert_called_once_with( - basic_plugin, - source = mock_params["source"], - make = mock_params["make"], - model = mock_params["model"], - version = expected_version, - imp = mock_params["imp"], - hash_alg = mock_params["hash_alg"], - ) - mock_unique_everseen.assert_not_called() - basic_plugin.owner.getHosts.assert_not_called() - mock_add_firmware.assert_called_once_with( - basic_plugin, - source = mock_params["source"], - make = mock_params["make"], - model = mock_params["model"], - version = expected_version, - imp = mock_params["imp"], - hash_value = mock_params["hash"], - hash_alg = mock_params["hash_alg"], - cleanup = mock_exit_stack.return_value.__enter__.return_value, - ) - basic_plugin.owner.call.assert_not_called() - mock_exit_stack.return_value.__enter__.return_value.pop_all.assert_called_once_with() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/version_regex/test_command_stack_commands_add_firmware_version_regex__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/version_regex/test_command_stack_commands_add_firmware_version_regex__init__.py deleted file mode 100644 index e7c676334..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/version_regex/test_command_stack_commands_add_firmware_version_regex__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.add.firmware.version_regex import Command - -class TestAddFirmwareVersionRegexCommand: - """A test case to hold the tests for the add firmware version_regex stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args and params.""" - mock_params = {"foo": "bar", "baz": "bag"} - mock_args = ["foo", "bar", "baz"] - - command.run(params = mock_params, args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/version_regex/test_command_stack_commands_add_firmware_version_regex_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/version_regex/test_command_stack_commands_add_firmware_version_regex_plugin_basic.py deleted file mode 100644 index 80cc58ac3..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/firmware/version_regex/test_command_stack_commands_add_firmware_version_regex_plugin_basic.py +++ /dev/null @@ -1,298 +0,0 @@ -from unittest.mock import create_autospec, patch, ANY -import pytest -from stack.commands import DatabaseConnection -from stack.commands.add.firmware import command -from stack.commands.add.firmware.version_regex.plugin_basic import Plugin -from stack.exception import ArgError, ParamError, StackError - -class TestAddVersionRegexBasicPlugin: - """A test case for the add firmware version_regex basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Make sure the plugin returns the correct provides information.""" - assert basic_plugin.provides() == "basic" - - def test_validate_args(self, basic_plugin): - """Test that validate_args works if the args list has one element.""" - basic_plugin.validate_args(args = ["foo"]) - - @pytest.mark.parametrize("test_input", ([], ["foo", "bar"])) - def test_validate_args_failure(self, test_input, basic_plugin): - """Test that validate_args fails with bad input.""" - with pytest.raises(ArgError): - basic_plugin.validate_args(args = test_input) - - def test_validate_regex(self, basic_plugin): - """Test that validate_regex works if the regex provided is valid.""" - basic_plugin.validate_regex(regex = "(foo)bar") - - @pytest.mark.parametrize("test_input", ("", "(", ")", "(foobar")) - def test_validate_regex_failure(self, test_input, basic_plugin): - """Test that validate_regex fails with bad input.""" - with pytest.raises(ArgError): - basic_plugin.validate_regex(regex = test_input) - - def test_validate_name(self, basic_plugin): - """Test that validate_name works if the name provided is not empty and is unique.""" - basic_plugin.owner.version_regex_exists.return_value = False - mock_name = "foo" - - basic_plugin.validate_name(name = mock_name) - - basic_plugin.owner.version_regex_exists.assert_called_once_with(name = mock_name) - - @pytest.mark.parametrize( - "test_input, return_value", - ( - ("", False), - ("foo", True), - ) - ) - def test_validate_name_failure(self, test_input, return_value, basic_plugin): - """Test that validate_name fails with bad input.""" - basic_plugin.owner.version_regex_exists.return_value = return_value - - with pytest.raises(ParamError): - basic_plugin.validate_name(name = test_input) - - @patch(target = "stack.commands.add.firmware.version_regex.plugin_basic.unique_everseen", autospec = True) - @patch(target = "stack.commands.add.firmware.version_regex.plugin_basic.ExitStack", autospec = True) - @patch.object(target = Plugin, attribute = "validate_name", autospec = True) - @patch.object(target = Plugin, attribute = "validate_regex", autospec = True) - @patch.object(target = Plugin, attribute = "validate_args", autospec = True) - def test_run( - self, - mock_validate_args, - mock_validate_regex, - mock_validate_name, - mock_exit_stack, - mock_unique_everseen, - basic_plugin, - ): - """Test that run performs the expected action in the case where all arguments and parameters are valid.""" - mock_args = ["mock_regex"] - # We're using uppercase to ensure that lower is used correctly when processing args and params. - mock_models = ["MOCK_MODEL1", "MOCK_MODEL2", "MOCK_MODEL3"] - expected_models = tuple((string.lower() for string in mock_models)) - mock_params = { - "name": "MOCK_NAME", - "description": "MOCK_DESCRIPTION", - "make": "MOCK_MAKE", - "models": ", ".join(mock_models), - } - expected_name = mock_params["name"].lower() - expected_make = mock_params["make"].lower() - basic_plugin.owner.fillParams.return_value = mock_params.values() - mock_unique_everseen.return_value = (string for string in expected_models) - - basic_plugin.run(args = (mock_params, mock_args)) - - # Make sure the params were filled with defaults. - basic_plugin.owner.fillParams.assert_called_once_with( - names = [ - ("name", ""), - ("description", ""), - ("make", ""), - ("models", ""), - ], - params = mock_params, - ) - # Expect all the validation functions to be called to validate params and args. - mock_validate_args.assert_called_once_with(basic_plugin, args = mock_args) - mock_validate_regex.assert_called_once_with(basic_plugin, regex = mock_args[0]) - mock_validate_name.assert_called_once_with(basic_plugin, name = expected_name) - # Expect the models to be made unique, forced to lowercase, and validated. - assert expected_models == tuple(*mock_unique_everseen.call_args[0]) - basic_plugin.owner.ensure_models_exist.assert_called_once_with( - make = expected_make, - models = expected_models, - ) - # Expect the DB entries to be made. - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - (mock_args[0], expected_name, mock_params["description"]), - ) - basic_plugin.owner.call.assert_called_once_with( - command = "set.firmware.model.version_regex", - args = [*expected_models, f"make={expected_make}", f"version_regex={expected_name}"] - ) - # Make sure that ExitStack was used to guard against failures. - mock_exit_stack.return_value.__enter__.return_value.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware.version_regex", - args = [expected_name], - ) - mock_exit_stack.return_value.__enter__.return_value.pop_all.assert_called_once_with() - - @patch(target = "stack.commands.add.firmware.version_regex.plugin_basic.unique_everseen", autospec = True) - @patch(target = "stack.commands.add.firmware.version_regex.plugin_basic.ExitStack", autospec = True) - @patch.object(target = Plugin, attribute = "validate_name", autospec = True) - @patch.object(target = Plugin, attribute = "validate_regex", autospec = True) - @patch.object(target = Plugin, attribute = "validate_args", autospec = True) - def test_run_no_models( - self, - mock_validate_args, - mock_validate_regex, - mock_validate_name, - mock_exit_stack, - mock_unique_everseen, - basic_plugin, - ): - """Test that run performs the expected action in the case where all arguments and parameters are valid but not models are provided.""" - mock_args = ["mock_regex"] - # We're using uppercase to ensure that lower is used correctly when processing args and params. - mock_params = { - "name": "MOCK_NAME", - "description": "MOCK_DESCRIPTION", - "make": "MOCK_MAKE", - "models": "", - } - expected_name = mock_params["name"].lower() - expected_make = mock_params["make"].lower() - basic_plugin.owner.fillParams.return_value = mock_params.values() - - basic_plugin.run(args = (mock_params, mock_args)) - - # Make sure the params were filled with defaults. - basic_plugin.owner.fillParams.assert_called_once_with( - names = [ - ("name", ""), - ("description", ""), - ("make", ""), - ("models", ""), - ], - params = mock_params, - ) - # Expect all the validation functions to be called to validate params and args. - mock_validate_args.assert_called_once_with(basic_plugin, args = mock_args) - mock_validate_regex.assert_called_once_with(basic_plugin, regex = mock_args[0]) - mock_validate_name.assert_called_once_with(basic_plugin, name = expected_name) - basic_plugin.owner.ensure_make_exists.assert_called_once_with(make = expected_make) - # Expect the models to not be validated. - mock_unique_everseen.assert_not_called() - basic_plugin.owner.ensure_models_exist.assert_not_called() - # Expect the DB entries to be made. - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - (mock_args[0], expected_name, mock_params["description"]), - ) - basic_plugin.owner.call.assert_called_once_with( - command = "set.firmware.make.version_regex", - args = [expected_make, f"version_regex={expected_name}"] - ) - # Make sure that ExitStack was used to guard against failures. - mock_exit_stack.return_value.__enter__.return_value.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware.version_regex", - args = [expected_name], - ) - mock_exit_stack.return_value.__enter__.return_value.pop_all.assert_called_once_with() - - @pytest.mark.parametrize( - "failure_mock, mock_models", - ( - ("ensure_make_exists", ""), - ("ensure_models_exist", "MOCK_MODEL1, MOCK_MODEL2, MOCK_MODEL3"), - ("validate_name", ""), - ("validate_name", "MOCK_MODEL1, MOCK_MODEL2, MOCK_MODEL3"), - ("validate_regex", ""), - ("validate_regex", "MOCK_MODEL1, MOCK_MODEL2, MOCK_MODEL3"), - ("validate_args", ""), - ("validate_args", "MOCK_MODEL1, MOCK_MODEL2, MOCK_MODEL3"), - ), - ) - @patch(target = "stack.commands.add.firmware.version_regex.plugin_basic.unique_everseen", autospec = True) - @patch(target = "stack.commands.add.firmware.version_regex.plugin_basic.ExitStack", autospec = True) - @patch.object(target = Plugin, attribute = "validate_name", autospec = True) - @patch.object(target = Plugin, attribute = "validate_regex", autospec = True) - @patch.object(target = Plugin, attribute = "validate_args", autospec = True) - def test_run_validation_errors( - self, - mock_validate_args, - mock_validate_regex, - mock_validate_name, - mock_exit_stack, - mock_unique_everseen, - failure_mock, - mock_models, - basic_plugin, - ): - """Test that run fails when the parameters do not validate.""" - mock_args = ["mock_regex"] - mock_params = { - "name": "MOCK_NAME", - "description": "MOCK_DESCRIPTION", - "make": "MOCK_MAKE", - "models": mock_models, - } - basic_plugin.owner.fillParams.return_value = mock_params.values() - validation_function_mocks = { - "validate_args": mock_validate_args, - "validate_regex": mock_validate_regex, - "validate_name": mock_validate_name, - "ensure_make_exists": basic_plugin.owner.ensure_make_exists, - "ensure_models_exist": basic_plugin.owner.ensure_models_exist, - } - # Set the appropriate mock call to fail - validation_function_mocks[failure_mock].side_effect = StackError("Test error") - - with pytest.raises(StackError): - basic_plugin.run(args = (mock_params, mock_args)) - - # Make sure the database calls are not made if the arguments don't validate - basic_plugin.owner.db.execute.assert_not_called() - - @pytest.mark.parametrize("mock_models", ("", "MOCK_MODEL1, MOCK_MODEL2, MOCK_MODEL3")) - @patch(target = "stack.commands.add.firmware.version_regex.plugin_basic.unique_everseen", autospec = True) - @patch(target = "stack.commands.add.firmware.version_regex.plugin_basic.ExitStack", autospec = True) - @patch.object(target = Plugin, attribute = "validate_name", autospec = True) - @patch.object(target = Plugin, attribute = "validate_regex", autospec = True) - @patch.object(target = Plugin, attribute = "validate_args", autospec = True) - def test_run_set_relation_fails( - self, - mock_validate_args, - mock_validate_regex, - mock_validate_name, - mock_exit_stack, - mock_unique_everseen, - mock_models, - basic_plugin, - ): - """Test that run fails and cleans up when setting the relation to the make or model fails.""" - mock_args = ["mock_regex"] - mock_params = { - "name": "MOCK_NAME", - "description": "MOCK_DESCRIPTION", - "make": "MOCK_MAKE", - "models": mock_models, - } - basic_plugin.owner.fillParams.return_value = mock_params.values() - basic_plugin.owner.call.side_effect = StackError("Test error") - - with pytest.raises(StackError): - basic_plugin.run(args = (mock_params, mock_args)) - - # Make sure the cleanup is setup to be run, and don't expect it to be dismissed because of the error. - mock_exit_stack.return_value.__enter__.return_value.callback.assert_called_once_with( - basic_plugin.owner.call, - command = "remove.firmware.version_regex", - args = [mock_params["name"].lower()], - ) - mock_exit_stack.return_value.__enter__.return_value.pop_all.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/host/firmware/mapping/test_command_stack_commands_add_host_firmware_mapping__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/host/firmware/mapping/test_command_stack_commands_add_host_firmware_mapping__init__.py deleted file mode 100644 index 692f37240..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/host/firmware/mapping/test_command_stack_commands_add_host_firmware_mapping__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.add.host.firmware.mapping import Command - -class TestAddHostFirmwareMappingCommand: - """A test case to hold the tests for the add host firmware mapping stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"fizz": "buzz"} - - command.run(args = mock_args, params = mock_params) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/add/host/firmware/mapping/test_command_stack_commands_add_host_firmware_mapping_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/add/host/firmware/mapping/test_command_stack_commands_add_host_firmware_mapping_plugin_basic.py deleted file mode 100644 index 84194c717..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/add/host/firmware/mapping/test_command_stack_commands_add_host_firmware_mapping_plugin_basic.py +++ /dev/null @@ -1,161 +0,0 @@ -from unittest.mock import create_autospec, patch, ANY, call -import pytest -from contextlib import ExitStack -from stack.commands import DatabaseConnection -from stack.commands.add.host.firmware.mapping import Command -from stack.commands.add.host.firmware.mapping.plugin_basic import Plugin -from stack.exception import CommandError - -class TestAddHostFirmwareMappingBasicPlugin: - """A test case for the add host firmware mapping basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure the plugin returns the correct provides information.""" - assert basic_plugin.provides() == "basic" - - def test_ensure_unique_mappings(self, basic_plugin): - """Test that ensure_unique_mappings queries the db as expected.""" - mock_hosts = ["foo", "bar"] - mock_make = "baz" - mock_model = "bag" - mock_version = "bam" - basic_plugin.owner.db.select.return_value = [] - - basic_plugin.ensure_unique_mappings( - hosts = mock_hosts, - version = mock_version, - make = mock_make, - model = mock_model, - ) - - basic_plugin.owner.db.select.assert_called_once_with( - ANY, - (mock_hosts, mock_make, mock_model, mock_version), - ) - - def test_ensure_unique_mappings_error(self, basic_plugin): - """Test that ensure_unique_mappings fails if matching mappings are found.""" - mock_hosts = ["foo", "bar"] - mock_make = "baz" - mock_model = "bag" - mock_version = "bam" - basic_plugin.owner.db.select.return_value = [[mock_hosts[0], mock_make, mock_model, mock_version]] - - with pytest.raises(CommandError): - basic_plugin.ensure_unique_mappings( - hosts = mock_hosts, - version = mock_version, - make = mock_make, - model = mock_model, - ) - - @patch.object(target = Plugin, attribute = "ensure_unique_mappings", autospec = True) - @patch(target = "stack.commands.add.host.firmware.mapping.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.add.host.firmware.mapping.plugin_basic.unique_everseen", autospec = True) - def test_run( - self, - mock_unique_everseen, - mock_lowered, - mock_ensure_unique_mappings, - basic_plugin, - ): - """Test that run works as expected when the params and args are valid.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"version": "bam!", "make": "fizz", "model": "buzz"} - expected_hosts = tuple(mock_args) - mock_lowered.return_value = mock_params.values() - mock_unique_everseen.return_value = mock_args - basic_plugin.owner.getHosts.return_value = expected_hosts - basic_plugin.owner.db.select.return_value = [["1"]] - - basic_plugin.run(args = (mock_params, mock_args)) - - assert [call(mock_args), call(basic_plugin.owner.fillParams.return_value)] == mock_lowered.mock_calls - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.getHosts.assert_called_once_with(args = expected_hosts) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [ - ("version", ""), - ("make", ""), - ("model", ""), - ], - params = mock_params, - ) - basic_plugin.owner.ensure_firmware_exists.assert_called_once_with( - make = mock_params["make"], - model = mock_params["model"], - version = mock_params["version"], - ) - mock_ensure_unique_mappings.assert_called_once_with( - basic_plugin, - hosts = expected_hosts, - make = mock_params["make"], - model = mock_params["model"], - version = mock_params["version"], - ) - basic_plugin.owner.db.select.assert_called_once_with(ANY, (expected_hosts,)) - basic_plugin.owner.get_firmware_id.assert_called_once_with( - make = mock_params["make"], - model = mock_params["model"], - version = mock_params["version"], - ) - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - [(basic_plugin.owner.db.select.return_value[0][0], basic_plugin.owner.get_firmware_id.return_value)], - many = True, - ) - - @pytest.mark.parametrize( - "failure_mock", - ("getHosts", "ensure_firmware_exists", "ensure_unique_mappings"), - ) - @patch.object(target = Plugin, attribute = "ensure_unique_mappings", autospec = True) - @patch(target = "stack.commands.add.host.firmware.mapping.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.add.host.firmware.mapping.plugin_basic.unique_everseen", autospec = True) - def test_run_errors( - self, - mock_unique_everseen, - mock_lowered, - mock_ensure_unique_mappings, - failure_mock, - basic_plugin, - ): - """Test that run fails when invalid arguments or parameters are provided.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"version": "bam!", "make": "fizz", "model": "buzz"} - expected_hosts = tuple(mock_args) - mock_lowered.return_value = mock_params.values() - mock_unique_everseen.return_value = mock_args - basic_plugin.owner.getHosts.return_value = expected_hosts - basic_plugin.owner.db.select.return_value = [["1"]] - mock_validation_functions = { - "getHosts": basic_plugin.owner.getHosts, - "ensure_firmware_exists": basic_plugin.owner.ensure_firmware_exists, - "ensure_unique_mappings": mock_ensure_unique_mappings, - } - mock_validation_functions[failure_mock].side_effect = CommandError( - cmd = basic_plugin.owner, - msg = "Test error", - ) - - with pytest.raises(CommandError): - basic_plugin.run(args = (mock_params, mock_args)) - - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/imp/test_command_stack_commands_list_firmware_imp__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/imp/test_command_stack_commands_list_firmware_imp__init__.py deleted file mode 100644 index fcbc5e318..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/imp/test_command_stack_commands_list_firmware_imp__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -from unittest.mock import patch, call -import pytest -from stack.commands.list.firmware.imp import Command - -class TestListFirmwareImpCommand: - """A test case to hold the tests for the list firmware imp stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "endOutput", autospec = True) - @patch.object(target = Command, attribute = "addOutput", autospec = True) - @patch.object(target = Command, attribute = "beginOutput", autospec = True) - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, mock_beginOutput, mock_addOutput, mock_endOutput, command): - """Test that run will run the plugins, gather the results, and output them as expected.""" - mock_header = ["imp"] - mock_first_value = ("foo", []) - mock_second_value = ("bar", []) - mock_runPlugins.return_value = ( - ( - "basic", - { - "keys": mock_header, - "values": [mock_first_value, mock_second_value], - }, - ), - ) - - command.run("unused", "unused") - - mock_runPlugins.assert_called_once_with(command) - mock_beginOutput.assert_called_once_with(command) - assert [ - call(command, owner = mock_first_value[0], vals = mock_first_value[1]), - call(command, owner = mock_second_value[0], vals = mock_second_value[1]), - ] == mock_addOutput.mock_calls - mock_endOutput.assert_called_once_with(command, header = mock_header) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/imp/test_command_stack_commands_list_firmware_imp_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/imp/test_command_stack_commands_list_firmware_imp_plugin_basic.py deleted file mode 100644 index 55d42ab80..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/imp/test_command_stack_commands_list_firmware_imp_plugin_basic.py +++ /dev/null @@ -1,41 +0,0 @@ -from unittest.mock import create_autospec, ANY -import pytest -from stack.commands import DatabaseConnection -from stack.commands.list.firmware import Command -from stack.commands.list.firmware.imp.plugin_basic import Plugin - -class TestListFirmwareImpBasicPlugin: - """A test case for the list firmware imp basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - def test_run(self, basic_plugin): - """Test that run queries the DB as expected.""" - basic_plugin.owner.db.select.return_value = [["foo"], ["bar"]] - expected_results = { - "keys": ["imp"], - "values": [(row[0], []) for row in basic_plugin.owner.db.select.return_value], - } - - assert expected_results == basic_plugin.run(args = "unused") - - basic_plugin.owner.db.select.assert_called_once_with(ANY) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/make/test_command_stack_commands_list_firmware_make__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/make/test_command_stack_commands_list_firmware_make__init__.py deleted file mode 100644 index c4aa4e4fb..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/make/test_command_stack_commands_list_firmware_make__init__.py +++ /dev/null @@ -1,65 +0,0 @@ -from unittest.mock import patch, call -import pytest -from stack.commands.list.firmware.make import Command - -class TestListFirmwareMakeCommand: - """A test case to hold the tests for the list firmware make stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "str2bool", autospec = True) - @patch.object(target = Command, attribute = "fillParams", autospec = True) - @patch.object(target = Command, attribute = "endOutput", autospec = True) - @patch.object(target = Command, attribute = "addOutput", autospec = True) - @patch.object(target = Command, attribute = "beginOutput", autospec = True) - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run( - self, - mock_runPlugins, - mock_beginOutput, - mock_addOutput, - mock_endOutput, - mock_fillParams, - mock_str2bool, - command, - ): - """Test that run will run the plugins, gather the results, and output them as expected.""" - mock_header = ["make", "version_regex_name"] - mock_first_value = ("foo", ["bar"]) - mock_second_value = ("baz", ["bag"]) - mock_runPlugins.return_value = ( - ( - "basic", - { - "keys": mock_header, - "values": [mock_first_value, mock_second_value], - }, - ), - ) - mock_params = {"expanded": "True"} - mock_fillParams.return_value = (mock_params["expanded"],) - mock_str2bool.return_value = True - - command.run(args = "unused", params = mock_params) - - mock_fillParams.assert_called_once_with(command, names = [("expanded", False)], params = mock_params) - mock_str2bool.assert_called_once_with(command, mock_params["expanded"]) - mock_runPlugins.assert_called_once_with(command, args = mock_str2bool.return_value) - mock_beginOutput.assert_called_once_with(command) - assert [ - call(command, owner = mock_first_value[0], vals = mock_first_value[1]), - call(command, owner = mock_second_value[0], vals = mock_second_value[1]), - ] == mock_addOutput.mock_calls - mock_endOutput.assert_called_once_with(command, header = mock_header) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/make/test_command_stack_commands_list_firmware_make_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/make/test_command_stack_commands_list_firmware_make_plugin_basic.py deleted file mode 100644 index f3f43b619..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/make/test_command_stack_commands_list_firmware_make_plugin_basic.py +++ /dev/null @@ -1,53 +0,0 @@ -from unittest.mock import create_autospec, ANY -import pytest -from stack.commands import DatabaseConnection -from stack.commands.list.firmware import Command -from stack.commands.list.firmware.make.plugin_basic import Plugin - -class TestListFirmwareMakeBasicPlugin: - """A test case for the list firmware make basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - def test_run(self, basic_plugin): - """Test that run queries the DB as expected when expanded is true.""" - basic_plugin.owner.db.select.return_value = [["foo", "bar"], ["baz", "bag"]] - expected_results = { - "keys": ["make", "version_regex_name"], - "values": [(row[0], row[1:]) for row in basic_plugin.owner.db.select.return_value], - } - - assert expected_results == basic_plugin.run(args = True) - - basic_plugin.owner.db.select.assert_called_once_with(ANY) - - def test_run_expanded_false(self, basic_plugin): - """Test that run queries the DB as expected when expanded is false.""" - basic_plugin.owner.db.select.return_value = [["foo"], ["bar"]] - expected_results = { - "keys": ["make"], - "values": [(row[0], []) for row in basic_plugin.owner.db.select.return_value], - } - - assert expected_results == basic_plugin.run(args = False) - - basic_plugin.owner.db.select.assert_called_once_with(ANY) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/model/test_command_stack_commands_list_firmware_model__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/model/test_command_stack_commands_list_firmware_model__init__.py deleted file mode 100644 index 94c2f5951..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/model/test_command_stack_commands_list_firmware_model__init__.py +++ /dev/null @@ -1,62 +0,0 @@ -from unittest.mock import patch, call -import pytest -from stack.commands.list.firmware.model import Command - -class TestListFirmwareModelCommand: - """A test case to hold the tests for the list firmware model stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "str2bool", autospec = True) - @patch.object(target = Command, attribute = "fillParams", autospec = True) - @patch.object(target = Command, attribute = "endOutput", autospec = True) - @patch.object(target = Command, attribute = "addOutput", autospec = True) - @patch.object(target = Command, attribute = "beginOutput", autospec = True) - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run( - self, - mock_runPlugins, - mock_beginOutput, - mock_addOutput, - mock_endOutput, - mock_fillParams, - mock_str2bool, - command, - ): - """Test that run will run the plugins, gather the results, and output them as expected.""" - mock_header = ["make", "model", "implementation", "version_regex_name"] - mock_first_value = ("foo", ["bar", "baz", "bag"]) - mock_second_value = ("fizz", ["buzz", "bizz", "bam!"]) - mock_runPlugins.return_value = ( - ( - "basic", - { - "keys": mock_header, - "values": [mock_first_value, mock_second_value], - }, - ), - ) - mock_params = {"expanded": "True", "make": "foo"} - mock_args = ["foo"] - - command.run(args = mock_args, params = mock_params) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) - mock_beginOutput.assert_called_once_with(command) - assert [ - call(command, owner = mock_first_value[0], vals = mock_first_value[1]), - call(command, owner = mock_second_value[0], vals = mock_second_value[1]), - ] == mock_addOutput.mock_calls - mock_endOutput.assert_called_once_with(command, header = mock_header) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/model/test_command_stack_commands_list_firmware_model_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/model/test_command_stack_commands_list_firmware_model_plugin_basic.py deleted file mode 100644 index 6475aa627..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/model/test_command_stack_commands_list_firmware_model_plugin_basic.py +++ /dev/null @@ -1,229 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch, call -import pytest -from stack.commands import DatabaseConnection -from stack.commands.list.firmware import Command -from stack.commands.list.firmware.model.plugin_basic import Plugin -from stack.exception import CommandError - -class TestListFirmwareModelBasicPlugin: - """A test case for the list firmware model basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - def test_validate_make(self, basic_plugin): - """Test that validate make works as expected in the case where a make is provided.""" - mock_make = "foo" - - basic_plugin.validate_make(make = mock_make) - - basic_plugin.owner.ensure_make_exists.assert_called_once_with(make = mock_make) - - def test_validate_make_skip(self, basic_plugin): - """Test that validate make works as expected in the case where a make is not provided.""" - mock_make = "" - - basic_plugin.validate_make(make = mock_make) - - basic_plugin.owner.ensure_make_exists.assert_not_called() - - def test_validate_make_error(self, basic_plugin): - """Test that validate fails when ensuring the make exists fails.""" - mock_make = "foo" - basic_plugin.owner.ensure_make_exists.side_effect = CommandError( - cmd = basic_plugin.owner, - msg = "Test error", - ) - - with pytest.raises(CommandError): - basic_plugin.validate_make(make = mock_make) - - def test_validate_models(self, basic_plugin): - """Test that validate models works as expected in the case where a make and some models are provided.""" - mock_make = "foo" - mock_models = ["bar", "baz"] - - basic_plugin.validate_models(make = mock_make, models = mock_models) - - basic_plugin.owner.ensure_models_exist.assert_called_once_with(make = mock_make, models = mock_models) - - def test_validate_models_skip(self, basic_plugin): - """Test that validate models works as expected in the case where models are not provided.""" - mock_make = "foo" - mock_models = [] - - basic_plugin.validate_models(make = mock_make, models = mock_models) - - basic_plugin.owner.ensure_models_exist.assert_not_called() - - def test_validate_models_error(self, basic_plugin): - """Test that validate fails when ensuring the models exist fails.""" - mock_make = "foo" - mock_models = ["bar", "baz"] - basic_plugin.owner.ensure_models_exist.side_effect = CommandError( - cmd = basic_plugin.owner, - msg = "Test error", - ) - - with pytest.raises(CommandError): - basic_plugin.validate_models(make = mock_make, models = mock_models) - - @pytest.mark.parametrize( - "make, models, expected", - ( - ("foo", ["bar", "baz"], [["bar", "baz"], "foo"]), - ("foo", [], ["foo"]), - ("", [], []), - ) - ) - def test_get_expanded_results(self, make, models, expected, basic_plugin): - """Test that get expanded results is called as expected based on the provided args.""" - basic_plugin.owner.db.select.return_value = [ - ["foo", "bar", "baz", "bag"], - ["fizz", "buzz", "bizz", "bam!"], - ] - expected_results = { - "keys": ["make", "model", "implementation", "version_regex_name"], - "values": [(row[0], row[1:]) for row in basic_plugin.owner.db.select.return_value], - } - - assert expected_results == basic_plugin.get_expanded_results(make = make, models = models) - - basic_plugin.owner.db.select.assert_called_once_with(ANY, expected) - - @pytest.mark.parametrize( - "make, models, expected", - ( - ("foo", ["bar", "baz"], [["bar", "baz"], "foo"]), - ("foo", [], ["foo"]), - ("", [], []), - ) - ) - def test_get_results(self, make, models, expected, basic_plugin): - """Test that get results is called as expected based on the provided args.""" - basic_plugin.owner.db.select.return_value = [ - ["foo", "bar", "baz", "bag"], - ["fizz", "buzz", "bizz", "bam!"], - ] - expected_results = { - "keys": ["make", "model", "implementation", "version_regex_name"], - "values": [(row[0], row[1:]) for row in basic_plugin.owner.db.select.return_value], - } - - assert expected_results == basic_plugin.get_expanded_results(make = make, models = models) - - basic_plugin.owner.db.select.assert_called_once_with(ANY, expected) - - @patch.object(target = Plugin, attribute = "get_results", autospec = True) - @patch.object(target = Plugin, attribute = "get_expanded_results", autospec = True) - @patch.object(target = Plugin, attribute = "validate_models", autospec = True) - @patch.object(target = Plugin, attribute = "validate_make", autospec = True) - @patch(target = "stack.commands.list.firmware.model.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.list.firmware.model.plugin_basic.unique_everseen", autospec = True) - def test_run( - self, - mock_unique_everseen, - mock_lowered, - mock_validate_make, - mock_validate_models, - mock_get_expanded_results, - mock_get_results, - basic_plugin, - ): - """Test that run works as expected when expanded is True.""" - mock_args = ["foo", "bar", "baz"] - expected_args = tuple(mock_args) - mock_params = {"expanded": "true", "make": "fizz"} - mock_unique_everseen.return_value = mock_args - mock_lowered.return_value = mock_params.values() - basic_plugin.owner.str2bool.return_value = True - - basic_plugin.run(args = (mock_params, mock_args)) - - # Ensure the params and args were processed and validated as expected - assert [call(mock_args), call(basic_plugin.owner.fillParams.return_value)] == mock_lowered.mock_calls - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [("expanded", "false"), ("make", "")], - params = mock_params, - ) - basic_plugin.owner.str2bool.assert_called_once_with(mock_params["expanded"]) - mock_validate_make.assert_called_once_with(basic_plugin, make = mock_params["make"]) - mock_validate_models.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - models = expected_args, - ) - # Since expanded was True, ensure that we called the right function to get the expanded results. - mock_get_expanded_results.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - models = expected_args, - ) - mock_get_results.assert_not_called() - - @patch.object(target = Plugin, attribute = "get_results", autospec = True) - @patch.object(target = Plugin, attribute = "get_expanded_results", autospec = True) - @patch.object(target = Plugin, attribute = "validate_models", autospec = True) - @patch.object(target = Plugin, attribute = "validate_make", autospec = True) - @patch(target = "stack.commands.list.firmware.model.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.list.firmware.model.plugin_basic.unique_everseen", autospec = True) - def test_run_expanded_false( - self, - mock_unique_everseen, - mock_lowered, - mock_validate_make, - mock_validate_models, - mock_get_expanded_results, - mock_get_results, - basic_plugin, - ): - """Test that run works as expected when expanded is False.""" - mock_args = ["foo", "bar", "baz"] - expected_args = tuple(mock_args) - mock_params = {"expanded": "false", "make": "fizz"} - mock_unique_everseen.return_value = mock_args - mock_lowered.return_value = mock_params.values() - basic_plugin.owner.str2bool.return_value = False - - basic_plugin.run(args = (mock_params, mock_args)) - - # Ensure the params and args were processed and validated as expected - assert [call(mock_args), call(basic_plugin.owner.fillParams.return_value)] == mock_lowered.mock_calls - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [("expanded", "false"), ("make", "")], - params = mock_params, - ) - basic_plugin.owner.str2bool.assert_called_once_with(mock_params["expanded"]) - mock_validate_make.assert_called_once_with(basic_plugin, make = mock_params["make"]) - mock_validate_models.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - models = expected_args, - ) - # Since expanded was True, ensure that we called the right function to get the expanded results. - mock_get_expanded_results.assert_not_called() - mock_get_results.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - models = expected_args, - ) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/test_command_stack_commands_list_firmware__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/test_command_stack_commands_list_firmware__init__.py deleted file mode 100644 index 4d5dc2857..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/test_command_stack_commands_list_firmware__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -from unittest.mock import patch, call -import pytest -from stack.commands.list.firmware import Command - -class TestListFirmwareCommand: - """A test case to hold the tests for the list firmware stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "str2bool", autospec = True) - @patch.object(target = Command, attribute = "fillParams", autospec = True) - @patch.object(target = Command, attribute = "endOutput", autospec = True) - @patch.object(target = Command, attribute = "addOutput", autospec = True) - @patch.object(target = Command, attribute = "beginOutput", autospec = True) - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run( - self, - mock_runPlugins, - mock_beginOutput, - mock_addOutput, - mock_endOutput, - mock_fillParams, - mock_str2bool, - command, - ): - """Test that run will run the plugins, gather the results, and output them as expected.""" - mock_params = {"expanded": "true"} - mock_fillParams.return_value = mock_params.values() - mock_str2bool.return_value = True - mock_header = ["make", "model", "version"] - mock_first_value = ("foo", ["bar", "baz"]) - mock_second_value = ("bar", ["fizz", "buzz"]) - mock_runPlugins.return_value = ( - ( - "basic", - { - "keys": mock_header, - "values": [mock_first_value, mock_second_value], - }, - ), - ) - - command.run(params = mock_params, args = "unused") - - mock_fillParams.assert_called_once_with( - command, - names = [("expanded", False)], - params = mock_params, - ) - mock_str2bool.assert_called_once_with(command, mock_params["expanded"]) - mock_runPlugins.assert_called_once_with(command, args = mock_str2bool.return_value) - mock_beginOutput.assert_called_once_with(command) - assert [ - call(command, owner = mock_first_value[0], vals = mock_first_value[1]), - call(command, owner = mock_second_value[0], vals = mock_second_value[1]), - ] == mock_addOutput.mock_calls - mock_endOutput.assert_called_once_with(command, header = mock_header) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/test_command_stack_commands_list_firmware_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/test_command_stack_commands_list_firmware_plugin_basic.py deleted file mode 100644 index 05e91cd6f..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/test_command_stack_commands_list_firmware_plugin_basic.py +++ /dev/null @@ -1,59 +0,0 @@ -from unittest.mock import create_autospec, ANY -import pytest -from stack.commands import DatabaseConnection -from stack.commands.list.firmware import Command -from stack.commands.list.firmware.plugin_basic import Plugin - -class TestListFirmwareBasicPlugin: - """A test case for the list firmware imp basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - def test_run(self, basic_plugin): - """Test that run queries the DB as expected.""" - basic_plugin.owner.db.select.return_value = [ - ["foo", "bar", "baz", "fizz", "buzz", "bam!"], - ["this", "is", "a", "test", "return", "value"], - ] - expected_results = { - "keys": ["make", "model", "version", "source", "hash", "hash_alg"], - "values": [(row[0], row[1:]) for row in basic_plugin.owner.db.select.return_value], - } - - assert expected_results == basic_plugin.run(args = True) - - basic_plugin.owner.db.select.assert_called_once_with(ANY) - - def test_run_expanded_false(self, basic_plugin): - """Test that run queries the DB as expected when expanded is False.""" - basic_plugin.owner.db.select.return_value = [ - ["foo", "bar", "baz"], - ["fizz", "buzz", "bam!"], - ] - expected_results = { - "keys": ["make", "model", "version"], - "values": [(row[0], row[1:]) for row in basic_plugin.owner.db.select.return_value], - } - - assert expected_results == basic_plugin.run(args = False) - - basic_plugin.owner.db.select.assert_called_once_with(ANY) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/version_regex/test_command_stack_commands_list_firmware_version_regex__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/version_regex/test_command_stack_commands_list_firmware_version_regex__init__.py deleted file mode 100644 index b3d95a392..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/version_regex/test_command_stack_commands_list_firmware_version_regex__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -from unittest.mock import patch, call -import pytest -from stack.commands.list.firmware.version_regex import Command - -class TestListFirmwareVersionRegexCommand: - """A test case to hold the tests for the list firmware version_regex stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "endOutput", autospec = True) - @patch.object(target = Command, attribute = "addOutput", autospec = True) - @patch.object(target = Command, attribute = "beginOutput", autospec = True) - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, mock_beginOutput, mock_addOutput, mock_endOutput, command): - """Test that run will run the plugins, gather the results, and output them as expected.""" - mock_header = ["name", "regex", "description"] - mock_first_value = ("foo", ["bar", "baz"]) - mock_second_value = ("bag", ["fizz", "buzz"]) - mock_runPlugins.return_value = ( - ( - "basic", - { - "keys": mock_header, - "values": [mock_first_value, mock_second_value], - }, - ), - ) - - command.run("unused", "unused") - - mock_runPlugins.assert_called_once_with(command) - mock_beginOutput.assert_called_once_with(command) - assert [ - call(command, owner = mock_first_value[0], vals = mock_first_value[1]), - call(command, owner = mock_second_value[0], vals = mock_second_value[1]), - ] == mock_addOutput.mock_calls - mock_endOutput.assert_called_once_with(command, header = mock_header) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/version_regex/test_command_stack_commands_list_firmware_version_regex_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/version_regex/test_command_stack_commands_list_firmware_version_regex_plugin_basic.py deleted file mode 100644 index fa5d72e11..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/list/firmware/version_regex/test_command_stack_commands_list_firmware_version_regex_plugin_basic.py +++ /dev/null @@ -1,41 +0,0 @@ -from unittest.mock import create_autospec, ANY -import pytest -from stack.commands import DatabaseConnection -from stack.commands.list.firmware import Command -from stack.commands.list.firmware.version_regex.plugin_basic import Plugin - -class TestListFirmwareVersionRegexBasicPlugin: - """A test case for the list firmware version_regex basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - def test_run(self, basic_plugin): - """Test that run queries the DB as expected.""" - basic_plugin.owner.db.select.return_value = [["foo", "bar", "baz"]] - expected_results = { - "keys": ["name", "regex", "description"], - "values": [(row[0], row[1:]) for row in basic_plugin.owner.db.select.return_value], - } - - assert expected_results == basic_plugin.run(args = "unused") - - basic_plugin.owner.db.select.assert_called_once_with(ANY) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/list/host/firmware/mapping/test_command_stack_commands_list_host_firmware_mapping_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/list/host/firmware/mapping/test_command_stack_commands_list_host_firmware_mapping_plugin_basic.py deleted file mode 100644 index a2ec90253..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/list/host/firmware/mapping/test_command_stack_commands_list_host_firmware_mapping_plugin_basic.py +++ /dev/null @@ -1,79 +0,0 @@ -from unittest.mock import create_autospec -import pytest -from stack.commands import DatabaseConnection -from stack.commands.list.host.firmware.mapping import Command -from stack.commands.list.host.firmware.mapping.plugin_basic import Plugin - -@pytest.fixture -def basic_plugin(): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - -def test_provides(basic_plugin): - """Test that the provides returns the basic value.""" - assert basic_plugin.provides() == "basic" - -@pytest.mark.parametrize("hosts", ("", "backend-0-0", "backend-0-1", "backend-0-0 backend-0-1")) -@pytest.mark.parametrize( - "make, model, versions", - ( - ("", "", ""), - ("mellanox", "", ""), - ("mellanox", "m7800", ""), - ("mellanox", "m7800", "1.2.3"), - ("dell", "", ""), - ("dell", "x1052-software", ""), - ("dell", "x1052-software", "1.2.3.4"), - ), -) -@pytest.mark.parametrize("sort", ("nodes.Name", "firmware_make.name", "firmware_model.name", "firmware.version",)) -def test_get_firmware_mappings( - hosts, - make, - model, - versions, - sort, - basic_plugin, -): - """Test that get_firmware_mappings filters correctly based on provided arguments.""" - # Run the function. - basic_plugin.get_firmware_mappings( - hosts = hosts, - versions = versions, - make = make, - model = model, - sort = sort, - ) - - query_string = basic_plugin.owner.db.select.call_args[0][0] - query_args = basic_plugin.owner.db.select.call_args[0][1] - # Assert we find the expected where clause and query args. - if hosts: - assert "nodes.Name IN %s" in query_string - assert hosts in query_args - - if make: - assert "firmware_make.name=%s" in query_string - assert make in query_args - - if model: - assert "firmware_model.name=%s" in query_string - assert model in query_args - - if versions: - assert "firmware.version IN %s" in query_string - assert versions in query_args - - assert sort in query_string diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/imp/test_command_stack_commands_remove_firmware_imp__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/imp/test_command_stack_commands_remove_firmware_imp__init__.py deleted file mode 100644 index fa680c182..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/imp/test_command_stack_commands_remove_firmware_imp__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.remove.firmware.imp import Command - -class TestRemoveFirmwareImpCommand: - """A test case to hold the tests for the remove firmware imp stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - - command.run(params = "unused", args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = mock_args) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/imp/test_command_stack_commands_remove_firmware_imp_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/imp/test_command_stack_commands_remove_firmware_imp_plugin_basic.py deleted file mode 100644 index 48baf57eb..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/imp/test_command_stack_commands_remove_firmware_imp_plugin_basic.py +++ /dev/null @@ -1,67 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch -import pytest -from pymysql import IntegrityError -from stack.commands import DatabaseConnection -from stack.commands.remove.firmware import Command -from stack.exception import ArgRequired, CommandError -from stack.commands.remove.firmware.imp.plugin_basic import Plugin - -class TestRemoveFirmwareImpBasicPlugin: - """A test case for the remove firmware make version_regex basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - @patch(target = "stack.commands.remove.firmware.imp.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.imp.plugin_basic.unique_everseen", autospec = True) - def test_run(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run updates the database as expected when all arguments are valid.""" - mock_args = ["foo", "bar", "baz"] - expected_args = tuple(mock_args) - mock_unique_everseen.return_value = (arg for arg in mock_args) - - basic_plugin.run(args = mock_args) - - basic_plugin.owner.db.execute.assert_called_once_with(ANY, (expected_args,)) - mock_lowered.assert_called_once_with(mock_args) - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.ensure_imps_exist.assert_called_once_with(imps = expected_args) - - @patch(target = "stack.commands.remove.firmware.imp.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.imp.plugin_basic.unique_everseen", autospec = True) - def test_run_imps_do_not_exist(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run fails if ensure imps exist fails.""" - basic_plugin.owner.ensure_imps_exist.side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = ["foo", "bar", "baz"]) - - # Make sure the DB is not modified with bad arguments. - basic_plugin.owner.db.execute.assert_not_called() - - @patch(target = "stack.commands.remove.firmware.imp.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.imp.plugin_basic.unique_everseen", autospec = True) - def test_run_integrity_error(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run fails if there is an integrity error and turns it into a command error.""" - basic_plugin.owner.db.execute.side_effect = IntegrityError("Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = ["foo", "bar", "baz"]) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/test_command_stack_commands_remove_firmware_make__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/test_command_stack_commands_remove_firmware_make__init__.py deleted file mode 100644 index f666f814d..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/test_command_stack_commands_remove_firmware_make__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.remove.firmware.make import Command - -class TestRemoveFirmwareMakeCommand: - """A test case to hold the tests for the remove firmware make stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - - command.run(params = "unused", args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = mock_args) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/test_command_stack_commands_remove_firmware_make_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/test_command_stack_commands_remove_firmware_make_plugin_basic.py deleted file mode 100644 index 63d26f6ab..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/test_command_stack_commands_remove_firmware_make_plugin_basic.py +++ /dev/null @@ -1,86 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch, call -import pytest -from pymysql import IntegrityError -from stack.commands import DatabaseConnection -from stack.commands.remove.firmware import Command -from stack.exception import ArgRequired, CommandError -from stack.commands.remove.firmware.make.plugin_basic import Plugin - -class TestRemoveFirmwareMakeBasicPlugin: - """A test case for the remove firmware make basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - def test_remove_related_models(self, basic_plugin): - """Test that remove_related_models works as expected.""" - mock_makes = ["foo", "bar", "baz"] - mock_models = [["fizz"], ["buzz"]] - expected_models = [mock_model[0] for mock_model in mock_models] - basic_plugin.owner.db.select.return_value = mock_models - - basic_plugin.remove_related_models(makes = mock_makes) - - assert [call(ANY, make) for make in mock_makes] == basic_plugin.owner.db.select.mock_calls - assert [ - call(command = "remove.firmware.model", args = [*expected_models, f"make={make}"]) for make in mock_makes - ] == basic_plugin.owner.call.mock_calls - - def test_remove_related_models_no_models(self, basic_plugin): - """Test that remove_related_models works as expected when no models exist for the makes.""" - mock_makes = ["foo", "bar", "baz"] - mock_models = [] - basic_plugin.owner.db.select.return_value = mock_models - - basic_plugin.remove_related_models(makes = mock_makes) - - assert [call(ANY, make) for make in mock_makes] == basic_plugin.owner.db.select.mock_calls - basic_plugin.owner.call.assert_not_called() - - @patch.object(target = Plugin, attribute = "remove_related_models", autospec = True) - @patch(target = "stack.commands.remove.firmware.make.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.make.plugin_basic.unique_everseen", autospec = True) - def test_run(self, mock_unique_everseen, mock_lowered, mock_remove_related_models, basic_plugin): - """Test that run updates the database as expected when all arguments are valid.""" - mock_args = ["foo", "bar", "baz"] - expected_args = tuple(mock_args) - mock_unique_everseen.return_value = (arg for arg in mock_args) - - basic_plugin.run(args = mock_args) - - basic_plugin.owner.db.execute.assert_called_once_with(ANY, (expected_args,)) - mock_lowered.assert_called_once_with(mock_args) - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.ensure_makes_exist.assert_called_once_with(makes = expected_args) - mock_remove_related_models.assert_called_once_with(basic_plugin, makes = expected_args) - - @patch.object(target = Plugin, attribute = "remove_related_models", autospec = True) - @patch(target = "stack.commands.remove.firmware.make.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.make.plugin_basic.unique_everseen", autospec = True) - def test_run_makes_do_not_exist(self, mock_unique_everseen, mock_lowered, mock_remove_related_models, basic_plugin): - """Test that run fails if ensure makes exist fails.""" - basic_plugin.owner.ensure_makes_exist.side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = ["foo", "bar", "baz"]) - - # Make sure the DB is not modified with bad arguments. - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/version_regex/test_command_stack_commands_remove_firmware_make_version_regex__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/version_regex/test_command_stack_commands_remove_firmware_make_version_regex__init__.py deleted file mode 100644 index d063733bb..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/version_regex/test_command_stack_commands_remove_firmware_make_version_regex__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.remove.firmware.make.version_regex import Command - -class TestRemoveFirmwareMakeVersionRegexCommand: - """A test case to hold the tests for the remove firmware make version_regex stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - - command.run(params = "unused", args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = mock_args) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/version_regex/test_command_stack_commands_remove_firmware_make_version_regex_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/version_regex/test_command_stack_commands_remove_firmware_make_version_regex_plugin_basic.py deleted file mode 100644 index 0534cf58a..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/make/version_regex/test_command_stack_commands_remove_firmware_make_version_regex_plugin_basic.py +++ /dev/null @@ -1,67 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch -import pytest -from stack.commands import DatabaseConnection -from stack.commands.remove.firmware import Command -from stack.exception import ArgRequired, CommandError -from stack.commands.remove.firmware.make.version_regex.plugin_basic import Plugin - -class TestRemoveFirmwareMakeVersionRegexBasicPlugin: - """A test case for the remove firmware make version_regex basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - @patch(target = "stack.commands.remove.firmware.make.version_regex.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.make.version_regex.plugin_basic.unique_everseen", autospec = True) - def test_run(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run updates the database as expected when all arguments are valid.""" - mock_args = ["foo", "bar", "baz"] - expected_args = tuple(mock_args) - mock_unique_everseen.return_value = (arg for arg in mock_args) - - basic_plugin.run(args = mock_args) - - basic_plugin.owner.db.execute.assert_called_once_with(ANY, (expected_args,)) - mock_lowered.assert_called_once_with(mock_args) - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.ensure_makes_exist.assert_called_once_with(makes = expected_args) - - @patch(target = "stack.commands.remove.firmware.make.version_regex.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.make.version_regex.plugin_basic.unique_everseen", autospec = True) - def test_run_missing_args(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run fails if no args are passed.""" - with pytest.raises(ArgRequired): - basic_plugin.run(args = []) - - # Make sure the DB is not modified with bad arguments. - basic_plugin.owner.db.execute.assert_not_called() - - @patch(target = "stack.commands.remove.firmware.make.version_regex.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.make.version_regex.plugin_basic.unique_everseen", autospec = True) - def test_run_makes_do_not_exist(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run fails if no ensure makes exist fails.""" - basic_plugin.owner.ensure_makes_exist.side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = ["foo", "bar", "baz"]) - - # Make sure the DB is not modified with bad arguments. - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/test_command_stack_commands_remove_firmware_model__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/test_command_stack_commands_remove_firmware_model__init__.py deleted file mode 100644 index b00889409..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/test_command_stack_commands_remove_firmware_model__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.remove.firmware.model import Command - -class TestRemoveFirmwareModelCommand: - """A test case to hold the tests for the remove firmware model stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"fizz": "buzz"} - - command.run(params = mock_params, args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/test_command_stack_commands_remove_firmware_model_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/test_command_stack_commands_remove_firmware_model_plugin_basic.py deleted file mode 100644 index 9a1f532ef..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/test_command_stack_commands_remove_firmware_model_plugin_basic.py +++ /dev/null @@ -1,106 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch, call -import pytest -from pymysql import IntegrityError -from stack.commands import DatabaseConnection -from stack.commands.remove.firmware import Command -from stack.exception import ArgRequired, CommandError -from stack.commands.remove.firmware.model.plugin_basic import Plugin - -class TestRemoveFirmwareModelBasicPlugin: - """A test case for the remove firmware model basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - def test_remove_related_firmware(self, basic_plugin): - """Test that remove_related_firmware works as expected.""" - mock_make = "bam!" - mock_models = ["foo", "bar", "baz"] - mock_firmwares = [["fizz"], ["buzz"]] - expected_firmwares = [mock_firmware[0] for mock_firmware in mock_firmwares] - basic_plugin.owner.db.select.return_value = mock_firmwares - - basic_plugin.remove_related_firmware(make = mock_make, models = mock_models) - - assert [call(ANY, (mock_make, model)) for model in mock_models] == basic_plugin.owner.db.select.mock_calls - assert [ - call( - command = "remove.firmware", - args = [*expected_firmwares, f"make={mock_make}", f"model={model}"], - ) - for model in mock_models - ] == basic_plugin.owner.call.mock_calls - - def test_remove_related_firmware_no_firmware(self, basic_plugin): - """Test that remove_related_firmware works as expected when there are no related firmwares.""" - mock_make = "bam!" - mock_models = ["foo", "bar", "baz"] - mock_firmwares = [] - basic_plugin.owner.db.select.return_value = mock_firmwares - - basic_plugin.remove_related_firmware(make = mock_make, models = mock_models) - - assert [call(ANY, (mock_make, model)) for model in mock_models] == basic_plugin.owner.db.select.mock_calls - basic_plugin.owner.call.assert_not_called() - - @patch.object(target = Plugin, attribute = "remove_related_firmware", autospec = True) - @patch(target = "stack.commands.remove.firmware.model.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.model.plugin_basic.unique_everseen", autospec = True) - def test_run(self, mock_unique_everseen, mock_lowered, mock_remove_related_firmware, basic_plugin): - """Test that run updates the database as expected when all arguments are valid.""" - mock_args = ["foo", "bar", "baz"] - expected_args = tuple(mock_args) - mock_params = {"make": "fizz"} - mock_unique_everseen.return_value = (arg for arg in mock_args) - mock_lowered.return_value = mock_params.values() - - basic_plugin.run(args = (mock_params, mock_args)) - - basic_plugin.owner.db.execute.assert_called_once_with(ANY, (expected_args, mock_params["make"])) - assert [call(basic_plugin.owner.fillParams.return_value), call(mock_args)] == mock_lowered.mock_calls - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.ensure_models_exist.assert_called_once_with( - make = mock_params["make"], - models = expected_args, - ) - mock_remove_related_firmware.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - models = expected_args, - ) - - @patch.object(target = Plugin, attribute = "remove_related_firmware", autospec = True) - @patch(target = "stack.commands.remove.firmware.model.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.model.plugin_basic.unique_everseen", autospec = True) - def test_run_models_do_not_exist(self, mock_unique_everseen, mock_lowered, mock_remove_related_firmware, basic_plugin): - """Test that run fails if ensure models exist fails.""" - mock_args = ["foo", "bar", "baz"] - expected_args = tuple(mock_args) - mock_params = {"make": "fizz"} - mock_unique_everseen.return_value = (arg for arg in mock_args) - mock_lowered.return_value = mock_params.values() - basic_plugin.owner.ensure_models_exist.side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = (mock_params, mock_args)) - - # model sure the DB is not modified with bad arguments. - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/version_regex/test_command_stack_commands_remove_firmware_model_version_regex__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/version_regex/test_command_stack_commands_remove_firmware_model_version_regex__init__.py deleted file mode 100644 index a12f853ea..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/version_regex/test_command_stack_commands_remove_firmware_model_version_regex__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.remove.firmware.model.version_regex import Command - -class TestRemoveFirmwareModelVersionRegexCommand: - """A test case to hold the tests for the remove firmware model version_regex stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"fizz": "buzz"} - - command.run(params = mock_params, args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/version_regex/test_command_stack_commands_remove_firmware_model_version_regex_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/version_regex/test_command_stack_commands_remove_firmware_model_version_regex_plugin_basic.py deleted file mode 100644 index 4b7952706..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/model/version_regex/test_command_stack_commands_remove_firmware_model_version_regex_plugin_basic.py +++ /dev/null @@ -1,75 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch, MagicMock, call -import pytest -from stack.commands import DatabaseConnection -from stack.commands.remove.firmware import Command -from stack.exception import ArgRequired, ParamRequired, ParamError, CommandError -from stack.commands.remove.firmware.model.version_regex.plugin_basic import Plugin - -class TestRemoveFirmwareModelVersionRegexBasicPlugin: - """A test case for the remove firmware model version_regex basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns "basic".""" - assert basic_plugin.provides() == "basic" - - @patch(target = "stack.commands.remove.firmware.model.version_regex.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.model.version_regex.plugin_basic.unique_everseen", autospec = True) - def test_run(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run updates the database as expected when all arguments are valid.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"make": "fizz"} - expected_args = tuple(mock_args) - expected_param = mock_params["make"] - mock_unique_everseen.return_value = (arg for arg in mock_args) - # Since we don't care about the first return value, as it should be passed directly into unique_everseen(), - # we just use a MagicMock so we can do equality testing later in assert_called_once_with(). - first_lowered_return = MagicMock() - mock_lowered.side_effect = ( - first_lowered_return, - (make for make in mock_params.values()), - ) - - basic_plugin.run(args = (mock_params, mock_args)) - - # Ensure that the database update was performed - basic_plugin.owner.db.execute.assert_called_once_with(ANY, (expected_args, expected_param)) - # Ensure that the expected calls happened for validating args and params. - assert [call(mock_args), call(basic_plugin.owner.fillParams.return_value)] == mock_lowered.mock_calls - mock_unique_everseen.assert_called_once_with(first_lowered_return) - basic_plugin.owner.ensure_models_exist.assert_called_once_with(models = expected_args, make = expected_param) - - @patch(target = "stack.commands.remove.firmware.model.version_regex.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.model.version_regex.plugin_basic.unique_everseen", autospec = True) - def test_run_errors( - self, - mock_unique_everseen, - mock_lowered, - basic_plugin, - ): - """Test that run fails if the arguments do not validate.""" - # Set the appropriate mock function to fail. - basic_plugin.owner.ensure_models_exist.side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test failure") - mock_lowered.return_value = ("foo",) - - with pytest.raises(CommandError): - basic_plugin.run(args = ({"foo": "bar"}, ["baz"])) - - # Ensure that the database update was not performed due to argument and/or param errors - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/test_command_stack_commands_remove_firmware__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/test_command_stack_commands_remove_firmware__init__.py deleted file mode 100644 index e2ee9a65f..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/test_command_stack_commands_remove_firmware__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.remove.firmware import Command - -class TestRemoveFirmwareCommand: - """A test case to hold the tests for the remove firmware stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"make": "fizz", "model": "buzz"} - - command.run(params = mock_params, args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/test_command_stack_commands_remove_firmware_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/test_command_stack_commands_remove_firmware_plugin_basic.py deleted file mode 100644 index d9ce31dbf..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/test_command_stack_commands_remove_firmware_plugin_basic.py +++ /dev/null @@ -1,185 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch, call -import pytest -from pymysql import IntegrityError -from stack.commands import DatabaseConnection -from stack.commands.remove.firmware import Command -from stack.exception import ArgRequired, CommandError -from stack.commands.remove.firmware.plugin_basic import Plugin - -class TestRemoveFirmwareBasicPlugin: - """A test case for the remove firmware make version_regex basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - @pytest.mark.parametrize( - "make, model, versions", - ( - ("", "", []), - ("foo", "", []), - ("foo", "bar", []), - ("foo", "bar", ["fizz", "buzz"]) - ), - ) - def test_validate_inputs(self, make, model, versions, basic_plugin): - """Test that validate inputs works as expected when all inputs are valid.""" - basic_plugin.validate_inputs(make = make, model = model, versions = versions) - - if make: - basic_plugin.owner.ensure_make_exists.assert_called_once_with( - make = make, - ) - else: - basic_plugin.owner.ensure_make_exists.assert_not_called() - - if model: - basic_plugin.owner.ensure_model_exists.assert_called_once_with( - make = make, - model = model, - ) - else: - basic_plugin.owner.ensure_model_exists.assert_not_called() - - if versions: - basic_plugin.owner.ensure_firmwares_exist.assert_called_once_with( - make = make, - model = model, - versions = versions, - ) - else: - basic_plugin.owner.ensure_firmwares_exist.assert_not_called() - - @pytest.mark.parametrize("failure_mock", ("ensure_make_exists", "ensure_model_exists", "ensure_firmwares_exist")) - def test_validate_inputs_errors(self, failure_mock, basic_plugin): - """Test that validate inputs works as expected when all inputs are valid.""" - mock_validation_functions = { - "ensure_make_exists": basic_plugin.owner.ensure_make_exists, - "ensure_model_exists": basic_plugin.owner.ensure_model_exists, - "ensure_firmwares_exist": basic_plugin.owner.ensure_firmwares_exist, - } - mock_validation_functions[failure_mock].side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.validate_inputs(make = "foo", model = "bar", versions = ["baz"]) - - @pytest.mark.parametrize( - "mock_make, mock_model, mock_versions, expected_query_params", - ( - ("", "", [], []), - ("foo", "", [], ["foo"]), - ("foo", "bar", [], ["foo", "bar"]), - ("foo", "bar", ["fizz", "buzz"], [["fizz", "buzz"], "foo", "bar"]), - ), - ) - def test_build_query( - self, - mock_make, - mock_model, - mock_versions, - expected_query_params, - basic_plugin, - ): - """Test that build query builds up the query as expected based on input arguments.""" - query, query_params = basic_plugin.build_query( - make = mock_make, - model = mock_model, - versions = mock_versions, - ) - - assert expected_query_params == query_params - - @patch.object(target = Plugin, attribute = "build_query", autospec = True) - @patch.object(target = Plugin, attribute = "validate_inputs", autospec = True) - @patch(target = "stack.commands.remove.firmware.plugin_basic.Path", autospec = True) - @patch(target = "stack.commands.remove.firmware.plugin_basic.unique_everseen", autospec = True) - @patch(target = "stack.commands.remove.firmware.plugin_basic.lowered", autospec = True) - def test_run( - self, - mock_lowered, - mock_unique_everseen, - mock_path, - mock_validate_inputs, - mock_build_query, - basic_plugin, - ): - """Test that run works as expected when the params and args are valid.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"make": "fizz", "model": "buzz"} - expected_versions = tuple(mock_args) - mock_unique_everseen.return_value = expected_versions - mock_lowered.return_value = mock_params.values() - mock_query = "amockquery" - mock_query_params = ["aparam", "anotherparam"] - mock_build_query.return_value = (mock_query, mock_query_params) - basic_plugin.owner.db.select.return_value = [["id", "path"]] - - basic_plugin.run(args = (mock_params, mock_args)) - - assert [call(mock_args), call(basic_plugin.owner.fillParams.return_value)] == mock_lowered.mock_calls - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - mock_validate_inputs.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - model = mock_params["model"], - versions = expected_versions, - ) - mock_build_query.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - model = mock_params["model"], - versions = expected_versions, - ) - basic_plugin.owner.db.select.assert_called_once_with(mock_query, mock_query_params) - chained = call(basic_plugin.owner.db.select.return_value[0][1]).resolve(strict = True).unlink() - assert chained.call_list() == mock_path.mock_calls - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - basic_plugin.owner.db.select.return_value[0][0], - ) - - @patch.object(target = Plugin, attribute = "build_query", autospec = True) - @patch.object(target = Plugin, attribute = "validate_inputs", autospec = True) - @patch(target = "stack.commands.remove.firmware.plugin_basic.Path", autospec = True) - @patch(target = "stack.commands.remove.firmware.plugin_basic.unique_everseen", autospec = True) - @patch(target = "stack.commands.remove.firmware.plugin_basic.lowered", autospec = True) - def test_run_errors( - self, - mock_lowered, - mock_unique_everseen, - mock_path, - mock_validate_inputs, - mock_build_query, - basic_plugin, - ): - """Test that run fails when params and/or args are invalid.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"make": "fizz", "model": "buzz"} - expected_versions = tuple(mock_args) - mock_unique_everseen.return_value = expected_versions - mock_lowered.return_value = mock_params.values() - mock_query = "amockquery" - mock_query_params = ["aparam", "anotherparam"] - mock_build_query.return_value = (mock_query, mock_query_params) - basic_plugin.owner.db.select.return_value = [["id", "path"]] - mock_validate_inputs.side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/version_regex/test_command_stack_commands_remove_firmware_version_regex__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/version_regex/test_command_stack_commands_remove_firmware_version_regex__init__.py deleted file mode 100644 index 3585b4269..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/version_regex/test_command_stack_commands_remove_firmware_version_regex__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.remove.firmware.version_regex import Command - -class TestRemoveFirmwareVersionRegexCommand: - """A test case to hold the tests for the remove firmware version_regex stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - - command.run(params = "unused", args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = mock_args) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/version_regex/test_command_stack_commands_remove_firmware_version_regex_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/version_regex/test_command_stack_commands_remove_firmware_version_regex_plugin_basic.py deleted file mode 100644 index 4040351a6..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/firmware/version_regex/test_command_stack_commands_remove_firmware_version_regex_plugin_basic.py +++ /dev/null @@ -1,57 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch -import pytest -from stack.commands import DatabaseConnection -from stack.commands.remove.firmware import Command -from stack.exception import ArgRequired, CommandError -from stack.commands.remove.firmware.version_regex.plugin_basic import Plugin - -class TestRemoveFirmwareVersionRegexBasicPlugin: - """A test case for the remove firmware version_regex basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - @patch(target = "stack.commands.remove.firmware.version_regex.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.version_regex.plugin_basic.unique_everseen", autospec = True) - def test_run(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run updates the database as expected when all arguments are valid.""" - mock_args = ["foo", "bar", "baz"] - expected_args = tuple(mock_args) - mock_unique_everseen.return_value = (arg for arg in mock_args) - - basic_plugin.run(args = mock_args) - - basic_plugin.owner.db.execute.assert_called_once_with(ANY, (expected_args,)) - mock_lowered.assert_called_once_with(mock_args) - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.ensure_version_regexes_exist.assert_called_once_with(names = expected_args) - - @patch(target = "stack.commands.remove.firmware.version_regex.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.firmware.version_regex.plugin_basic.unique_everseen", autospec = True) - def test_run_regexes_do_not_exist(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run fails if no ensure_version_regexes_exist fails.""" - basic_plugin.owner.ensure_version_regexes_exist.side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = ["foo", "bar", "baz"]) - - # sure the DB is not modified with bad arguments. - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/host/firmware/mapping/test_command_stack_commands_remove_host_firmware_mapping__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/host/firmware/mapping/test_command_stack_commands_remove_host_firmware_mapping__init__.py deleted file mode 100644 index 929665a89..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/host/firmware/mapping/test_command_stack_commands_remove_host_firmware_mapping__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.remove.host.firmware.mapping import Command - -class TestRemoveHostFirmwareMappingCommand: - """A test case to hold the tests for the remove host firmware mapping stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"fizz": "buzz"} - - command.run(params = mock_params, args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/remove/host/firmware/mapping/test_command_stack_commands_remove_host_firmware_mapping_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/remove/host/firmware/mapping/test_command_stack_commands_remove_host_firmware_mapping_plugin_basic.py deleted file mode 100644 index 58fd78b31..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/remove/host/firmware/mapping/test_command_stack_commands_remove_host_firmware_mapping_plugin_basic.py +++ /dev/null @@ -1,351 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch, call -import pytest -from stack.commands import DatabaseConnection -from stack.commands.remove.host.firmware.mapping import Command -from stack.exception import CommandError -from stack.commands.remove.host.firmware.mapping.plugin_basic import Plugin - -class TestRemoveHostFirmwareMappingBasicPlugin: - """A test case for the remove host firmware mapping basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - def test_validate_make(self, basic_plugin): - """Ensure the make is validated if it exists.""" - mock_make = "foo" - - basic_plugin.validate_make(make = mock_make) - - basic_plugin.owner.ensure_make_exists.assert_called_once_with( - make = mock_make, - ) - - def test_validate_make_not_provided(self, basic_plugin): - """Ensure the make is not validated if not provided.""" - mock_make = "" - - basic_plugin.validate_make(make = mock_make) - - basic_plugin.owner.ensure_make_exists.assert_not_called() - - def test_validate_make_error(self, basic_plugin): - """Ensure that validation fails when the make is invalid.""" - mock_make = "foo" - basic_plugin.owner.ensure_make_exists.side_effect = CommandError( - cmd = basic_plugin.owner, - msg = "Test error", - ) - - with pytest.raises(CommandError): - basic_plugin.validate_make(make = mock_make) - - def test_validate_model(self, basic_plugin): - """Ensure the model is validated if it exists.""" - mock_make = "foo" - mock_model = "bar" - - basic_plugin.validate_model(make = mock_make, model = mock_model) - - basic_plugin.owner.ensure_model_exists.assert_called_once_with( - make = mock_make, - model = mock_model, - ) - - def test_validate_model_not_provided(self, basic_plugin): - """Ensure the model is not validated if not provided.""" - mock_make = "foo" - mock_model = "" - - basic_plugin.validate_model(make = mock_make, model = mock_model) - - basic_plugin.owner.ensure_model_exists.assert_not_called() - - def test_validate_model_error(self, basic_plugin): - """Ensure that validation fails when the model is invalid.""" - mock_make = "foo" - mock_model = "bar" - basic_plugin.owner.ensure_model_exists.side_effect = CommandError( - cmd = basic_plugin.owner, - msg = "Test error", - ) - - with pytest.raises(CommandError): - basic_plugin.validate_model(make = mock_make, model = mock_model) - - @pytest.mark.parametrize( - "hosts, versions, make, model", - ( - (["foo"], ["bar"], "baz", "bag"), - (["foo"], [], "baz", "bag"), - (["foo"], [], "baz", ""), - (["foo"], [], "", ""), - ([], ["bar"], "baz", "bag"), - ([], [], "baz", "bag"), - ([], [], "baz", ""), - ([], [], "", ""), - ) - ) - def test_get_firmware_mappings_to_remove(self, hosts, versions, make, model, basic_plugin): - """Test that get_firmware_mappings_to_remove works as expected for every valid argument combination.""" - test_inputs = { - "hosts": hosts, - "versions": versions, - "make": make, - "model": model, - } - basic_plugin.owner.db.select.return_value = [["1"]] - expected_query_params = list(value for value in test_inputs.values() if value) - - assert [basic_plugin.owner.db.select.return_value[0][0]] == basic_plugin.get_firmware_mappings_to_remove(**test_inputs) - basic_plugin.owner.db.select.assert_called_once_with(ANY, expected_query_params) - - @patch.object(target = Plugin, attribute = "get_firmware_mappings_to_remove", autospec = True) - @patch.object(target = Plugin, attribute = "validate_model", autospec = True) - @patch.object(target = Plugin, attribute = "validate_make", autospec = True) - @patch(target = "stack.commands.remove.host.firmware.mapping.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.host.firmware.mapping.plugin_basic.unique_everseen", autospec = True) - def test_run( - self, - mock_unique_everseen, - mock_lowered, - mock_validate_make, - mock_validate_model, - mock_get_firmware_mappings_to_remove, - basic_plugin, - ): - """Test that run works as expected when all params and args are provided and valid.""" - mock_args = ["foo", "bar"] - expected_hosts = tuple(mock_args) - mock_params = {"make": "fizz", "model": "buzz", "versions": "bazz, bang"} - expected_versions = tuple(version.strip() for version in mock_params["versions"].split(",") if version.strip()) - mock_lowered.return_value = mock_params.values() - mock_unique_everseen.side_effect = ( - mock_args, - expected_versions, - ) - basic_plugin.owner.getHosts.return_value = expected_hosts - mock_get_firmware_mappings_to_remove.return_value = ["1", "2"] - - basic_plugin.run(args = (mock_params, mock_args)) - - assert [call(mock_args), call(basic_plugin.owner.fillParams.return_value)] == mock_lowered.mock_calls - mock_unique_everseen.assert_any_call(mock_lowered.return_value) - # Check the generator expression passed to the second call of unique_everseen - assert tuple(mock_unique_everseen.call_args_list[1][0][0]) == expected_versions - basic_plugin.owner.getHosts.assert_called_once_with(args = expected_hosts) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [ - ("make", ""), - ("model", ""), - ("versions", ""), - ], - params = mock_params, - ) - mock_validate_make.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - ) - mock_validate_model.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - model = mock_params["model"], - ) - basic_plugin.owner.ensure_firmwares_exist.assert_called_once_with( - make = mock_params["make"], - model = mock_params["model"], - versions = expected_versions, - ) - mock_get_firmware_mappings_to_remove.assert_called_once_with( - basic_plugin, - hosts = expected_hosts, - make = mock_params["make"], - model = mock_params["model"], - versions = expected_versions, - ) - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - (mock_get_firmware_mappings_to_remove.return_value,), - ) - - @patch.object(target = Plugin, attribute = "get_firmware_mappings_to_remove", autospec = True) - @patch.object(target = Plugin, attribute = "validate_model", autospec = True) - @patch.object(target = Plugin, attribute = "validate_make", autospec = True) - @patch(target = "stack.commands.remove.host.firmware.mapping.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.host.firmware.mapping.plugin_basic.unique_everseen", autospec = True) - def test_run_no_hosts_or_versions( - self, - mock_unique_everseen, - mock_lowered, - mock_validate_make, - mock_validate_model, - mock_get_firmware_mappings_to_remove, - basic_plugin, - ): - """Test that run works as expected when hosts and versions are not provided.""" - mock_args = [] - expected_hosts = tuple(mock_args) - mock_params = {"make": "fizz", "model": "buzz", "versions": ""} - mock_lowered.return_value = mock_params.values() - mock_unique_everseen.return_value = mock_args - mock_get_firmware_mappings_to_remove.return_value = ["1", "2"] - - basic_plugin.run(args = (mock_params, mock_args)) - - assert [call(mock_args), call(basic_plugin.owner.fillParams.return_value)] == mock_lowered.mock_calls - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.getHosts.assert_not_called() - basic_plugin.owner.fillParams.assert_called_once_with( - names = [ - ("make", ""), - ("model", ""), - ("versions", ""), - ], - params = mock_params, - ) - mock_validate_make.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - ) - mock_validate_model.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - model = mock_params["model"], - ) - basic_plugin.owner.ensure_firmwares_exist.assert_not_called() - mock_get_firmware_mappings_to_remove.assert_called_once_with( - basic_plugin, - hosts = expected_hosts, - make = mock_params["make"], - model = mock_params["model"], - versions = mock_params["versions"], - ) - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - (mock_get_firmware_mappings_to_remove.return_value,), - ) - - @patch.object(target = Plugin, attribute = "get_firmware_mappings_to_remove", autospec = True) - @patch.object(target = Plugin, attribute = "validate_model", autospec = True) - @patch.object(target = Plugin, attribute = "validate_make", autospec = True) - @patch(target = "stack.commands.remove.host.firmware.mapping.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.host.firmware.mapping.plugin_basic.unique_everseen", autospec = True) - def test_run_no_mappings_to_remove( - self, - mock_unique_everseen, - mock_lowered, - mock_validate_make, - mock_validate_model, - mock_get_firmware_mappings_to_remove, - basic_plugin, - ): - """Test that run works as expected when there are no mappings to remove.""" - mock_args = ["foo", "bar"] - expected_hosts = tuple(mock_args) - mock_params = {"make": "fizz", "model": "buzz", "versions": "bazz, bang"} - expected_versions = tuple(version.strip() for version in mock_params["versions"].split(",") if version.strip()) - mock_lowered.return_value = mock_params.values() - mock_unique_everseen.side_effect = ( - mock_args, - expected_versions, - ) - basic_plugin.owner.getHosts.return_value = expected_hosts - mock_get_firmware_mappings_to_remove.return_value = [] - - basic_plugin.run(args = (mock_params, mock_args)) - - assert [call(mock_args), call(basic_plugin.owner.fillParams.return_value)] == mock_lowered.mock_calls - mock_unique_everseen.assert_any_call(mock_lowered.return_value) - # Check the generator expression passed to the second call of unique_everseen - assert tuple(mock_unique_everseen.call_args_list[1][0][0]) == expected_versions - basic_plugin.owner.getHosts.assert_called_once_with(args = expected_hosts) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [ - ("make", ""), - ("model", ""), - ("versions", ""), - ], - params = mock_params, - ) - mock_validate_make.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - ) - mock_validate_model.assert_called_once_with( - basic_plugin, - make = mock_params["make"], - model = mock_params["model"], - ) - basic_plugin.owner.ensure_firmwares_exist.assert_called_once_with( - make = mock_params["make"], - model = mock_params["model"], - versions = expected_versions, - ) - mock_get_firmware_mappings_to_remove.assert_called_once_with( - basic_plugin, - hosts = expected_hosts, - make = mock_params["make"], - model = mock_params["model"], - versions = expected_versions, - ) - basic_plugin.owner.db.execute.assert_not_called() - - @pytest.mark.parametrize("failure_mock", ("validate_make", "validate_model", "ensure_firmwares_exist")) - @patch.object(target = Plugin, attribute = "get_firmware_mappings_to_remove", autospec = True) - @patch.object(target = Plugin, attribute = "validate_model", autospec = True) - @patch.object(target = Plugin, attribute = "validate_make", autospec = True) - @patch(target = "stack.commands.remove.host.firmware.mapping.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.remove.host.firmware.mapping.plugin_basic.unique_everseen", autospec = True) - def test_run_errors( - self, - mock_unique_everseen, - mock_lowered, - mock_validate_make, - mock_validate_model, - mock_get_firmware_mappings_to_remove, - failure_mock, - basic_plugin, - ): - """Test that run fails when the params or args are invalid.""" - mock_args = ["foo", "bar"] - expected_hosts = tuple(mock_args) - mock_params = {"make": "fizz", "model": "buzz", "versions": "bazz, bang"} - expected_versions = tuple(version.strip() for version in mock_params["versions"].split(",") if version.strip()) - mock_lowered.return_value = mock_params.values() - mock_unique_everseen.side_effect = ( - mock_args, - expected_versions, - ) - basic_plugin.owner.getHosts.return_value = expected_hosts - mock_validation_functions = { - "validate_make": mock_validate_make, - "validate_model": mock_validate_model, - "ensure_firmwares_exist": basic_plugin.owner.ensure_firmwares_exist, - } - mock_validation_functions[failure_mock].side_effect = CommandError( - cmd = basic_plugin.owner, - msg = "test error", - ) - - with pytest.raises(CommandError): - basic_plugin.run(args = (mock_params, mock_args)) - - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/make/version_regex/test_command_stack_commands_set_firmware_make_version_regex__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/make/version_regex/test_command_stack_commands_set_firmware_make_version_regex__init__.py deleted file mode 100644 index d7cf8659f..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/make/version_regex/test_command_stack_commands_set_firmware_make_version_regex__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.set.firmware.make.version_regex import Command - -class TestSetFirmwareMakeVersionRegexCommand: - """A test case to hold the tests for the set firmware make version_regex stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_params = {"fizz": "buzz"} - mock_args = ["foo", "bar", "baz"] - - command.run(params = mock_params, args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/make/version_regex/test_command_stack_commands_set_firmware_make_version_regex_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/make/version_regex/test_command_stack_commands_set_firmware_make_version_regex_plugin_basic.py deleted file mode 100644 index efc4a0532..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/make/version_regex/test_command_stack_commands_set_firmware_make_version_regex_plugin_basic.py +++ /dev/null @@ -1,77 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch, call -import pytest -from stack.commands import DatabaseConnection -from stack.commands.set.firmware.make.version_regex import Command -from stack.exception import CommandError -from stack.commands.set.firmware.make.version_regex.plugin_basic import Plugin - -class TestSetFirmwareMakeVersionRegexBasicPlugin: - """A test case for the set firmware make version_regex basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - @patch(target = "stack.commands.set.firmware.make.version_regex.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.set.firmware.make.version_regex.plugin_basic.unique_everseen", autospec = True) - def test_run(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run updates the database as expected when all arguments are valid.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"version_regex": "mock_name"} - expected_args = tuple(mock_args) - mock_unique_everseen.return_value = (arg for arg in mock_args) - mock_lowered.return_value = mock_params.values() - - basic_plugin.run(args = (mock_params, mock_args)) - - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - (basic_plugin.owner.get_version_regex_id.return_value, expected_args), - ) - basic_plugin.owner.get_version_regex_id.assert_called_once_with(name = mock_params["version_regex"]) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [("version_regex", "")], - params = mock_params, - ) - assert [call(mock_args), call(basic_plugin.owner.fillParams.return_value)] == mock_lowered.mock_calls - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.ensure_makes_exist.assert_called_once_with(makes = expected_args) - basic_plugin.owner.ensure_version_regex_exists.assert_called_once_with(name = mock_params["version_regex"]) - - @pytest.mark.parametrize("failure_mock", ("ensure_makes_exist", "ensure_version_regex_exists")) - @patch(target = "stack.commands.set.firmware.make.version_regex.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.set.firmware.make.version_regex.plugin_basic.unique_everseen", autospec = True) - def test_run_missing_args(self, mock_unique_everseen, mock_lowered, failure_mock, basic_plugin): - """Test that run fails when any of the exist* functions fail.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"version_regex": "mock_name"} - mock_unique_everseen.return_value = (arg for arg in mock_args) - mock_lowered.return_value = mock_params.values() - mock_validation_functions = { - "ensure_makes_exist": basic_plugin.owner.ensure_makes_exist, - "ensure_version_regex_exists": basic_plugin.owner.ensure_version_regex_exists, - } - mock_validation_functions[failure_mock].side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = (mock_params, mock_args)) - - # Make sure the DB is not modified with bad arguments. - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/imp/test_command_stack_commands_set_firmware_model_imp__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/imp/test_command_stack_commands_set_firmware_model_imp__init__.py deleted file mode 100644 index cd739bbbc..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/imp/test_command_stack_commands_set_firmware_model_imp__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.set.firmware.model.imp import Command - -class TestSetFirmwareModelImpCommand: - """A test case to hold the tests for the set firmware model imp stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_params = {"fizz": "buzz"} - mock_args = ["foo", "bar", "baz"] - - command.run(params = mock_params, args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/imp/test_command_stack_commands_set_firmware_model_imp_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/imp/test_command_stack_commands_set_firmware_model_imp_plugin_basic.py deleted file mode 100644 index 01794cfc0..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/imp/test_command_stack_commands_set_firmware_model_imp_plugin_basic.py +++ /dev/null @@ -1,77 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch, call -import pytest -from stack.commands import DatabaseConnection -from stack.commands.set.firmware.model.imp import Command -from stack.exception import CommandError -from stack.commands.set.firmware.model.imp.plugin_basic import Plugin - -class TestSetFirmwareModelImpBasicPlugin: - """A test case for the set firmware model imp basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - @patch(target = "stack.commands.set.firmware.model.imp.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.set.firmware.model.imp.plugin_basic.unique_everseen", autospec = True) - def test_run(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run updates the database as expected when all arguments are valid.""" - mock_args = ["foo", "bar", "baz"] - mock_params = { "imp": "mock_imp", "make": "mock_make"} - expected_args = tuple(mock_args) - mock_unique_everseen.return_value = (arg for arg in mock_args) - mock_lowered.return_value = mock_params.values() - - basic_plugin.run(args = (mock_params, mock_args)) - - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - (basic_plugin.owner.get_imp_id.return_value, mock_params["make"], expected_args), - ) - basic_plugin.owner.get_imp_id.assert_called_once_with(imp = mock_params["imp"]) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [("imp", ""), ("make", "")], - params = mock_params, - ) - assert [call(mock_args), call(basic_plugin.owner.fillParams.return_value)] == mock_lowered.mock_calls - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.ensure_models_exist.assert_called_once_with(make = mock_params["make"], models = expected_args) - basic_plugin.owner.ensure_imp_exists.assert_called_once_with(imp = mock_params["imp"]) - - @pytest.mark.parametrize("failure_mock", ("ensure_models_exist", "ensure_imp_exists")) - @patch(target = "stack.commands.set.firmware.model.imp.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.set.firmware.model.imp.plugin_basic.unique_everseen", autospec = True) - def test_run_missing_args(self, mock_unique_everseen, mock_lowered, failure_mock, basic_plugin): - """Test that run fails when any of the exist* functions fail.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"imp": "mock_imp", "make": "mock_make"} - mock_unique_everseen.return_value = (arg for arg in mock_args) - mock_lowered.return_value = mock_params.values() - mock_validation_functions = { - "ensure_models_exist": basic_plugin.owner.ensure_models_exist, - "ensure_imp_exists": basic_plugin.owner.ensure_imp_exists, - } - mock_validation_functions[failure_mock].side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = (mock_params, mock_args)) - - # model sure the DB is not modified with bad arguments. - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/version_regex/test_command_stack_commands_set_firmware_model_version_regex__init__.py b/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/version_regex/test_command_stack_commands_set_firmware_model_version_regex__init__.py deleted file mode 100644 index 8d7e5dea4..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/version_regex/test_command_stack_commands_set_firmware_model_version_regex__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest.mock import patch -import pytest -from stack.commands.set.firmware.model.version_regex import Command - -class TestSetFirmwareModelVersionRegexCommand: - """A test case to hold the tests for the set firmware model version_regex stacki Command class.""" - - class CommandUnderTest(Command): - """A class derived from the Command class under test used to override __init__. - - This allows easier instantiation for testing purposes by excluding the base Command - class initialization code. - """ - def __init__(self): - pass - - @pytest.fixture - def command(self): - """Fixture to create and return a Command class instance for testing.""" - return self.CommandUnderTest() - - @patch.object(target = Command, attribute = "runPlugins", autospec = True) - def test_run(self, mock_runPlugins, command): - """Test that run will run the plugins passing through the args.""" - mock_params = {"fizz": "buzz"} - mock_args = ["foo", "bar", "baz"] - - command.run(params = mock_params, args = mock_args) - - mock_runPlugins.assert_called_once_with(command, args = (mock_params, mock_args)) diff --git a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/version_regex/test_command_stack_commands_set_firmware_model_version_regex_plugin_basic.py b/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/version_regex/test_command_stack_commands_set_firmware_model_version_regex_plugin_basic.py deleted file mode 100644 index b48cb1ff4..000000000 --- a/test-framework/test-suites/unit/tests/command/stack/commands/set/firmware/model/version_regex/test_command_stack_commands_set_firmware_model_version_regex_plugin_basic.py +++ /dev/null @@ -1,77 +0,0 @@ -from unittest.mock import create_autospec, ANY, patch, call -import pytest -from stack.commands import DatabaseConnection -from stack.commands.set.firmware.model.version_regex import Command -from stack.exception import CommandError -from stack.commands.set.firmware.model.version_regex.plugin_basic import Plugin - -class TestSetFirmwareModelVersionRegexBasicPlugin: - """A test case for the set firmware model version_regex basic plugin.""" - - @pytest.fixture - def basic_plugin(self): - """A fixture that returns the plugin instance for use in tests. - - This sets up the required mocks needed to construct the plugin class. - """ - mock_command = create_autospec( - spec = Command, - instance = True, - ) - mock_command.db = create_autospec( - spec = DatabaseConnection, - spec_set = True, - instance = True, - ) - return Plugin(command = mock_command) - - def test_provides(self, basic_plugin): - """Ensure that provides returns 'basic'.""" - assert basic_plugin.provides() == "basic" - - @patch(target = "stack.commands.set.firmware.model.version_regex.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.set.firmware.model.version_regex.plugin_basic.unique_everseen", autospec = True) - def test_run(self, mock_unique_everseen, mock_lowered, basic_plugin): - """Test that run updates the database as expected when all arguments are valid.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"make": "mock_make", "version_regex": "mock_name"} - expected_args = tuple(mock_args) - mock_unique_everseen.return_value = (arg for arg in mock_args) - mock_lowered.return_value = mock_params.values() - - basic_plugin.run(args = (mock_params, mock_args)) - - basic_plugin.owner.db.execute.assert_called_once_with( - ANY, - (basic_plugin.owner.get_version_regex_id.return_value, expected_args, mock_params["make"]), - ) - basic_plugin.owner.get_version_regex_id.assert_called_once_with(name = mock_params["version_regex"]) - basic_plugin.owner.fillParams.assert_called_once_with( - names = [("make", ""), ("version_regex", "")], - params = mock_params, - ) - assert [call(mock_args), call(basic_plugin.owner.fillParams.return_value)] == mock_lowered.mock_calls - mock_unique_everseen.assert_called_once_with(mock_lowered.return_value) - basic_plugin.owner.ensure_models_exist.assert_called_once_with(make = mock_params["make"], models = expected_args) - basic_plugin.owner.ensure_version_regex_exists.assert_called_once_with(name = mock_params["version_regex"]) - - @pytest.mark.parametrize("failure_mock", ("ensure_models_exist", "ensure_version_regex_exists")) - @patch(target = "stack.commands.set.firmware.model.version_regex.plugin_basic.lowered", autospec = True) - @patch(target = "stack.commands.set.firmware.model.version_regex.plugin_basic.unique_everseen", autospec = True) - def test_run_missing_args(self, mock_unique_everseen, mock_lowered, failure_mock, basic_plugin): - """Test that run fails when any of the exist* functions fail.""" - mock_args = ["foo", "bar", "baz"] - mock_params = {"make": "mock_make", "version_regex": "mock_name"} - mock_unique_everseen.return_value = (arg for arg in mock_args) - mock_lowered.return_value = mock_params.values() - mock_validation_functions = { - "ensure_models_exist": basic_plugin.owner.ensure_models_exist, - "ensure_version_regex_exists": basic_plugin.owner.ensure_version_regex_exists, - } - mock_validation_functions[failure_mock].side_effect = CommandError(cmd = basic_plugin.owner, msg = "Test error") - - with pytest.raises(CommandError): - basic_plugin.run(args = (mock_params, mock_args)) - - # model sure the DB is not modified with bad arguments. - basic_plugin.owner.db.execute.assert_not_called() diff --git a/test-framework/test-suites/unit/tests/pylib/stack/test_pylib_stack_firmware.py b/test-framework/test-suites/unit/tests/pylib/stack/test_pylib_stack_firmware.py deleted file mode 100644 index 1a4637a34..000000000 --- a/test-framework/test-suites/unit/tests/pylib/stack/test_pylib_stack_firmware.py +++ /dev/null @@ -1,238 +0,0 @@ -from unittest.mock import patch, call -import hashlib -import pytest -import stack.download -import stack.firmware - -class TestSupportedSchemes: - """A test case for the schemes enum logic.""" - - def test_pretty_string(self): - """Ensure pretty_string works as expected.""" - assert ", ".join( - stack.firmware.SUPPORTED_SCHEMES.__members__.keys() - ) == stack.firmware.SUPPORTED_SCHEMES.pretty_string() - - @pytest.mark.parametrize("scheme", stack.firmware.SUPPORTED_SCHEMES) - def test__str__(self, scheme): - """Ensure the __str__ for each enum instance works as expected.""" - assert scheme.name == str(scheme) - -class TestFirmware: - """A test case to hold the tests for the firmware utilities.""" - - @pytest.mark.parametrize("hash_alg", stack.firmware.SUPPORTED_HASH_ALGS) - def test_ensure_hash_alg_supported(self, hash_alg): - """Test that ensure_hash_alg_supported works with all supported hash algs.""" - stack.firmware.ensure_hash_alg_supported(hash_alg = hash_alg) - - def test_ensure_hash_alg_supported_error(self): - """Test that ensure_hash_alg_supported fails with an unsupported hash algorithm.""" - with pytest.raises(stack.firmware.FirmwareError): - stack.firmware.ensure_hash_alg_supported(hash_alg = "foo") - - @pytest.mark.parametrize("hash_alg", stack.firmware.SUPPORTED_HASH_ALGS) - @patch(target = "stack.firmware.ensure_hash_alg_supported", autospec = True) - @patch(target = "stack.firmware.Path", autospec = True) - def test_calculate_hash(self, mock_path, mock_ensure_hash_alg_supported, hash_alg): - """Test that calculate_hash works for all supported hash types.""" - mock_path.return_value.read_bytes.return_value = b"bar" - - try: - expected_hash = hashlib.new( - name = hash_alg, - data = mock_path.return_value.read_bytes.return_value - ).hexdigest() - except TypeError: - # Need to handle shake_128 and shake_256 case where a digest length is required. - expected_hash = hashlib.new( - name = hash_alg, - data = mock_path.return_value.read_bytes.return_value - ).hexdigest(256) - - # Call providing the expected hash - mock_file = "foo" - assert expected_hash == stack.firmware.calculate_hash( - file_path = mock_file, - hash_alg = hash_alg, - hash_value = expected_hash, - ) - # Expect the hash to be validated - mock_ensure_hash_alg_supported.assert_called_once_with(hash_alg = hash_alg) - # Expect the path to be used to read the file - mock_path.assert_called_once_with(mock_file) - mock_path.return_value.read_bytes.assert_called_once_with() - - # Reset mocks for the next set of assertions - mock_path.reset_mock() - # Call without providing the expected hash - assert expected_hash == stack.firmware.calculate_hash( - file_path = mock_file, - hash_alg = hash_alg, - ) - # Expect the path to be used to read the file - mock_path.assert_called_once_with(mock_file) - mock_path.return_value.read_bytes.assert_called_once_with() - - @pytest.mark.parametrize("hash_alg", stack.firmware.SUPPORTED_HASH_ALGS) - @patch(target = "stack.firmware.ensure_hash_alg_supported", autospec = True) - @patch(target = "stack.firmware.Path", autospec = True) - def test_calculate_hash_mismatched_provided_hash(self, mock_path, mock_ensure_hash_alg_supported, hash_alg): - """Test that calculate_hash fails if the provided hash doesn't match the calculated one.""" - mock_path.return_value.read_bytes.return_value = b"bar" - - with pytest.raises(stack.firmware.FirmwareError): - stack.firmware.calculate_hash( - file_path = "foo", - hash_alg = hash_alg, - hash_value = "foo", - ) - - @patch(target = "stack.firmware.ensure_hash_alg_supported", autospec = True) - @patch(target = "stack.firmware.Path", autospec = True) - def test_calculate_hash_unsupported_hash(self, mock_path, mock_ensure_hash_alg_supported): - """Test that calculate_hash fails if the provided hash_alg isn't supported.""" - mock_path.return_value.read_bytes.return_value = b"bar" - mock_ensure_hash_alg_supported.side_effect = stack.firmware.FirmwareError("Test error") - - with pytest.raises(stack.firmware.FirmwareError): - stack.firmware.calculate_hash( - file_path = "foo", - hash_alg = "foo", - ) - - @pytest.mark.parametrize("scheme", stack.firmware.SUPPORTED_SCHEMES) - @patch(target = "uuid.uuid4", autospec = True) - @patch(target = "stack.firmware.Path", autospec = True) - @patch(target = "stack.firmware.BASE_PATH", autospec = True) - @patch(target = "stack.download.fetch", autospec = True) - def test_fetch_firmware(self, mock_fetch, mock_base_path, mock_path, mock_uuid4, scheme): - """Test that fetch_firmware works as expected for each supported scheme.""" - mock_url = f"{scheme}://localhost/foo/bar" - mock_make = "foo" - mock_model = "bar" - mock_username = "baz" - - result = stack.firmware.fetch_firmware( - source = mock_url, - make = mock_make, - model = mock_model, - username = mock_username, - ) - - # Make sure base_dir is used to properly build the target directory - chained_calls = call.__truediv__(mock_make).__truediv__(mock_model).resolve().mkdir(parents = True, exist_ok = True) - call_list = chained_calls.call_list() - call_list.append(call.__truediv__().__truediv__().resolve().__truediv__(mock_uuid4.return_value.hex)) - assert all(mock_call in mock_base_path.mock_calls for mock_call in call_list) - # Make sure the final file is built using the hex uuid4 - mock_base_path.__truediv__.return_value.__truediv__.return_value.resolve.return_value.__truediv__.assert_called_once_with( - mock_uuid4.return_value.hex - ) - mock_final_file = mock_base_path.__truediv__.return_value.__truediv__.return_value.resolve.return_value.__truediv__.return_value - # Make sure the returned file is the final file - assert mock_final_file == result - - # We make assertions based on the scheme in use. - if scheme == stack.firmware.SUPPORTED_SCHEMES.file: - # Ensure the file was constructed using the url path - mock_path.assert_called_once_with("/foo/bar") - mock_path.return_value.resolve.assert_called_once_with(strict = True) - # Make sure the final file is written - mock_final_file.write_bytes.assert_called_once_with( - mock_path.return_value.resolve.return_value.read_bytes.return_value - ) - - elif scheme in (stack.firmware.SUPPORTED_SCHEMES.http, stack.firmware.SUPPORTED_SCHEMES.https): - # Ensure the source was downloaded - mock_fetch.assert_called_once_with( - url = mock_url, - file_path = mock_final_file, - verbose = True, - username = mock_username, - ) - - @pytest.mark.parametrize("scheme", stack.firmware.SUPPORTED_SCHEMES) - @patch(target = "uuid.uuid4", autospec = True) - @patch(target = "stack.firmware.Path", autospec = True) - @patch(target = "stack.firmware.BASE_PATH", autospec = True) - @patch(target = "stack.download.fetch", autospec = True) - def test_fetch_firmware_errors(self, mock_fetch, mock_base_path, mock_path, mock_uuid4, scheme): - """Test that fetch_firmware fails as expected for each supported scheme when fetching from the source fails.""" - mock_url = f"{scheme}://localhost/foo/bar" - mock_make = "foo" - mock_model = "bar" - - # Set up exceptions - mock_path.return_value.resolve.side_effect = FileNotFoundError("Test error") - mock_fetch.side_effect = stack.download.FetchError("Test error") - - with pytest.raises(stack.firmware.FirmwareError): - stack.firmware.fetch_firmware( - source = mock_url, - make = mock_make, - model = mock_model, - ) - - @patch(target = "uuid.uuid4", autospec = True) - @patch(target = "stack.firmware.Path", autospec = True) - @patch(target = "stack.firmware.BASE_PATH", autospec = True) - @patch(target = "stack.download.fetch", autospec = True) - def test_fetch_firmware_unsupoorted_scheme(self, mock_fetch, mock_base_path, mock_path, mock_uuid4): - """Test that fetch_firmware fails when given an unsupported scheme.""" - mock_url = f"baz://localhost/foo/bar" - mock_make = "foo" - mock_model = "bar" - - with pytest.raises(stack.firmware.FirmwareError): - stack.firmware.fetch_firmware( - source = mock_url, - make = mock_make, - model = mock_model, - ) - - @patch(target = "stack.firmware.SUPPORTED_SCHEMES") - @patch(target = "uuid.uuid4", autospec = True) - @patch(target = "stack.firmware.Path", autospec = True) - @patch(target = "stack.firmware.BASE_PATH", autospec = True) - @patch(target = "stack.download.fetch", autospec = True) - def test_fetch_firmware_forgotten_scheme(self, mock_fetch, mock_base_path, mock_path, mock_uuid4, mock_schemes): - """Test that fetch_firmware fails when a case is not added to handle a supported scheme.""" - mock_url = f"baz://localhost/foo/bar" - mock_make = "foo" - mock_model = "bar" - mock_schemes.__getitem__.return_value = "baz" - - with pytest.raises(RuntimeError): - stack.firmware.fetch_firmware( - source = mock_url, - make = mock_make, - model = mock_model, - ) - - @patch(target = "uuid.uuid4", autospec = True) - @patch(target = "stack.firmware.Path", autospec = True) - @patch(target = "stack.firmware.BASE_PATH", autospec = True) - @patch(target = "stack.download.fetch", autospec = True) - def test_fetch_firmware_local_path(self, mock_fetch, mock_base_path, mock_path, mock_uuid4): - """Test that fetch_firmware works as expected when just a local path is provided.""" - mock_url = f"/foo/bar" - mock_make = "foo" - mock_model = "bar" - mock_username = "baz" - mock_final_file = mock_base_path.__truediv__.return_value.__truediv__.return_value.resolve.return_value.__truediv__.return_value - - stack.firmware.fetch_firmware( - source = mock_url, - make = mock_make, - model = mock_model, - username = mock_username, - ) - - # Ensure the file was constructed using the local path. - mock_path.assert_called_once_with("/foo/bar") - mock_path.return_value.resolve.assert_called_once_with(strict = True) - # Make sure the final file is written - mock_final_file.write_bytes.assert_called_once_with( - mock_path.return_value.resolve.return_value.read_bytes.return_value - )