From 00254f7ea71dbdb2a1bf4303e2c00e4c3bd9f53f Mon Sep 17 00:00:00 2001 From: Jacob Boddey Date: Fri, 30 Jun 2023 17:01:56 +0100 Subject: [PATCH 01/15] Add ntp support test --- .../python/src/test_orc/test_orchestrator.py | 13 ++-- modules/test/base/python/src/test_module.py | 9 +-- .../test/conn/python/src/connection_module.py | 66 ++++++++-------- modules/test/nmap/nmap.Dockerfile | 4 +- modules/test/ntp/bin/start_test_module | 42 ++++++++++ modules/test/ntp/conf/module_config.json | 22 ++++++ modules/test/ntp/ntp.Dockerfile | 20 +++++ modules/test/ntp/python/requirements.txt | 1 + modules/test/ntp/python/src/ntp_module.py | 77 +++++++++++++++++++ modules/test/ntp/python/src/run.py | 75 ++++++++++++++++++ resources/devices/template/device_config.json | 29 +++++++ 11 files changed, 311 insertions(+), 47 deletions(-) create mode 100644 modules/test/ntp/bin/start_test_module create mode 100644 modules/test/ntp/conf/module_config.json create mode 100644 modules/test/ntp/ntp.Dockerfile create mode 100644 modules/test/ntp/python/requirements.txt create mode 100644 modules/test/ntp/python/src/ntp_module.py create mode 100644 modules/test/ntp/python/src/run.py diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py index 4bc9fc003..e9f095fa4 100644 --- a/framework/python/src/test_orc/test_orchestrator.py +++ b/framework/python/src/test_orc/test_orchestrator.py @@ -13,16 +13,14 @@ # limitations under the License. """Provides high level management of the test orchestrator.""" -import getpass import os import json import time import shutil import docker from docker.types import Mount -from common import logger +from common import logger, util from test_orc.module import TestModule -from common import util LOG_NAME = "test_orc" LOGGER = logger.get_logger("test_orc") @@ -102,7 +100,7 @@ def _generate_results(self, device): results[module.name] = module_results except (FileNotFoundError, PermissionError, json.JSONDecodeError) as results_error: - LOGGER.error("Error occured whilst obbtaining results for module " + module.name) + LOGGER.error(f"Error occured whilst obbtaining results for module {module.name}") LOGGER.debug(results_error) out_file = os.path.join( @@ -140,17 +138,18 @@ def _run_test_module(self, module, device): container_runtime_dir = os.path.join( self._root_path, "runtime/test/" + device.mac_addr.replace(":", "") + "/" + module.name) - network_runtime_dir = os.path.join(self._root_path, "runtime/network") os.makedirs(container_runtime_dir) + network_runtime_dir = os.path.join(self._root_path, "runtime/network") + device_startup_capture = os.path.join( self._root_path, "runtime/test/" + device.mac_addr.replace(":", "") + "/startup.pcap") util.run_command(f'chown -R {self._host_user} {device_startup_capture}') device_monitor_capture = os.path.join( - self._root_path, "runtime/test/" + device.mac_addr.replace(":", "") + - "/monitor.pcap") + self._root_path, 'runtime/test/' + device.mac_addr.replace(":", "") + + '/monitor.pcap') util.run_command(f'chown -R {self._host_user} {device_monitor_capture}') client = docker.from_env() diff --git a/modules/test/base/python/src/test_module.py b/modules/test/base/python/src/test_module.py index 5342e36f8..2a892b810 100644 --- a/modules/test/base/python/src/test_module.py +++ b/modules/test/base/python/src/test_module.py @@ -89,14 +89,13 @@ def run_tests(self): else: result = getattr(self, test_method_name)() else: - LOGGER.info('Test ' + test['name'] + ' not resolved. Skipping') + LOGGER.info(f'Test {test["name"]} not resolved. Skipping') result = None else: - LOGGER.info('Test ' + test['name'] + ' disabled. Skipping') + LOGGER.info(f'Test {test["name"]} disabled. Skipping') if result is not None: - success = None - if isinstance(result,bool): - test['result'] = 'compliant' if result else 'non-compliant' + if isinstance(result, bool): + test['result'] = 'compliant' if result else 'non-compliant' else: test['result'] = 'compliant' if result[0] else 'non-compliant' test['result_details'] = result[1] diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 196c335d8..580358e23 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -18,9 +18,9 @@ from scapy.all import * from test_module import TestModule -LOG_NAME = "test_connection" +LOG_NAME = 'test_connection' LOGGER = None -OUI_FILE="/usr/local/etc/oui.txt" +OUI_FILE='/usr/local/etc/oui.txt' DHCP_SERVER_CAPTURE_FILE = '/runtime/network/dhcp-1.pcap' STARTUP_CAPTURE_FILE = '/runtime/device/startup.pcap' MONITOR_CAPTURE_FILE = '/runtime/device/monitor.pcap' @@ -30,37 +30,37 @@ class ConnectionModule(TestModule): """Connection Test module""" def __init__(self, module): - super().__init__(module_name=module, log_name=LOG_NAME) + super(module_name=module, log_name=LOG_NAME) global LOGGER LOGGER = self._get_logger() def _connection_mac_address(self): - LOGGER.info("Running connection.mac_address") + LOGGER.info('Running connection.mac_address') if self._device_mac is not None: - LOGGER.info("MAC address found: " + self._device_mac) - return True, "MAC address found: " + self._device_mac + LOGGER.info('MAC address found: ' + self._device_mac) + return True, 'MAC address found: ' + self._device_mac else: - LOGGER.info("No MAC address found: " + self._device_mac) - return False, "No MAC address found." + LOGGER.info('No MAC address found: ' + self._device_mac) + return False, 'No MAC address found.' def _connection_mac_oui(self): - LOGGER.info("Running connection.mac_oui") + LOGGER.info('Running connection.mac_oui') manufacturer = self._get_oui_manufacturer(self._device_mac) if manufacturer is not None: - LOGGER.info("OUI Manufacturer found: " + manufacturer) - return True, "OUI Manufacturer found: " + manufacturer + LOGGER.info('OUI Manufacturer found: ' + manufacturer) + return True, 'OUI Manufacturer found: ' + manufacturer else: - LOGGER.info("No OUI Manufacturer found for: " + self._device_mac) - return False, "No OUI Manufacturer found for: " + self._device_mac + LOGGER.info('No OUI Manufacturer found for: ' + self._device_mac) + return False, 'No OUI Manufacturer found for: ' + self._device_mac def _connection_single_ip(self): - LOGGER.info("Running connection.single_ip") + LOGGER.info('Running connection.single_ip') result = None if self._device_mac is None: - LOGGER.info("No MAC address found: ") - return result, "No MAC address found." - + LOGGER.info('No MAC address found: ') + return result, 'No MAC address found.' + # Read all the pcap files containing DHCP packet information packets = rdpcap(DHCP_SERVER_CAPTURE_FILE) packets.append(rdpcap(STARTUP_CAPTURE_FILE)) @@ -68,33 +68,33 @@ def _connection_single_ip(self): # Extract MAC addresses from DHCP packets mac_addresses = set() - LOGGER.info("Inspecting: " + str(len(packets)) + " packets") + LOGGER.info('Inspecting: ' + str(len(packets)) + ' packets') for packet in packets: # Option[1] = message-type, option 3 = DHCPREQUEST - if DHCP in packet and packet[DHCP].options[0][1] == 3: - mac_address = packet[Ether].src - mac_addresses.add(mac_address.upper()) + if DHCP in packet and packet[DHCP].options[0][1] == 3: + mac_address = packet[Ether].src + mac_addresses.add(mac_address.upper()) # Check if the device mac address is in the list of DHCPREQUESTs result = self._device_mac.upper() in mac_addresses - LOGGER.info("DHCPREQUEST detected from device: " + str(result)) + LOGGER.info('DHCPREQUEST detected from device: ' + str(result)) # Check the unique MAC addresses to see if they match the device for mac_address in mac_addresses: - LOGGER.info("DHCPREQUEST from MAC address: " + mac_address) - result &= self._device_mac.upper() == mac_address + LOGGER.info('DHCPREQUEST from MAC address: ' + mac_address) + result &= self._device_mac.upper() == mac_address return result def _connection_target_ping(self): - LOGGER.info("Running connection.target_ping") + LOGGER.info('Running connection.target_ping') # If the ipv4 address wasn't resolved yet, try again if self._device_ipv4_addr is None: - self._device_ipv4_addr = self._get_device_ipv4(self) + self._device_ipv4_addr = self._get_device_ipv4(self) if self._device_ipv4_addr is None: - LOGGER.error("No device IP could be resolved") + LOGGER.error('No device IP could be resolved') sys.exit(1) else: return self._ping(self._device_ipv4_addr) @@ -102,12 +102,12 @@ def _connection_target_ping(self): def _get_oui_manufacturer(self,mac_address): # Do some quick fixes on the format of the mac_address # to match the oui file pattern - mac_address = mac_address.replace(":","-").upper() - with open(OUI_FILE, "r") as file: - for line in file: - if mac_address.startswith(line[:8]): - start = line.index("(hex)") + len("(hex)") - return line[start:].strip() # Extract the company name + mac_address = mac_address.replace(':','-').upper() + with open(OUI_FILE, 'r', encoding='utf-8') as file: + for line in file: + if mac_address.startswith(line[:8]): + start = line.index('(hex)') + len('(hex)') + return line[start:].strip() # Extract the company name return None def _ping(self, host): diff --git a/modules/test/nmap/nmap.Dockerfile b/modules/test/nmap/nmap.Dockerfile index c1a2f96ce..8f9a9fb33 100644 --- a/modules/test/nmap/nmap.Dockerfile +++ b/modules/test/nmap/nmap.Dockerfile @@ -4,10 +4,10 @@ FROM test-run/base-test:latest ARG MODULE_NAME=nmap ARG MODULE_DIR=modules/test/$MODULE_NAME -#Load the requirements file +# Load the requirements file COPY $MODULE_DIR/python/requirements.txt /testrun/python -#Install all python requirements for the module +# Install all python requirements for the module RUN pip3 install -r /testrun/python/requirements.txt # Copy over all configuration files diff --git a/modules/test/ntp/bin/start_test_module b/modules/test/ntp/bin/start_test_module new file mode 100644 index 000000000..a09349cf9 --- /dev/null +++ b/modules/test/ntp/bin/start_test_module @@ -0,0 +1,42 @@ +#!/bin/bash + +# An example startup script that does the bare minimum to start +# a test module via a pyhon script. Each test module should include a +# start_test_module file that overwrites this one to boot all of its +# specific requirements to run. + +# Define where the python source files are located +PYTHON_SRC_DIR=/testrun/python/src + +# Fetch module name +MODULE_NAME=$1 + +# Default interface should be veth0 for all containers +DEFAULT_IFACE=veth0 + +# Allow a user to define an interface by passing it into this script +DEFINED_IFACE=$2 + +# Select which interace to use +if [[ -z $DEFINED_IFACE || "$DEFINED_IFACE" == "null" ]] +then + echo "No interface defined, defaulting to veth0" + INTF=$DEFAULT_IFACE +else + INTF=$DEFINED_IFACE +fi + +# Create and set permissions on the log files +LOG_FILE=/runtime/output/$MODULE_NAME.log +RESULT_FILE=/runtime/output/$MODULE_NAME-result.json +touch $LOG_FILE +touch $RESULT_FILE +chown $HOST_USER $LOG_FILE +chown $HOST_USER $RESULT_FILE + +# Run the python scrip that will execute the tests for this module +# -u flag allows python print statements +# to be logged by docker by running unbuffered +python3 -u $PYTHON_SRC_DIR/run.py "-m $MODULE_NAME" + +echo Module has finished \ No newline at end of file diff --git a/modules/test/ntp/conf/module_config.json b/modules/test/ntp/conf/module_config.json new file mode 100644 index 000000000..6455bb4eb --- /dev/null +++ b/modules/test/ntp/conf/module_config.json @@ -0,0 +1,22 @@ +{ + "config": { + "meta": { + "name": "ntp", + "display_name": "NTP", + "description": "NTP test" + }, + "network": false, + "docker": { + "depends_on": "base", + "enable_container": true, + "timeout": 30 + }, + "tests":[ + { + "name": "ntp.network.ntp_support", + "description": "Verify the device requests network time sync as per RFC 5905: NTPv4", + "expected_behavior": "The device sends NTPv4 request to the configured server" + } + ] + } +} diff --git a/modules/test/ntp/ntp.Dockerfile b/modules/test/ntp/ntp.Dockerfile new file mode 100644 index 000000000..33b06287e --- /dev/null +++ b/modules/test/ntp/ntp.Dockerfile @@ -0,0 +1,20 @@ +# Image name: test-run/ntp-test +FROM test-run/base-test:latest + +ARG MODULE_NAME=ntp +ARG MODULE_DIR=modules/test/$MODULE_NAME + +# Load the requirements file +COPY $MODULE_DIR/python/requirements.txt /testrun/python + +# Install all python requirements for the module +RUN pip3 install -r /testrun/python/requirements.txt + +# Copy over all configuration files +COPY $MODULE_DIR/conf /testrun/conf + +# Copy over all binary files +COPY $MODULE_DIR/bin /testrun/bin + +# Copy over all python files +COPY $MODULE_DIR/python /testrun/python \ No newline at end of file diff --git a/modules/test/ntp/python/requirements.txt b/modules/test/ntp/python/requirements.txt new file mode 100644 index 000000000..93b351f44 --- /dev/null +++ b/modules/test/ntp/python/requirements.txt @@ -0,0 +1 @@ +scapy \ No newline at end of file diff --git a/modules/test/ntp/python/src/ntp_module.py b/modules/test/ntp/python/src/ntp_module.py new file mode 100644 index 000000000..d0cc79091 --- /dev/null +++ b/modules/test/ntp/python/src/ntp_module.py @@ -0,0 +1,77 @@ +# 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. + +"""NTP test module""" +from test_module import TestModule +from scapy.all import rdpcap, NTP, IP + +LOG_NAME = 'test_ntp' +NTP_SERVER_CAPTURE_FILE = '/runtime/network/ntp.pcap' +STARTUP_CAPTURE_FILE = '/runtime/device/startup.pcap' +MONITOR_CAPTURE_FILE = '/runtime/device/monitor.pcap' +LOGGER = None + +class NTPModule(TestModule): + """NTP Test module""" + + def __init__(self, module): + super().__init__(module_name=module, log_name=LOG_NAME) + # TODO: This should be fetched dynamically + self._ntp_server = '10.10.10.5' + + global LOGGER + LOGGER = self._get_logger() + + def _ntp_network_ntp_support(self): + LOGGER.info('Running ntp.network.ntp_support') + + packet_capture = rdpcap(MONITOR_CAPTURE_FILE) + + device_sends_ntp4 = False + device_sends_ntp3 = False + + for packet in packet_capture: + + if NTP in packet and packet.src == self._device_mac: + if packet[NTP].version == 4: + device_sends_ntp4 = True + LOGGER.info(f'Device sent NTPv4 request to {packet[IP].dst}') + elif packet[NTP].version == 3: + device_sends_ntp3 = True + LOGGER.info(f'Device sent NTPv3 request to {packet[IP].dst}') + + if not (device_sends_ntp3 or device_sends_ntp4): + LOGGER.info('Device has not sent any NTP requests') + + return device_sends_ntp4 and not device_sends_ntp3 + + def _ntp_network_ntp_dhcp(self): + LOGGER.info('Running ntp.network.ntp_dhcp') + + packet_capture = rdpcap(MONITOR_CAPTURE_FILE) + + device_sends_ntp = False + + for packet in packet_capture: + + if NTP in packet and packet.src == self._device_mac: + device_sends_ntp = True + if packet[IP].dst == self._ntp_server: + LOGGER.info('Device sent NTP request to DHCP provided NTP server') + return True + + if not device_sends_ntp: + LOGGER.info('Device has not sent any NTP requests') + + return False diff --git a/modules/test/ntp/python/src/run.py b/modules/test/ntp/python/src/run.py new file mode 100644 index 000000000..685bb4083 --- /dev/null +++ b/modules/test/ntp/python/src/run.py @@ -0,0 +1,75 @@ +# 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. + +"""Run NTP test module""" +import argparse +import signal +import sys +import logger + +from ntp_module import NTPModule + +LOG_NAME = "ntp_runner" +LOGGER = logger.get_logger(LOG_NAME) + + +class NTPModuleRunner: + """Run the NTP module tests.""" + def __init__(self, module): + + signal.signal(signal.SIGINT, self._handler) + signal.signal(signal.SIGTERM, self._handler) + signal.signal(signal.SIGABRT, self._handler) + signal.signal(signal.SIGQUIT, self._handler) + self.add_logger(module) + + LOGGER.info("Starting NTP test module") + + self._test_module = NTPModule(module) + self._test_module.run_tests() + + LOGGER.info("NTP test module finished") + + def add_logger(self, module): + global LOGGER + LOGGER = logger.get_logger(LOG_NAME, module) + + def _handler(self, signum): + LOGGER.debug("SigtermEnum: " + str(signal.SIGTERM)) + LOGGER.debug("Exit signal received: " + str(signum)) + if signum in (2, signal.SIGTERM): + LOGGER.info("Exit signal received. Stopping test module...") + LOGGER.info("Test module stopped") + sys.exit(1) + + +def run(): + parser = argparse.ArgumentParser( + description="NTP Module Help", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument( + "-m", + "--module", + help="Define the module name to be used to create the log file") + + args = parser.parse_args() + + # For some reason passing in the args from bash adds an extra + # space before the argument so we'll just strip out extra space + NTPModuleRunner(args.module.strip()) + + +if __name__ == "__main__": + run() diff --git a/resources/devices/template/device_config.json b/resources/devices/template/device_config.json index 3bb804b22..1e92de25d 100644 --- a/resources/devices/template/device_config.json +++ b/resources/devices/template/device_config.json @@ -14,6 +14,35 @@ } } }, + "connection": { + "enabled": true, + "tests": { + "connection.mac_address": { + "enabled": true + }, + "connection.mac_oui": { + "enabled": true + }, + "connection.target_ping": { + "enabled": true + } + , + "connection.single_ip": { + "enabled": true + } + } + }, + "ntp": { + "enabled": true, + "tests": { + "ntp.network.ntp_support": { + "enabled": true + }, + "ntp.network.ntp_dhcp": { + "enabled": true + } + } + }, "baseline": { "enabled": false, "tests": { From 185c7e1a66eb468e52d1bd59d92cc7f90fff93e2 Mon Sep 17 00:00:00 2001 From: Jacob Boddey Date: Fri, 30 Jun 2023 17:16:31 +0100 Subject: [PATCH 02/15] Add extra log message --- modules/test/ntp/conf/module_config.json | 5 +++++ modules/test/ntp/python/src/ntp_module.py | 2 ++ testing/test_pylint | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/test/ntp/conf/module_config.json b/modules/test/ntp/conf/module_config.json index 6455bb4eb..a45d7c401 100644 --- a/modules/test/ntp/conf/module_config.json +++ b/modules/test/ntp/conf/module_config.json @@ -16,6 +16,11 @@ "name": "ntp.network.ntp_support", "description": "Verify the device requests network time sync as per RFC 5905: NTPv4", "expected_behavior": "The device sends NTPv4 request to the configured server" + }, + { + "name": "ntp.network.ntp_dhcp", + "description": "Verify the device accepts an NTP server address over DHCP", + "expected_behavior": "The device can accept an NTP server address provided by the DHCP server" } ] } diff --git a/modules/test/ntp/python/src/ntp_module.py b/modules/test/ntp/python/src/ntp_module.py index d0cc79091..4bfcba106 100644 --- a/modules/test/ntp/python/src/ntp_module.py +++ b/modules/test/ntp/python/src/ntp_module.py @@ -73,5 +73,7 @@ def _ntp_network_ntp_dhcp(self): if not device_sends_ntp: LOGGER.info('Device has not sent any NTP requests') + else: + LOGGER.info('Device has not sent NTP requests to DHCP provided NTP server') return False diff --git a/testing/test_pylint b/testing/test_pylint index e3ade62b5..00662cc76 100755 --- a/testing/test_pylint +++ b/testing/test_pylint @@ -1,6 +1,6 @@ #!/bin/bash -ERROR_LIMIT=1100 +ERROR_LIMIT=100 sudo cmd/install From 459ae305730e38636fd45ef9a1a78c71fddedcd4 Mon Sep 17 00:00:00 2001 From: Jacob Boddey Date: Fri, 7 Jul 2023 19:56:01 +0100 Subject: [PATCH 03/15] Modify descriptions --- modules/test/ntp/conf/module_config.json | 8 ++++---- modules/test/ntp/python/src/ntp_module.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/test/ntp/conf/module_config.json b/modules/test/ntp/conf/module_config.json index a45d7c401..288474868 100644 --- a/modules/test/ntp/conf/module_config.json +++ b/modules/test/ntp/conf/module_config.json @@ -14,13 +14,13 @@ "tests":[ { "name": "ntp.network.ntp_support", - "description": "Verify the device requests network time sync as per RFC 5905: NTPv4", - "expected_behavior": "The device sends NTPv4 request to the configured server" + "description": "Does the device request network time sync as client as per RFC 5905 - Network Time Protocol Version 4: Protocol and Algorithms Specification", + "expected_behavior": "The device sends an NTPv4 request to the configured NTP server." }, { "name": "ntp.network.ntp_dhcp", - "description": "Verify the device accepts an NTP server address over DHCP", - "expected_behavior": "The device can accept an NTP server address provided by the DHCP server" + "description": "Accept NTP address over DHCP", + "expected_behavior": "Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)" } ] } diff --git a/modules/test/ntp/python/src/ntp_module.py b/modules/test/ntp/python/src/ntp_module.py index 4bfcba106..4053ce98a 100644 --- a/modules/test/ntp/python/src/ntp_module.py +++ b/modules/test/ntp/python/src/ntp_module.py @@ -36,7 +36,7 @@ def __init__(self, module): def _ntp_network_ntp_support(self): LOGGER.info('Running ntp.network.ntp_support') - packet_capture = rdpcap(MONITOR_CAPTURE_FILE) + packet_capture = rdpcap(STARTUP_CAPTURE_FILE) + rdpcap(MONITOR_CAPTURE_FILE) device_sends_ntp4 = False device_sends_ntp3 = False @@ -59,7 +59,7 @@ def _ntp_network_ntp_support(self): def _ntp_network_ntp_dhcp(self): LOGGER.info('Running ntp.network.ntp_dhcp') - packet_capture = rdpcap(MONITOR_CAPTURE_FILE) + packet_capture = rdpcap(STARTUP_CAPTURE_FILE) + rdpcap(MONITOR_CAPTURE_FILE) device_sends_ntp = False From 085785d2333f8a355ad9e1c2a9534f29857d5509 Mon Sep 17 00:00:00 2001 From: Jacob Boddey Date: Fri, 7 Jul 2023 20:06:13 +0100 Subject: [PATCH 04/15] Pylint --- framework/python/src/net_orc/network_validator.py | 12 ++++++------ framework/python/src/test_orc/test_orchestrator.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/framework/python/src/net_orc/network_validator.py b/framework/python/src/net_orc/network_validator.py index a4c51eb2d..f82787af5 100644 --- a/framework/python/src/net_orc/network_validator.py +++ b/framework/python/src/net_orc/network_validator.py @@ -193,7 +193,7 @@ def _get_os_user(self): LOGGER.error('An OS error occurred while retrieving the login name.') except Exception as error: # Catch any other unexpected exceptions - LOGGER.error('An exception occurred:', error) + LOGGER.error('An exception occurred:', error) return user def _get_user(self): @@ -203,15 +203,15 @@ def _get_user(self): except (KeyError, ImportError, ModuleNotFoundError, OSError) as e: # Handle specific exceptions individually if isinstance(e, KeyError): - LOGGER.error("USER environment variable not set or unavailable.") + LOGGER.error('USER environment variable not set or unavailable.') elif isinstance(e, ImportError): - LOGGER.error("Unable to import the getpass module.") + LOGGER.error('Unable to import the getpass module.') elif isinstance(e, ModuleNotFoundError): - LOGGER.error("The getpass module was not found.") + LOGGER.error('The getpass module was not found.') elif isinstance(e, OSError): - LOGGER.error("An OS error occurred while retrieving the username.") + LOGGER.error('An OS error occurred while retrieving the username.') else: - LOGGER.error("An exception occurred:", e) + LOGGER.error('An exception occurred:', e) return user def _get_device_status(self, module): diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py index e9f095fa4..fef4e5bb5 100644 --- a/framework/python/src/test_orc/test_orchestrator.py +++ b/framework/python/src/test_orc/test_orchestrator.py @@ -59,7 +59,7 @@ def start(self): # Setup the output directory self._host_user = util.get_host_user() os.makedirs(RUNTIME_DIR, exist_ok=True) - util.run_command(f'chown -R {self._host_user} {RUNTIME_DIR}') + util.run_command(f"chown -R {self._host_user} {RUNTIME_DIR}") self._load_test_modules() self.build_test_modules() @@ -108,7 +108,7 @@ def _generate_results(self, device): "runtime/test/" + device.mac_addr.replace(":", "") + "/results.json") with open(out_file, "w", encoding="utf-8") as f: json.dump(results, f, indent=2) - util.run_command(f'chown -R {self._host_user} {out_file}') + util.run_command(f"chown -R {self._host_user} {out_file}") return results def test_in_progress(self): @@ -145,12 +145,12 @@ def _run_test_module(self, module, device): device_startup_capture = os.path.join( self._root_path, "runtime/test/" + device.mac_addr.replace(":", "") + "/startup.pcap") - util.run_command(f'chown -R {self._host_user} {device_startup_capture}') + util.run_command(f"chown -R {self._host_user} {device_startup_capture}") device_monitor_capture = os.path.join( - self._root_path, 'runtime/test/' + device.mac_addr.replace(":", "") + - '/monitor.pcap') - util.run_command(f'chown -R {self._host_user} {device_monitor_capture}') + self._root_path, "runtime/test/" + device.mac_addr.replace(":", "") + + "/monitor.pcap") + util.run_command(f"chown -R {self._host_user} {device_monitor_capture}") client = docker.from_env() From 8a3ebce4d3fbdf88f3125aa46a688005e3284f62 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Fri, 7 Jul 2023 16:08:21 -0600 Subject: [PATCH 05/15] formatting --- .../python/src/grpc_server/dhcp_config.py | 45 ++++-- .../src/grpc_server/dhcp_config_test.py | 150 +++++++++--------- .../python/src/grpc_server/dhcp_server.py | 74 ++++----- .../python/src/grpc_server/network_service.py | 25 +-- .../python/src/grpc_server/radvd_server.py | 26 ++- .../python/src/grpc_server/dhcp_config.py | 47 ++++-- .../src/grpc_server/dhcp_config_test.py | 150 +++++++++--------- .../python/src/grpc_server/dhcp_server.py | 74 ++++----- .../python/src/grpc_server/network_service.py | 25 +-- .../python/src/grpc_server/radvd_server.py | 26 ++- .../test/conn/python/src/connection_module.py | 101 ++++++------ 11 files changed, 389 insertions(+), 354 deletions(-) diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py index 444faa87c..ae5fb1d97 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py @@ -186,13 +186,18 @@ def __str__(self): config += '\tprimary;' if self.primary else 'secondary;' config += '\n\t{ADDRESS_KEY} {ADDRESS};' if self.address is not None else '' config += '\n\t{PORT_KEY} {PORT};' if self.port is not None else '' - config += '\n\t{PEER_ADDRESS_KEY} {PEER_ADDRESS};' if self.peer_address is not None else '' - config += '\n\t{PEER_PORT_KEY} {PEER_PORT};' if self.peer_port is not None else '' - config += '\n\t{MAX_RESPONSE_DELAY_KEY} {MAX_RESPONSE_DELAY};' if self.max_response_delay is not None else '' - config += '\n\t{MAX_UNACKED_UPDATES_KEY} {MAX_UNACKED_UPDATES};' if self.max_unacked_updates is not None else '' + config += ('\n\t{PEER_ADDRESS_KEY} {PEER_ADDRESS};' + if self.peer_address is not None else '') + config += ('\n\t{PEER_PORT_KEY} {PEER_PORT};' + if self.peer_port is not None else '') + config += ('\n\t{MAX_RESPONSE_DELAY_KEY} {MAX_RESPONSE_DELAY};' + if self.max_response_delay is not None else '') + config += ('\n\t{MAX_UNACKED_UPDATES_KEY} {MAX_UNACKED_UPDATES};' + if self.max_unacked_updates is not None else '') config += '\n\t{MCLT_KEY} {MCLT};' if self.mclt is not None else '' config += '\n\t{SPLIT_KEY} {SPLIT};' if self.split is not None else '' - config += '\n\t{LOAD_BALANCE_MAX_SECONDS_KEY} {LOAD_BALANCE_MAX_SECONDS};' if self.load_balance_max_seconds is not None else '' + config += ('\n\t{LOAD_BALANCE_MAX_SECONDS_KEY} {LOAD_BALANCE_MAX_SECONDS};' + if self.load_balance_max_seconds is not None else '') config += '\n\r}}' config = config.format( @@ -220,9 +225,9 @@ def __str__(self): if not self.enabled: lines = config.strip().split('\n') - for i in range(len(lines)-1): + for i in range(len(lines) - 1): lines[i] = '#' + lines[i] - lines[-1] = '#' + lines[-1].strip() # Handle the last line separately + lines[-1] = '#' + lines[-1].strip() # Handle the last line separately config = '\n'.join(lines) return config @@ -302,15 +307,20 @@ def __init__(self, subnet): def __str__(self): config = 'subnet {SUBNET_OPTION} netmask {SUBNET_MASK_OPTION} {{' - config += '\n\t{NTP_OPTION_KEY} {NTP_OPTION};' if self._ntp_servers is not None else '' - config += '\n\t{SUBNET_MASK_OPTION_KEY} {SUBNET_MASK_OPTION};' if self._subnet_mask is not None else '' - config += '\n\t{BROADCAST_OPTION_KEY} {BROADCAST_OPTION};' if self._broadcast is not None else '' - config += '\n\t{ROUTER_OPTION_KEY} {ROUTER_OPTION};' if self._routers is not None else '' - config += '\n\t{DNS_OPTION_KEY} {DNS_OPTION};' if self._dns_servers is not None else '' - config += '\n\t{INTERFACE_KEY} {INTERFACE_OPTION};' if self._interface is not None else '' + config += ('\n\t{NTP_OPTION_KEY} {NTP_OPTION};' + if self._ntp_servers is not None else '') + config += ('\n\t{SUBNET_MASK_OPTION_KEY} {SUBNET_MASK_OPTION};' + if self._subnet_mask is not None else '') + config += ('\n\t{BROADCAST_OPTION_KEY} {BROADCAST_OPTION};' + if self._broadcast is not None else '') + config += ('\n\t{ROUTER_OPTION_KEY} {ROUTER_OPTION};' + if self._routers is not None else '') + config += ('\n\t{DNS_OPTION_KEY} {DNS_OPTION};' + if self._dns_servers is not None else '') + config += ('\n\t{INTERFACE_KEY} {INTERFACE_OPTION};' + if self._interface is not None else '') config += '\n\t{AUTHORITATIVE_KEY};' if self._authoritative else '' - config = config.format(length='multi-line', SUBNET_OPTION=self._subnet, NTP_OPTION_KEY=NTP_OPTION_KEY, @@ -407,8 +417,11 @@ def __init__(self, pool): def __str__(self): config = 'pool {{' - config += '\n\t\t{FAILOVER_KEY} "{FAILOVER}";' if self.failover_peer is not None else '' - config += '\n\t\t{RANGE_KEY} {RANGE_START} {RANGE_END};' if self.range_start is not None and self.range_end is not None else '' + config += ('\n\t\t{FAILOVER_KEY} "{FAILOVER}";' + if self.failover_peer is not None else '') + config += ('\n\t\t{RANGE_KEY} {RANGE_START} {RANGE_END};' + if self.range_start is not None and self.range_end is not None + else '') config += '\n\t}}' config = config.format( diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py index 2cc78403a..d9a0b0190 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py @@ -20,84 +20,88 @@ DHCP_CONFIG = None + def get_config_file_path(): - dhcp_config = DHCPConfig() - current_dir = os.path.dirname(os.path.abspath(__file__)) - module_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(current_dir)))) - conf_file = os.path.join(module_dir,CONFIG_FILE) - return conf_file + current_dir = os.path.dirname(os.path.abspath(__file__)) + module_dir = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(current_dir)))) + conf_file = os.path.join(module_dir, CONFIG_FILE) + return conf_file + def get_config(): - dhcp_config = DHCPConfig() - dhcp_config.resolve_config(get_config_file_path()) - return dhcp_config + dhcp_config = DHCPConfig() + dhcp_config.resolve_config(get_config_file_path()) + return dhcp_config + class DHCPConfigTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - # Resolve the config - global DHCP_CONFIG - DHCP_CONFIG = get_config() - - def test_resolve_config(self): - print('Test Resolve Config:\n' + str(DHCP_CONFIG)) - - # Resolve the raw config file - with open(get_config_file_path(),'r') as f: - lines = f.readlines() - - # Get the resolved config as a - conf_parts = str(DHCP_CONFIG).split('\n') - - # dhcpd conf is not picky about spacing so we just - # need to check contents of each line for matching - # to make sure evertying matches - for i in range(len(lines)): - self.assertEqual(lines[i].strip(),conf_parts[i].strip()) - - def test_disable_failover(self): - DHCP_CONFIG.disable_failover() - print('Test Disable Config:\n' + str(DHCP_CONFIG)) - config_lines = str(DHCP_CONFIG._peer).split('\n') - for line in config_lines: - self.assertTrue(line.startswith('#')) - - def test_enable_failover(self): - DHCP_CONFIG.enable_failover() - print('Test Enable Config:\n' + str(DHCP_CONFIG)) - config_lines = str(DHCP_CONFIG._peer).split('\n') - for line in config_lines: - self.assertFalse(line.startswith('#')) - - def test_add_reserved_host(self): - DHCP_CONFIG.add_reserved_host('test','00:11:22:33:44:55','192.168.10.5') - host = DHCP_CONFIG.get_reserved_host('00:11:22:33:44:55') - self.assertIsNotNone(host) - print('AddHostConfig:\n' + str(DHCP_CONFIG)) - - def test_delete_reserved_host(self): - DHCP_CONFIG.delete_reserved_host('00:11:22:33:44:55') - host = DHCP_CONFIG.get_reserved_host('00:11:22:33:44:55') - self.assertIsNone(host) - print('DeleteHostConfig:\n' + str(DHCP_CONFIG)) - - def test_resolve_config_with_hosts(self): - DHCP_CONFIG.add_reserved_host('test','00:11:22:33:44:55','192.168.10.5') - config_with_hosts = DHCPConfig() - config_with_hosts.make(str(DHCP_CONFIG)) - host = config_with_hosts.get_reserved_host('00:11:22:33:44:55') - self.assertIsNotNone(host) - print("ResolveConfigWithHosts:\n" + str(config_with_hosts)) + @classmethod + def setUpClass(cls): + # Resolve the config + global DHCP_CONFIG + DHCP_CONFIG = get_config() + + def test_resolve_config(self): + print('Test Resolve Config:\n' + str(DHCP_CONFIG)) + + # Resolve the raw config file + with open(get_config_file_path(), 'r', encoding='UTF-8') as f: + lines = f.readlines() + + # Get the resolved config as a + conf_parts = str(DHCP_CONFIG).split('\n') + + # dhcpd conf is not picky about spacing so we just + # need to check contents of each line for matching + # to make sure evertying matches + for i in range(len(lines)): + self.assertEqual(lines[i].strip(), conf_parts[i].strip()) + + def test_disable_failover(self): + DHCP_CONFIG.disable_failover() + print('Test Disable Config:\n' + str(DHCP_CONFIG)) + config_lines = str(DHCP_CONFIG._peer).split('\n') + for line in config_lines: + self.assertTrue(line.startswith('#')) + + def test_enable_failover(self): + DHCP_CONFIG.enable_failover() + print('Test Enable Config:\n' + str(DHCP_CONFIG)) + config_lines = str(DHCP_CONFIG._peer).split('\n') + for line in config_lines: + self.assertFalse(line.startswith('#')) + + def test_add_reserved_host(self): + DHCP_CONFIG.add_reserved_host('test', '00:11:22:33:44:55', '192.168.10.5') + host = DHCP_CONFIG.get_reserved_host('00:11:22:33:44:55') + self.assertIsNotNone(host) + print('AddHostConfig:\n' + str(DHCP_CONFIG)) + + def test_delete_reserved_host(self): + DHCP_CONFIG.delete_reserved_host('00:11:22:33:44:55') + host = DHCP_CONFIG.get_reserved_host('00:11:22:33:44:55') + self.assertIsNone(host) + print('DeleteHostConfig:\n' + str(DHCP_CONFIG)) + + def test_resolve_config_with_hosts(self): + DHCP_CONFIG.add_reserved_host('test', '00:11:22:33:44:55', '192.168.10.5') + config_with_hosts = DHCPConfig() + config_with_hosts.make(str(DHCP_CONFIG)) + host = config_with_hosts.get_reserved_host('00:11:22:33:44:55') + self.assertIsNotNone(host) + print('ResolveConfigWithHosts:\n' + str(config_with_hosts)) + if __name__ == '__main__': - suite = unittest.TestSuite() - suite.addTest(DHCPConfigTest('test_resolve_config')) - suite.addTest(DHCPConfigTest('test_disable_failover')) - suite.addTest(DHCPConfigTest('test_enable_failover')) - suite.addTest(DHCPConfigTest('test_add_reserved_host')) - suite.addTest(DHCPConfigTest('test_delete_reserved_host')) - suite.addTest(DHCPConfigTest('test_resolve_config_with_hosts')) - - runner = unittest.TextTestRunner() - runner.run(suite) \ No newline at end of file + suite = unittest.TestSuite() + suite.addTest(DHCPConfigTest('test_resolve_config')) + suite.addTest(DHCPConfigTest('test_disable_failover')) + suite.addTest(DHCPConfigTest('test_enable_failover')) + suite.addTest(DHCPConfigTest('test_add_reserved_host')) + suite.addTest(DHCPConfigTest('test_delete_reserved_host')) + suite.addTest(DHCPConfigTest('test_resolve_config_with_hosts')) + + runner = unittest.TextTestRunner() + runner.run(suite) diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py index 2f67b0c2d..bc64a868e 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py @@ -13,6 +13,7 @@ # limitations under the License. """Contains all the necessary classes to maintain the DHCP server""" +import sys import time from common import logger from common import util @@ -35,78 +36,78 @@ def __init__(self): self.dhcp_config.resolve_config() def restart(self): - LOGGER.info("Restarting DHCP Server") - isc_started = util.run_command("service isc-dhcp-server restart", False) + LOGGER.info('Restarting DHCP Server') + isc_started = util.run_command('service isc-dhcp-server restart', False) radvd_started = self.radvd.restart() started = isc_started and radvd_started - LOGGER.info("DHCP Restarted: " + str(started)) + LOGGER.info('DHCP Restarted: ' + str(started)) return started def start(self): - LOGGER.info("Starting DHCP Server") - isc_started = util.run_command("service isc-dhcp-server start", False) + LOGGER.info('Starting DHCP Server') + isc_started = util.run_command('service isc-dhcp-server start', False) radvd_started = self.radvd.start() started = isc_started and radvd_started - LOGGER.info("DHCP Started: " + str(started)) + LOGGER.info('DHCP Started: ' + str(started)) return started def stop(self): - LOGGER.info("Stopping DHCP Server") - isc_stopped = util.run_command("service isc-dhcp-server stop", False) + LOGGER.info('Stopping DHCP Server') + isc_stopped = util.run_command('service isc-dhcp-server stop', False) radvd_stopped = self.radvd.stop() stopped = isc_stopped and radvd_stopped - LOGGER.info("DHCP Stopped: " + str(stopped)) + LOGGER.info('DHCP Stopped: ' + str(stopped)) return stopped def is_running(self): - LOGGER.info("Checking DHCP Status") - response = util.run_command("service isc-dhcp-server status") - isc_running = response[0] == 'Status of ISC DHCPv4 server: dhcpd is running.' + LOGGER.info('Checking DHCP Status') + response = util.run_command('service isc-dhcp-server status') + isc_running = response[ + 0] == 'Status of ISC DHCPv4 server: dhcpd is running.' radvd_running = self.radvd.is_running() running = isc_running and radvd_running - LOGGER.info("DHCP Status: " + str(running)) + LOGGER.info('DHCP Status: ' + str(running)) return running def boot(self): - LOGGER.info("Booting DHCP Server") + LOGGER.info('Booting DHCP Server') isc_booted = False radvd_booted = False if self.is_running(): - LOGGER.info("Stopping isc-dhcp-server") + LOGGER.info('Stopping isc-dhcp-server') stopped = self.stop() - LOGGER.info("isc-dhcp-server stopped: " + str(stopped)) + LOGGER.info('isc-dhcp-server stopped: ' + str(stopped)) if self.radvd.is_running(): - LOGGER.info("Stopping RADVD") + LOGGER.info('Stopping RADVD') stopped = self.radvd.stop() - LOGGER.info("radvd stopped: " + str(stopped)) + LOGGER.info('radvd stopped: ' + str(stopped)) - LOGGER.info("Starting isc-dhcp-server") + LOGGER.info('Starting isc-dhcp-server') if self.start(): isc_booted = False # Scan for 5 seconds if not yet ready - for i in range(5): + for _ in range(5): time.sleep(1) isc_booted = self.is_running() if isc_booted: - break; - LOGGER.info("isc-dhcp-server started: " + str(isc_booted)) + break + LOGGER.info('isc-dhcp-server started: ' + str(isc_booted)) - LOGGER.info("Starting RADVD") + LOGGER.info('Starting RADVD') if self.radvd.start(): radvd_booted = False # Scan for 5 seconds if not yet ready - for i in range(5): + for _ in range(5): time.sleep(1) radvd_booted = self.radvd.is_running() if radvd_booted: - break; - LOGGER.info("RADVD started: " + str(radvd_booted)) - - + break + LOGGER.info('RADVD started: ' + str(radvd_booted)) return isc_booted and radvd_booted + def run(): dhcp_server = DHCPServer() booted = dhcp_server.boot() @@ -117,14 +118,15 @@ def run(): config = str(dhcp_server.dhcp_config) while True: - dhcp_server.dhcp_config.resolve_config() - new_config = str(dhcp_server.dhcp_config) - if config != new_config: - LOGGER.info("DHCP Config Changed") - config = new_config - success = dhcp_server.restart() - success = dhcp_server.radvd.restart() - time.sleep(1) + dhcp_server.dhcp_config.resolve_config() + new_config = str(dhcp_server.dhcp_config) + if config != new_config: + LOGGER.info('DHCP Config Changed') + config = new_config + dhcp_server.restart() + dhcp_server.radvd.restart() + time.sleep(1) + if __name__ == '__main__': run() diff --git a/modules/network/dhcp-1/python/src/grpc_server/network_service.py b/modules/network/dhcp-1/python/src/grpc_server/network_service.py index a693ac3a1..92726025d 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/network_service.py +++ b/modules/network/dhcp-1/python/src/grpc_server/network_service.py @@ -25,6 +25,7 @@ LOG_NAME = 'network_service' LOGGER = None + class NetworkService(pb2_grpc.NetworkModule): """gRPC endpoints for the DHCP Server""" @@ -47,7 +48,7 @@ def RestartDHCPServer(self, request, context): # pylint: disable=W0613 started = self._dhcp_server.restart() LOGGER.info('DHCP server restarted: ' + (str(started))) return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to restart DHCP server: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -59,7 +60,7 @@ def StartDHCPServer(self, request, context): # pylint: disable=W0613 started = self._dhcp_server.start() LOGGER.info('DHCP server started: ' + (str(started))) return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to start DHCP server: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -71,12 +72,12 @@ def StopDHCPServer(self, request, context): # pylint: disable=W0613 stopped = self._dhcp_server.stop() LOGGER.info('DHCP server stopped: ' + (str(stopped))) return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to stop DHCP server: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) return pb2.Response(code=500, message=fail_message) - + def AddReservedLease(self, request, context): # pylint: disable=W0613 LOGGER.info('Add reserved lease called') try: @@ -86,7 +87,7 @@ def AddReservedLease(self, request, context): # pylint: disable=W0613 dhcp_config.write_config() LOGGER.info('Reserved lease added') return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to add reserved lease: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -100,7 +101,7 @@ def DeleteReservedLease(self, request, context): # pylint: disable=W0613 dhcp_config.write_config() LOGGER.info('Reserved lease deleted') return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to delete reserved lease: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -114,7 +115,7 @@ def DisableFailover(self, request, contest): # pylint: disable=W0613 dhcp_config.write_config() LOGGER.info('Failover disabled') return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to disable failover: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -128,7 +129,7 @@ def EnableFailover(self, request, contest): # pylint: disable=W0613 dhcp_config.write_config() LOGGER.info('Failover enabled') return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to enable failover: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -143,7 +144,7 @@ def GetDHCPRange(self, request, context): # pylint: disable=W0613 try: pool = self._get_dhcp_config()._subnets[0].pools[0] return pb2.DHCPRange(code=200, start=pool.range_start, end=pool.range_end) - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to get DHCP range: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -161,7 +162,7 @@ def GetLease(self, request, context): # pylint: disable=W0613 return pb2.Response(code=200, message=str(lease)) else: return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to get lease: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -179,7 +180,7 @@ def SetDHCPRange(self, request, context): # pylint: disable=W0613 dhcp_config.write_config() LOGGER.info('DHCP range set') return pb2.Response(code=200, message='DHCP Range Set') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to set DHCP range: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -191,4 +192,4 @@ def GetStatus(self, request, context): # pylint: disable=W0613 """ dhcp_status = self._dhcp_server.is_running() message = str({'dhcpStatus': dhcp_status}) - return pb2.Response(code=200, message=message) \ No newline at end of file + return pb2.Response(code=200, message=message) diff --git a/modules/network/dhcp-1/python/src/grpc_server/radvd_server.py b/modules/network/dhcp-1/python/src/grpc_server/radvd_server.py index 48e063e61..8bb1d0539 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/radvd_server.py +++ b/modules/network/dhcp-1/python/src/grpc_server/radvd_server.py @@ -13,10 +13,8 @@ # limitations under the License. """Contains all the necessary classes to maintain the DHCP server""" -import time from common import logger from common import util -from dhcp_config import DHCPConfig CONFIG_FILE = '/etc/dhcp/dhcpd.conf' LOG_NAME = 'radvd' @@ -31,25 +29,25 @@ def __init__(self): LOGGER = logger.get_logger(LOG_NAME, 'dhcp-1') def restart(self): - LOGGER.info("Restarting RADVD Server") - response = util.run_command("radvd-service restart", False) - LOGGER.info("RADVD Restarted: " + str(response)) + LOGGER.info('Restarting RADVD Server') + response = util.run_command('radvd-service restart', False) + LOGGER.info('RADVD Restarted: ' + str(response)) return response def start(self): - LOGGER.info("Starting RADVD Server") - response = util.run_command("radvd-service start", False) - LOGGER.info("RADVD Started: " + str(response)) + LOGGER.info('Starting RADVD Server') + response = util.run_command('radvd-service start', False) + LOGGER.info('RADVD Started: ' + str(response)) return response def stop(self): - LOGGER.info("Stopping RADVD Server") - response = util.run_command("radvd-service stop", False) - LOGGER.info("RADVD Stopped: " + str(response)) + LOGGER.info('Stopping RADVD Server') + response = util.run_command('radvd-service stop', False) + LOGGER.info('RADVD Stopped: ' + str(response)) return response def is_running(self): - LOGGER.info("Checking RADVD Status") - response = util.run_command("radvd-service status") - LOGGER.info("RADVD Status: " + str(response)) + LOGGER.info('Checking RADVD Status') + response = util.run_command('radvd-service status') + LOGGER.info('RADVD Status: ' + str(response)) return response[0] == 'radvd service is running.' diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py index 33cb5938c..5da5e4cf2 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py +++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py @@ -186,13 +186,18 @@ def __str__(self): config += '\tprimary;' if self.primary else 'secondary;' config += '\n\t{ADDRESS_KEY} {ADDRESS};' if self.address is not None else '' config += '\n\t{PORT_KEY} {PORT};' if self.port is not None else '' - config += '\n\t{PEER_ADDRESS_KEY} {PEER_ADDRESS};' if self.peer_address is not None else '' - config += '\n\t{PEER_PORT_KEY} {PEER_PORT};' if self.peer_port is not None else '' - config += '\n\t{MAX_RESPONSE_DELAY_KEY} {MAX_RESPONSE_DELAY};' if self.max_response_delay is not None else '' - config += '\n\t{MAX_UNACKED_UPDATES_KEY} {MAX_UNACKED_UPDATES};' if self.max_unacked_updates is not None else '' + config += ('\n\t{PEER_ADDRESS_KEY} {PEER_ADDRESS};' + if self.peer_address is not None else '') + config += ('\n\t{PEER_PORT_KEY} {PEER_PORT};' + if self.peer_port is not None else '') + config += ('\n\t{MAX_RESPONSE_DELAY_KEY} {MAX_RESPONSE_DELAY};' + if self.max_response_delay is not None else '') + config += ('\n\t{MAX_UNACKED_UPDATES_KEY} {MAX_UNACKED_UPDATES};' + if self.max_unacked_updates is not None else '') config += '\n\t{MCLT_KEY} {MCLT};' if self.mclt is not None else '' config += '\n\t{SPLIT_KEY} {SPLIT};' if self.split is not None else '' - config += '\n\t{LOAD_BALANCE_MAX_SECONDS_KEY} {LOAD_BALANCE_MAX_SECONDS};' if self.load_balance_max_seconds is not None else '' + config += ('\n\t{LOAD_BALANCE_MAX_SECONDS_KEY} {LOAD_BALANCE_MAX_SECONDS};' + if self.load_balance_max_seconds is not None else '') config += '\n\r}}' config = config.format( @@ -220,9 +225,9 @@ def __str__(self): if not self.enabled: lines = config.strip().split('\n') - for i in range(len(lines)-1): + for i in range(len(lines) - 1): lines[i] = '#' + lines[i] - lines[-1] = '#' + lines[-1].strip() # Handle the last line separately + lines[-1] = '#' + lines[-1].strip() # Handle the last line separately config = '\n'.join(lines) return config @@ -302,15 +307,20 @@ def __init__(self, subnet): def __str__(self): config = 'subnet {SUBNET_OPTION} netmask {SUBNET_MASK_OPTION} {{' - config += '\n\t{NTP_OPTION_KEY} {NTP_OPTION};' if self._ntp_servers is not None else '' - config += '\n\t{SUBNET_MASK_OPTION_KEY} {SUBNET_MASK_OPTION};' if self._subnet_mask is not None else '' - config += '\n\t{BROADCAST_OPTION_KEY} {BROADCAST_OPTION};' if self._broadcast is not None else '' - config += '\n\t{ROUTER_OPTION_KEY} {ROUTER_OPTION};' if self._routers is not None else '' - config += '\n\t{DNS_OPTION_KEY} {DNS_OPTION};' if self._dns_servers is not None else '' - config += '\n\t{INTERFACE_KEY} {INTERFACE_OPTION};' if self._interface is not None else '' + config += ('\n\t{NTP_OPTION_KEY} {NTP_OPTION};' + if self._ntp_servers is not None else '') + config += ('\n\t{SUBNET_MASK_OPTION_KEY} {SUBNET_MASK_OPTION};' + if self._subnet_mask is not None else '') + config += ('\n\t{BROADCAST_OPTION_KEY} {BROADCAST_OPTION};' + if self._broadcast is not None else '') + config += ('\n\t{ROUTER_OPTION_KEY} {ROUTER_OPTION};' + if self._routers is not None else '') + config += ('\n\t{DNS_OPTION_KEY} {DNS_OPTION};' + if self._dns_servers is not None else '') + config += ('\n\t{INTERFACE_KEY} {INTERFACE_OPTION};' + if self._interface is not None else '') config += '\n\t{AUTHORITATIVE_KEY};' if self._authoritative else '' - config = config.format(length='multi-line', SUBNET_OPTION=self._subnet, NTP_OPTION_KEY=NTP_OPTION_KEY, @@ -407,8 +417,11 @@ def __init__(self, pool): def __str__(self): config = 'pool {{' - config += '\n\t\t{FAILOVER_KEY} "{FAILOVER}";' if self.failover_peer is not None else '' - config += '\n\t\t{RANGE_KEY} {RANGE_START} {RANGE_END};' if self.range_start is not None and self.range_end is not None else '' + config += ('\n\t\t{FAILOVER_KEY} "{FAILOVER}";' + if self.failover_peer is not None else '') + config += ('\n\t\t{RANGE_KEY} {RANGE_START} {RANGE_END};' + if self.range_start is not None and self.range_end is not None + else '') config += '\n\t}}' config = config.format( @@ -490,4 +503,4 @@ def resolve_host(self, reserved_host): self.hw_addr = part.strip().split(HARDWARE_KEY)[1].strip().split(';')[0] elif FIXED_ADDRESS_KEY in part: self.fixed_addr = part.strip().split( - FIXED_ADDRESS_KEY)[1].strip().split(';')[0] \ No newline at end of file + FIXED_ADDRESS_KEY)[1].strip().split(';')[0] diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py index 2cc78403a..d9a0b0190 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py +++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py @@ -20,84 +20,88 @@ DHCP_CONFIG = None + def get_config_file_path(): - dhcp_config = DHCPConfig() - current_dir = os.path.dirname(os.path.abspath(__file__)) - module_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(current_dir)))) - conf_file = os.path.join(module_dir,CONFIG_FILE) - return conf_file + current_dir = os.path.dirname(os.path.abspath(__file__)) + module_dir = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(current_dir)))) + conf_file = os.path.join(module_dir, CONFIG_FILE) + return conf_file + def get_config(): - dhcp_config = DHCPConfig() - dhcp_config.resolve_config(get_config_file_path()) - return dhcp_config + dhcp_config = DHCPConfig() + dhcp_config.resolve_config(get_config_file_path()) + return dhcp_config + class DHCPConfigTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - # Resolve the config - global DHCP_CONFIG - DHCP_CONFIG = get_config() - - def test_resolve_config(self): - print('Test Resolve Config:\n' + str(DHCP_CONFIG)) - - # Resolve the raw config file - with open(get_config_file_path(),'r') as f: - lines = f.readlines() - - # Get the resolved config as a - conf_parts = str(DHCP_CONFIG).split('\n') - - # dhcpd conf is not picky about spacing so we just - # need to check contents of each line for matching - # to make sure evertying matches - for i in range(len(lines)): - self.assertEqual(lines[i].strip(),conf_parts[i].strip()) - - def test_disable_failover(self): - DHCP_CONFIG.disable_failover() - print('Test Disable Config:\n' + str(DHCP_CONFIG)) - config_lines = str(DHCP_CONFIG._peer).split('\n') - for line in config_lines: - self.assertTrue(line.startswith('#')) - - def test_enable_failover(self): - DHCP_CONFIG.enable_failover() - print('Test Enable Config:\n' + str(DHCP_CONFIG)) - config_lines = str(DHCP_CONFIG._peer).split('\n') - for line in config_lines: - self.assertFalse(line.startswith('#')) - - def test_add_reserved_host(self): - DHCP_CONFIG.add_reserved_host('test','00:11:22:33:44:55','192.168.10.5') - host = DHCP_CONFIG.get_reserved_host('00:11:22:33:44:55') - self.assertIsNotNone(host) - print('AddHostConfig:\n' + str(DHCP_CONFIG)) - - def test_delete_reserved_host(self): - DHCP_CONFIG.delete_reserved_host('00:11:22:33:44:55') - host = DHCP_CONFIG.get_reserved_host('00:11:22:33:44:55') - self.assertIsNone(host) - print('DeleteHostConfig:\n' + str(DHCP_CONFIG)) - - def test_resolve_config_with_hosts(self): - DHCP_CONFIG.add_reserved_host('test','00:11:22:33:44:55','192.168.10.5') - config_with_hosts = DHCPConfig() - config_with_hosts.make(str(DHCP_CONFIG)) - host = config_with_hosts.get_reserved_host('00:11:22:33:44:55') - self.assertIsNotNone(host) - print("ResolveConfigWithHosts:\n" + str(config_with_hosts)) + @classmethod + def setUpClass(cls): + # Resolve the config + global DHCP_CONFIG + DHCP_CONFIG = get_config() + + def test_resolve_config(self): + print('Test Resolve Config:\n' + str(DHCP_CONFIG)) + + # Resolve the raw config file + with open(get_config_file_path(), 'r', encoding='UTF-8') as f: + lines = f.readlines() + + # Get the resolved config as a + conf_parts = str(DHCP_CONFIG).split('\n') + + # dhcpd conf is not picky about spacing so we just + # need to check contents of each line for matching + # to make sure evertying matches + for i in range(len(lines)): + self.assertEqual(lines[i].strip(), conf_parts[i].strip()) + + def test_disable_failover(self): + DHCP_CONFIG.disable_failover() + print('Test Disable Config:\n' + str(DHCP_CONFIG)) + config_lines = str(DHCP_CONFIG._peer).split('\n') + for line in config_lines: + self.assertTrue(line.startswith('#')) + + def test_enable_failover(self): + DHCP_CONFIG.enable_failover() + print('Test Enable Config:\n' + str(DHCP_CONFIG)) + config_lines = str(DHCP_CONFIG._peer).split('\n') + for line in config_lines: + self.assertFalse(line.startswith('#')) + + def test_add_reserved_host(self): + DHCP_CONFIG.add_reserved_host('test', '00:11:22:33:44:55', '192.168.10.5') + host = DHCP_CONFIG.get_reserved_host('00:11:22:33:44:55') + self.assertIsNotNone(host) + print('AddHostConfig:\n' + str(DHCP_CONFIG)) + + def test_delete_reserved_host(self): + DHCP_CONFIG.delete_reserved_host('00:11:22:33:44:55') + host = DHCP_CONFIG.get_reserved_host('00:11:22:33:44:55') + self.assertIsNone(host) + print('DeleteHostConfig:\n' + str(DHCP_CONFIG)) + + def test_resolve_config_with_hosts(self): + DHCP_CONFIG.add_reserved_host('test', '00:11:22:33:44:55', '192.168.10.5') + config_with_hosts = DHCPConfig() + config_with_hosts.make(str(DHCP_CONFIG)) + host = config_with_hosts.get_reserved_host('00:11:22:33:44:55') + self.assertIsNotNone(host) + print('ResolveConfigWithHosts:\n' + str(config_with_hosts)) + if __name__ == '__main__': - suite = unittest.TestSuite() - suite.addTest(DHCPConfigTest('test_resolve_config')) - suite.addTest(DHCPConfigTest('test_disable_failover')) - suite.addTest(DHCPConfigTest('test_enable_failover')) - suite.addTest(DHCPConfigTest('test_add_reserved_host')) - suite.addTest(DHCPConfigTest('test_delete_reserved_host')) - suite.addTest(DHCPConfigTest('test_resolve_config_with_hosts')) - - runner = unittest.TextTestRunner() - runner.run(suite) \ No newline at end of file + suite = unittest.TestSuite() + suite.addTest(DHCPConfigTest('test_resolve_config')) + suite.addTest(DHCPConfigTest('test_disable_failover')) + suite.addTest(DHCPConfigTest('test_enable_failover')) + suite.addTest(DHCPConfigTest('test_add_reserved_host')) + suite.addTest(DHCPConfigTest('test_delete_reserved_host')) + suite.addTest(DHCPConfigTest('test_resolve_config_with_hosts')) + + runner = unittest.TextTestRunner() + runner.run(suite) diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py index 1431d6ddd..93f88c376 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py +++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py @@ -13,6 +13,7 @@ # limitations under the License. """Contains all the necessary classes to maintain the DHCP server""" +import sys import time from common import logger from common import util @@ -35,78 +36,78 @@ def __init__(self): self.dhcp_config.resolve_config() def restart(self): - LOGGER.info("Restarting DHCP Server") - isc_started = util.run_command("service isc-dhcp-server restart", False) + LOGGER.info('Restarting DHCP Server') + isc_started = util.run_command('service isc-dhcp-server restart', False) radvd_started = self.radvd.restart() started = isc_started and radvd_started - LOGGER.info("DHCP Restarted: " + str(started)) + LOGGER.info('DHCP Restarted: ' + str(started)) return started def start(self): - LOGGER.info("Starting DHCP Server") - isc_started = util.run_command("service isc-dhcp-server start", False) + LOGGER.info('Starting DHCP Server') + isc_started = util.run_command('service isc-dhcp-server start', False) radvd_started = self.radvd.start() started = isc_started and radvd_started - LOGGER.info("DHCP Started: " + str(started)) + LOGGER.info('DHCP Started: ' + str(started)) return started def stop(self): - LOGGER.info("Stopping DHCP Server") - isc_stopped = util.run_command("service isc-dhcp-server stop", False) + LOGGER.info('Stopping DHCP Server') + isc_stopped = util.run_command('service isc-dhcp-server stop', False) radvd_stopped = self.radvd.stop() stopped = isc_stopped and radvd_stopped - LOGGER.info("DHCP Stopped: " + str(stopped)) + LOGGER.info('DHCP Stopped: ' + str(stopped)) return stopped def is_running(self): - LOGGER.info("Checking DHCP Status") - response = util.run_command("service isc-dhcp-server status") - isc_running = response[0] == 'Status of ISC DHCPv4 server: dhcpd is running.' + LOGGER.info('Checking DHCP Status') + response = util.run_command('service isc-dhcp-server status') + isc_running = response[ + 0] == 'Status of ISC DHCPv4 server: dhcpd is running.' radvd_running = self.radvd.is_running() running = isc_running and radvd_running - LOGGER.info("DHCP Status: " + str(running)) + LOGGER.info('DHCP Status: ' + str(running)) return running def boot(self): - LOGGER.info("Booting DHCP Server") + LOGGER.info('Booting DHCP Server') isc_booted = False radvd_booted = False if self.is_running(): - LOGGER.info("Stopping isc-dhcp-server") + LOGGER.info('Stopping isc-dhcp-server') stopped = self.stop() - LOGGER.info("isc-dhcp-server stopped: " + str(stopped)) + LOGGER.info('isc-dhcp-server stopped: ' + str(stopped)) if self.radvd.is_running(): - LOGGER.info("Stopping RADVD") + LOGGER.info('Stopping RADVD') stopped = self.radvd.stop() - LOGGER.info("radvd stopped: " + str(stopped)) + LOGGER.info('radvd stopped: ' + str(stopped)) - LOGGER.info("Starting isc-dhcp-server") + LOGGER.info('Starting isc-dhcp-server') if self.start(): isc_booted = False # Scan for 5 seconds if not yet ready - for i in range(5): + for _ in range(5): time.sleep(1) isc_booted = self.is_running() if isc_booted: - break; - LOGGER.info("isc-dhcp-server started: " + str(isc_booted)) + break + LOGGER.info('isc-dhcp-server started: ' + str(isc_booted)) - LOGGER.info("Starting RADVD") + LOGGER.info('Starting RADVD') if self.radvd.start(): radvd_booted = False # Scan for 5 seconds if not yet ready - for i in range(5): + for _ in range(5): time.sleep(1) radvd_booted = self.radvd.is_running() if radvd_booted: - break; - LOGGER.info("RADVD started: " + str(radvd_booted)) - - + break + LOGGER.info('RADVD started: ' + str(radvd_booted)) return isc_booted and radvd_booted + def run(): dhcp_server = DHCPServer() booted = dhcp_server.boot() @@ -117,14 +118,15 @@ def run(): config = str(dhcp_server.dhcp_config) while True: - dhcp_server.dhcp_config.resolve_config() - new_config = str(dhcp_server.dhcp_config) - if config != new_config: - LOGGER.info("DHCP Config Changed") - config = new_config - success = dhcp_server.restart() - success = dhcp_server.radvd.restart() - time.sleep(1) + dhcp_server.dhcp_config.resolve_config() + new_config = str(dhcp_server.dhcp_config) + if config != new_config: + LOGGER.info('DHCP Config Changed') + config = new_config + dhcp_server.restart() + dhcp_server.radvd.restart() + time.sleep(1) + if __name__ == '__main__': run() diff --git a/modules/network/dhcp-2/python/src/grpc_server/network_service.py b/modules/network/dhcp-2/python/src/grpc_server/network_service.py index 5af9e6c44..f9deba965 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/network_service.py +++ b/modules/network/dhcp-2/python/src/grpc_server/network_service.py @@ -25,6 +25,7 @@ LOG_NAME = 'network_service' LOGGER = None + class NetworkService(pb2_grpc.NetworkModule): """gRPC endpoints for the DHCP Server""" @@ -47,7 +48,7 @@ def RestartDHCPServer(self, request, context): # pylint: disable=W0613 started = self._dhcp_server.restart() LOGGER.info('DHCP server restarted: ' + (str(started))) return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to restart DHCP server: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -59,7 +60,7 @@ def StartDHCPServer(self, request, context): # pylint: disable=W0613 started = self._dhcp_server.start() LOGGER.info('DHCP server started: ' + (str(started))) return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to start DHCP server: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -71,12 +72,12 @@ def StopDHCPServer(self, request, context): # pylint: disable=W0613 stopped = self._dhcp_server.stop() LOGGER.info('DHCP server stopped: ' + (str(stopped))) return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to stop DHCP server: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) return pb2.Response(code=500, message=fail_message) - + def AddReservedLease(self, request, context): # pylint: disable=W0613 LOGGER.info('Add reserved lease called') try: @@ -86,7 +87,7 @@ def AddReservedLease(self, request, context): # pylint: disable=W0613 dhcp_config.write_config() LOGGER.info('Reserved lease added') return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to add reserved lease: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -100,7 +101,7 @@ def DeleteReservedLease(self, request, context): # pylint: disable=W0613 dhcp_config.write_config() LOGGER.info('Reserved lease deleted') return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to delete reserved lease: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -114,7 +115,7 @@ def DisableFailover(self, request, contest): # pylint: disable=W0613 dhcp_config.write_config() LOGGER.info('Failover disabled') return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to disable failover: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -128,7 +129,7 @@ def EnableFailover(self, request, contest): # pylint: disable=W0613 dhcp_config.write_config() LOGGER.info('Failover enabled') return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to enable failover: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -143,7 +144,7 @@ def GetDHCPRange(self, request, context): # pylint: disable=W0613 try: pool = self._get_dhcp_config()._subnets[0].pools[0] return pb2.DHCPRange(code=200, start=pool.range_start, end=pool.range_end) - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to get DHCP range: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -161,7 +162,7 @@ def GetLease(self, request, context): # pylint: disable=W0613 return pb2.Response(code=200, message=str(lease)) else: return pb2.Response(code=200, message='{}') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to get lease: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -179,7 +180,7 @@ def SetDHCPRange(self, request, context): # pylint: disable=W0613 dhcp_config.write_config() LOGGER.info('DHCP range set') return pb2.Response(code=200, message='DHCP Range Set') - except Exception as e: # pylint: disable=W0718 + except Exception as e: # pylint: disable=W0718 fail_message = 'Failed to set DHCP range: ' + str(e) LOGGER.error(fail_message) LOGGER.error(traceback.format_exc()) @@ -191,4 +192,4 @@ def GetStatus(self, request, context): # pylint: disable=W0613 """ dhcp_status = self._dhcp_server.is_running() message = str({'dhcpStatus': dhcp_status}) - return pb2.Response(code=200, message=message) \ No newline at end of file + return pb2.Response(code=200, message=message) diff --git a/modules/network/dhcp-2/python/src/grpc_server/radvd_server.py b/modules/network/dhcp-2/python/src/grpc_server/radvd_server.py index 0c6ef90d6..bc5d8b55f 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/radvd_server.py +++ b/modules/network/dhcp-2/python/src/grpc_server/radvd_server.py @@ -13,10 +13,8 @@ # limitations under the License. """Contains all the necessary classes to maintain the DHCP server""" -import time from common import logger from common import util -from dhcp_config import DHCPConfig CONFIG_FILE = '/etc/dhcp/dhcpd.conf' LOG_NAME = 'radvd' @@ -31,25 +29,25 @@ def __init__(self): LOGGER = logger.get_logger(LOG_NAME, 'dhcp-2') def restart(self): - LOGGER.info("Restarting RADVD Server") - response = util.run_command("radvd-service restart", False) - LOGGER.info("RADVD Restarted: " + str(response)) + LOGGER.info('Restarting RADVD Server') + response = util.run_command('radvd-service restart', False) + LOGGER.info('RADVD Restarted: ' + str(response)) return response def start(self): - LOGGER.info("Starting RADVD Server") - response = util.run_command("radvd-service start", False) - LOGGER.info("RADVD Started: " + str(response)) + LOGGER.info('Starting RADVD Server') + response = util.run_command('radvd-service start', False) + LOGGER.info('RADVD Started: ' + str(response)) return response def stop(self): - LOGGER.info("Stopping RADVD Server") - response = util.run_command("radvd-service stop", False) - LOGGER.info("RADVD Stopped: " + str(response)) + LOGGER.info('Stopping RADVD Server') + response = util.run_command('radvd-service stop', False) + LOGGER.info('RADVD Stopped: ' + str(response)) return response def is_running(self): - LOGGER.info("Checking RADVD Status") - response = util.run_command("radvd-service status") - LOGGER.info("RADVD Status: " + str(response)) + LOGGER.info('Checking RADVD Status') + response = util.run_command('radvd-service status') + LOGGER.info('RADVD Status: ' + str(response)) return response[0] == 'radvd service is running.' diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 5b3bf7038..fd60bc122 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -11,17 +11,17 @@ # 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. - """Connection test module""" import util import sys -from scapy.all import * +import json +from scapy.all import rdpcap, DHCP, Ether from test_module import TestModule from dhcp1.client import Client as DHCPClient1 -LOG_NAME = "test_connection" +LOG_NAME = 'test_connection' LOGGER = None -OUI_FILE="/usr/local/etc/oui.txt" +OUI_FILE = '/usr/local/etc/oui.txt' DHCP_SERVER_CAPTURE_FILE = '/runtime/network/dhcp-1.pcap' STARTUP_CAPTURE_FILE = '/runtime/device/startup.pcap' MONITOR_CAPTURE_FILE = '/runtime/device/monitor.pcap' @@ -35,10 +35,11 @@ def __init__(self, module): global LOGGER LOGGER = self._get_logger() self.dhcp1_client = DHCPClient1() - - # ToDo: Move this into some level of testing, leave for + + # ToDo: Move this into some level of testing, leave for # reference until tests are implemented with these calls - # response = self.dhcp1_client.add_reserved_lease('test','00:11:22:33:44:55','10.10.10.21') + # response = self.dhcp1_client.add_reserved_lease( + # 'test','00:11:22:33:44:55','10.10.10.21') # print("AddLeaseResp: " + str(response)) # response = self.dhcp1_client.delete_reserved_lease('00:11:22:33:44:55') @@ -63,52 +64,53 @@ def __init__(self, module): # print("Set Range: " + str(response)) def _connection_dhcp_address(self): - LOGGER.info("Running connection.dhcp_address") + LOGGER.info('Running connection.dhcp_address') response = self.dhcp1_client.get_lease(self._device_mac) - LOGGER.info("DHCP Lease resolved:\n" + str(response)) + LOGGER.info('DHCP Lease resolved:\n' + str(response)) if response.code == 200: - lease = eval(response.message) + #lease = eval(response.message) + lease = json.loads(response.message) if 'ip' in lease: ip_addr = lease['ip'] - LOGGER.info("IP Resolved: " + ip_addr) - LOGGER.info("Attempting to ping device..."); + LOGGER.info('IP Resolved: ' + ip_addr) + LOGGER.info('Attempting to ping device...') ping_success = self._ping(self._device_ipv4_addr) - LOGGER.info("Ping Success: " + str(ping_success)) + LOGGER.info('Ping Success: ' + str(ping_success)) if ping_success: - return True, "Device responded to leased ip address" + return True, 'Device responded to leased ip address' else: - return False, "Device did not respond to leased ip address" + return False, 'Device did not respond to leased ip address' else: - LOGGER.info("No DHCP lease found for: " + self._device_mac) - return False, "No DHCP lease found for: " + self._device_mac + LOGGER.info('No DHCP lease found for: ' + self._device_mac) + return False, 'No DHCP lease found for: ' + self._device_mac def _connection_mac_address(self): - LOGGER.info("Running connection.mac_address") + LOGGER.info('Running connection.mac_address') if self._device_mac is not None: - LOGGER.info("MAC address found: " + self._device_mac) - return True, "MAC address found: " + self._device_mac + LOGGER.info('MAC address found: ' + self._device_mac) + return True, 'MAC address found: ' + self._device_mac else: - LOGGER.info("No MAC address found: " + self._device_mac) - return False, "No MAC address found." + LOGGER.info('No MAC address found: ' + self._device_mac) + return False, 'No MAC address found.' def _connection_mac_oui(self): - LOGGER.info("Running connection.mac_oui") + LOGGER.info('Running connection.mac_oui') manufacturer = self._get_oui_manufacturer(self._device_mac) if manufacturer is not None: - LOGGER.info("OUI Manufacturer found: " + manufacturer) - return True, "OUI Manufacturer found: " + manufacturer + LOGGER.info('OUI Manufacturer found: ' + manufacturer) + return True, 'OUI Manufacturer found: ' + manufacturer else: - LOGGER.info("No OUI Manufacturer found for: " + self._device_mac) - return False, "No OUI Manufacturer found for: " + self._device_mac + LOGGER.info('No OUI Manufacturer found for: ' + self._device_mac) + return False, 'No OUI Manufacturer found for: ' + self._device_mac def _connection_single_ip(self): - LOGGER.info("Running connection.single_ip") + LOGGER.info('Running connection.single_ip') result = None if self._device_mac is None: - LOGGER.info("No MAC address found: ") - return result, "No MAC address found." - + LOGGER.info('No MAC address found: ') + return result, 'No MAC address found.' + # Read all the pcap files containing DHCP packet information packets = rdpcap(DHCP_SERVER_CAPTURE_FILE) packets.append(rdpcap(STARTUP_CAPTURE_FILE)) @@ -116,50 +118,47 @@ def _connection_single_ip(self): # Extract MAC addresses from DHCP packets mac_addresses = set() - LOGGER.info("Inspecting: " + str(len(packets)) + " packets") + LOGGER.info('Inspecting: ' + str(len(packets)) + ' packets') for packet in packets: # Option[1] = message-type, option 3 = DHCPREQUEST - if DHCP in packet and packet[DHCP].options[0][1] == 3: - mac_address = packet[Ether].src - mac_addresses.add(mac_address.upper()) + if DHCP in packet and packet[DHCP].options[0][1] == 3: + mac_address = packet[Ether].src + mac_addresses.add(mac_address.upper()) # Check if the device mac address is in the list of DHCPREQUESTs result = self._device_mac.upper() in mac_addresses - LOGGER.info("DHCPREQUEST detected from device: " + str(result)) + LOGGER.info('DHCPREQUEST detected from device: ' + str(result)) # Check the unique MAC addresses to see if they match the device for mac_address in mac_addresses: - LOGGER.info("DHCPREQUEST from MAC address: " + mac_address) - result &= self._device_mac.upper() == mac_address + LOGGER.info('DHCPREQUEST from MAC address: ' + mac_address) + result &= self._device_mac.upper() == mac_address return result - def _connection_target_ping(self): - LOGGER.info("Running connection.target_ping") + LOGGER.info('Running connection.target_ping') # If the ipv4 address wasn't resolved yet, try again if self._device_ipv4_addr is None: - self._device_ipv4_addr = self._get_device_ipv4(self) - + self._device_ipv4_addr = self._get_device_ipv4(self) if self._device_ipv4_addr is None: - LOGGER.error("No device IP could be resolved") + LOGGER.error('No device IP could be resolved') sys.exit(1) else: return self._ping(self._device_ipv4_addr) - def _get_oui_manufacturer(self,mac_address): + def _get_oui_manufacturer(self, mac_address): # Do some quick fixes on the format of the mac_address # to match the oui file pattern - mac_address = mac_address.replace(":","-").upper() - with open(OUI_FILE, "r") as file: - for line in file: - if mac_address.startswith(line[:8]): - start = line.index("(hex)") + len("(hex)") - return line[start:].strip() # Extract the company name + mac_address = mac_address.replace(':', '-').upper() + with open(OUI_FILE, 'r', encoding='UTF-8') as file: + for line in file: + if mac_address.startswith(line[:8]): + start = line.index('(hex)') + len('(hex)') + return line[start:].strip() # Extract the company name return None def _ping(self, host): cmd = 'ping -c 1 ' + str(host) success = util.run_command(cmd, output=False) return success - \ No newline at end of file From 9d68bd0e69621711d0ba4b4f22c9879bb89e2224 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Tue, 11 Jul 2023 11:05:22 -0600 Subject: [PATCH 06/15] Change isc-dhcp service setup Fix dhcpd logging Add start and stop methods to grpc dhcp client Add dhcp2 client Inttial private_addr test --- modules/network/dhcp-1/bin/isc-dhcp-service | 56 +++++++++ .../network/dhcp-1/bin/start_network_service | 4 +- modules/network/dhcp-1/conf/dhcpd.conf | 5 +- modules/network/dhcp-1/conf/isc-dhcp-server | 2 +- .../python/src/grpc_server/dhcp_config.py | 50 ++++++-- .../src/grpc_server/dhcp_config_test.py | 9 ++ .../python/src/grpc_server/dhcp_lease.py | 2 +- .../python/src/grpc_server/dhcp_server.py | 10 +- modules/network/dhcp-2/bin/isc-dhcp-service | 56 +++++++++ .../network/dhcp-2/bin/start_network_service | 4 +- modules/network/dhcp-2/conf/dhcpd.conf | 51 +++++---- .../python/src/grpc_server/dhcp_config.py | 2 +- .../python/src/grpc_server/dhcp_server.py | 10 +- .../python/src/grpc/proto/dhcp1/client.py | 10 ++ .../python/src/grpc/proto/dhcp2/client.py | 108 ++++++++++++++++++ modules/test/conn/conf/module_config.json | 21 +++- .../test/conn/python/src/connection_module.py | 87 ++++++++++++++ testing/test_pylint | 82 ++++++------- testing/unit_test/run_tests.sh | 34 +++--- 19 files changed, 494 insertions(+), 109 deletions(-) create mode 100644 modules/network/dhcp-1/bin/isc-dhcp-service create mode 100644 modules/network/dhcp-2/bin/isc-dhcp-service create mode 100644 modules/test/base/python/src/grpc/proto/dhcp2/client.py diff --git a/modules/network/dhcp-1/bin/isc-dhcp-service b/modules/network/dhcp-1/bin/isc-dhcp-service new file mode 100644 index 000000000..de029515b --- /dev/null +++ b/modules/network/dhcp-1/bin/isc-dhcp-service @@ -0,0 +1,56 @@ +#!/bin/bash + +# 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. + +CONFIG_FILE=/etc/dhcp/dhcpd.conf +DHCP_PID_FILE=/var/run/dhcpd.pid +DHCP_LOG_FILE=/runtime/network/dhcp1-dhcpd.log + +stop_dhcp(){ + # Directly kill by PID file reference + if [ -f "$DHCP_PID_FILE" ]; then + kill -9 $(cat $DHCP_PID_FILE) || true + rm -f $DHCP_PID_FILE + fi +} + +start_dhcp(){ + /usr/sbin/dhcpd -d &> $DHCP_LOG_FILE & +} + +case "$1" in + start) + start_dhcp + ;; + stop) + stop_dhcp + ;; + restart) + stop_dhcp + sleep 1 + start_dhcp + ;; + status) + if [ -f "$DHCP_PID_FILE" ]; then + echo "isc-dhcp service is running." + else + echo "isc-dhcp service is not running." + fi + ;; + *) + echo "Usage: $0 {start|stop|status|restart}" + exit 1 + ;; +esac \ No newline at end of file diff --git a/modules/network/dhcp-1/bin/start_network_service b/modules/network/dhcp-1/bin/start_network_service index 82b4c6e33..7c46cc785 100644 --- a/modules/network/dhcp-1/bin/start_network_service +++ b/modules/network/dhcp-1/bin/start_network_service @@ -44,7 +44,9 @@ cp /testrun/conf/isc-dhcp-server /etc/default/ cp /testrun/conf/dhcpd.conf /etc/dhcp/dhcpd.conf cp /testrun/conf/radvd.conf /etc/radvd.conf -# Move the radvd-sevice file to the correct location + +# Move the service files to the correct location +cp /testrun/bin/isc-dhcp-service /usr/local/bin/ cp /testrun/bin/radvd-service /usr/local/bin/ # Start the DHCP Server diff --git a/modules/network/dhcp-1/conf/dhcpd.conf b/modules/network/dhcp-1/conf/dhcpd.conf index ee171279c..df804acf9 100644 --- a/modules/network/dhcp-1/conf/dhcpd.conf +++ b/modules/network/dhcp-1/conf/dhcpd.conf @@ -1,4 +1,5 @@ -default-lease-time 300; +default-lease-time 30; +max-lease-time 30; failover peer "failover-peer" { primary; @@ -8,7 +9,7 @@ failover peer "failover-peer" { peer port 647; max-response-delay 60; max-unacked-updates 10; - mclt 3600; + mclt 30; split 128; load balance max seconds 3; } diff --git a/modules/network/dhcp-1/conf/isc-dhcp-server b/modules/network/dhcp-1/conf/isc-dhcp-server index 44db95cd9..161f52d80 100644 --- a/modules/network/dhcp-1/conf/isc-dhcp-server +++ b/modules/network/dhcp-1/conf/isc-dhcp-server @@ -1,4 +1,4 @@ # On what interfaces should the DHCP server (dhcpd) serve DHCP requests? # Separate multiple interfaces with spaces, e.g. "eth0 eth1". INTERFACESv4="veth0" -#INTERFACESv6="veth0" +#INTERFACESv6="veth0" \ No newline at end of file diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py index 444faa87c..7461c1703 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py @@ -28,7 +28,7 @@ class DHCPConfig: """Represents the DHCP Servers configuration and gives access to modify it""" def __init__(self): - self._default_lease_time = 300 + self._default_lease_time = 30 self._subnets = [] self._peer = None self._reserved_hosts = [] @@ -122,10 +122,35 @@ def set_range(self, start, end, subnet=0, pool=0): octets[-1] = '0' dhcp_subnet = '.'.join(octets) - #Update the subnet and range - self._subnets[subnet].set_subnet(dhcp_subnet) + # Calcualte the netmask from the range + prefix = self.calculate_prefix_length(start,end) + netmask = self.calculate_netmask(prefix) + + #Update the subnet, range and netmask + self._subnets[subnet].set_subnet(dhcp_subnet,netmask) self._subnets[subnet].pools[pool].set_range(start, end) + def calculate_prefix_length(self,start_ip, end_ip): + start_octets = start_ip.split('.') + end_octets = end_ip.split('.') + + start_int = int(''.join(format(int(octet), '08b') for octet in start_octets), 2) + end_int = int(''.join(format(int(octet), '08b') for octet in end_octets), 2) + + xor_result = start_int ^ end_int + prefix_length = 32 - xor_result.bit_length() + + return prefix_length + + def calculate_netmask(self,prefix_length): + num_network_bits = prefix_length + num_host_bits = 32 - num_network_bits + + netmask_int = (2 ** num_network_bits - 1) << num_host_bits + netmask_octets = [(netmask_int >> (i * 8)) & 0xff for i in range(3, -1, -1)] + + return '.'.join(str(octet) for octet in netmask_octets) + def __str__(self): # Encode the top level config options @@ -350,12 +375,21 @@ def set_subnet(self, subnet, netmask=None): self._subnet = subnet self._subnet_mask = netmask - # Calculate the broadcast from the subnet - octets = subnet.split('.') - octets[-1] = '255' - dhcp_broadcast = '.'.join(octets) + # Calculate the broadcast from the subnet and netmask + broadcast = self.calculate_broadcast_address(subnet,netmask) + self._broadcast = broadcast + + def calculate_broadcast_address(self,subnet_address, netmask): + subnet_octets = subnet_address.split('.') + netmask_octets = netmask.split('.') + + subnet_int = int(''.join(format(int(octet), '08b') for octet in subnet_octets), 2) + netmask_int = int(''.join(format(int(octet), '08b') for octet in netmask_octets), 2) + + broadcast_int = subnet_int | (~netmask_int & 0xffffffff) + broadcast_octets = [(broadcast_int >> (i * 8)) & 0xff for i in range(3, -1, -1)] - self._broadcast = dhcp_broadcast + return '.'.join(str(octet) for octet in broadcast_octets) def resolve_subnet(self, subnet): subnet_parts = subnet.split('\n') diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py index 2cc78403a..40270299d 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py @@ -90,6 +90,14 @@ def test_resolve_config_with_hosts(self): self.assertIsNotNone(host) print("ResolveConfigWithHosts:\n" + str(config_with_hosts)) + def test_set_subnet_range(self): + DHCP_CONFIG.set_range('10.0.0.0', '10.255.255.255') + print("SetSubnetRange:\n" + str(DHCP_CONFIG)) + DHCP_CONFIG.set_range('172.16.0.0', '172.31.255.255') + print("SetSubnetRange:\n" + str(DHCP_CONFIG)) + DHCP_CONFIG.set_range('192.168.0.0', '192.168.255.255') + print("SetSubnetRange:\n" + str(DHCP_CONFIG)) + if __name__ == '__main__': suite = unittest.TestSuite() suite.addTest(DHCPConfigTest('test_resolve_config')) @@ -98,6 +106,7 @@ def test_resolve_config_with_hosts(self): suite.addTest(DHCPConfigTest('test_add_reserved_host')) suite.addTest(DHCPConfigTest('test_delete_reserved_host')) suite.addTest(DHCPConfigTest('test_resolve_config_with_hosts')) + suite.addTest(DHCPConfigTest('test_set_subnet_range')) runner = unittest.TextTestRunner() runner.run(suite) \ No newline at end of file diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_lease.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_lease.py index 0d2f43e3b..dd7ba9516 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_lease.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_lease.py @@ -39,7 +39,7 @@ def _make_lease(self, lease): self.hw_addr = sections[0] self.ip = sections[1] self.hostname = sections[2] - self.expires = sections[3] + '' '' + sections[4] + self.expires = sections[3] + ' ' + sections[4] self.manufacturer = ' '.join(sections[5:]) def get_millis(self, timestamp): diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py index 2f67b0c2d..a1f11b393 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py @@ -36,7 +36,7 @@ def __init__(self): def restart(self): LOGGER.info("Restarting DHCP Server") - isc_started = util.run_command("service isc-dhcp-server restart", False) + isc_started = util.run_command("isc-dhcp-service restart", False) radvd_started = self.radvd.restart() started = isc_started and radvd_started LOGGER.info("DHCP Restarted: " + str(started)) @@ -44,7 +44,7 @@ def restart(self): def start(self): LOGGER.info("Starting DHCP Server") - isc_started = util.run_command("service isc-dhcp-server start", False) + isc_started = util.run_command("isc-dhcp-service start", False) radvd_started = self.radvd.start() started = isc_started and radvd_started LOGGER.info("DHCP Started: " + str(started)) @@ -52,7 +52,7 @@ def start(self): def stop(self): LOGGER.info("Stopping DHCP Server") - isc_stopped = util.run_command("service isc-dhcp-server stop", False) + isc_stopped = util.run_command("isc-dhcp-service stop", False) radvd_stopped = self.radvd.stop() stopped = isc_stopped and radvd_stopped LOGGER.info("DHCP Stopped: " + str(stopped)) @@ -60,8 +60,8 @@ def stop(self): def is_running(self): LOGGER.info("Checking DHCP Status") - response = util.run_command("service isc-dhcp-server status") - isc_running = response[0] == 'Status of ISC DHCPv4 server: dhcpd is running.' + response = util.run_command("isc-dhcp-service status") + isc_running = response[0] == 'isc-dhcp service is running.' radvd_running = self.radvd.is_running() running = isc_running and radvd_running LOGGER.info("DHCP Status: " + str(running)) diff --git a/modules/network/dhcp-2/bin/isc-dhcp-service b/modules/network/dhcp-2/bin/isc-dhcp-service new file mode 100644 index 000000000..ee6df0341 --- /dev/null +++ b/modules/network/dhcp-2/bin/isc-dhcp-service @@ -0,0 +1,56 @@ +#!/bin/bash + +# 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. + +CONFIG_FILE=/etc/dhcp/dhcpd.conf +DHCP_PID_FILE=/var/run/dhcpd.pid +DHCP_LOG_FILE=/runtime/network/dhcp2-dhcpd.log + +stop_dhcp(){ + # Directly kill by PID file reference + if [ -f "$DHCP_PID_FILE" ]; then + kill -9 $(cat $DHCP_PID_FILE) || true + rm -f $DHCP_PID_FILE + fi +} + +start_dhcp(){ + /usr/sbin/dhcpd -d &> $DHCP_LOG_FILE & +} + +case "$1" in + start) + start_dhcp + ;; + stop) + stop_dhcp + ;; + restart) + stop_dhcp + sleep 1 + start_dhcp + ;; + status) + if [ -f "$DHCP_PID_FILE" ]; then + echo "isc-dhcp service is running." + else + echo "isc-dhcp service is not running." + fi + ;; + *) + echo "Usage: $0 {start|stop|status|restart}" + exit 1 + ;; +esac \ No newline at end of file diff --git a/modules/network/dhcp-2/bin/start_network_service b/modules/network/dhcp-2/bin/start_network_service index ed7d3125e..5acea606b 100644 --- a/modules/network/dhcp-2/bin/start_network_service +++ b/modules/network/dhcp-2/bin/start_network_service @@ -44,7 +44,9 @@ cp /testrun/conf/isc-dhcp-server /etc/default/ cp /testrun/conf/dhcpd.conf /etc/dhcp/dhcpd.conf cp /testrun/conf/radvd.conf /etc/radvd.conf -# Move the radvd-sevice file to the correct location + +# Move the service files to the correct location +cp /testrun/bin/isc-dhcp-service /usr/local/bin/ cp /testrun/bin/radvd-service /usr/local/bin/ # Start the DHCP Server diff --git a/modules/network/dhcp-2/conf/dhcpd.conf b/modules/network/dhcp-2/conf/dhcpd.conf index dcc47a4fe..5a6c82410 100644 --- a/modules/network/dhcp-2/conf/dhcpd.conf +++ b/modules/network/dhcp-2/conf/dhcpd.conf @@ -1,25 +1,26 @@ -default-lease-time 300; - -failover peer "failover-peer" { - secondary; - address 10.10.10.3; - port 647; - peer address 10.10.10.2; - peer port 847; - max-response-delay 60; - max-unacked-updates 10; - load balance max seconds 3; -} - -subnet 10.10.10.0 netmask 255.255.255.0 { - option ntp-servers 10.10.10.5; - option subnet-mask 255.255.255.0; - option broadcast-address 10.10.10.255; - option routers 10.10.10.1; - option domain-name-servers 10.10.10.4; - interface veth0; - pool { - failover peer "failover-peer"; - range 10.10.10.10 10.10.10.20; - } -} +default-lease-time 30; +max-lease-time 30; + +failover peer "failover-peer" { + secondary; + address 10.10.10.3; + port 647; + peer address 10.10.10.2; + peer port 847; + max-response-delay 60; + max-unacked-updates 10; + load balance max seconds 3; +} + +subnet 10.10.10.0 netmask 255.255.255.0 { + option ntp-servers 10.10.10.5; + option subnet-mask 255.255.255.0; + option broadcast-address 10.10.10.255; + option routers 10.10.10.1; + option domain-name-servers 10.10.10.4; + interface veth0; + pool { + failover peer "failover-peer"; + range 10.10.10.10 10.10.10.20; + } +} diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py index 33cb5938c..a177dfe97 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py +++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py @@ -28,7 +28,7 @@ class DHCPConfig: """Represents the DHCP Servers configuration and gives access to modify it""" def __init__(self): - self._default_lease_time = 300 + self._default_lease_time = 30 self._subnets = [] self._peer = None self._reserved_hosts = [] diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py index 1431d6ddd..5c9b3d791 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py +++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py @@ -36,7 +36,7 @@ def __init__(self): def restart(self): LOGGER.info("Restarting DHCP Server") - isc_started = util.run_command("service isc-dhcp-server restart", False) + isc_started = util.run_command("isc-dhcp-service restart", False) radvd_started = self.radvd.restart() started = isc_started and radvd_started LOGGER.info("DHCP Restarted: " + str(started)) @@ -44,7 +44,7 @@ def restart(self): def start(self): LOGGER.info("Starting DHCP Server") - isc_started = util.run_command("service isc-dhcp-server start", False) + isc_started = util.run_command("isc-dhcp-service start", False) radvd_started = self.radvd.start() started = isc_started and radvd_started LOGGER.info("DHCP Started: " + str(started)) @@ -52,7 +52,7 @@ def start(self): def stop(self): LOGGER.info("Stopping DHCP Server") - isc_stopped = util.run_command("service isc-dhcp-server stop", False) + isc_stopped = util.run_command("isc-dhcp-service stop", False) radvd_stopped = self.radvd.stop() stopped = isc_stopped and radvd_stopped LOGGER.info("DHCP Stopped: " + str(stopped)) @@ -60,8 +60,8 @@ def stop(self): def is_running(self): LOGGER.info("Checking DHCP Status") - response = util.run_command("service isc-dhcp-server status") - isc_running = response[0] == 'Status of ISC DHCPv4 server: dhcpd is running.' + response = util.run_command("isc-dhcp-service status") + isc_running = response[0] == 'isc-dhcp service is running.' radvd_running = self.radvd.is_running() running = isc_running and radvd_running LOGGER.info("DHCP Status: " + str(running)) diff --git a/modules/test/base/python/src/grpc/proto/dhcp1/client.py b/modules/test/base/python/src/grpc/proto/dhcp1/client.py index 921929edb..8a24e9be9 100644 --- a/modules/test/base/python/src/grpc/proto/dhcp1/client.py +++ b/modules/test/base/python/src/grpc/proto/dhcp1/client.py @@ -86,6 +86,16 @@ def get_status(self): return response + def stop_dhcp_server(self): + # Create a request message + request = pb2.StopDHCPServerRequest() + + # Make the RPC call + response = self._stub.StopDHCPServer(request) + + return response + + def set_dhcp_range(self,start,end): # Create a request message request = pb2.SetDHCPRangeRequest() diff --git a/modules/test/base/python/src/grpc/proto/dhcp2/client.py b/modules/test/base/python/src/grpc/proto/dhcp2/client.py new file mode 100644 index 000000000..7712a7f13 --- /dev/null +++ b/modules/test/base/python/src/grpc/proto/dhcp2/client.py @@ -0,0 +1,108 @@ +import grpc +import grpc_pb2_grpc as pb2_grpc +import grpc_pb2 as pb2 + +DEFAULT_PORT = '5001' +DEFAULT_HOST = '10.10.10.3' # Default DHCP2 server + + +class Client(): + + def __init__(self, port=DEFAULT_PORT, host=DEFAULT_HOST): + self._port = port + self._host = host + + # Create a gRPC channel to connect to the server + self._channel = grpc.insecure_channel(self._host + ':' + self._port) + + # Create a gRPC stub + self._stub = pb2_grpc.NetworkModuleStub(self._channel) + + def add_reserved_lease(self, hostname, hw_addr, ip_addr): + # Create a request message + request = pb2.AddReservedLeaseRequest() + request.hostname = hostname + request.hw_addr = hw_addr + request.ip_addr = ip_addr + + # Make the RPC call + response = self._stub.AddReservedLease(request) + + return response + + def delete_reserved_lease(self, hw_addr): + # Create a request message + request = pb2.DeleteReservedLeaseRequest() + request.hw_addr = hw_addr + + # Make the RPC call + response = self._stub.DeleteReservedLease(request) + + return response + + def disable_failover(self): + # Create a request message + request = pb2.DisableFailoverRequest() + + # Make the RPC call + response = self._stub.DisableFailover(request) + + return response + + def enable_failover(self): + # Create a request message + request = pb2.EnableFailoverRequest() + + # Make the RPC call + response = self._stub.EnableFailover(request) + + return response + + def get_dhcp_range(self): + # Create a request message + request = pb2.GetDHCPRangeRequest() + + # Make the RPC call + response = self._stub.GetDHCPRange(request) + + return response + + def get_lease(self,hw_addr): + # Create a request message + request = pb2.GetLeaseRequest() + request.hw_addr=hw_addr + + # Make the RPC call + response = self._stub.GetLease(request) + + return response + + def get_status(self): + # Create a request message + request = pb2.GetStatusRequest() + + # Make the RPC call + response = self._stub.GetStatus(request) + + return response + + def stop_dhcp_server(self): + # Create a request message + request = pb2.StopDHCPServerRequest() + + # Make the RPC call + response = self._stub.StopDHCPServer(request) + + return response + + + def set_dhcp_range(self,start,end): + # Create a request message + request = pb2.SetDHCPRangeRequest() + request.start=start + request.end=end + + # Make the RPC call + response = self._stub.SetDHCPRange(request) + + return response diff --git a/modules/test/conn/conf/module_config.json b/modules/test/conn/conf/module_config.json index 4053b4e26..482cb2ec0 100644 --- a/modules/test/conn/conf/module_config.json +++ b/modules/test/conn/conf/module_config.json @@ -9,7 +9,7 @@ "docker": { "depends_on": "base", "enable_container": true, - "timeout": 30 + "timeout": 600 }, "tests": [ { @@ -27,6 +27,25 @@ "description": "The device under test hs a MAC address prefix that is registered against a known manufacturer.", "expected_behavior": "The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database." }, + { + "name": "connection.private_address", + "description": "The device under test accepts an IP address that is compliant with RFC 1918 Address Allocation for Private Internets.", + "expected_behavior": "The device under test accepts IP addresses within all ranges specified in RFC 1918 and communicates using these addresses. The Internet Assigned Numbers Authority (IANA) has reserved the following three blocks of the IP address space for private internets. 10.0.0.0 - 10.255.255.255.255 (10/8 prefix). 172.16.0.0 - 172.31.255.255 (172.16/12 prefix). 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)", + "config": [ + { + "start": "10.0.0.100", + "end": "10.0.0.200" + }, + { + "start":"172.16.0.0", + "end":"172.16.255.255" + }, + { + "start":"192.168.0.0", + "end":"192.168.255.255" + } + ] + }, { "name": "connection.single_ip", "description": "The network switch port connected to the device reports only one IP address for the device under test.", diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 5b3bf7038..586ab95c9 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -18,6 +18,7 @@ from scapy.all import * from test_module import TestModule from dhcp1.client import Client as DHCPClient1 +from dhcp2.client import Client as DHCPClient2 LOG_NAME = "test_connection" LOGGER = None @@ -35,6 +36,7 @@ def __init__(self, module): global LOGGER LOGGER = self._get_logger() self.dhcp1_client = DHCPClient1() + self.dhcp2_client = DHCPClient2() # ToDo: Move this into some level of testing, leave for # reference until tests are implemented with these calls @@ -62,6 +64,91 @@ def __init__(self, module): # response = self.dhcp1_client.set_dhcp_range('10.10.10.20','10.10.10.30') # print("Set Range: " + str(response)) + def _connection_private_address(self,config): + # Shutdown the secondary DHCP Server + LOGGER.info("Running connection.private_address") + LOGGER.info("Stopping secondary DHCP server") + response = self.dhcp2_client.stop_dhcp_server() + if response.code == 200: + LOGGER.info("Secondary DHCP server stop command success") + time.sleep(3) # Give some time for the server to stop + LOGGER.info("Checking secondary DHCP server status") + response = self.dhcp2_client.get_status() + if response.code == 200: + LOGGER.info("Secondary DHCP server stopped") + + # Move primary DHCP server from failover into a single DHCP server config + LOGGER.info("Configuring primary DHCP server") + response = self.dhcp1_client.disable_failover() + if response.code == 200: + LOGGER.info("Checking current device lease") + response = self.dhcp1_client.get_lease(self._device_mac) + if response.code == 200: + lease = eval(response.message) + LOGGER.info("Current lease found") + if 'ip' in lease: + ip_addr = lease['ip'] + LOGGER.info("IP Resolved: " + ip_addr) + LOGGER.info("Attempting to ping device..."); + ping_success = self._ping(self._device_ipv4_addr) + LOGGER.info("Ping Success: " + str(ping_success)) + LOGGER.info("Current lease confirmed active in device") + + LOGGER.info("Private subnets configured for testing: " + str(config)) + + for subnet in config: + lease = self._get_cur_lease() + if lease is not None: + self._test_subnet(subnet,lease) + + return False, 'Test not yet implemented' + + def _test_subnet(self,subnet,lease): + if self._change_subnet(subnet): + expiration = datetime.strptime(lease['expires'], '%Y-%m-%d %H:%M:%S') + time_to_expire = expiration - datetime.now() + LOGGER.info("Time until lease expiration: " + str(time_to_expire)) + LOGGER.info("Waiting for current lease to expire: " + str(expiration)) + if time_to_expire.total_seconds() > 0: + time.sleep(time_to_expire.total_seconds() + 5) # Wait until the expiration time and padd 5 seconds + LOGGER.info("Current lease expired. Checking for new lease") + for _ in range(5): + LOGGER.info("Checking for new lease") + lease = self._get_cur_lease() + if lease is not None: + LOGGER.info("New Lease found: " + str(lease)) + LOGGER.info("Validating subnet for new lease...") + break + else: + LOGGER.info("New lease not found. Waiting to check again") + time.sleep(5) + + def _change_subnet(self,subnet): + LOGGER.info("Changing subnet to: " + str(subnet)) + response = self.dhcp1_client.set_dhcp_range(subnet['start'],subnet['end']) + if response.code == 200: + LOGGER.info("Subnet change request accepted. Confirming change...") + response = self.dhcp1_client.get_dhcp_range() + if response.code == 200: + if response.start == subnet['start'] and response.end == subnet['end']: + LOGGER.info("Subnet change confirmed") + return True + LOGGER.error("Failed to confirm subnet change") + else: + LOGGER.error("Subnet change request failed.") + return False + + def _get_cur_lease(self): + LOGGER.info("Checking current device lease") + response = self.dhcp1_client.get_lease(self._device_mac) + if response.code == 200: + lease = eval(response.message) + if lease: # Check if non-empty lease + LOGGER.info("Current lease found") + return lease + else: + return None + def _connection_dhcp_address(self): LOGGER.info("Running connection.dhcp_address") response = self.dhcp1_client.get_lease(self._device_mac) diff --git a/testing/test_pylint b/testing/test_pylint index 5cd1dff73..d98a5d20d 100755 --- a/testing/test_pylint +++ b/testing/test_pylint @@ -1,41 +1,41 @@ -#!/bin/bash - -# 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. - -ERROR_LIMIT=1100 - -sudo cmd/install - -source venv/bin/activate -sudo pip3 install pylint - -files=$(find . -path ./venv -prune -o -name '*.py' -print) - -OUT=pylint.out - -rm -f $OUT && touch $OUT - -pylint $files -ry --extension-pkg-allow-list=docker --evaluation="error + warning + refactor + convention" 2>/dev/null | tee -a $OUT - -new_errors=$(cat $OUT | grep -oP "(?!=^Your code has been rated at)([0-9]+)(?=\.00/10[ \(]?)" ) - -echo "$new_errors > $ERROR_LIMIT?" -if (( $new_errors > $ERROR_LIMIT)); then - echo new errors $new_errors > error limit $ERROR_LIMIT - echo failing .. - exit 1 -fi - -exit 0 +#!/bin/bash + +# 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. + +ERROR_LIMIT=1100 + +sudo cmd/install + +source venv/bin/activate +sudo pip3 install pylint + +files=$(find . -path ./venv -prune -o -name '*.py' -print) + +OUT=pylint.out + +rm -f $OUT && touch $OUT + +pylint $files -ry --extension-pkg-allow-list=docker --evaluation="error + warning + refactor + convention" 2>/dev/null | tee -a $OUT + +new_errors=$(cat $OUT | grep -oP "(?!=^Your code has been rated at)([0-9]+)(?=\.00/10[ \(]?)" ) + +echo "$new_errors > $ERROR_LIMIT?" +if (( $new_errors > $ERROR_LIMIT)); then + echo new errors $new_errors > error limit $ERROR_LIMIT + echo failing .. + exit 1 +fi + +exit 0 diff --git a/testing/unit_test/run_tests.sh b/testing/unit_test/run_tests.sh index 5b1ed6257..6dcde902b 100644 --- a/testing/unit_test/run_tests.sh +++ b/testing/unit_test/run_tests.sh @@ -1,18 +1,18 @@ -#!/bin/bash -e - -# This script should be run from within the unit_test directory. If -# it is run outside this directory, paths will not be resolved correctly. - -# Move into the root directory of test-run -pushd ../../ >/dev/null 2>&1 - -echo "Root Dir: $PWD" - -# Setup the python path -export PYTHONPATH="$PWD/framework/python/src" - -# Run the DHCP Unit tests -python3 -u $PWD/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py -python3 -u $PWD/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py - +#!/bin/bash -e + +# This script should be run from within the unit_test directory. If +# it is run outside this directory, paths will not be resolved correctly. + +# Move into the root directory of test-run +pushd ../../ >/dev/null 2>&1 + +echo "Root Dir: $PWD" + +# Setup the python path +export PYTHONPATH="$PWD/framework/python/src" + +# Run the DHCP Unit tests +python3 -u $PWD/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py +#python3 -u $PWD/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py + popd >/dev/null 2>&1 \ No newline at end of file From 3c93a74a09b2f230d84280834020c386e30cf39c Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Tue, 11 Jul 2023 12:00:11 -0600 Subject: [PATCH 07/15] Add max lease time Add unit tests --- testing/unit_test/run_tests.sh | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/testing/unit_test/run_tests.sh b/testing/unit_test/run_tests.sh index 6dcde902b..5b1ed6257 100644 --- a/testing/unit_test/run_tests.sh +++ b/testing/unit_test/run_tests.sh @@ -1,18 +1,18 @@ -#!/bin/bash -e - -# This script should be run from within the unit_test directory. If -# it is run outside this directory, paths will not be resolved correctly. - -# Move into the root directory of test-run -pushd ../../ >/dev/null 2>&1 - -echo "Root Dir: $PWD" - -# Setup the python path -export PYTHONPATH="$PWD/framework/python/src" - -# Run the DHCP Unit tests -python3 -u $PWD/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py -#python3 -u $PWD/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py - +#!/bin/bash -e + +# This script should be run from within the unit_test directory. If +# it is run outside this directory, paths will not be resolved correctly. + +# Move into the root directory of test-run +pushd ../../ >/dev/null 2>&1 + +echo "Root Dir: $PWD" + +# Setup the python path +export PYTHONPATH="$PWD/framework/python/src" + +# Run the DHCP Unit tests +python3 -u $PWD/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py +python3 -u $PWD/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py + popd >/dev/null 2>&1 \ No newline at end of file From d6b6d0b94179945249f1c4b591499e06c85687aa Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Tue, 11 Jul 2023 12:27:54 -0600 Subject: [PATCH 08/15] fix last commit --- .../python/src/grpc_server/dhcp_config.py | 13 +++- .../src/grpc_server/dhcp_config_test.py | 36 +++++------ .../python/src/grpc_server/dhcp_config.py | 61 ++++++++++++++++--- .../src/grpc_server/dhcp_config_test.py | 10 ++- .../test/conn/python/src/connection_module.py | 2 + 5 files changed, 92 insertions(+), 30 deletions(-) diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py index 0e9b364f8..725fc34b4 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py @@ -22,6 +22,7 @@ CONFIG_FILE = '/etc/dhcp/dhcpd.conf' DEFAULT_LEASE_TIME_KEY = 'default-lease-time' +MAX_LEASE_TIME_KEY = 'max-lease-time' class DHCPConfig: @@ -29,6 +30,7 @@ class DHCPConfig: def __init__(self): self._default_lease_time = 30 + self._max_lease_time = 30 self._subnets = [] self._peer = None self._reserved_hosts = [] @@ -153,11 +155,18 @@ def calculate_netmask(self,prefix_length): def __str__(self): + config = ('{DEFAULT_LEASE_TIME_KEY} {DEFAULT_LEASE_TIME};' + if self._default_lease_time is not None else '') + config += ('\n\r{MAX_LEASE_TIME_KEY} {MAX_LEASE_TIME};' + if self._max_lease_time is not None else '') + # Encode the top level config options - config = """{DEFAULT_LEASE_TIME_KEY} {DEFAULT_LEASE_TIME};""" + #config = """{DEFAULT_LEASE_TIME_KEY} {DEFAULT_LEASE_TIME};""" config = config.format(length='multi-line', DEFAULT_LEASE_TIME_KEY=DEFAULT_LEASE_TIME_KEY, - DEFAULT_LEASE_TIME=self._default_lease_time) + DEFAULT_LEASE_TIME=self._default_lease_time, + MAX_LEASE_TIME_KEY=MAX_LEASE_TIME_KEY, + MAX_LEASE_TIME=self._max_lease_time) # Encode the failover peer config += '\n\n' + str(self._peer) diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py index adb231df2..12d15326f 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py @@ -93,23 +93,23 @@ def test_resolve_config_with_hosts(self): self.assertIsNotNone(host) print('ResolveConfigWithHosts:\n' + str(config_with_hosts)) - - def test_set_subnet_range(self): - DHCP_CONFIG.set_range('10.0.0.0', '10.255.255.255') - print("SetSubnetRange:\n" + str(DHCP_CONFIG)) - DHCP_CONFIG.set_range('172.16.0.0', '172.31.255.255') - print("SetSubnetRange:\n" + str(DHCP_CONFIG)) - DHCP_CONFIG.set_range('192.168.0.0', '192.168.255.255') - print("SetSubnetRange:\n" + str(DHCP_CONFIG)) + def test_set_subnet_range(self): + range_start = '10.0.0.100' + range_end = '10.0.0.200' + DHCP_CONFIG.set_range(range_start, range_end) + subnets = DHCP_CONFIG.resolve_subnets(str(DHCP_CONFIG)) + pool = subnets[0].pools[0] + self.assertTrue(pool.range_start == range_start and pool.range_end == range_end) + print("SetSubnetRange:\n" + str(DHCP_CONFIG)) if __name__ == '__main__': - suite = unittest.TestSuite() - suite.addTest(DHCPConfigTest('test_resolve_config')) - suite.addTest(DHCPConfigTest('test_disable_failover')) - suite.addTest(DHCPConfigTest('test_enable_failover')) - suite.addTest(DHCPConfigTest('test_add_reserved_host')) - suite.addTest(DHCPConfigTest('test_delete_reserved_host')) - suite.addTest(DHCPConfigTest('test_resolve_config_with_hosts')) - suite.addTest(DHCPConfigTest('test_set_subnet_range')) - runner = unittest.TextTestRunner() - runner.run(suite) + suite = unittest.TestSuite() + suite.addTest(DHCPConfigTest('test_resolve_config')) + suite.addTest(DHCPConfigTest('test_disable_failover')) + suite.addTest(DHCPConfigTest('test_enable_failover')) + suite.addTest(DHCPConfigTest('test_add_reserved_host')) + suite.addTest(DHCPConfigTest('test_delete_reserved_host')) + suite.addTest(DHCPConfigTest('test_resolve_config_with_hosts')) + suite.addTest(DHCPConfigTest('test_set_subnet_range')) + runner = unittest.TextTestRunner() + runner.run(suite) diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py index 8593523ec..dc45e83a4 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py +++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py @@ -22,6 +22,7 @@ CONFIG_FILE = '/etc/dhcp/dhcpd.conf' DEFAULT_LEASE_TIME_KEY = 'default-lease-time' +MAX_LEASE_TIME_KEY = 'max-lease-time' class DHCPConfig: @@ -29,6 +30,7 @@ class DHCPConfig: def __init__(self): self._default_lease_time = 30 + self._max_lease_time = 30 self._subnets = [] self._peer = None self._reserved_hosts = [] @@ -122,17 +124,49 @@ def set_range(self, start, end, subnet=0, pool=0): octets[-1] = '0' dhcp_subnet = '.'.join(octets) - #Update the subnet and range - self._subnets[subnet].set_subnet(dhcp_subnet) + # Calcualte the netmask from the range + prefix = self.calculate_prefix_length(start,end) + netmask = self.calculate_netmask(prefix) + + #Update the subnet, range and netmask + self._subnets[subnet].set_subnet(dhcp_subnet,netmask) self._subnets[subnet].pools[pool].set_range(start, end) + def calculate_prefix_length(self,start_ip, end_ip): + start_octets = start_ip.split('.') + end_octets = end_ip.split('.') + + start_int = int(''.join(format(int(octet), '08b') for octet in start_octets), 2) + end_int = int(''.join(format(int(octet), '08b') for octet in end_octets), 2) + + xor_result = start_int ^ end_int + prefix_length = 32 - xor_result.bit_length() + + return prefix_length + + def calculate_netmask(self,prefix_length): + num_network_bits = prefix_length + num_host_bits = 32 - num_network_bits + + netmask_int = (2 ** num_network_bits - 1) << num_host_bits + netmask_octets = [(netmask_int >> (i * 8)) & 0xff for i in range(3, -1, -1)] + + return '.'.join(str(octet) for octet in netmask_octets) + def __str__(self): + config = ('{DEFAULT_LEASE_TIME_KEY} {DEFAULT_LEASE_TIME};' + if self._default_lease_time is not None else '') + config += ('\n\r{MAX_LEASE_TIME_KEY} {MAX_LEASE_TIME};' + if self._max_lease_time is not None else '') + # Encode the top level config options - config = """{DEFAULT_LEASE_TIME_KEY} {DEFAULT_LEASE_TIME};""" + #config = """{DEFAULT_LEASE_TIME_KEY} {DEFAULT_LEASE_TIME};""" config = config.format(length='multi-line', DEFAULT_LEASE_TIME_KEY=DEFAULT_LEASE_TIME_KEY, - DEFAULT_LEASE_TIME=self._default_lease_time) + DEFAULT_LEASE_TIME=self._default_lease_time, + MAX_LEASE_TIME_KEY=MAX_LEASE_TIME_KEY, + MAX_LEASE_TIME=self._max_lease_time) # Encode the failover peer config += '\n\n' + str(self._peer) @@ -360,12 +394,21 @@ def set_subnet(self, subnet, netmask=None): self._subnet = subnet self._subnet_mask = netmask - # Calculate the broadcast from the subnet - octets = subnet.split('.') - octets[-1] = '255' - dhcp_broadcast = '.'.join(octets) + # Calculate the broadcast from the subnet and netmask + broadcast = self.calculate_broadcast_address(subnet,netmask) + self._broadcast = broadcast + + def calculate_broadcast_address(self,subnet_address, netmask): + subnet_octets = subnet_address.split('.') + netmask_octets = netmask.split('.') + + subnet_int = int(''.join(format(int(octet), '08b') for octet in subnet_octets), 2) + netmask_int = int(''.join(format(int(octet), '08b') for octet in netmask_octets), 2) + + broadcast_int = subnet_int | (~netmask_int & 0xffffffff) + broadcast_octets = [(broadcast_int >> (i * 8)) & 0xff for i in range(3, -1, -1)] - self._broadcast = dhcp_broadcast + return '.'.join(str(octet) for octet in broadcast_octets) def resolve_subnet(self, subnet): subnet_parts = subnet.split('\n') diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py index d9a0b0190..12d15326f 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py +++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py @@ -93,6 +93,14 @@ def test_resolve_config_with_hosts(self): self.assertIsNotNone(host) print('ResolveConfigWithHosts:\n' + str(config_with_hosts)) + def test_set_subnet_range(self): + range_start = '10.0.0.100' + range_end = '10.0.0.200' + DHCP_CONFIG.set_range(range_start, range_end) + subnets = DHCP_CONFIG.resolve_subnets(str(DHCP_CONFIG)) + pool = subnets[0].pools[0] + self.assertTrue(pool.range_start == range_start and pool.range_end == range_end) + print("SetSubnetRange:\n" + str(DHCP_CONFIG)) if __name__ == '__main__': suite = unittest.TestSuite() @@ -102,6 +110,6 @@ def test_resolve_config_with_hosts(self): suite.addTest(DHCPConfigTest('test_add_reserved_host')) suite.addTest(DHCPConfigTest('test_delete_reserved_host')) suite.addTest(DHCPConfigTest('test_resolve_config_with_hosts')) - + suite.addTest(DHCPConfigTest('test_set_subnet_range')) runner = unittest.TextTestRunner() runner.run(suite) diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index bbfb17cbb..5c9ec0789 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -15,6 +15,8 @@ import util import sys import json +import time +from datetime import datetime from scapy.all import rdpcap, DHCP, Ether from test_module import TestModule from dhcp1.client import Client as DHCPClient1 From 8aec3fc181c1f455ab078b9d5321c61a5ebe6863 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Tue, 11 Jul 2023 13:07:21 -0600 Subject: [PATCH 09/15] finish initial work on test --- .../test/conn/python/src/connection_module.py | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 5c9ec0789..1501e5b8b 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -99,12 +99,38 @@ def _connection_private_address(self,config): LOGGER.info("Private subnets configured for testing: " + str(config)) + results = [] for subnet in config: - lease = self._get_cur_lease() - if lease is not None: - self._test_subnet(subnet,lease) + result = {} + try: + lease = self._get_cur_lease() + if lease is not None: + result = self._test_subnet(subnet,lease) + if result: + result = {'result':True,'details':'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' passed'} + else: + result = {'result':False,'details':'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' failed'} + except Exception as e: + result = {'result':False,'details':'Subnet test failed: ' + str(e)} + results.append(result) + + final_result = None + final_result_details = '' + for result in results: + if final_result is None: + final_result = result['result'] + else: + final_result &= result['result'] + final_result_details += result['details'] + '\n' + + return final_result, final_result_details + + def is_ip_in_range(self,ip, start_ip, end_ip): + ip_int = int(''.join(format(int(octet), '08b') for octet in ip.split('.')), 2) + start_int = int(''.join(format(int(octet), '08b') for octet in start_ip.split('.')), 2) + end_int = int(''.join(format(int(octet), '08b') for octet in end_ip.split('.')), 2) - return False, 'Test not yet implemented' + return start_int <= ip_int <= end_int def _test_subnet(self,subnet,lease): if self._change_subnet(subnet): @@ -121,7 +147,9 @@ def _test_subnet(self,subnet,lease): if lease is not None: LOGGER.info("New Lease found: " + str(lease)) LOGGER.info("Validating subnet for new lease...") - break + in_range = self.is_ip_in_range(lease['ip'],subnet['start'],subnet['end']) + LOGGER.info("Lease within subnet: " + str(in_range)) + return in_range else: LOGGER.info("New lease not found. Waiting to check again") time.sleep(5) From cfe5f6535fd94027fa7b6abd5309dff9e5d114e5 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Tue, 11 Jul 2023 13:43:51 -0600 Subject: [PATCH 10/15] pylinting --- .../python/src/grpc_server/dhcp_config.py | 28 ++++++---- .../src/grpc_server/dhcp_config_test.py | 6 +- .../python/src/grpc_server/dhcp_leases.py | 6 +- .../python/src/grpc_server/dhcp_server.py | 16 +++--- .../python/src/grpc_server/dhcp_config.py | 28 ++++++---- .../src/grpc_server/dhcp_config_test.py | 6 +- .../python/src/grpc_server/dhcp_lease.py | 2 +- .../python/src/grpc_server/dhcp_server.py | 16 +++--- .../python/src/grpc/proto/dhcp1/client.py | 17 +++++- .../python/src/grpc/proto/dhcp2/client.py | 16 +++++- .../test/conn/python/src/connection_module.py | 56 +++++++++---------- 11 files changed, 119 insertions(+), 78 deletions(-) diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py index 725fc34b4..d2fb4db81 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config.py @@ -125,18 +125,19 @@ def set_range(self, start, end, subnet=0, pool=0): dhcp_subnet = '.'.join(octets) # Calcualte the netmask from the range - prefix = self.calculate_prefix_length(start,end) + prefix = self.calculate_prefix_length(start, end) netmask = self.calculate_netmask(prefix) #Update the subnet, range and netmask - self._subnets[subnet].set_subnet(dhcp_subnet,netmask) + self._subnets[subnet].set_subnet(dhcp_subnet, netmask) self._subnets[subnet].pools[pool].set_range(start, end) - def calculate_prefix_length(self,start_ip, end_ip): + def calculate_prefix_length(self, start_ip, end_ip): start_octets = start_ip.split('.') end_octets = end_ip.split('.') - start_int = int(''.join(format(int(octet), '08b') for octet in start_octets), 2) + start_int = int( + ''.join(format(int(octet), '08b') for octet in start_octets), 2) end_int = int(''.join(format(int(octet), '08b') for octet in end_octets), 2) xor_result = start_int ^ end_int @@ -144,11 +145,11 @@ def calculate_prefix_length(self,start_ip, end_ip): return prefix_length - def calculate_netmask(self,prefix_length): + def calculate_netmask(self, prefix_length): num_network_bits = prefix_length num_host_bits = 32 - num_network_bits - netmask_int = (2 ** num_network_bits - 1) << num_host_bits + netmask_int = (2**num_network_bits - 1) << num_host_bits netmask_octets = [(netmask_int >> (i * 8)) & 0xff for i in range(3, -1, -1)] return '.'.join(str(octet) for octet in netmask_octets) @@ -156,7 +157,7 @@ def calculate_netmask(self,prefix_length): def __str__(self): config = ('{DEFAULT_LEASE_TIME_KEY} {DEFAULT_LEASE_TIME};' - if self._default_lease_time is not None else '') + if self._default_lease_time is not None else '') config += ('\n\r{MAX_LEASE_TIME_KEY} {MAX_LEASE_TIME};' if self._max_lease_time is not None else '') @@ -395,18 +396,21 @@ def set_subnet(self, subnet, netmask=None): self._subnet_mask = netmask # Calculate the broadcast from the subnet and netmask - broadcast = self.calculate_broadcast_address(subnet,netmask) + broadcast = self.calculate_broadcast_address(subnet, netmask) self._broadcast = broadcast - def calculate_broadcast_address(self,subnet_address, netmask): + def calculate_broadcast_address(self, subnet_address, netmask): subnet_octets = subnet_address.split('.') netmask_octets = netmask.split('.') - subnet_int = int(''.join(format(int(octet), '08b') for octet in subnet_octets), 2) - netmask_int = int(''.join(format(int(octet), '08b') for octet in netmask_octets), 2) + subnet_int = int( + ''.join(format(int(octet), '08b') for octet in subnet_octets), 2) + netmask_int = int( + ''.join(format(int(octet), '08b') for octet in netmask_octets), 2) broadcast_int = subnet_int | (~netmask_int & 0xffffffff) - broadcast_octets = [(broadcast_int >> (i * 8)) & 0xff for i in range(3, -1, -1)] + broadcast_octets = [(broadcast_int >> (i * 8)) & 0xff + for i in range(3, -1, -1)] return '.'.join(str(octet) for octet in broadcast_octets) diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py index 12d15326f..3cd97b875 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_config_test.py @@ -99,8 +99,10 @@ def test_set_subnet_range(self): DHCP_CONFIG.set_range(range_start, range_end) subnets = DHCP_CONFIG.resolve_subnets(str(DHCP_CONFIG)) pool = subnets[0].pools[0] - self.assertTrue(pool.range_start == range_start and pool.range_end == range_end) - print("SetSubnetRange:\n" + str(DHCP_CONFIG)) + self.assertTrue(pool.range_start == range_start + and pool.range_end == range_end) + print('SetSubnetRange:\n' + str(DHCP_CONFIG)) + if __name__ == '__main__': suite = unittest.TestSuite() diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_leases.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_leases.py index 698277a02..aa2945759 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_leases.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_leases.py @@ -58,9 +58,9 @@ def get_leases(self): leases = [] lease_list_raw = self._get_lease_list() LOGGER.info('Raw Leases:\n' + str(lease_list_raw) + '\n') - lease_list_start = lease_list_raw.find('=========',0) - lease_list_start = lease_list_raw.find('\n',lease_list_start) - lease_list = lease_list_raw[lease_list_start+1:] + lease_list_start = lease_list_raw.find('=========', 0) + lease_list_start = lease_list_raw.find('\n', lease_list_start) + lease_list = lease_list_raw[lease_list_start + 1:] lines = lease_list.split('\n') for line in lines: try: diff --git a/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py b/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py index d939647c2..aa6afb8c1 100644 --- a/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py +++ b/modules/network/dhcp-1/python/src/grpc_server/dhcp_server.py @@ -36,32 +36,32 @@ def __init__(self): self.dhcp_config.resolve_config() def restart(self): - LOGGER.info("Restarting DHCP Server") - isc_started = util.run_command("isc-dhcp-service restart", False) + LOGGER.info('Restarting DHCP Server') + isc_started = util.run_command('isc-dhcp-service restart', False) radvd_started = self.radvd.restart() started = isc_started and radvd_started LOGGER.info('DHCP Restarted: ' + str(started)) return started def start(self): - LOGGER.info("Starting DHCP Server") - isc_started = util.run_command("isc-dhcp-service start", False) + LOGGER.info('Starting DHCP Server') + isc_started = util.run_command('isc-dhcp-service start', False) radvd_started = self.radvd.start() started = isc_started and radvd_started LOGGER.info('DHCP Started: ' + str(started)) return started def stop(self): - LOGGER.info("Stopping DHCP Server") - isc_stopped = util.run_command("isc-dhcp-service stop", False) + LOGGER.info('Stopping DHCP Server') + isc_stopped = util.run_command('isc-dhcp-service stop', False) radvd_stopped = self.radvd.stop() stopped = isc_stopped and radvd_stopped LOGGER.info('DHCP Stopped: ' + str(stopped)) return stopped def is_running(self): - LOGGER.info("Checking DHCP Status") - response = util.run_command("isc-dhcp-service status") + LOGGER.info('Checking DHCP Status') + response = util.run_command('isc-dhcp-service status') isc_running = response[0] == 'isc-dhcp service is running.' radvd_running = self.radvd.is_running() running = isc_running and radvd_running diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py index dc45e83a4..5357ba7ed 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py +++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config.py @@ -125,18 +125,19 @@ def set_range(self, start, end, subnet=0, pool=0): dhcp_subnet = '.'.join(octets) # Calcualte the netmask from the range - prefix = self.calculate_prefix_length(start,end) + prefix = self.calculate_prefix_length(start, end) netmask = self.calculate_netmask(prefix) #Update the subnet, range and netmask - self._subnets[subnet].set_subnet(dhcp_subnet,netmask) + self._subnets[subnet].set_subnet(dhcp_subnet, netmask) self._subnets[subnet].pools[pool].set_range(start, end) - def calculate_prefix_length(self,start_ip, end_ip): + def calculate_prefix_length(self, start_ip, end_ip): start_octets = start_ip.split('.') end_octets = end_ip.split('.') - start_int = int(''.join(format(int(octet), '08b') for octet in start_octets), 2) + start_int = int( + ''.join(format(int(octet), '08b') for octet in start_octets), 2) end_int = int(''.join(format(int(octet), '08b') for octet in end_octets), 2) xor_result = start_int ^ end_int @@ -144,11 +145,11 @@ def calculate_prefix_length(self,start_ip, end_ip): return prefix_length - def calculate_netmask(self,prefix_length): + def calculate_netmask(self, prefix_length): num_network_bits = prefix_length num_host_bits = 32 - num_network_bits - netmask_int = (2 ** num_network_bits - 1) << num_host_bits + netmask_int = (2**num_network_bits - 1) << num_host_bits netmask_octets = [(netmask_int >> (i * 8)) & 0xff for i in range(3, -1, -1)] return '.'.join(str(octet) for octet in netmask_octets) @@ -156,7 +157,7 @@ def calculate_netmask(self,prefix_length): def __str__(self): config = ('{DEFAULT_LEASE_TIME_KEY} {DEFAULT_LEASE_TIME};' - if self._default_lease_time is not None else '') + if self._default_lease_time is not None else '') config += ('\n\r{MAX_LEASE_TIME_KEY} {MAX_LEASE_TIME};' if self._max_lease_time is not None else '') @@ -395,18 +396,21 @@ def set_subnet(self, subnet, netmask=None): self._subnet_mask = netmask # Calculate the broadcast from the subnet and netmask - broadcast = self.calculate_broadcast_address(subnet,netmask) + broadcast = self.calculate_broadcast_address(subnet, netmask) self._broadcast = broadcast - def calculate_broadcast_address(self,subnet_address, netmask): + def calculate_broadcast_address(self, subnet_address, netmask): subnet_octets = subnet_address.split('.') netmask_octets = netmask.split('.') - subnet_int = int(''.join(format(int(octet), '08b') for octet in subnet_octets), 2) - netmask_int = int(''.join(format(int(octet), '08b') for octet in netmask_octets), 2) + subnet_int = int( + ''.join(format(int(octet), '08b') for octet in subnet_octets), 2) + netmask_int = int( + ''.join(format(int(octet), '08b') for octet in netmask_octets), 2) broadcast_int = subnet_int | (~netmask_int & 0xffffffff) - broadcast_octets = [(broadcast_int >> (i * 8)) & 0xff for i in range(3, -1, -1)] + broadcast_octets = [(broadcast_int >> (i * 8)) & 0xff + for i in range(3, -1, -1)] return '.'.join(str(octet) for octet in broadcast_octets) diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py index 12d15326f..3cd97b875 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py +++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_config_test.py @@ -99,8 +99,10 @@ def test_set_subnet_range(self): DHCP_CONFIG.set_range(range_start, range_end) subnets = DHCP_CONFIG.resolve_subnets(str(DHCP_CONFIG)) pool = subnets[0].pools[0] - self.assertTrue(pool.range_start == range_start and pool.range_end == range_end) - print("SetSubnetRange:\n" + str(DHCP_CONFIG)) + self.assertTrue(pool.range_start == range_start + and pool.range_end == range_end) + print('SetSubnetRange:\n' + str(DHCP_CONFIG)) + if __name__ == '__main__': suite = unittest.TestSuite() diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_lease.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_lease.py index 0d2f43e3b..dd7ba9516 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_lease.py +++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_lease.py @@ -39,7 +39,7 @@ def _make_lease(self, lease): self.hw_addr = sections[0] self.ip = sections[1] self.hostname = sections[2] - self.expires = sections[3] + '' '' + sections[4] + self.expires = sections[3] + ' ' + sections[4] self.manufacturer = ' '.join(sections[5:]) def get_millis(self, timestamp): diff --git a/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py b/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py index f465b8b04..bc405d05e 100644 --- a/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py +++ b/modules/network/dhcp-2/python/src/grpc_server/dhcp_server.py @@ -36,32 +36,32 @@ def __init__(self): self.dhcp_config.resolve_config() def restart(self): - LOGGER.info("Restarting DHCP Server") - isc_started = util.run_command("isc-dhcp-service restart", False) + LOGGER.info('Restarting DHCP Server') + isc_started = util.run_command('isc-dhcp-service restart', False) radvd_started = self.radvd.restart() started = isc_started and radvd_started LOGGER.info('DHCP Restarted: ' + str(started)) return started def start(self): - LOGGER.info("Starting DHCP Server") - isc_started = util.run_command("isc-dhcp-service start", False) + LOGGER.info('Starting DHCP Server') + isc_started = util.run_command('isc-dhcp-service start', False) radvd_started = self.radvd.start() started = isc_started and radvd_started LOGGER.info('DHCP Started: ' + str(started)) return started def stop(self): - LOGGER.info("Stopping DHCP Server") - isc_stopped = util.run_command("isc-dhcp-service stop", False) + LOGGER.info('Stopping DHCP Server') + isc_stopped = util.run_command('isc-dhcp-service stop', False) radvd_stopped = self.radvd.stop() stopped = isc_stopped and radvd_stopped LOGGER.info('DHCP Stopped: ' + str(stopped)) return stopped def is_running(self): - LOGGER.info("Checking DHCP Status") - response = util.run_command("isc-dhcp-service status") + LOGGER.info('Checking DHCP Status') + response = util.run_command('isc-dhcp-service status') isc_running = response[0] == 'isc-dhcp service is running.' radvd_running = self.radvd.is_running() running = isc_running and radvd_running diff --git a/modules/test/base/python/src/grpc/proto/dhcp1/client.py b/modules/test/base/python/src/grpc/proto/dhcp1/client.py index 8a24e9be9..cab9d744c 100644 --- a/modules/test/base/python/src/grpc/proto/dhcp1/client.py +++ b/modules/test/base/python/src/grpc/proto/dhcp1/client.py @@ -1,13 +1,28 @@ +# 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 +"""gRPC client module for the primary DHCP Server""" import grpc import grpc_pb2_grpc as pb2_grpc import grpc_pb2 as pb2 + DEFAULT_PORT = '5001' DEFAULT_HOST = '10.10.10.2' # Default DHCP1 server class Client(): - + """gRPC Client for the primary DHCP server""" def __init__(self, port=DEFAULT_PORT, host=DEFAULT_HOST): self._port = port self._host = host diff --git a/modules/test/base/python/src/grpc/proto/dhcp2/client.py b/modules/test/base/python/src/grpc/proto/dhcp2/client.py index 7712a7f13..c5856cb4a 100644 --- a/modules/test/base/python/src/grpc/proto/dhcp2/client.py +++ b/modules/test/base/python/src/grpc/proto/dhcp2/client.py @@ -1,3 +1,17 @@ +# 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 +"""gRPC client module for the secondary DHCP Server""" import grpc import grpc_pb2_grpc as pb2_grpc import grpc_pb2 as pb2 @@ -7,7 +21,7 @@ class Client(): - + """gRPC Client for the secondary DHCP server""" def __init__(self, port=DEFAULT_PORT, host=DEFAULT_HOST): self._port = port self._host = host diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 1501e5b8b..cf84b281f 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -69,35 +69,35 @@ def __init__(self, module): def _connection_private_address(self,config): # Shutdown the secondary DHCP Server - LOGGER.info("Running connection.private_address") - LOGGER.info("Stopping secondary DHCP server") + LOGGER.info('Running connection.private_address') + LOGGER.info('Stopping secondary DHCP server') response = self.dhcp2_client.stop_dhcp_server() if response.code == 200: - LOGGER.info("Secondary DHCP server stop command success") + LOGGER.info('Secondary DHCP server stop command success') time.sleep(3) # Give some time for the server to stop - LOGGER.info("Checking secondary DHCP server status") + LOGGER.info('Checking secondary DHCP server status') response = self.dhcp2_client.get_status() if response.code == 200: - LOGGER.info("Secondary DHCP server stopped") + LOGGER.info('Secondary DHCP server stoppe') # Move primary DHCP server from failover into a single DHCP server config - LOGGER.info("Configuring primary DHCP server") + LOGGER.info('Configuring primary DHCP server') response = self.dhcp1_client.disable_failover() if response.code == 200: - LOGGER.info("Checking current device lease") + LOGGER.info('Checking current device lease') response = self.dhcp1_client.get_lease(self._device_mac) if response.code == 200: lease = eval(response.message) - LOGGER.info("Current lease found") + LOGGER.info('Current lease found') if 'ip' in lease: ip_addr = lease['ip'] - LOGGER.info("IP Resolved: " + ip_addr) - LOGGER.info("Attempting to ping device..."); + LOGGER.info('IP Resolved: ' + ip_addr) + LOGGER.info('Attempting to ping device...') ping_success = self._ping(self._device_ipv4_addr) - LOGGER.info("Ping Success: " + str(ping_success)) - LOGGER.info("Current lease confirmed active in device") + LOGGER.info('Ping Success: ' + str(ping_success)) + LOGGER.info('Current lease confirmed active in device') - LOGGER.info("Private subnets configured for testing: " + str(config)) + LOGGER.info('Private subnets configured for testing: ' + str(config)) results = [] for subnet in config: @@ -136,46 +136,46 @@ def _test_subnet(self,subnet,lease): if self._change_subnet(subnet): expiration = datetime.strptime(lease['expires'], '%Y-%m-%d %H:%M:%S') time_to_expire = expiration - datetime.now() - LOGGER.info("Time until lease expiration: " + str(time_to_expire)) - LOGGER.info("Waiting for current lease to expire: " + str(expiration)) + LOGGER.info('Time until lease expiration: ' + str(time_to_expire)) + LOGGER.info('Waiting for current lease to expire: ' + str(expiration)) if time_to_expire.total_seconds() > 0: time.sleep(time_to_expire.total_seconds() + 5) # Wait until the expiration time and padd 5 seconds - LOGGER.info("Current lease expired. Checking for new lease") + LOGGER.info('Current lease expired. Checking for new lease') for _ in range(5): - LOGGER.info("Checking for new lease") + LOGGER.info('Checking for new lease') lease = self._get_cur_lease() if lease is not None: - LOGGER.info("New Lease found: " + str(lease)) - LOGGER.info("Validating subnet for new lease...") + LOGGER.info('New Lease found: ' + str(lease)) + LOGGER.info('Validating subnet for new lease...') in_range = self.is_ip_in_range(lease['ip'],subnet['start'],subnet['end']) - LOGGER.info("Lease within subnet: " + str(in_range)) + LOGGER.info('Lease within subnet: ' + str(in_range)) return in_range else: - LOGGER.info("New lease not found. Waiting to check again") + LOGGER.info('New lease not found. Waiting to check again') time.sleep(5) def _change_subnet(self,subnet): - LOGGER.info("Changing subnet to: " + str(subnet)) + LOGGER.info('Changing subnet to: ' + str(subnet)) response = self.dhcp1_client.set_dhcp_range(subnet['start'],subnet['end']) if response.code == 200: - LOGGER.info("Subnet change request accepted. Confirming change...") + LOGGER.info('Subnet change request accepted. Confirming change...') response = self.dhcp1_client.get_dhcp_range() if response.code == 200: if response.start == subnet['start'] and response.end == subnet['end']: - LOGGER.info("Subnet change confirmed") + LOGGER.info('Subnet change confirmed') return True - LOGGER.error("Failed to confirm subnet change") + LOGGER.error('Failed to confirm subnet change') else: - LOGGER.error("Subnet change request failed.") + LOGGER.error('Subnet change request failed.') return False def _get_cur_lease(self): - LOGGER.info("Checking current device lease") + LOGGER.info('Checking current device lease') response = self.dhcp1_client.get_lease(self._device_mac) if response.code == 200: lease = eval(response.message) if lease: # Check if non-empty lease - LOGGER.info("Current lease found") + LOGGER.info('Current lease found') return lease else: return None From 8d23ebb73ccc71913d3fc834c657ce19f7b307a1 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Tue, 11 Jul 2023 14:18:30 -0600 Subject: [PATCH 11/15] Breakup test and allow better failure reporting --- .../test/conn/python/src/connection_module.py | 218 ++++++++++-------- 1 file changed, 121 insertions(+), 97 deletions(-) diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index cf84b281f..9336d5ca0 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -70,50 +70,21 @@ def __init__(self, module): def _connection_private_address(self,config): # Shutdown the secondary DHCP Server LOGGER.info('Running connection.private_address') - LOGGER.info('Stopping secondary DHCP server') - response = self.dhcp2_client.stop_dhcp_server() - if response.code == 200: - LOGGER.info('Secondary DHCP server stop command success') - time.sleep(3) # Give some time for the server to stop - LOGGER.info('Checking secondary DHCP server status') - response = self.dhcp2_client.get_status() - if response.code == 200: - LOGGER.info('Secondary DHCP server stoppe') - - # Move primary DHCP server from failover into a single DHCP server config - LOGGER.info('Configuring primary DHCP server') - response = self.dhcp1_client.disable_failover() - if response.code == 200: - LOGGER.info('Checking current device lease') - response = self.dhcp1_client.get_lease(self._device_mac) - if response.code == 200: - lease = eval(response.message) - LOGGER.info('Current lease found') - if 'ip' in lease: - ip_addr = lease['ip'] - LOGGER.info('IP Resolved: ' + ip_addr) - LOGGER.info('Attempting to ping device...') - ping_success = self._ping(self._device_ipv4_addr) - LOGGER.info('Ping Success: ' + str(ping_success)) - LOGGER.info('Current lease confirmed active in device') - - LOGGER.info('Private subnets configured for testing: ' + str(config)) - results = [] - for subnet in config: - result = {} - try: - lease = self._get_cur_lease() - if lease is not None: - result = self._test_subnet(subnet,lease) - if result: - result = {'result':True,'details':'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' passed'} - else: - result = {'result':False,'details':'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' failed'} - except Exception as e: - result = {'result':False,'details':'Subnet test failed: ' + str(e)} - results.append(result) + dhcp_setup = self.setup_single_dhcp_server() + if dhcp_setup[0]: + LOGGER.info(dhcp_setup[1]) + lease = self._get_cur_lease() + if lease is not None: + if self._is_lease_active(lease): + results = self.test_subnets(config) + else: + return None, "Failed to confirm a valid active lease for the device" + else: + LOGGER.error(dhcp_setup[1]) + return None, 'Failed to setup DHCP server for test' + # Process and return final results final_result = None final_result_details = '' for result in results: @@ -125,61 +96,6 @@ def _connection_private_address(self,config): return final_result, final_result_details - def is_ip_in_range(self,ip, start_ip, end_ip): - ip_int = int(''.join(format(int(octet), '08b') for octet in ip.split('.')), 2) - start_int = int(''.join(format(int(octet), '08b') for octet in start_ip.split('.')), 2) - end_int = int(''.join(format(int(octet), '08b') for octet in end_ip.split('.')), 2) - - return start_int <= ip_int <= end_int - - def _test_subnet(self,subnet,lease): - if self._change_subnet(subnet): - expiration = datetime.strptime(lease['expires'], '%Y-%m-%d %H:%M:%S') - time_to_expire = expiration - datetime.now() - LOGGER.info('Time until lease expiration: ' + str(time_to_expire)) - LOGGER.info('Waiting for current lease to expire: ' + str(expiration)) - if time_to_expire.total_seconds() > 0: - time.sleep(time_to_expire.total_seconds() + 5) # Wait until the expiration time and padd 5 seconds - LOGGER.info('Current lease expired. Checking for new lease') - for _ in range(5): - LOGGER.info('Checking for new lease') - lease = self._get_cur_lease() - if lease is not None: - LOGGER.info('New Lease found: ' + str(lease)) - LOGGER.info('Validating subnet for new lease...') - in_range = self.is_ip_in_range(lease['ip'],subnet['start'],subnet['end']) - LOGGER.info('Lease within subnet: ' + str(in_range)) - return in_range - else: - LOGGER.info('New lease not found. Waiting to check again') - time.sleep(5) - - def _change_subnet(self,subnet): - LOGGER.info('Changing subnet to: ' + str(subnet)) - response = self.dhcp1_client.set_dhcp_range(subnet['start'],subnet['end']) - if response.code == 200: - LOGGER.info('Subnet change request accepted. Confirming change...') - response = self.dhcp1_client.get_dhcp_range() - if response.code == 200: - if response.start == subnet['start'] and response.end == subnet['end']: - LOGGER.info('Subnet change confirmed') - return True - LOGGER.error('Failed to confirm subnet change') - else: - LOGGER.error('Subnet change request failed.') - return False - - def _get_cur_lease(self): - LOGGER.info('Checking current device lease') - response = self.dhcp1_client.get_lease(self._device_mac) - if response.code == 200: - lease = eval(response.message) - if lease: # Check if non-empty lease - LOGGER.info('Current lease found') - return lease - else: - return None - def _connection_dhcp_address(self): LOGGER.info('Running connection.dhcp_address') response = self.dhcp1_client.get_lease(self._device_mac) @@ -279,3 +195,111 @@ def _ping(self, host): cmd = 'ping -c 1 ' + str(host) success = util.run_command(cmd, output=False) return success + + def setup_single_dhcp_server(self): + # Shutdown the secondary DHCP Server + LOGGER.info('Stopping secondary DHCP server') + response = self.dhcp2_client.stop_dhcp_server() + if response.code == 200: + LOGGER.info('Secondary DHCP server stop command success') + time.sleep(3) # Give some time for the server to stop + LOGGER.info('Checking secondary DHCP server status') + response = self.dhcp2_client.get_status() + if response.code == 200: + LOGGER.info('Secondary DHCP server stopped') + return True, 'Single DHCP server configured' + else: + return False, 'DHCP server still running' + else: + return False, 'DHCP server stop command failed' + + # Move primary DHCP server from failover into a single DHCP server config + LOGGER.info('Configuring primary DHCP server') + response = self.dhcp1_client.disable_failover() + if response.code == 200: + LOGGER.info('Checking current device lease') + else: + return False, 'Failed to disable primary DHCP server failover' + + LOGGER.info('Private subnets configured for testing: ' + str(config)) + + def is_ip_in_range(self,ip, start_ip, end_ip): + ip_int = int(''.join(format(int(octet), '08b') for octet in ip.split('.')), 2) + start_int = int(''.join(format(int(octet), '08b') for octet in start_ip.split('.')), 2) + end_int = int(''.join(format(int(octet), '08b') for octet in end_ip.split('.')), 2) + + return start_int <= ip_int <= end_int + + def _test_subnet(self,subnet,lease): + if self._change_subnet(subnet): + expiration = datetime.strptime(lease['expires'], '%Y-%m-%d %H:%M:%S') + time_to_expire = expiration - datetime.now() + LOGGER.info('Time until lease expiration: ' + str(time_to_expire)) + LOGGER.info('Waiting for current lease to expire: ' + str(expiration)) + if time_to_expire.total_seconds() > 0: + time.sleep(time_to_expire.total_seconds() + 5) # Wait until the expiration time and padd 5 seconds + LOGGER.info('Current lease expired. Checking for new lease') + for _ in range(5): + LOGGER.info('Checking for new lease') + lease = self._get_cur_lease() + if lease is not None: + LOGGER.info('New Lease found: ' + str(lease)) + LOGGER.info('Validating subnet for new lease...') + in_range = self.is_ip_in_range(lease['ip'],subnet['start'],subnet['end']) + LOGGER.info('Lease within subnet: ' + str(in_range)) + return in_range + else: + LOGGER.info('New lease not found. Waiting to check again') + time.sleep(5) + + def _change_subnet(self,subnet): + LOGGER.info('Changing subnet to: ' + str(subnet)) + response = self.dhcp1_client.set_dhcp_range(subnet['start'],subnet['end']) + if response.code == 200: + LOGGER.info('Subnet change request accepted. Confirming change...') + response = self.dhcp1_client.get_dhcp_range() + if response.code == 200: + if response.start == subnet['start'] and response.end == subnet['end']: + LOGGER.info('Subnet change confirmed') + return True + LOGGER.error('Failed to confirm subnet change') + else: + LOGGER.error('Subnet change request failed.') + return False + + def _get_cur_lease(self): + LOGGER.info('Checking current device lease') + response = self.dhcp1_client.get_lease(self._device_mac) + if response.code == 200: + lease = eval(response.message) + if lease: # Check if non-empty lease + return lease + else: + return None + + def _is_lease_active(self, lease): + if 'ip' in lease: + ip_addr = lease['ip'] + LOGGER.info('Lease IP Resolved: ' + ip_addr) + LOGGER.info('Attempting to ping device...') + ping_success = self._ping(self._device_ipv4_addr) + LOGGER.info('Ping Success: ' + str(ping_success)) + LOGGER.info('Current lease confirmed active in device') + return ping_success + + def test_subnets(self,subnets): + results = [] + for subnet in subnets: + result = {} + try: + lease = self._get_cur_lease() + if lease is not None: + result = self._test_subnet(subnet,lease) + if result: + result = {'result':True,'details':'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' passed'} + else: + result = {'result':False,'details':'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' failed'} + except Exception as e: + result = {'result':False,'details':'Subnet test failed: ' + str(e)} + results.append(result) + return results \ No newline at end of file From f0e333220a9edcc871198ebb15d42b9a92619c0b Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Tue, 11 Jul 2023 15:51:35 -0600 Subject: [PATCH 12/15] restore network after test --- .../python/src/grpc/proto/dhcp1/client.py | 8 ++++ .../python/src/grpc/proto/dhcp2/client.py | 8 ++++ .../test/conn/python/src/connection_module.py | 46 ++++++++++++++++++- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/modules/test/base/python/src/grpc/proto/dhcp1/client.py b/modules/test/base/python/src/grpc/proto/dhcp1/client.py index cab9d744c..8707957ce 100644 --- a/modules/test/base/python/src/grpc/proto/dhcp1/client.py +++ b/modules/test/base/python/src/grpc/proto/dhcp1/client.py @@ -110,6 +110,14 @@ def stop_dhcp_server(self): return response + def start_dhcp_server(self): + # Create a request message + request = pb2.StartDHCPServerRequest() + + # Make the RPC call + response = self._stub.StartDHCPServer(request) + + return response def set_dhcp_range(self,start,end): # Create a request message diff --git a/modules/test/base/python/src/grpc/proto/dhcp2/client.py b/modules/test/base/python/src/grpc/proto/dhcp2/client.py index c5856cb4a..e0d953ee5 100644 --- a/modules/test/base/python/src/grpc/proto/dhcp2/client.py +++ b/modules/test/base/python/src/grpc/proto/dhcp2/client.py @@ -109,6 +109,14 @@ def stop_dhcp_server(self): return response + def start_dhcp_server(self): + # Create a request message + request = pb2.StartDHCPServerRequest() + + # Make the RPC call + response = self._stub.StartDHCPServer(request) + + return response def set_dhcp_range(self,start,end): # Create a request message diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 9336d5ca0..02e8ebc6a 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -70,6 +70,16 @@ def __init__(self, module): def _connection_private_address(self,config): # Shutdown the secondary DHCP Server LOGGER.info('Running connection.private_address') + response = self.dhcp1_client.get_dhcp_range() + cur_range = {} + if response.code == 200: + cur_range['start'] = response.start + cur_range['end'] = response.end + LOGGER.info('Current DHCP subnet range: ' + str(cur_range)) + else: + LOGGER.error('Failed to resolve current subnet range required for restoring network') + return None, 'Failed to resolve current subnet range required for restoring network' + results = [] dhcp_setup = self.setup_single_dhcp_server() if dhcp_setup[0]: @@ -94,6 +104,11 @@ def _connection_private_address(self,config): final_result &= result['result'] final_result_details += result['details'] + '\n' + try: + self.restore_failover_dhcp_server(cur_range) + except Exception as e: + LOGGER.error('Failed to restore DHCP server configuration') + return final_result, final_result_details def _connection_dhcp_address(self): @@ -196,6 +211,24 @@ def _ping(self, host): success = util.run_command(cmd, output=False) return success + def restore_failover_dhcp_server(self,subnet): + # Configure the subnet range + if self._change_subnet(subnet): + if self.enable_failover(): + response = self.dhcp2_client.start_dhcp_server() + if response.code == 200: + LOGGER.info("DHCP server configuration restored") + return True + else: + LOGGER.error('Failed to start secondary DHCP server') + return False + else: + LOGGER.error("Failed to enabled failover in primary DHCP server") + return False + else: + LOGGER.error('Failed to restore original subnet') + False + def setup_single_dhcp_server(self): # Shutdown the secondary DHCP Server LOGGER.info('Stopping secondary DHCP server') @@ -217,12 +250,23 @@ def setup_single_dhcp_server(self): LOGGER.info('Configuring primary DHCP server') response = self.dhcp1_client.disable_failover() if response.code == 200: - LOGGER.info('Checking current device lease') + LOGGER.info('Primary DHCP server failover disabled') else: return False, 'Failed to disable primary DHCP server failover' LOGGER.info('Private subnets configured for testing: ' + str(config)) + def enable_failover(self): + # Move primary DHCP server to primary failover + LOGGER.info('Configuring primary failover DHCP server') + response = self.dhcp1_client.enable_failover() + if response.code == 200: + LOGGER.info('Primary DHCP server enabled') + return True + else: + LOGGER.error('Failed to disable primary DHCP server failover') + return False + def is_ip_in_range(self,ip, start_ip, end_ip): ip_int = int(''.join(format(int(octet), '08b') for octet in ip.split('.')), 2) start_int = int(''.join(format(int(octet), '08b') for octet in start_ip.split('.')), 2) From 918e3f9ad3ae5ed1ded1ed893fcf5f63f4939091 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Tue, 11 Jul 2023 16:20:10 -0600 Subject: [PATCH 13/15] Wait for device to get a lease from original dhcp range after network restore --- .../test/conn/python/src/connection_module.py | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 02e8ebc6a..b9f6c1b89 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -105,9 +105,29 @@ def _connection_private_address(self,config): final_result_details += result['details'] + '\n' try: + # Restore failover configuration of DHCP servers self.restore_failover_dhcp_server(cur_range) + + # Wait for the current lease to expire + self._wait_for_lease_expire(self._get_cur_lease()) + + # Wait for a new lease to be provided before exiting test + # to prevent other test modules from failing + for _ in range(5): + LOGGER.info('Checking for new lease') + lease = self._get_cur_lease() + if lease is not None: + LOGGER.info('New Lease found: ' + str(lease)) + LOGGER.info('Validating subnet for new lease...') + in_range = self.is_ip_in_range(lease['ip'],cur_range['start'],cur_range['end']) + LOGGER.info('Lease within subnet: ' + str(in_range)) + break + else: + LOGGER.info('New lease not found. Waiting to check again') + time.sleep(5) + except Exception as e: - LOGGER.error('Failed to restore DHCP server configuration') + LOGGER.error('Failed to restore DHCP server configuration: ' + str(e)) return final_result, final_result_details @@ -296,6 +316,15 @@ def _test_subnet(self,subnet,lease): LOGGER.info('New lease not found. Waiting to check again') time.sleep(5) + def _wait_for_lease_expire(self,lease): + expiration = datetime.strptime(lease['expires'], '%Y-%m-%d %H:%M:%S') + time_to_expire = expiration - datetime.now() + LOGGER.info('Time until lease expiration: ' + str(time_to_expire)) + LOGGER.info('Waiting for current lease to expire: ' + str(expiration)) + if time_to_expire.total_seconds() > 0: + time.sleep(time_to_expire.total_seconds() + 5) # Wait until the expiration time and padd 5 seconds + LOGGER.info('Current lease expired.') + def _change_subnet(self,subnet): LOGGER.info('Changing subnet to: ' + str(subnet)) response = self.dhcp1_client.set_dhcp_range(subnet['start'],subnet['end']) From 7797332a6d2d1d70aeb7d5212c2ccf602909dc03 Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Tue, 11 Jul 2023 16:37:56 -0600 Subject: [PATCH 14/15] pylinting --- .../test/conn/python/src/connection_module.py | 110 ++++++++++-------- 1 file changed, 63 insertions(+), 47 deletions(-) diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index b9f6c1b89..5e417cf4d 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -14,7 +14,6 @@ """Connection test module""" import util import sys -import json import time from datetime import datetime from scapy.all import rdpcap, DHCP, Ether @@ -39,8 +38,8 @@ def __init__(self, module): LOGGER = self._get_logger() self.dhcp1_client = DHCPClient1() self.dhcp2_client = DHCPClient2() - - # ToDo: Move this into some level of testing, leave for + + # ToDo: Move this into some level of testing, leave for # reference until tests are implemented with these calls # response = self.dhcp1_client.add_reserved_lease( # 'test','00:11:22:33:44:55','10.10.10.21') @@ -67,7 +66,7 @@ def __init__(self, module): # response = self.dhcp1_client.set_dhcp_range('10.10.10.20','10.10.10.30') # print("Set Range: " + str(response)) - def _connection_private_address(self,config): + def _connection_private_address(self, config): # Shutdown the secondary DHCP Server LOGGER.info('Running connection.private_address') response = self.dhcp1_client.get_dhcp_range() @@ -77,8 +76,10 @@ def _connection_private_address(self,config): cur_range['end'] = response.end LOGGER.info('Current DHCP subnet range: ' + str(cur_range)) else: - LOGGER.error('Failed to resolve current subnet range required for restoring network') - return None, 'Failed to resolve current subnet range required for restoring network' + LOGGER.error('Failed to resolve current subnet range required ' + 'for restoring network') + return None, ('Failed to resolve current subnet range required ' + 'for restoring network') results = [] dhcp_setup = self.setup_single_dhcp_server() @@ -89,7 +90,7 @@ def _connection_private_address(self,config): if self._is_lease_active(lease): results = self.test_subnets(config) else: - return None, "Failed to confirm a valid active lease for the device" + return None, 'Failed to confirm a valid active lease for the device' else: LOGGER.error(dhcp_setup[1]) return None, 'Failed to setup DHCP server for test' @@ -114,19 +115,20 @@ def _connection_private_address(self,config): # Wait for a new lease to be provided before exiting test # to prevent other test modules from failing for _ in range(5): - LOGGER.info('Checking for new lease') - lease = self._get_cur_lease() - if lease is not None: - LOGGER.info('New Lease found: ' + str(lease)) - LOGGER.info('Validating subnet for new lease...') - in_range = self.is_ip_in_range(lease['ip'],cur_range['start'],cur_range['end']) - LOGGER.info('Lease within subnet: ' + str(in_range)) - break - else: - LOGGER.info('New lease not found. Waiting to check again') - time.sleep(5) + LOGGER.info('Checking for new lease') + lease = self._get_cur_lease() + if lease is not None: + LOGGER.info('New Lease found: ' + str(lease)) + LOGGER.info('Validating subnet for new lease...') + in_range = self.is_ip_in_range(lease['ip'], cur_range['start'], + cur_range['end']) + LOGGER.info('Lease within subnet: ' + str(in_range)) + break + else: + LOGGER.info('New lease not found. Waiting to check again') + time.sleep(5) - except Exception as e: + except Exception as e: # pylint: disable=W0718 LOGGER.error('Failed to restore DHCP server configuration: ' + str(e)) return final_result, final_result_details @@ -136,7 +138,7 @@ def _connection_dhcp_address(self): response = self.dhcp1_client.get_lease(self._device_mac) LOGGER.info('DHCP Lease resolved:\n' + str(response)) if response.code == 200: - lease = eval(response.message) # pylint: disable=E0203 + lease = eval(response.message) # pylint: disable=W0123 if 'ip' in lease: ip_addr = lease['ip'] LOGGER.info('IP Resolved: ' + ip_addr) @@ -231,23 +233,23 @@ def _ping(self, host): success = util.run_command(cmd, output=False) return success - def restore_failover_dhcp_server(self,subnet): + def restore_failover_dhcp_server(self, subnet): # Configure the subnet range if self._change_subnet(subnet): if self.enable_failover(): response = self.dhcp2_client.start_dhcp_server() if response.code == 200: - LOGGER.info("DHCP server configuration restored") + LOGGER.info('DHCP server configuration restored') return True else: LOGGER.error('Failed to start secondary DHCP server') return False else: - LOGGER.error("Failed to enabled failover in primary DHCP server") + LOGGER.error('Failed to enabled failover in primary DHCP server') return False else: LOGGER.error('Failed to restore original subnet') - False + return False def setup_single_dhcp_server(self): # Shutdown the secondary DHCP Server @@ -255,7 +257,7 @@ def setup_single_dhcp_server(self): response = self.dhcp2_client.stop_dhcp_server() if response.code == 200: LOGGER.info('Secondary DHCP server stop command success') - time.sleep(3) # Give some time for the server to stop + time.sleep(3) # Give some time for the server to stop LOGGER.info('Checking secondary DHCP server status') response = self.dhcp2_client.get_status() if response.code == 200: @@ -274,8 +276,6 @@ def setup_single_dhcp_server(self): else: return False, 'Failed to disable primary DHCP server failover' - LOGGER.info('Private subnets configured for testing: ' + str(config)) - def enable_failover(self): # Move primary DHCP server to primary failover LOGGER.info('Configuring primary failover DHCP server') @@ -287,21 +287,25 @@ def enable_failover(self): LOGGER.error('Failed to disable primary DHCP server failover') return False - def is_ip_in_range(self,ip, start_ip, end_ip): - ip_int = int(''.join(format(int(octet), '08b') for octet in ip.split('.')), 2) - start_int = int(''.join(format(int(octet), '08b') for octet in start_ip.split('.')), 2) - end_int = int(''.join(format(int(octet), '08b') for octet in end_ip.split('.')), 2) + def is_ip_in_range(self, ip, start_ip, end_ip): + ip_int = int(''.join(format(int(octet), '08b') for octet in ip.split('.')), + 2) + start_int = int( + ''.join(format(int(octet), '08b') for octet in start_ip.split('.')), 2) + end_int = int( + ''.join(format(int(octet), '08b') for octet in end_ip.split('.')), 2) return start_int <= ip_int <= end_int - def _test_subnet(self,subnet,lease): + def _test_subnet(self, subnet, lease): if self._change_subnet(subnet): expiration = datetime.strptime(lease['expires'], '%Y-%m-%d %H:%M:%S') time_to_expire = expiration - datetime.now() LOGGER.info('Time until lease expiration: ' + str(time_to_expire)) LOGGER.info('Waiting for current lease to expire: ' + str(expiration)) if time_to_expire.total_seconds() > 0: - time.sleep(time_to_expire.total_seconds() + 5) # Wait until the expiration time and padd 5 seconds + time.sleep(time_to_expire.total_seconds() + + 5) # Wait until the expiration time and padd 5 seconds LOGGER.info('Current lease expired. Checking for new lease') for _ in range(5): LOGGER.info('Checking for new lease') @@ -309,25 +313,27 @@ def _test_subnet(self,subnet,lease): if lease is not None: LOGGER.info('New Lease found: ' + str(lease)) LOGGER.info('Validating subnet for new lease...') - in_range = self.is_ip_in_range(lease['ip'],subnet['start'],subnet['end']) + in_range = self.is_ip_in_range(lease['ip'], subnet['start'], + subnet['end']) LOGGER.info('Lease within subnet: ' + str(in_range)) return in_range else: LOGGER.info('New lease not found. Waiting to check again') time.sleep(5) - def _wait_for_lease_expire(self,lease): + def _wait_for_lease_expire(self, lease): expiration = datetime.strptime(lease['expires'], '%Y-%m-%d %H:%M:%S') time_to_expire = expiration - datetime.now() LOGGER.info('Time until lease expiration: ' + str(time_to_expire)) LOGGER.info('Waiting for current lease to expire: ' + str(expiration)) if time_to_expire.total_seconds() > 0: - time.sleep(time_to_expire.total_seconds() + 5) # Wait until the expiration time and padd 5 seconds + time.sleep(time_to_expire.total_seconds() + + 5) # Wait until the expiration time and padd 5 seconds LOGGER.info('Current lease expired.') - def _change_subnet(self,subnet): + def _change_subnet(self, subnet): LOGGER.info('Changing subnet to: ' + str(subnet)) - response = self.dhcp1_client.set_dhcp_range(subnet['start'],subnet['end']) + response = self.dhcp1_client.set_dhcp_range(subnet['start'], subnet['end']) if response.code == 200: LOGGER.info('Subnet change request accepted. Confirming change...') response = self.dhcp1_client.get_dhcp_range() @@ -344,8 +350,8 @@ def _get_cur_lease(self): LOGGER.info('Checking current device lease') response = self.dhcp1_client.get_lease(self._device_mac) if response.code == 200: - lease = eval(response.message) - if lease: # Check if non-empty lease + lease = eval(response.message) # pylint: disable=W0123 + if lease: # Check if non-empty lease return lease else: return None @@ -360,19 +366,29 @@ def _is_lease_active(self, lease): LOGGER.info('Current lease confirmed active in device') return ping_success - def test_subnets(self,subnets): + def test_subnets(self, subnets): results = [] for subnet in subnets: result = {} try: lease = self._get_cur_lease() if lease is not None: - result = self._test_subnet(subnet,lease) + result = self._test_subnet(subnet, lease) if result: - result = {'result':True,'details':'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' passed'} + result = { + 'result': + True, + 'details': + 'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' passed' + } else: - result = {'result':False,'details':'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' failed'} - except Exception as e: - result = {'result':False,'details':'Subnet test failed: ' + str(e)} + result = { + 'result': + False, + 'details': + 'Subnet ' + subnet['start'] + '-' + subnet['end'] + ' failed' + } + except Exception as e: # pylint: disable=W0718 + result = {'result': False, 'details': 'Subnet test failed: ' + str(e)} results.append(result) - return results \ No newline at end of file + return results From 60fb589d5fd6151d7ab6da16bf769edc39f33c0c Mon Sep 17 00:00:00 2001 From: jhughesbiot Date: Tue, 11 Jul 2023 21:32:27 -0600 Subject: [PATCH 15/15] Fix ipv6 tests --- modules/network/dhcp-1/conf/radvd.conf | 23 +++++++++---------- .../test/conn/python/src/connection_module.py | 11 ++++----- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/modules/network/dhcp-1/conf/radvd.conf b/modules/network/dhcp-1/conf/radvd.conf index 89995785f..2f0c75d9d 100644 --- a/modules/network/dhcp-1/conf/radvd.conf +++ b/modules/network/dhcp-1/conf/radvd.conf @@ -1,13 +1,12 @@ -interface veth0 -{ - AdvSendAdvert on; - AdvManagedFlag off; - MinRtrAdvInterval 30; - MaxRtrAdvInterval 60; - prefix fd10:77be:4186::/64 { - AdvOnLink on; - AdvAutonomous on; - AdvRouterAddr on; - AdvSourceLLAddress off; - }; +interface veth0 +{ + AdvSendAdvert on; + AdvManagedFlag off; + MinRtrAdvInterval 30; + MaxRtrAdvInterval 60; + prefix fd10:77be:4186::/64 { + AdvOnLink on; + AdvAutonomous on; + AdvRouterAddr on; + }; }; \ No newline at end of file diff --git a/modules/test/conn/python/src/connection_module.py b/modules/test/conn/python/src/connection_module.py index 18c271d8e..387b19773 100644 --- a/modules/test/conn/python/src/connection_module.py +++ b/modules/test/conn/python/src/connection_module.py @@ -16,7 +16,7 @@ import sys import time from datetime import datetime -from scapy.all import rdpcap, DHCP, Ether +from scapy.all import rdpcap, DHCP, Ether, IPv6 from test_module import TestModule from dhcp1.client import Client as DHCPClient1 from dhcp2.client import Client as DHCPClient2 @@ -153,7 +153,6 @@ def _connection_dhcp_address(self): else: LOGGER.info('No DHCP lease found for: ' + self._device_mac) return False, 'No DHCP lease found for: ' + self._device_mac - self._ipv6_addr = None def _connection_mac_address(self): LOGGER.info('Running connection.mac_address') @@ -242,7 +241,7 @@ def _connection_ipv6_slaac(self): if ICMPv6ND_NS in packet: ipv6_addr = str(packet[ICMPv6ND_NS].tgt) if ipv6_addr.startswith(SLAAC_PREFIX): - self._ipv6_addr = ipv6_addr + self._device_ipv6_addr = ipv6_addr LOGGER.info(f"Device has formed SLAAC address {ipv6_addr}") return True @@ -255,12 +254,12 @@ def _connection_ipv6_slaac(self): def _connection_ipv6_ping(self): LOGGER.info("Running connection.ipv6_ping") - if self._ipv6_addr is None: + if self._device_ipv6_addr is None: LOGGER.info("No IPv6 SLAAC address found. Cannot ping") return - if self._ping(self._ipv6_addr): - LOGGER.info(f"Device responds to IPv6 ping on {self._ipv6_addr}") + if self._ping(self._device_ipv6_addr): + LOGGER.info(f"Device responds to IPv6 ping on {self._device_ipv6_addr}") return True else: LOGGER.info("Device does not respond to IPv6 ping")