Skip to content

Latest commit

 

History

History
588 lines (460 loc) · 23.6 KB

File metadata and controls

588 lines (460 loc) · 23.6 KB

Network Namespace Refactoring Proposal

Current State Analysis

The Monolith: network_namespace_service.py (1,548 lines)

The NetworkNamespaceService class is a large monolithic service that handles multiple distinct responsibilities:

Current Responsibilities (30+ methods):

  1. Namespace Management (~15% of code)

    • _prepare_namespace() - Creates and prepares namespaces
    • _prepare_root() - Prepares root namespace
    • revert_to_root() - Moves interfaces/PHYs back to root
    • start_app_in_namespace() / stop_app_in_namespace() - App lifecycle in namespaces
  2. Adapter/Interface Management (~20% of code)

    • get_interfaces() - Lists available interfaces
    • PHY operations (moving PHYs between namespaces)
    • Interface creation/deletion (iw phy interface add/del)
    • Interface bring up/down operations
  3. WPA Supplicant Management (~25% of code)

    • _write_config() - Generates wpa_supplicant config files
    • _generate_network_block() - Creates network blocks for WPA config
    • _generate_global_header() - Global WPA settings
    • _start_or_restart_supplicant() - Starts/stops wpa_supplicant
    • parse_wpa_log() - Parses WPA connection logs
    • get_status() - Gets WPA connection status
    • remove_network() - Removes network configs
  4. DHCP Management (~5% of code)

    • _write_dhcp_config() - Creates DHCP config files
    • _restart_dhcp_with_timeout() - Manages DHCP client
  5. Connection Monitoring (~15% of code)

    • _monitor_connection_async() - Background thread for connection monitoring
    • stop_connection_monitor() / stop_all_connection_monitors() - Monitor lifecycle
  6. Route Management (~3% of code)

    • _set_default_route() - Sets default routes in namespaces
  7. Configuration Management (~10% of code)

    • activate_config() / deactivate_config() - Main orchestration methods
    • _validate_config() - Comprehensive validation
  8. Utility/Helper Methods (~7% of code)

    • _run() - Command execution wrapper
    • _ns_exec() - Namespace command execution (CRITICAL utility)
    • _safe_unlink() - Safe file deletion
    • _log_event() - Event logging
    • _parse_key_mgmt() - Security parsing

Existing Model: NetworkNamespace in models/network/namespace/namespace.py

The existing NetworkNamespace class provides:

  • Static methods for basic namespace operations (create, list, exists, destroy)
  • Interface listing within namespaces
  • Process management within namespaces
  • Command execution in namespaces

Assessment: This is a good foundation but is underutilized. The service duplicates some functionality (e.g., namespace creation) instead of using this model.

Note: There's a bug in the current model - line 12 has get_logger import commented out but it's used on line 13. This should be fixed during refactoring.

Pattern Analysis: core/ and profiler/ Folders

core/ folder structure:

  • Focused, single-responsibility modules (auth.py, config.py, database.py, etc.)
  • Each module handles one domain concern
  • Clear separation of concerns

profiler/ folder structure:

  • models.py - Data models
  • schemas.py - Pydantic schemas
  • service.py - Service layer (thin, delegates to core functions)
  • cli.py - CLI interface

Key Insight: Both patterns separate core functionality from service orchestration. The profiler/ pattern is particularly relevant - it has a thin service layer that delegates to focused core functions.

Why Changes Should Be Made

Problems with Current Structure

  1. Maintainability Issues

    • 1,548 lines in a single file makes it difficult to navigate and understand
    • Multiple concerns mixed together (namespaces, adapters, WPA, DHCP, monitoring)
    • Hard to test individual components in isolation
    • Code reviews are difficult due to file size
  2. Code Duplication

    • NetworkNamespaceService._ns_exec() duplicates functionality from NetworkNamespace.run_command_in_namespace()
    • Namespace creation logic exists in both the model and service
    • Interface management scattered across methods
  3. Poor Separation of Concerns

    • Service class handles both high-level orchestration AND low-level command execution
    • Adapter management mixed with namespace management
    • WPA supplicant config generation mixed with namespace lifecycle
  4. Testing Challenges

    • Difficult to mock individual components
    • Testing requires instantiating entire service with all dependencies
    • Cannot test namespace operations without WPA/DHCP dependencies
  5. Reusability Issues

    • _ns_exec() is a critical utility but buried in the service class
    • Namespace execution logic should be reusable across the codebase
    • Adapter management functions could be useful elsewhere
  6. Inconsistent with Project Patterns

    • Other modules (core/, profiler/) follow focused, modular structure
    • Network namespace code doesn't follow established patterns

