From 30b14f520e7f8b77d66c7e09c6bfe8365783b095 Mon Sep 17 00:00:00 2001 From: Aaron Shaw Date: Thu, 6 Apr 2023 16:28:19 +0100 Subject: [PATCH] feat: add parsing for onboarding key - add onboarding key location to all hardware variants - add get_onboarding_location function to get correct key - add additional tests for new functions and previous - add test for non existent i2c bus Closes: #235 --- hm_pyhelper/hardware_definitions.py | 25 +++++++++ hm_pyhelper/miner_param.py | 44 +++++++++++++++ hm_pyhelper/tests/test_miner_param.py | 80 ++++++++++++++++++++++++++- setup.py | 2 +- 4 files changed, 147 insertions(+), 4 deletions(-) diff --git a/hm_pyhelper/hardware_definitions.py b/hm_pyhelper/hardware_definitions.py index ef0de3f..1669c18 100644 --- a/hm_pyhelper/hardware_definitions.py +++ b/hm_pyhelper/hardware_definitions.py @@ -27,6 +27,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypi3-64'], 'SPIBUS': 'spidev1.2', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 38, 'MAC': 'eth0', 'STATUS': 25, @@ -48,6 +49,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypi3-64'], 'SPIBUS': 'spidev1.2', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 38, 'MAC': 'eth0', 'STATUS': 25, @@ -71,6 +73,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['rockpi-4b-rk3399'], 'SPIBUS': 'spidev32766.0', 'SWARM_KEY_URI': ['ecc://i2c-7:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-7:96?slot=0'], 'RESET': 149, 'MAC': 'eth0', 'STATUS': 156, @@ -96,6 +99,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberry-pi'], 'SPIBUS': 'spidev1.2', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 22, 'MAC': 'wlan0', 'STATUS': 24, @@ -117,6 +121,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberry-pi'], 'SPIBUS': 'spidev1.2', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 22, 'MAC': 'wlan0', 'STATUS': 24, @@ -138,6 +143,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['radxa-zero'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-3:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-3:96?slot=0'], 'RESET': 415, 'MAC': 'wlan0', 'STATUS': 421, @@ -159,6 +165,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberry-pi'], 'SPIBUS': 'spidev1.2', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 4, 'MAC': 'wlan0', 'STATUS': 26, @@ -180,6 +187,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['beaglebone-black'], 'SPIBUS': 'spidev1.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 60, 'MAC': 'eth0', 'STATUS': 31, @@ -201,6 +209,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['beaglebone-pocket'], 'SPIBUS': 'spidev1.2', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 60, 'MAC': 'wlan0', 'STATUS': 31, @@ -222,6 +231,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['rockpi-4b-rk3399'], 'SPIBUS': 'spidev32766.0', 'SWARM_KEY_URI': ['ecc://i2c-7:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-7:96?slot=0'], 'RESET': 149, 'MAC': 'eth0', 'STATUS': 156, @@ -245,6 +255,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['asus-tinker-board'], 'SPIBUS': 'spidev2.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 167, 'MAC': 'eth0', 'STATUS': 163, @@ -266,6 +277,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 25, 'MAC': 'wlan0', 'STATUS': 20, @@ -309,6 +321,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 17, 'MAC': 'wlan0', 'STATUS': 22, @@ -330,6 +343,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 23, 'MAC': 'wlan0', 'STATUS': 22, @@ -351,6 +365,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 17, 'MAC': 'wlan0', 'STATUS_LED': { @@ -377,6 +392,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-0:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-0:96?slot=0'], 'RESET': 23, 'MAC': 'eth0', 'STATUS': 17, @@ -398,6 +414,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 4, 'MAC': 'wlan0', 'STATUS_LED': { @@ -426,6 +443,7 @@ def is_raspberry_pi() -> bool: 'raspberrypi4-64'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 22, 'MAC': 'eth0', 'STATUS': 20, @@ -449,6 +467,7 @@ def is_raspberry_pi() -> bool: 'raspberrypi4-64'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 17, 'MAC': 'eth0', 'STATUS': 20, @@ -470,6 +489,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypicm4-ioboard'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 17, 'MAC': 'wlan0', 'STATUS': 22, @@ -491,6 +511,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['radxa-cm3-rpicm4-ioboard'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 17, 'MAC': 'wlan0', 'STATUS': 22, @@ -512,6 +533,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypicm4-ioboard'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-10:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-10:96?slot=0'], 'RESET': 23, 'MAC': 'wlan0', 'STATUS': 22, @@ -577,6 +599,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypi4-64'], 'SPIBUS': 'spidev0.0', # There is a CSN1 pin which is connected to GPIO6 (HAT Pin 31) 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 22, 'MAC': 'eth0', 'STATUS': 21, # Stub. There is no status LED on X3. I2C-3 is used for display @@ -599,6 +622,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypi3-64'], 'SPIBUS': 'spidev1.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 38, 'MAC': 'eth0', 'STATUS': 25, @@ -620,6 +644,7 @@ def is_raspberry_pi() -> bool: 'BALENA_DEVICE_TYPE': ['raspberrypicm4-ioboard'], 'SPIBUS': 'spidev0.0', 'SWARM_KEY_URI': ['ecc://i2c-1:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-1:96?slot=0'], 'RESET': 17, 'MAC': 'wlan0', 'STATUS': 22, diff --git a/hm_pyhelper/miner_param.py b/hm_pyhelper/miner_param.py index dbf58da..f2b977a 100644 --- a/hm_pyhelper/miner_param.py +++ b/hm_pyhelper/miner_param.py @@ -154,6 +154,50 @@ def get_ecc_location() -> str: return ecc_location +def get_onboarding_location() -> str: + onboarding_list = get_variant_attribute(os.getenv('VARIANT'), 'ONBOARDING_KEY_URI') + onboarding_location = None + + try: + with open("/var/nebra/onboarding_file", 'r') as data: + generated_onboarding_location = str(data.read()).rstrip('\n') + + if len(generated_onboarding_location) < 10: + generated_onboarding_location = None + else: + LOGGER.info("Generated onboarding key location file found: " + + generated_onboarding_location) + except FileNotFoundError: + # No onboarding key location file found, create variable with value None + generated_onboarding_location = None + + if os.getenv('ONBOARDING_KEY_URI_OVERRIDE'): + onboarding_location = os.getenv('ONBOARDING_KEY_URI_OVERRIDE') + elif generated_onboarding_location is not None: + onboarding_location = generated_onboarding_location + elif len(onboarding_list) == 1: + onboarding_location = onboarding_list[0] + else: + for location in onboarding_list: + parse_result = urlparse(location) + i2c_bus = parse_i2c_bus(parse_result.hostname) + i2c_address = parse_i2c_address(parse_result.port) + command = f'i2cdetect -y {i2c_bus}' + parameter = f'{i2c_address} --' + + if config_search_param(command, parameter): + onboarding_location = location + with open("/var/nebra/onboarding_file", "w") as file: + file.write(onboarding_location) + return onboarding_location + + if not onboarding_location: + LOGGER.error("Can't find onboarding key. Ensure ONBOARDING_KEY_URI is " + "correct in hardware definitions.") + + return onboarding_location + + def get_gateway_mfr_command(sub_command: str, slot: int = False) -> list: gateway_mfr_path = get_gateway_mfr_path() command = [gateway_mfr_path] diff --git a/hm_pyhelper/tests/test_miner_param.py b/hm_pyhelper/tests/test_miner_param.py index 2177dd5..6ccc89d 100644 --- a/hm_pyhelper/tests/test_miner_param.py +++ b/hm_pyhelper/tests/test_miner_param.py @@ -4,14 +4,15 @@ import sys from unittest.mock import ANY, mock_open, patch, Mock from packaging.version import Version -from hm_pyhelper.exceptions import ECCMalfunctionException, \ +from hm_pyhelper.exceptions import ECCMalfunctionException, UnknownVariantAttributeException, \ MinerFailedToFetchMacAddress, GatewayMFRInvalidVersion, GatewayMFRExecutionException, \ - GatewayMFRFileNotFoundException, UnsupportedGatewayMfrVersion + GatewayMFRFileNotFoundException, UnsupportedGatewayMfrVersion, UnknownVariantException from hm_pyhelper.lock_singleton import ResourceBusyError from hm_pyhelper.miner_param import retry_get_region, await_spi_available, \ provision_key, run_gateway_mfr, get_gateway_mfr_path, config_search_param, get_ecc_location, \ did_gateway_mfr_test_result_include_miner_key_pass, parse_i2c_address, parse_i2c_bus, \ - get_mac_address, get_public_keys_rust, get_gateway_mfr_version, get_gateway_mfr_command + get_mac_address, get_public_keys_rust, get_gateway_mfr_version, get_gateway_mfr_command, \ + get_onboarding_location sys.path.append("..") @@ -64,10 +65,12 @@ 'NEBHNT-WITH-ECC-ADDRESS': { 'KEY_STORAGE_BUS': '/dev/i2c-X', 'SWARM_KEY_URI': ['ecc://i2c-X:96?slot=0'], + 'ONBOARDING_KEY_URI': ['ecc://i2c-X:96?slot=0'], }, 'NEBHNT-NO-ECC-ADDRESS': { 'NO_KEY_STORAGE_BUS': '/dev/i2c-X', 'NO_KEY_SWARM_KEY_URI': ['ecc://i2c-X:96?slot=0'], + 'NO_ONBOARDING_KEY_URI': ['ecc://i2c-X:96?slot=0'], }, 'NEBHNT-MULTIPLE-ECC-ADDRESS': { 'KEY_STORAGE_BUS': '/dev/i2c-2', @@ -78,6 +81,8 @@ ECC_FILE_DATA = 'ecc://i2c-Y:96?slot=1' ECC_FILE_DATA_BLANK = None +ONBOARDING_FILE_DATA = 'ecc://i2c-Z:96?slot=10' +ONBOARDING_FILE_DATA_BLANK = None class SubprocessResult(object): @@ -262,6 +267,64 @@ def test_get_ecc_location_generated_ecc(self): expected_result = 'ecc://i2c-Y:96?slot=1' self.assertEqual(actual_result, expected_result) + @patch("builtins.open", mock_open(read_data=ONBOARDING_FILE_DATA)) + def test_get_onboarding_location_generated_ecc(self): + actual_result = get_onboarding_location() + expected_result = 'ecc://i2c-Z:96?slot=10' + self.assertEqual(actual_result, expected_result) + + @patch.dict('os.environ', {"ONBOARDING_KEY_URI_OVERRIDE": "override-test"}) + def test_get_onboarding_override(self): + actual_result = get_onboarding_location() + expected_result = "override-test" + self.assertEqual(actual_result, expected_result) + + @patch("builtins.open", mock_open(read_data=ONBOARDING_FILE_DATA_BLANK)) + @patch.dict('os.environ', {"VARIANT": "NEBHNT-MULTIPLE-ECC-ADDRESS"}) + @patch('subprocess.Popen') + def test_get_onboarding_key_multi_KEY_URI(self, mock_subproc_popen): + process_mock = Mock() + attrs = {'communicate.return_value': (str.encode("60 --"), 'error')} + process_mock.configure_mock(**attrs) + mock_subproc_popen.return_value = process_mock + + actual_result = get_onboarding_location() + expected_result = 'ecc://i2c-3:96?slot=0' + self.assertEqual(actual_result, expected_result) + + @patch("builtins.open", mock_open(read_data=ONBOARDING_FILE_DATA_BLANK)) + @patch.dict('os.environ', {"VARIANT": "NEBHNT-MULTIPLE-ECC-ADDRESS"}) + @patch('subprocess.Popen') + def test_get_onboarding_key_multi_KEY_58(self, mock_subproc_popen): + process_mock = Mock() + attrs = {'communicate.return_value': (str.encode("58 --"), 'error')} + process_mock.configure_mock(**attrs) + mock_subproc_popen.return_value = process_mock + + actual_result = get_onboarding_location() + expected_result = 'ecc://i2c-4:88?slot=15' + self.assertEqual(actual_result, expected_result) + + @patch.dict('os.environ', {"VARIANT": "NEBHNT-NO-ECC-ADDRESS"}) + def test_get_onboarding_key_missing(self): + with self.assertRaises(UnknownVariantAttributeException): + get_onboarding_location() + + @patch.dict('os.environ', {"VARIANT": "NEBHNT-NO-ECC-ADDRESS"}) + def test_get_ecc_key_missing(self): + with self.assertRaises(UnknownVariantAttributeException): + get_ecc_location() + + @patch.dict('os.environ', {"VARIANT": "MISSING"}) + def test_get_onboarding_key_missing_variant(self): + with self.assertRaises(UnknownVariantException): + get_onboarding_location() + + @patch.dict('os.environ', {"VARIANT": "MISSING"}) + def test_get_ecc_key_missing_variant(self): + with self.assertRaises(UnknownVariantException): + get_ecc_location() + @patch('hm_pyhelper.miner_param.get_gateway_mfr_command', return_value=['gateway_mfr', 'arg1', 'arg2']) @patch('subprocess.run', side_effect=FileNotFoundError()) @@ -434,6 +497,17 @@ def test_incorrect_param(self, mock_subproc_popen): result = config_search_param("somecommand", "60--") self.assertEqual(result, False) + @patch('subprocess.Popen') + def test_error_command(self, mock_subproc_popen): + process_mock = Mock() + attrs = {'communicate.return_value': (str.encode(''), + "Error: Could not open file `/dev/i2c-1' or `/dev/i2c/1': " + "No such file or directory")} + process_mock.configure_mock(**attrs) + mock_subproc_popen.return_value = process_mock + result = config_search_param("somecommand", "60--") + self.assertEqual(result, False) + def test_types(self): self.assertRaises(TypeError, config_search_param, 1, 2) self.assertRaises(TypeError, config_search_param, "123321", 1) diff --git a/setup.py b/setup.py index f12d917..059e109 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='hm_pyhelper', - version='0.13.59', + version='0.14.2', author="Nebra Ltd", author_email="support@nebra.com", description="Helium Python Helper",