diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py index f50b824b4..19a008450 100644 --- a/framework/python/src/api/api.py +++ b/framework/python/src/api/api.py @@ -584,13 +584,52 @@ async def get_report(self, response: Response, response.status_code = 404 return self._generate_msg(False, "Report could not be found") - async def get_results(self, response: Response, - device_name, timestamp): - - file_path = os.path.join(DEVICES_PATH, device_name, "reports", - timestamp, "results.zip") + async def get_results(self, request: Request, + response: Response, + device_name, timestamp): LOGGER.debug("Received get results " + f"request for {device_name} / {timestamp}") + + profile = None + + try: + req_raw = (await request.body()).decode("UTF-8") + req_json = json.loads(req_raw) + + # Check if profile has been specified + if "profile" in req_json: + profile_name = req_json.get("profile") + profile = self.get_session().get_profile(profile_name) + + if profile is None: + response.status_code = status.HTTP_404_NOT_FOUND + return self._generate_msg( + False, + "A profile with that name could not be found") + + except JSONDecodeError: + # Profile not specified + pass + + # Check if device exists + device = self.get_session().get_device_by_name(device_name) + if device is None: + response.status_code = status.HTTP_404_NOT_FOUND + return self._generate_msg(False, + "A device with that name could not be found") + + file_path = self._get_test_run().get_test_orc().zip_results( + device, + timestamp, + profile + ) + + if file_path is None: + response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR + return self._generate_msg( + False, + "An error occurred whilst archiving test results") + if os.path.isfile(file_path): return FileResponse(file_path) else: diff --git a/framework/python/src/common/risk_profile.py b/framework/python/src/common/risk_profile.py index eb81ba183..021a1c5b5 100644 --- a/framework/python/src/common/risk_profile.py +++ b/framework/python/src/common/risk_profile.py @@ -14,6 +14,9 @@ """Stores additional information about a device's risk""" from datetime import datetime +import os + +PROFILES_PATH = 'local/risk_profiles' class RiskProfile(): """Python representation of a risk profile""" @@ -40,3 +43,7 @@ def to_json(self): 'questions': self.questions } return json + + def get_file_path(self): + return os.path.join(PROFILES_PATH, + self.name + '.json') diff --git a/framework/python/src/common/session.py b/framework/python/src/common/session.py index 783bc5c34..5127cc221 100644 --- a/framework/python/src/common/session.py +++ b/framework/python/src/common/session.py @@ -257,6 +257,12 @@ def set_target_device(self, device): def get_target_device(self): return self._device + def get_device_by_name(self, device_name): + for device in self._device_repository: + if device.device_folder.lower() == device_name.lower(): + return device + return None + def get_device_repository(self): return self._device_repository @@ -374,7 +380,7 @@ def _load_profiles(self): json_data = json.load(f) risk_profile = RiskProfile(json_data) risk_profile.status = self.check_profile_status(risk_profile) - self._profiles.append(risk_profile) + self._profiles.append(risk_profile) except Exception as e: LOGGER.error('An error occurred whilst loading risk profiles') @@ -406,8 +412,7 @@ def validate_profile(self, profile_json): for format_q in self.get_profiles_format(): if self._get_profile_question( profile_json, format_q.get('question')) is None: - LOGGER.error( - 'Missing question: ' + format_q.get('question')) + LOGGER.error('Missing question: ' + format_q.get('question')) return False return True @@ -483,9 +488,11 @@ def update_profile(self, profile_json): risk_profile.questions = profile_json.get('questions') # Write file to disk - with open(os.path.join( - PROFILES_DIR, risk_profile.name + '.json'), 'w', - encoding='utf-8') as f: + with open( + os.path.join( + PROFILES_DIR, + risk_profile.name + '.json' + ), 'w', encoding='utf-8') as f: f.write(json.dumps(risk_profile.to_json())) return risk_profile diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py index 9ce9ef417..5ca763c93 100644 --- a/framework/python/src/core/testrun.py +++ b/framework/python/src/core/testrun.py @@ -479,4 +479,4 @@ def _stop_ui(self): if container is not None: container.kill() except docker.errors.NotFound: - return \ No newline at end of file + return diff --git a/framework/python/src/net_orc/network_orchestrator.py b/framework/python/src/net_orc/network_orchestrator.py index f5e43cb32..2099455c5 100644 --- a/framework/python/src/net_orc/network_orchestrator.py +++ b/framework/python/src/net_orc/network_orchestrator.py @@ -21,6 +21,7 @@ import subprocess import sys import docker +import time from docker.types import Mount from common import logger, util from net_orc.listener import Listener @@ -281,6 +282,7 @@ def _start_device_monitor(self, device): sniffer.start() while sniffer.running: + time.sleep(1) if not self._ip_ctrl.check_interface_status( self._session.get_device_interface()): sniffer.stop() diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py index 32135254d..11b61ec47 100644 --- a/framework/python/src/test_orc/test_orchestrator.py +++ b/framework/python/src/test_orc/test_orchestrator.py @@ -125,9 +125,6 @@ def run_test_modules(self): # Move testing output from runtime to local device folder timestamp_dir = self._timestamp_results(device) - # Archive the runtime directory - self._zip_results(timestamp_dir) - LOGGER.debug("Cleaning old test results...") self._cleanup_old_test_results(device) @@ -257,30 +254,49 @@ def _timestamp_results(self, device): return completed_results_dir - def _zip_results(self, dest_path): + def zip_results(self, + device, + timestamp, + profile): try: LOGGER.debug("Archiving test results") - # Define where to save the zip file - zip_location = os.path.join(dest_path, "results") + src_path = os.path.join(LOCAL_DEVICE_REPORTS.replace( + "{device_folder}", + device.device_folder), + timestamp) - # The runtime directory to include in ZIP - path_to_zip = os.path.join( - self._root_path, - RUNTIME_DIR) + # Define where to save the zip file + zip_location = os.path.join(LOCAL_DEVICE_REPORTS.replace( + "{device_folder}", device.device_folder), timestamp + ) + + # Include profile if specified + if profile is not None: + LOGGER.debug( + f"Copying profile {profile.name} to results directory") + shutil.copy(profile.get_file_path(), + os.path.join( + src_path, + "profile.json")) # Create ZIP file - shutil.make_archive(zip_location, "zip", path_to_zip) + if not os.path.exists(zip_location + ".zip"): + shutil.make_archive(zip_location, "zip", src_path) # Check that the ZIP was successfully created zip_file = zip_location + ".zip" LOGGER.info(f'''Archive {'created at ' + zip_file - if os.path.exists(zip_file) - else'creation failed'}''') + if os.path.exists(zip_file) + else'creation failed'}''') + + return zip_file except Exception as error: # pylint: disable=W0703 - LOGGER.error(f"Failed to create zip file: {error}") + LOGGER.error("Failed to create zip file") + LOGGER.debug(error) + return None def test_in_progress(self): return self._test_in_progress diff --git a/testing/api/test_api.py b/testing/api/test_api.py index e19f2ddf6..75811e3bb 100644 --- a/testing/api/test_api.py +++ b/testing/api/test_api.py @@ -426,7 +426,7 @@ def test_delete_device_success(empty_devices_dir, testrun): # pylint: disable=W0 print(mockito) # Validate structure - assert all([isinstance(x, dict) for x in all_devices]) + assert all(isinstance(x, dict) for x in all_devices) # TOOO uncomment when is done # assert set(dict_paths(mockito[0])) == set(dict_paths(all_devices[0]))