Proposed Structure

High-Level Organization

wlanpi_core/
├── namespaces/                    # NEW: Core namespace functionality (like core/ and profiler/)
│   ├── __init__.py
│   ├── namespace.py              # Namespace lifecycle (create, destroy, list, exists)
│   ├── execution.py              # Namespace command execution utilities
│   ├── interfaces.py             # Interface management within namespaces
│   └── processes.py              # Process management in namespaces
│
├── adapters/                      # NEW: Adapter/interface management
│   ├── __init__.py
│   ├── phy.py                    # PHY operations (move, attach, detach)
│   ├── interface.py              # Interface creation, deletion, bring up/down
│   └── discovery.py              # Interface discovery and listing
│
├── services/
│   └── network_namespace_service.py  # REFACTORED: Thin orchestration layer
│
├── utils/
│   ├── namespace_execution.py    # NEW: Common namespace execution utilities
│   └── ... (existing files)
│
└── models/
    └── network/
        └── namespace/
            ├── namespace.py       # EXTENDED: Enhanced NetworkNamespace model
            └── namespace_errors.py

Detailed Breakdown

1. wlanpi_core/namespaces/ - Core Namespace Functionality

Purpose: Core namespace operations, following the pattern of core/ and profiler/ folders. These are pure functions/classes that handle namespace operations without service orchestration.

namespaces/namespace.py (~200 lines)

  • Extend/enhance existing NetworkNamespace model from models/network/namespace/namespace.py
  • Namespace lifecycle: create, destroy, list, exists
  • Namespace validation and state checking
  • Move static methods from current model here, enhance with instance methods

namespaces/execution.py (~150 lines)

  • execute_in_namespace() - Core namespace command execution
  • execute_in_root() - Root namespace execution wrapper
  • Command building and namespace context management
  • Error handling for namespace execution

namespaces/interfaces.py (~200 lines)

  • get_interfaces_in_namespace() - List interfaces in a namespace
  • move_interface_to_namespace() - Move interface between namespaces
  • move_interface_to_root() - Move interface back to root
  • Interface state management (up/down)

namespaces/processes.py (~150 lines)

  • get_processes_in_namespace() - List processes in namespace
  • kill_processes_in_namespace() - Process termination
  • Process verification and cleanup

2. wlanpi_core/adapters/ - Adapter/Interface Management

Purpose: Physical adapter and interface management, separate from namespace concerns.

adapters/phy.py (~200 lines)

  • move_phy_to_namespace() - Move PHY to namespace
  • move_phy_to_root() - Move PHY back to root
  • get_phy_info() - Get PHY information
  • list_phys() - List available PHYs
  • PHY state validation

adapters/interface.py (~250 lines)

  • create_interface() - Create wireless interface from PHY
  • delete_interface() - Delete interface
  • bring_interface_up() / bring_interface_down() - Interface state
  • get_interface_info() - Interface information
  • Interface validation and existence checking

adapters/discovery.py (~100 lines)

  • list_interfaces() - List all system interfaces
  • get_interface_by_name() - Find specific interface
  • Interface filtering and querying

3. wlanpi_core/utils/namespace_execution.py - Common Utilities

Purpose: Reusable namespace execution utilities that can be used across the codebase.

utils/namespace_execution.py (~100 lines)

  • ns_exec() - Main namespace execution function (extracted from service)
  • run_in_namespace() - Convenience wrapper
  • run_in_root() - Root namespace wrapper
  • Command building helpers
  • Error handling utilities

4. wlanpi_core/services/network_namespace_service.py - Refactored Service

Purpose: Thin orchestration layer that coordinates between namespaces, adapters, WPA, and DHCP components.

Refactored Service Structure (~400-500 lines, down from 1,548)

  • Initialization: Config setup, directory management
  • Orchestration Methods:
    • activate_config() - Coordinates namespace + adapter + WPA + DHCP
    • deactivate_config() - Coordinates cleanup
  • Delegation: Delegates to focused modules instead of implementing everything
  • State Management: Connection monitoring, event logging
  • Configuration: WPA config generation, DHCP config (these could move to separate modules if they grow)

