-
Notifications
You must be signed in to change notification settings - Fork 15
Merge API into dev #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
125f08f
Start API
jboddey f47c1d0
Write interfaces
jboddey ceaac38
Get current configuration
jboddey 6e5cc2e
Set versions
jboddey aaed0e4
Add more API methods
jboddey 3d1c1ae
Correct no-ui flag
jboddey 2f17fce
Do not launch API on baseline test
jboddey 7c4fcda
Move loading devices back to Test Run core
jboddey 3d244af
Merge dev into api (#74)
jboddey 009d124
Merge branch 'dev' into api
jboddey 2724ed8
Fix testing command
jboddey 768ac10
Disable API on testing
jboddey 192bae8
Add API session
jboddey c176bcf
Remove old method
jboddey 664706e
Remove local vars
jboddey 73d5a18
Replace old var
jboddey f0e1350
Add device config
jboddey 6d9b5e5
Add device configs
jboddey ec04823
Fix paths
jboddey 9169adf
Change MAC address
jboddey b9fad2c
Revert mac
jboddey 215b55c
Fix copy path
jboddey b09514c
Debug loading devices
jboddey 9d6eaaf
Remove reference
jboddey ef3dd3c
Changes
jboddey 6c38faf
Re-add checks to prevent null values
jboddey f14ffeb
Fix variable
jboddey 975a894
Fix
jboddey bd99c5e
Use dict instead of string
jboddey 3908cde
Try without json conversion
jboddey 1b286a7
Container output to log
jboddey aa9f03f
Undo changes to nmap module
jboddey cd855da
Add post devices route
jboddey File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| # Copyright 2023 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| from fastapi import FastAPI, APIRouter, Response, Request, status | ||
| import json | ||
| from json import JSONDecodeError | ||
| import psutil | ||
| import threading | ||
| import uvicorn | ||
|
|
||
| from common import logger | ||
| from common.device import Device | ||
|
|
||
| LOGGER = logger.get_logger("api") | ||
|
|
||
| DEVICE_MAC_ADDR_KEY = "mac_addr" | ||
| DEVICE_MANUFACTURER_KEY = "manufacturer" | ||
| DEVICE_MODEL_KEY = "model" | ||
|
|
||
| class Api: | ||
| """Provide REST endpoints to manage Test Run""" | ||
|
|
||
| def __init__(self, test_run): | ||
|
|
||
| self._test_run = test_run | ||
| self._name = "TestRun API" | ||
| self._router = APIRouter() | ||
|
|
||
| self._session = self._test_run.get_session() | ||
|
|
||
| self._router.add_api_route("/system/interfaces", self.get_sys_interfaces) | ||
| self._router.add_api_route("/system/config", self.post_sys_config, | ||
| methods=["POST"]) | ||
| self._router.add_api_route("/system/config", self.get_sys_config) | ||
| self._router.add_api_route("/system/start", self.start_test_run, | ||
| methods=["POST"]) | ||
| self._router.add_api_route("/system/stop", self.stop_test_run, | ||
| methods=["POST"]) | ||
| self._router.add_api_route("/system/status", self.get_status) | ||
|
|
||
| self._router.add_api_route("/devices", self.get_devices) | ||
| self._router.add_api_route("/device", self.save_device, methods=["POST"]) | ||
|
|
||
| self._app = FastAPI() | ||
| self._app.include_router(self._router) | ||
|
|
||
| self._api_thread = threading.Thread(target=self._start, | ||
| name="Test Run API", | ||
| daemon=True) | ||
|
|
||
| def start(self): | ||
| LOGGER.info("Starting API") | ||
| self._api_thread.start() | ||
| LOGGER.info("API waiting for requests") | ||
|
|
||
| def _start(self): | ||
| uvicorn.run(self._app, log_config=None) | ||
|
|
||
| def stop(self): | ||
| LOGGER.info("Stopping API") | ||
|
|
||
| async def get_sys_interfaces(self): | ||
| addrs = psutil.net_if_addrs() | ||
| ifaces = [] | ||
| for iface in addrs: | ||
| ifaces.append(iface) | ||
| return ifaces | ||
|
|
||
| async def post_sys_config(self, request: Request, response: Response): | ||
| try: | ||
| config = (await request.body()).decode("UTF-8") | ||
| config_json = json.loads(config) | ||
| self._session.set_config(config_json) | ||
| # Catch JSON Decode error etc | ||
| except JSONDecodeError: | ||
| response.status_code = status.HTTP_400_BAD_REQUEST | ||
| return self._generate_msg(False, "Invalid JSON received") | ||
| return self._session.get_config() | ||
|
|
||
| async def get_sys_config(self): | ||
| return self._session.get_config() | ||
|
|
||
| async def get_devices(self): | ||
| return self._session.get_device_repository() | ||
|
|
||
| async def start_test_run(self, request: Request, response: Response): | ||
|
|
||
| LOGGER.debug("Received start command") | ||
|
|
||
| # Check request is valid | ||
| body = (await request.body()).decode("UTF-8") | ||
| body_json = None | ||
|
|
||
| try: | ||
| body_json = json.loads(body) | ||
| except JSONDecodeError: | ||
| response.status_code = status.HTTP_400_BAD_REQUEST | ||
| return self._generate_msg(False, "Invalid JSON received") | ||
|
|
||
| if "device" not in body_json or "mac_addr" not in body_json["device"]: | ||
| response.status_code = status.HTTP_400_BAD_REQUEST | ||
| return self._generate_msg(False, "Invalid request received") | ||
|
|
||
| device = self._session.get_device(body_json["device"]["mac_addr"]) | ||
|
|
||
| # Check Test Run is not already running | ||
| if self._test_run.get_session().get_status() != "Idle": | ||
| LOGGER.debug("Test Run is already running. Cannot start another instance") | ||
| response.status_code = status.HTTP_409_CONFLICT | ||
| return self._generate_msg(False, "Test Run is already running") | ||
|
|
||
| # Check if requested device is known in the device repository | ||
| if device is None: | ||
| response.status_code = status.HTTP_404_NOT_FOUND | ||
| return self._generate_msg(False, "A device with that MAC address could not be found") | ||
|
|
||
| # Check Test Run is able to start | ||
| if self._test_run.get_net_orc().check_config() is False: | ||
| response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR | ||
| return self._generate_msg(False, "Configured interfaces are not ready for use. Ensure both interfaces are connected.") | ||
|
|
||
| self._test_run.get_session().set_target_device(device) | ||
| LOGGER.info(f"Starting Test Run with device target {device.manufacturer} {device.model} with MAC address {device.mac_addr}") | ||
|
|
||
| thread = threading.Thread(target=self._start_test_run, | ||
| name="Test Run") | ||
| thread.start() | ||
| return self._test_run.get_session().to_json() | ||
|
|
||
| def _generate_msg(self, success, message): | ||
| msg_type = "success" | ||
| if not success: | ||
| msg_type = "error" | ||
| return json.loads('{"' + msg_type + '": "' + message + '"}') | ||
|
|
||
| def _start_test_run(self): | ||
| self._test_run.start() | ||
|
|
||
| async def stop_test_run(self): | ||
| LOGGER.debug("Received stop command. Stopping Test Run") | ||
| self._test_run.stop() | ||
| return self._generate_msg(True, "Test Run stopped") | ||
|
|
||
| async def get_status(self): | ||
| return self._test_run.get_session().to_json() | ||
|
|
||
| async def get_history(self): | ||
| LOGGER.info("Returning previous Test Runs to UI") | ||
|
|
||
| async def save_device(self, request: Request, response: Response): | ||
| LOGGER.debug("Received device post request") | ||
|
|
||
| try: | ||
| device_raw = (await request.body()).decode("UTF-8") | ||
| device_json = json.loads(device_raw) | ||
|
|
||
| if not self._validate_device_json(device_json): | ||
| response.status_code = status.HTTP_400_BAD_REQUEST | ||
| return self._generate_msg(False, "Invalid request received") | ||
|
|
||
| device = self._session.get_device(device_json.get(DEVICE_MAC_ADDR_KEY)) | ||
| if device is None: | ||
| # Create new device | ||
| device = Device() | ||
| device.mac_addr = device_json.get(DEVICE_MAC_ADDR_KEY) | ||
| response.status_code = status.HTTP_201_CREATED | ||
|
|
||
| device.manufacturer = device_json.get(DEVICE_MANUFACTURER_KEY) | ||
| device.model = device_json.get(DEVICE_MODEL_KEY) | ||
|
|
||
| self._session.save_device(device) | ||
|
|
||
| return device | ||
|
|
||
| # Catch JSON Decode error etc | ||
| except JSONDecodeError: | ||
| response.status_code = status.HTTP_400_BAD_REQUEST | ||
| return self._generate_msg(False, "Invalid JSON received") | ||
|
|
||
| def _validate_device_json(self, json_obj): | ||
| if not (DEVICE_MAC_ADDR_KEY in json_obj and | ||
| DEVICE_MANUFACTURER_KEY in json_obj and | ||
| DEVICE_MODEL_KEY in json_obj | ||
| ): | ||
| return False | ||
| return True | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.