Methods to Keep in Service:

  • activate_config() / deactivate_config() - Main orchestration
  • _monitor_connection_async() - Connection monitoring (or move to separate module)
  • _validate_config() - Config validation (or move to utils)
  • set_global_settings() - Service-level settings
  • get_status() - Status aggregation (delegates to WPA/namespace modules)

Methods to Move Out:

  • _prepare_namespace()namespaces/namespace.py
  • _prepare_root()adapters/interface.py + namespaces/namespace.py
  • _ns_exec()utils/namespace_execution.py
  • _run() → Keep as private helper or move to utils/general.py if reusable
  • PHY operations → adapters/phy.py
  • Interface operations → adapters/interface.py
  • WPA config generation → Could stay or move to wpa/ module if it grows

5. Enhanced models/network/namespace/namespace.py

Enhancements:

  • Keep as model/prototype but make it use the new namespaces/ modules
  • Add instance methods that delegate to namespaces/ functions
  • Maintain backward compatibility with existing code
  • Add convenience methods for common operations

File Size Estimates

Module Current Proposed Reduction
network_namespace_service.py 1,548 lines ~400-500 lines ~65% reduction
namespaces/namespace.py - ~200 lines New
namespaces/execution.py - ~150 lines New
namespaces/interfaces.py - ~200 lines New
namespaces/processes.py - ~150 lines New
adapters/phy.py - ~200 lines New
adapters/interface.py - ~250 lines New
adapters/discovery.py - ~100 lines New
utils/namespace_execution.py - ~100 lines New
Production Code Total 1,548 lines ~1,750 lines Better organized

Test Files (New):

Test Module Estimated Lines
tests/test_namespaces/ (4 files) ~800-1,000 lines
tests/test_adapters/ (3 files) ~600-800 lines
tests/test_utils/test_namespace_execution.py ~200-300 lines
tests/test_services/test_network_namespace_service.py ~400-500 lines
Test Code Total ~2,000-2,600 lines

Note: Production code lines may increase slightly due to better separation, imports, and documentation, but maintainability improves significantly. Test code is new and will provide comprehensive coverage for previously untested functionality.

Migration Strategy

  1. Phase 1: Extract Utilities

    • Extract _ns_exec() to utils/namespace_execution.py
    • Write comprehensive unit tests for the new utility module
    • Update service to use new utility
    • Run tests to ensure no regressions
    • Verify existing tests still pass
  2. Phase 2: Extract Namespace Operations

    • Create namespaces/ folder structure
    • Move namespace lifecycle operations
    • Write unit tests for each namespace module as it's created
    • Enhance NetworkNamespace model to use new modules
    • Update service to use new modules
    • Run full test suite to verify integration
  3. Phase 3: Extract Adapter Operations

    • Create adapters/ folder structure
    • Move PHY and interface operations
    • Write unit tests for each adapter module as it's created
    • Update service to use new modules
    • Run full test suite to verify integration
  4. Phase 4: Refactor Service

    • Simplify service to orchestration only
    • Write integration tests for service layer
    • Remove duplicated code
    • Update all callers
    • Run full test suite including integration tests
  5. Phase 5: Testing & Cleanup

    • Complete test coverage verification (aim for 80%+)
    • Run all tests (unit + integration)
    • Update documentation
    • Remove any remaining dead code
    • Verify no test regressions

Test Writing Strategy

Yes, tests will be written! The refactoring provides an excellent opportunity to add comprehensive test coverage for network namespace functionality, which currently has no tests.

Test Structure

Following the project's existing test patterns (pytest, unittest.mock, pytest-mock), tests will be organized as:

tests/
├── __init__.py
├── test_namespaces/              # NEW: Namespace module tests
│   ├── __init__.py
│   ├── test_namespace.py         # Namespace lifecycle tests
│   ├── test_execution.py         # Namespace execution tests
│   ├── test_interfaces.py        # Interface management tests
│   └── test_processes.py         # Process management tests
│
├── test_adapters/                # NEW: Adapter module tests
│   ├── __init__.py
│   ├── test_phy.py               # PHY operation tests
│   ├── test_interface.py         # Interface operation tests
│   └── test_discovery.py         # Interface discovery tests
│
├── test_utils/                   # NEW: Utility tests
│   ├── __init__.py
│   └── test_namespace_execution.py  # Namespace execution utility tests
│
├── test_services/                # NEW: Service tests
│   ├── __init__.py
│   └── test_network_namespace_service.py  # Service orchestration tests
│
└── ... (existing test files)

Test Writing Approach

1. Unit Tests for Core Modules (Write tests as modules are created)

Each new module will have comprehensive unit tests that:

  • Mock all external dependencies (subprocess, file I/O, etc.)
  • Test success paths
  • Test error paths and edge cases
  • Test input validation
  • Follow existing test patterns from test_run_command.py and test_auth.py

Example Test Structure for namespaces/execution.py:

# tests/test_namespaces/test_execution.py
import pytest
from unittest.mock import Mock, patch, MagicMock
from wlanpi_core.models.command_result import CommandResult
from wlanpi_core.models.runcommand_error import RunCommandError
from wlanpi_core.namespaces.execution import execute_in_namespace, execute_in_root

class TestNamespaceExecution:
    @patch('wlanpi_core.namespaces.execution.run_command')
    def test_execute_in_namespace_success(self, mock_run_command):
        """Test successful command execution in namespace"""
        mock_run_command.return_value = CommandResult("output", "", 0)
        
        result = execute_in_namespace("test_ns", ["ls", "-l"])
        
        mock_run_command.assert_called_once_with(
            ["sudo", "ip", "netns", "exec", "test_ns", "ls", "-l"]
        )
        assert result.stdout == "output"
        assert result.return_code == 0
    
    @patch('wlanpi_core.namespaces.execution.run_command')
    def test_execute_in_namespace_failure(self, mock_run_command):
        """Test command execution failure in namespace"""
        mock_run_command.side_effect = RunCommandError("Command failed", 1)
        
        with pytest.raises(RunCommandError) as exc_info:
            execute_in_namespace("test_ns", ["invalid", "command"])
        
        assert exc_info.value.return_code == 1
    
    @patch('wlanpi_core.namespaces.execution.run_command')
    def test_execute_in_root(self, mock_run_command):
        """Test command execution in root namespace"""
        mock_run_command.return_value = CommandResult("output", "", 0)
        
        result = execute_in_root(["ls", "-l"])
        
        mock_run_command.assert_called_once_with(
            ["sudo", "ls", "-l"]
        )
        assert result.stdout == "output"

2. Unit Tests for Adapter Modules

Tests will mock iw and ip commands to test PHY and interface operations:

# tests/test_adapters/test_phy.py
@patch('wlanpi_core.adapters.phy.run_command')
def test_move_phy_to_namespace_success(mock_run_command):
    """Test moving PHY to namespace"""
    mock_run_command.return_value = CommandResult("", "", 0)
    
    result = move_phy_to_namespace("phy0", "test_ns")
    
    mock_run_command.assert_called_once_with(
        ["sudo", "iw", "phy", "phy0", "set", "netns", "name", "test_ns"]
    )
    assert result is True

3. Integration Tests for Service Layer

The refactored service will be tested with mocked dependencies:

# tests/test_services/test_network_namespace_service.py
@pytest.fixture
def mock_namespace_ops(mocker):
    """Mock all namespace operations"""
    mocker.patch('wlanpi_core.namespaces.namespace.create_namespace')
    mocker.patch('wlanpi_core.adapters.phy.move_phy_to_namespace')
    mocker.patch('wlanpi_core.adapters.interface.create_interface')
    # ... etc

def test_activate_config_namespace(mock_namespace_ops):
    """Test activating a namespace configuration"""
    service = NetworkNamespaceService()
    config = NamespaceConfig(...)
    
    result = service.activate_config(config)
    
    assert result.status == "provisioned"
    # Verify all operations were called correctly

4. Test Coverage Goals

  • Unit Tests: 80%+ coverage for all new modules
  • Integration Tests: Full coverage of service orchestration paths
  • Error Handling: All error paths tested
  • Edge Cases: Boundary conditions and error scenarios

5. Mocking Strategy

Following existing patterns:

  • Mock run_command and run_command_async using unittest.mock.patch
  • Mock file I/O operations (config file reading/writing)
  • Mock subprocess calls for iw, ip, wpa_supplicant, etc.
  • Use pytest.fixture for common mock setups
  • Use pytest.mark.asyncio for async tests

6. Test Execution During Migration

Tests will be written incrementally as each phase is completed:

  • Phase 1: Write tests for utils/namespace_execution.py as it's extracted
  • Phase 2: Write tests for namespaces/ modules as they're created
  • Phase 3: Write tests for adapters/ modules as they're created
  • Phase 4: Write integration tests for refactored service
  • Phase 5: Full test suite execution, coverage verification

7. Test Utilities and Fixtures

Create shared test utilities:

# tests/conftest.py (or test_helpers.py)
@pytest.fixture
def mock_command_result():
    """Fixture for creating mock CommandResult objects"""
    def _create(stdout="", stderr="", return_code=0):
        return CommandResult(stdout, stderr, return_code)
    return _create

@pytest.fixture
def sample_namespace_config():
    """Fixture for creating sample namespace configs"""
    return NamespaceConfig(
        namespace="test_ns",
        interface="wlan0",
        phy="phy0",
        # ... etc
    )

8. Continuous Testing

  • Run tests after each phase completion
  • Ensure all existing tests still pass
  • Add new tests before refactoring each module
  • Use pytest-cov to track coverage improvements

Test Writing Checklist

For each new module:

  • Create test file in appropriate tests/ subdirectory
  • Write tests for all public functions/methods
  • Test success paths
  • Test error paths (RunCommandError, validation errors, etc.)
  • Test edge cases (None values, empty strings, invalid inputs)
  • Mock all external dependencies
  • Use pytest fixtures for common test data
  • Follow existing test naming conventions (test_<function_name>_<scenario>)
  • Add docstrings to test functions explaining what they test
  • Ensure tests are isolated (no shared state)
  • Run tests locally before committing
  • Verify coverage meets goals (80%+)

Benefits of Test-First Refactoring

  1. Confidence: Tests ensure refactoring doesn't break functionality
  2. Documentation: Tests serve as usage examples
  3. Regression Prevention: Catch bugs introduced during refactoring
  4. Future Safety: Tests protect against future regressions
  5. Code Quality: Writing tests forces better API design

Benefits of Proposed Structure

  1. Maintainability

    • Each module has a single, clear responsibility
    • Easier to locate and fix bugs
    • Easier code reviews
  2. Testability

    • Each module can be tested independently
    • Easier to mock dependencies
    • More focused unit tests
  3. Reusability

    • Namespace execution utilities available across codebase
    • Adapter management can be used by other services
    • Core namespace functions follow established patterns
  4. Consistency

    • Follows patterns established by core/ and profiler/ folders
    • Clear separation between models, core functions, and services
  5. Extensibility

    • Easy to add new namespace operations
    • Easy to add new adapter types
    • Clear extension points

Potential Concerns & Mitigations

Concern: Breaking changes to existing API

  • Mitigation: Keep service interface unchanged, only refactor internals

Concern: Import path changes

  • Mitigation: Use __init__.py files to maintain backward compatibility where possible

Concern: Testing during migration

  • Mitigation: Phased approach with testing at each phase

Concern: Increased complexity from more files

  • Mitigation: Better organization actually reduces cognitive load; each file is focused and easier to understand

Concern: Test maintenance overhead

  • Mitigation: Tests are an investment that pays dividends in reduced bugs, faster debugging, and safer refactoring. The test structure mirrors production code structure, making it easy to locate and update tests. Following existing test patterns ensures consistency and maintainability.

Recommendation

Proceed with refactoring following the proposed structure. The benefits significantly outweigh the costs:

  • Immediate: Better code organization, easier maintenance, comprehensive test coverage
  • Short-term: Improved testability, reduced bugs, safer refactoring
  • Long-term: Easier to extend, better alignment with project patterns, regression prevention

The phased migration strategy with incremental test writing minimizes risk and allows for incremental improvements. The addition of comprehensive tests ensures the refactoring is safe and provides long-term value by preventing regressions.

Key Success Factors:

  1. Write tests as each module is created (test-driven refactoring)
  2. Maintain existing test suite passing throughout migration
  3. Achieve 80%+ test coverage for new modules
  4. Follow existing test patterns for consistency