├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ ├── config.yml │ └── feature_request.yaml └── workflows │ └── ci.yml ├── .gitignore ├── COMMANDS.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── contributing │ ├── coding_style.md │ ├── please_read_first.md │ └── tests.md ├── daemon Common variables.MD ├── img │ └── logo.png ├── index.md ├── media │ ├── subnet_initalization.png │ └── vscode_autocomplete.png └── stylesheets │ └── extra.css ├── mkdocs.yml ├── poetry.lock ├── pyisckea ├── __init__.py ├── daemons │ ├── __init__.py │ ├── ctrlagent.py │ ├── ddns.py │ ├── dhcp4.py │ └── dhcp6.py ├── exceptions.py ├── kea.py ├── models │ ├── __init__.py │ ├── ctrlagent │ │ ├── __init__.py │ │ └── config.py │ ├── dhcp4 │ │ ├── __init__.py │ │ ├── client_class.py │ │ ├── config.py │ │ ├── lease.py │ │ ├── reservation.py │ │ ├── shared_network.py │ │ └── subnet.py │ ├── dhcp6 │ │ ├── __init__.py │ │ ├── client_class.py │ │ ├── config.py │ │ ├── lease.py │ │ ├── pd_pool.py │ │ ├── reservation.py │ │ ├── server_id.py │ │ ├── shared_network.py │ │ └── subnet.py │ ├── enums.py │ └── generic │ │ ├── __init__.py │ │ ├── api_response.py │ │ ├── authentication.py │ │ ├── base.py │ │ ├── client_class.py │ │ ├── config.py │ │ ├── control_socket.py │ │ ├── daemon.py │ │ ├── database.py │ │ ├── dhcp_common.py │ │ ├── dhcp_ddns.py │ │ ├── dhcp_queue_control.py │ │ ├── high_availability.py │ │ ├── hook.py │ │ ├── lease.py │ │ ├── logger.py │ │ ├── multi_threading.py │ │ ├── option_data.py │ │ ├── option_def.py │ │ ├── pool.py │ │ ├── relay.py │ │ ├── remote_map.py │ │ ├── remote_server.py │ │ ├── reservation.py │ │ ├── sanity_check.py │ │ ├── shared_network.py │ │ ├── sockets.py │ │ ├── status.py │ │ └── subnet.py └── parsers │ ├── __init__.py │ ├── ctrlagent.py │ ├── ddns.py │ ├── dhcp4.py │ ├── dhcp6.py │ ├── exceptions.py │ └── generic.py ├── pyproject.toml ├── requirements.txt └── tests ├── ci ├── models │ ├── test_ci_kea_ctrlagent_config_model.py │ ├── test_ci_kea_dhcp4_config_model.py │ └── test_ci_kea_dhcp6_config_model.py └── parsers │ ├── test_ci_kea_dhcp4_parser.py │ └── test_ci_kea_dhcp6_parser.py ├── configs ├── ctrlagent_api_config.json ├── ctrlagent_parsed_config.json ├── dhcp4_api_config.json ├── dhcp4_parsed_config.json └── dhcp6_api_config.json ├── conftest.py ├── ctrlagent ├── test_kea_ctrlagent.py └── test_kea_ctrlagent_parser.py ├── dhcp4 ├── remote │ ├── test_kea_dhcp4_remote.py │ ├── test_kea_dhcp4_remote_class4.py │ ├── test_kea_dhcp4_remote_global_parameter4.py │ ├── test_kea_dhcp4_remote_network4.py │ ├── test_kea_dhcp4_remote_option4_global.py │ ├── test_kea_dhcp4_remote_option4_network.py │ ├── test_kea_dhcp4_remote_option4_pool.py │ ├── test_kea_dhcp4_remote_option4_subnet.py │ ├── test_kea_dhcp4_remote_option_def4.py │ ├── test_kea_dhcp4_remote_reservation.py │ └── test_kea_dhcp4_remote_subnet4.py ├── test_kea_dhcp4.py ├── test_kea_dhcp4_cache.py ├── test_kea_dhcp4_class.py ├── test_kea_dhcp4_ha.py ├── test_kea_dhcp4_lease4.py ├── test_kea_dhcp4_network4.py ├── test_kea_dhcp4_parser.py ├── test_kea_dhcp4_statistics.py └── test_kea_dhcp4_subnet4.py ├── dhcp6 ├── remote │ ├── test_kea_dhcp6_remote.py │ ├── test_kea_dhcp6_remote_class6.py │ ├── test_kea_dhcp6_remote_global_parameter6.py │ ├── test_kea_dhcp6_remote_network6.py │ ├── test_kea_dhcp6_remote_option6_global.py │ ├── test_kea_dhcp6_remote_option6_network.py │ ├── test_kea_dhcp6_remote_option6_pd_pool.py │ ├── test_kea_dhcp6_remote_option6_pool.py │ ├── test_kea_dhcp6_remote_option6_subnet.py │ ├── test_kea_dhcp6_remote_option_def6.py │ ├── test_kea_dhcp6_remote_reservation.py │ └── test_kea_dhcp6_remote_subnet6.py ├── test_kea_dhcp6.py ├── test_kea_dhcp6_cache.py ├── test_kea_dhcp6_class.py ├── test_kea_dhcp6_ha.py ├── test_kea_dhcp6_lease6.py ├── test_kea_dhcp6_network6.py ├── test_kea_dhcp6_parser.py ├── test_kea_dhcp6_statistics.py └── test_kea_dhcp6_subnet6.py └── test_infrastructure ├── Dockerfiles ├── Dockerfile └── Dockerfile.perfdhcp ├── configs ├── ha │ ├── primary │ │ ├── kea-ctrl-agent.conf │ │ ├── kea-dhcp4-sql.conf │ │ └── kea-dhcp6-sql.conf │ └── standby │ │ ├── kea-ctrl-agent.conf │ │ ├── kea-dhcp4-sql.conf │ │ └── kea-dhcp6-sql.conf ├── kea-ctrl-agent.conf ├── kea-ddns.conf ├── kea-dhcp4-sql.conf ├── kea-dhcp4.conf ├── kea-dhcp6-sql.conf ├── kea-dhcp6.conf ├── mysqld.cnf └── supervisor │ ├── conf.d │ ├── kea-agent.conf │ ├── kea-dhcp4.conf │ └── kea-dhcp6.conf │ └── supervisord.conf ├── db-init ├── 01-create-schema.sql ├── 02-create-users.sql └── dhcpdb_create.mysql.license ├── docker-compose-ha-sql.yml ├── docker-compose-sql.yml └── docker-compose.yml /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | description: Report a bug that is currently in the latest pyisckea release 4 | labels: ["type: bug"] 5 | body: 6 | - type: dropdown 7 | attributes: 8 | label: Python version 9 | description: What version of Python are you currently running? 10 | options: 11 | - "3.9" 12 | - "3.10" 13 | - "3.11" 14 | validations: 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: How to reproduce this locally 19 | description: > 20 | Please note here the exact steps you take so that other people can reproduce the same bug and 21 | attempt to fix the issue. Ensure you are using the latest release of pyisckea and that you are 22 | not using any other modules that enhance pyisckea itself. 23 | placeholder: | 24 | 1. Setup Kea class 25 | 2. call dhcp4.subnet4_list() 26 | 3. Attempt to access .something variable from the returned subnets 27 | validations: 28 | required: true 29 | - type: textarea 30 | attributes: 31 | label: What should happen? 32 | description: Explain what you think should have happened 33 | placeholder: I should be able to access the data from `.something` when using the Subnet4 Pydantic Model 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: What happened? 39 | description: Explain what happened instead 40 | placeholder: The .something variable had no data in it 41 | validations: 42 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | description: Suggest a new feature or enhancement for pyisckea 4 | labels: ["type: feature"] 5 | body: 6 | - type: dropdown 7 | attributes: 8 | label: Daemon/Area 9 | options: 10 | - ctrlagent 11 | - dhcp4 12 | - dhcp6 13 | - ddns 14 | - Pydantic Models 15 | - All Daemons 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Feature/Enhancement you are proposing 21 | description: > 22 | Describe in detail what feature or enhancement you would like to add to 23 | pyisckea. 24 | validations: 25 | required: true 26 | - type: textarea 27 | attributes: 28 | label: Reason for Feature/Enhancement 29 | description: > 30 | Describe the purpose of this change and what it solves for you or the community. 31 | validations: 32 | required: true -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI 2 | on: 3 | release: 4 | types: [created] 5 | 6 | jobs: 7 | lint: 8 | name: Lint and Check Format 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: astral-sh/ruff-action@v3 14 | with: 15 | src: "./pyisckea" 16 | - uses: astral-sh/ruff-action@v3 17 | with: 18 | args: "format --check --diff" 19 | src: "./pyisckea" 20 | 21 | #test: 22 | # name: Run tests 23 | # runs-on: ubuntu-latest 24 | # 25 | # steps: 26 | # - uses: actions/checkout@v4 27 | # with: 28 | # persist-credentials: false 29 | # - name: Set up Python 30 | # uses: actions/setup-python@v5 31 | # with: 32 | # python-version: "3.12" 33 | # - name: Ruff Check 34 | # uses: jpetrucciani/ruff-check@main 35 | # with: 36 | # path: 'cgn_ec/' 37 | # - name: Test with pytest 38 | # run: | 39 | # pip install pytest pytest-cov 40 | # pytest tests/ --doctest-modules --junitxml=junit/test-results.xml --cov=com --cov- 41 | 42 | build: 43 | name: Build distribution 📦 44 | runs-on: ubuntu-latest 45 | 46 | steps: 47 | - uses: actions/checkout@v4 48 | with: 49 | persist-credentials: false 50 | - name: Set up Python 51 | uses: actions/setup-python@v5 52 | with: 53 | python-version: "3.12" 54 | - name: Install pypa/build 55 | run: >- 56 | python3 -m 57 | pip install 58 | build 59 | --user 60 | - name: Build a binary wheel and a source tarball 61 | run: python3 -m build 62 | - name: Store the distribution packages 63 | uses: actions/upload-artifact@v4 64 | with: 65 | name: python-package-distributions 66 | path: dist/ 67 | 68 | publish-to-pypi: 69 | name: >- 70 | Publish Python 🐍 distribution 📦 to PyPI 71 | if: startsWith(github.ref, 'refs/tags/') && github.event.release.prerelease == false 72 | needs: 73 | - build 74 | runs-on: ubuntu-latest 75 | environment: 76 | name: pypi 77 | url: https://pypi.org/p/cgn-ec 78 | permissions: 79 | id-token: write 80 | 81 | steps: 82 | - name: Download all the dists 83 | uses: actions/download-artifact@v4 84 | with: 85 | name: python-package-distributions 86 | path: dist/ 87 | - name: Publish distribution 📦 to PyPI 88 | uses: pypa/gh-action-pypi-publish@release/v1 89 | 90 | publish-to-testpypi: 91 | name: Publish Python 🐍 distribution 📦 to TestPyPI 92 | if: startsWith(github.ref, 'refs/tags/') && github.event.release.prerelease == true 93 | needs: 94 | - build 95 | runs-on: ubuntu-latest 96 | 97 | environment: 98 | name: testpypi 99 | url: https://test.pypi.org/p/cgn-ec 100 | 101 | permissions: 102 | id-token: write 103 | 104 | steps: 105 | - name: Download all the dists 106 | uses: actions/download-artifact@v4 107 | with: 108 | name: python-package-distributions 109 | path: dist/ 110 | - name: Publish distribution 📦 to TestPyPI 111 | uses: pypa/gh-action-pypi-publish@release/v1 112 | with: 113 | repository-url: https://test.pypi.org/legacy/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # venv 2 | .venv* 3 | 4 | # JetBrains 5 | .idea/ 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | test.py 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | pip-wheel-metadata/ 31 | share/python-wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | MANIFEST 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .nox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | *.py,cover 58 | .hypothesis/ 59 | .pytest_cache/ 60 | 61 | # Translations 62 | *.mo 63 | *.pot 64 | 65 | # Django stuff: 66 | *.log 67 | local_settings.py 68 | db.sqlite3 69 | db.sqlite3-journal 70 | 71 | # Flask stuff: 72 | instance/ 73 | .webassets-cache 74 | 75 | # Scrapy stuff: 76 | .scrapy 77 | 78 | # Sphinx documentation 79 | docs/_build/ 80 | 81 | # PyBuilder 82 | target/ 83 | 84 | # Jupyter Notebook 85 | .ipynb_checkpoints 86 | 87 | # IPython 88 | profile_default/ 89 | ipython_config.py 90 | 91 | # pyenv 92 | .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 102 | __pypackages__/ 103 | 104 | # Celery stuff 105 | celerybeat-schedule 106 | celerybeat.pid 107 | 108 | # SageMath parsed files 109 | *.sage.py 110 | 111 | # Environments 112 | .env 113 | .venv 114 | env/ 115 | venv/ 116 | ENV/ 117 | env.bak/ 118 | venv.bak/ 119 | 120 | # Spyder project settings 121 | .spyderproject 122 | .spyproject 123 | 124 | # Rope project settings 125 | .ropeproject 126 | 127 | # mkdocs documentation 128 | /site 129 | 130 | # mypy 131 | .mypy_cache/ 132 | .dmypy.json 133 | dmypy.json 134 | 135 | # Pyre type checker 136 | .pyre/ 137 | -------------------------------------------------------------------------------- /docs/contributing/coding_style.md: -------------------------------------------------------------------------------- 1 | # Main Guidelines 2 | - Use python type hinting in every function and class, including return types. 3 | - Always use [docstrings (google style)](https://google.github.io/styleguide/pyguide.html#381-docstrings) for Classes and Functions/Methods. 4 | - Always prefer double-quotes rather than single quotes. 5 | - Adhere as close as possible to google style docstrings, auto-generated documentation is dependent on it and a merge request will not make it into dev if they are missing. 6 | - Always indent with [4 spaces](https://peps.python.org/pep-0008/#tabs-or-spaces), no more, no less. 7 | - [Naming of classes](https://peps.python.org/pep-0008/#prescriptive-naming-conventions) should always be CamelCase and functions should be snake_case. 8 | - Never raise exceptions from the base Exception, write custom ones if required. 9 | 10 | # Other Guidelines 11 | - Minimum version of Python to use during development and testing must be 3.11. 12 | - Always use [f-strings](https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals) when possible as they are far more superior than % and .format() in most cases. 13 | - Always write [tests (pytest)](https://docs.pytest.org/en/stable/) for any new functions/API calls implemented or changed (if the logic has changed). 14 | - [List comprehensions](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) should always be used where possible unless the code is too ugly and readable, that is for you and the reviewer to agree with :-) 15 | - [Ruff](https://github.com/astral-sh/ruff) for formatting/linting. -------------------------------------------------------------------------------- /docs/contributing/please_read_first.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this project, please first discuss the change you wish to make via GitHub discussions or via an Issue thread. 4 | 5 | We don't currently have a dedicated code of conduct however, it is expected that you treat everyone with respect in the community. 6 | 7 | Any feedback for the contributing workflows whether it be the branch structure, coding style, tests or general feedback of the implementation is welcome and should be brought up either as a discussion/issue or informally over a chat like Slack. Do not use emails/GitHub Issues for informal discussions. 8 | 9 | ## Dev Setup 10 | 11 | `tests/test_infrastructure` contains various docker-compose configurations for different scenarios such as Database, HA, Database+HA, etc... You can run local tests on these servers to ensure everything is running as expected when you make a PR. -------------------------------------------------------------------------------- /docs/contributing/tests.md: -------------------------------------------------------------------------------- 1 | pytest is the testing framework of choice in this project. If you are not very familiar with the basics of pytest, I would recommend reading the documentation or having a look at the tests in this project to see the structure and provided functionality. 2 | 3 | Each API endpoint or class/function that interacts with a resource which changes potential production data must implement a test and pass before a merge request is accepted. 4 | 5 | ## Test Structure 6 | 7 | All tests belong in the tests directory of the project. The structure of this folder may change from time to time but the basis is that for every API endpoint, you try to group them cleanly into their respective files according to the API endpoint path, prefixed with test_. Here are some examples: 8 | 9 | 10 | TO DO. -------------------------------------------------------------------------------- /docs/daemon Common variables.MD: -------------------------------------------------------------------------------- 1 | ## All Daemons Common Global Params 2 | 3 | hooks_libraries 4 | loggers 5 | user_context 6 | comment 7 | unknown_map_entry 8 | 9 | ## Dhcp4 and Dhcp6 Common Global Params 10 | 11 | interfaces_config 12 | lease_database 13 | hosts_database 14 | hosts_databases 15 | host_reservation_identifiers 16 | client_classes 17 | option_def 18 | expired_leases_processing 19 | dhcp4o6_port 20 | control_socket 21 | dhcp_queue_control 22 | dhcp_ddns 23 | sanity_checks 24 | config_control 25 | server_tag 26 | hostname_char_set 27 | hostname_char_replacement 28 | statistic_default_sample_count 29 | statistic_default_sample_age 30 | dhcp_multi_threading 31 | early_global_reservations_lookup 32 | ip_reservations_unique 33 | reservations_lookup_first 34 | compatibility 35 | parked_packet_limit 36 | decline_probation_period 37 | 38 | ## Dhcp4 Specific Global Params 39 | shared_networks 40 | reservations 41 | subnet4_list 42 | echo_client_id 43 | match_client_id 44 | authoritative 45 | next_server 46 | server_hostname 47 | boot_file_name 48 | 49 | ## Dhcp6 Specific Global Params 50 | shared_networks 51 | reservations 52 | data_directory 53 | preferred_lifetime 54 | min_preferred_lifetime 55 | max_preferred_lifetime 56 | subnet6_list 57 | mac_sources 58 | relay_supplied_options 59 | server_id 60 | 61 | ## CtrlAgent Specific Global Params 62 | http_host 63 | http_port 64 | trust_anchor 65 | cert_file 66 | key_file 67 | cert_required 68 | authentication 69 | control_sockets 70 | 71 | ## DDNS Specific Global Params 72 | ip_address 73 | port 74 | dns_server_timeout 75 | ncr_protocol 76 | ncr_format 77 | forward_ddns 78 | reverse_ddns 79 | tsig_keys 80 | control_socket 81 | 82 | ## Dhcp4 and Dhcp6 Daemon Global Config, Shared Networks and Subnets common variables 83 | 84 | valid_lifetime 85 | min_valid_lifetime 86 | max_valid_lifetime 87 | renew_timer 88 | rebind_timer 89 | option_data 90 | reservation_mode 91 | reservations_global 92 | reservations_in_subnet 93 | reservations_out_of_pool 94 | calculate_tee_times 95 | t1_percent 96 | t2_percent 97 | cache_threshold 98 | cache_max_age 99 | ddns_send_updates 100 | ddns_override_no_update 101 | ddns_override_client_update 102 | ddns_replace_client_name 103 | ddns_generated_prefix 104 | ddns_qualifying_suffix 105 | ddns_update_on_renew 106 | ddns_use_conflict_resolution 107 | store_extended_info 108 | user_context 109 | comment 110 | unknown_map_entry -------------------------------------------------------------------------------- /docs/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veesix-networks/pyisckea/c701b965556c5e52a98d53e406d754728d993126/docs/img/logo.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # 2 |

3 | Logo 4 |

5 | 6 | pyisckea is a python module used to interact with the ISC Kea DHCP daemons running on an ISC Kea server. This module also implements Pydantic to improve the developer experience when working in a code editor like VSCode and also provide super fast data validation functionality on python objects before they get serialized and sent to the Kea APIs. 7 | 8 | ## Get Started 9 | 10 | 1) Install the module. 11 | 12 | ``` 13 | pip install pyisckea 14 | ``` 15 | 16 | 2) Import the Kea class. 17 | 18 | ```python 19 | from pyisckea import Kea 20 | 21 | server = Kea("http://localhost:8000") 22 | ``` 23 | 24 | 3) Call API commands based on the Daemon to interact with the APIs. 25 | 26 | ```python 27 | subnets_v4 = server.dhcp4.subnet4_list() 28 | 29 | for subnet in subnets_v4: 30 | print(subnet.subnet, subnet.option_data, subnet.relay, subnet.pools_list) 31 | 32 | my_subnet = server.dhcp6.subnet4_get(name="pyisckea-pytest") 33 | print(my_subnet.json(exclude_none=True, indent=4)) 34 | 35 | # { 36 | # "valid_lifetime": 4000, 37 | # "renew_timer": 1000, 38 | # "rebind_timer": 2000, 39 | # "option_data": [], 40 | # "calculate_tee_times": true, 41 | # "t1_percent": 0.5, 42 | # "t2_percent": 0.8, 43 | # "store_extended_info": false, 44 | # "name": "pyisckea-pytest", 45 | # "relay": { 46 | # "ip-addresses": [] 47 | # }, 48 | # "subnet6": [ 49 | # { 50 | # "valid_lifetime": 4000, 51 | # "renew_timer": 1000, 52 | # "rebind_timer": 2000, 53 | # "option_data": [], 54 | # "calculate_tee_times": true, 55 | # "t1_percent": 0.5, 56 | # "t2_percent": 0.8, 57 | # "store_extended_info": false, 58 | # "id": 40123, 59 | # "subnet": "2001:db8::/64", 60 | # "preferred_lifetime": 3600, 61 | # "pd_pools": [], 62 | # "rapid_commit": false 63 | # } 64 | # ], 65 | # "rapid_commit": false 66 | # } 67 | ``` 68 | 69 | 4) Utilize the Pydantic models which provide basic data validation. 70 | 71 | ```python 72 | from pyisckea.models.dhcp4.subnet import Subnet4 73 | 74 | my_subnet = Subnet4( 75 | id=1234, subnet="192.0.2.32/31", option_data=[{"code": 3, "data": "192.0.2.32"}] 76 | ) 77 | 78 | create_subnet = server.dhcp4.subnet4_add(subnets=[my_subnet]) 79 | print(create_subnet.result, create_subnet.text) 80 | 81 | # Note because subnet_cmds hook library is not loaded, we run into an exception here: 82 | # pyisckea.exceptions.KeaHookLibraryNotConfiguredException: Hook library 'subnet_cmds' is not configured for 'dhcp4' service. Please ensure this is enabled in the configuration for the 'dhcp4' daemon 83 | ``` 84 | 85 | ## Basic Authentication 86 | 87 | If you have basic authentication enabled on your Kea Servers, import the `BasicAuth` class from the `httpx` library and pass it into the `Kea` object like this: 88 | 89 | ```python 90 | from pyisckea.kea import Kea 91 | from httpx import BasicAuth 92 | 93 | auth = BasicAuth("kea", "secret123") 94 | 95 | 96 | api = Kea("http://localhost:8000", auth=auth) 97 | ``` 98 | 99 | ## TLS 100 | 101 | Create a context using the `ssl` library and then pass this context into a httpx `Client` object to be used with Kea like this: 102 | 103 | ```python 104 | from httpx import Client 105 | from pyisckea.kea import Kea 106 | 107 | ctx = ssl.create_default_context(cafile="/path/to/the/ca-cert.pem") 108 | ctx.load_cert_chain( 109 | certfile="/path/to/the/agent-cert.pem", keyfile="/path/to/the/agent-key.pem" 110 | ) 111 | 112 | client = Client(verify=ctx) 113 | api = Kea("http://localhost:8000", client=client) 114 | ``` 115 | 116 | ## API Reference 117 | 118 | All supported commands by the daemons are in the format of the API referenced commands with the exception of replacing any hyphen or space with an underscore. Eg. the build-report API command for all daemons is implemented as build_report so it heavily ties into the Kea predefined commands when looking at their documentation. Currently everything is built towards Kea 2.2.0. Pydantic variables will replace any hyphens with an underscore however when loading/exporting the data models, it will replace all keys with the hyphen to adhere to the Kea expected variables, ensure that the KeaBaseModel (located in from pyisckea.models.generic.base import KeaBaseModel instead of from pydantic import BaseModel) is used when creating any Pydantic models to inherit this functionality. -------------------------------------------------------------------------------- /docs/media/subnet_initalization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veesix-networks/pyisckea/c701b965556c5e52a98d53e406d754728d993126/docs/media/subnet_initalization.png -------------------------------------------------------------------------------- /docs/media/vscode_autocomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veesix-networks/pyisckea/c701b965556c5e52a98d53e406d754728d993126/docs/media/vscode_autocomplete.png -------------------------------------------------------------------------------- /docs/stylesheets/extra.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --md-primary-fg-color: #4c9c2d; 3 | --md-primary-fg-color--light: #4c9c2d; 4 | --md-primary-fg-color--dark: #4c9c2d; 5 | } -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: veesix ::networks - pyisckea 2 | site_url: https://docs.pyisckea.veesix-networks.co.uk 3 | repo_url: https://github.com/veesix-networks/pyisckea 4 | repo_name: veesix-networks/pyisckea 5 | logo: docs/img/logo.png 6 | theme: 7 | name: material 8 | icon: 9 | repo: fontawesome/brands/github 10 | palette: 11 | # Palette toggle for automatic mode 12 | - media: "(prefers-color-scheme)" 13 | toggle: 14 | icon: material/brightness-auto 15 | name: Switch to light mode 16 | primary: custom 17 | # Palette toggle for light mode 18 | - media: "(prefers-color-scheme: light)" 19 | scheme: default 20 | toggle: 21 | icon: material/brightness-7 22 | name: Switch to dark mode 23 | primary: custom 24 | # Palette toggle for dark mode 25 | - media: "(prefers-color-scheme: dark)" 26 | scheme: slate 27 | toggle: 28 | icon: material/brightness-4 29 | name: Switch to system preference 30 | primary: custom 31 | features: 32 | - navigation.instant 33 | - navigation.tabs 34 | - navigation.sections 35 | - content.code.copy 36 | extra_css: 37 | - stylesheets/extra.css 38 | nav: 39 | - Get Started: 40 | - Intro: index.md 41 | - Contributing: 42 | - PLEASE READ FIRST: contributing/please_read_first.md 43 | - Coding Style: contributing/coding_style.md 44 | - Tests: contributing/tests.md 45 | - Author: https://github.com/veesix-networks 46 | markdown_extensions: 47 | - tables 48 | - admonition 49 | - def_list 50 | - attr_list 51 | - pymdownx.emoji: 52 | emoji_index: !!python/name:material.extensions.emoji.twemoji 53 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 54 | - pymdownx.tasklist: 55 | custom_checkbox: true 56 | - pymdownx.highlight: 57 | anchor_linenums: true 58 | line_spans: __span 59 | pygments_lang_class: true 60 | - pymdownx.inlinehilite 61 | - pymdownx.snippets 62 | - pymdownx.superfences -------------------------------------------------------------------------------- /pyisckea/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["Kea"] 2 | 3 | from pyisckea.kea import Kea 4 | -------------------------------------------------------------------------------- /pyisckea/daemons/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["CtrlAgent", "Ddns", "Dhcp4", "Dhcp6"] 2 | 3 | from pyisckea.daemons.ctrlagent import CtrlAgent 4 | from pyisckea.daemons.ddns import Ddns 5 | from pyisckea.daemons.dhcp4 import Dhcp4 6 | from pyisckea.daemons.dhcp6 import Dhcp6 7 | -------------------------------------------------------------------------------- /pyisckea/exceptions.py: -------------------------------------------------------------------------------- 1 | class KeaException(Exception): 2 | def __init__(self, message: str = "Kea Exception encountered"): 3 | self.message = message 4 | super().__init__(self.message) 5 | 6 | 7 | class KeaGenericException(KeaException): 8 | # Result code 1 9 | def __init__( 10 | self, 11 | message: str = "Code 1: a general error or failure has occurred during the command processing.", 12 | ): 13 | self.message = message 14 | super().__init__(self.message) 15 | 16 | 17 | class KeaCommandNotSupportedException(KeaException): 18 | # Result code 2 19 | def __init__( 20 | self, 21 | message: str = "Code 2: the specified command is unsupported by the server receiving it.", 22 | ): 23 | self.message = message 24 | super().__init__(self.message) 25 | 26 | 27 | class KeaObjectNotFoundException(KeaException): 28 | # Result code 3 29 | def __init__( 30 | self, 31 | message: str = "Code 3: the requested operation has been completed but the requested resource was not found. This status code is returned when a command returns no resources or affects no resources.", 32 | ): 33 | self.message = message 34 | super().__init__(self.message) 35 | 36 | 37 | class KeaServerConflictException(KeaException): 38 | # Result code 4 39 | def __init__( 40 | self, 41 | message: str = "Code 4: the well-formed command has been processed but the requested changes could not be applied, because they were in conflict with the server state or its notion of the configuration.", 42 | ): 43 | self.message = message 44 | super().__init__(self.message) 45 | 46 | 47 | class KeaUnauthorizedAccessException(KeaException): 48 | def __init__( 49 | self, 50 | message: str = "Received 401 (Unauthorized response), please ensure username and password is configured, and use_basic_auth is set to True for the Kea object", 51 | ): 52 | self.message = message 53 | super().__init__(self.message) 54 | 55 | 56 | class KeaHookLibraryNotConfiguredException(KeaException): 57 | def __init__(self, service: str, hook: str): 58 | self.message = f"Hook library '{hook}' is not configured for '{service}' service. Please ensure this is enabled in the configuration for the '{service}' daemon" 59 | super().__init__(self.message) 60 | 61 | 62 | class KeaSharedNetworkNotFoundException(KeaException): 63 | def __init__(self, name: str): 64 | self.message = f"Shared Network '{name}' not found" 65 | super().__init__(self.message) 66 | 67 | 68 | class KeaSubnetNotFoundException(KeaException): 69 | def __init__(self, subnet_id: int): 70 | self.message = f"Subnet '{subnet_id}' not found" 71 | super().__init__(self.message) 72 | 73 | 74 | class KeaLeaseNotFoundException(KeaException): 75 | def __init__(self, lease: str): 76 | self.message = f"Lease '{lease}' not found" 77 | super().__init__(self.message) 78 | 79 | 80 | class KeaInvalidRemoteMapException(KeaException): 81 | def __init__(self, detailed_error: str): 82 | self.message = f"Remote map for cm_cmd remote API call is not correctly formatted. Detailed Error: {detailed_error}" 83 | super().__init__(self.message) 84 | 85 | 86 | class KeaRemoteServerNotFoundException(KeaException): 87 | def __init__(self, server_tag: str): 88 | self.message = f"Server with server_tag '{server_tag}' not found" 89 | super().__init__(self.message) 90 | 91 | 92 | class KeaConfigBackendNotConfiguredException(KeaException): 93 | def __init__(self): 94 | self.message = "Kea API reports that there is no configuration backends" 95 | super().__init__(self.message) 96 | 97 | 98 | class KeaUnknownHostReservationTypeException(KeaException): 99 | def __init__(self, reservation_type: str): 100 | self.message = ( 101 | f"Reservation type '{reservation_type}' is not a valid reservation type" 102 | ) 103 | super().__init__(self.message) 104 | 105 | 106 | class KeaReservationNotFoundException(KeaException): 107 | def __init__(self, reservation_data: str): 108 | self.message = f"Reservation '{reservation_data}' not found" 109 | super().__init__(self.message) 110 | 111 | 112 | class KeaClientClassNotFoundException(KeaException): 113 | def __init__(self, client_class: str): 114 | self.message = f"Client Class '{client_class}' not found" 115 | super().__init__(self.message) 116 | -------------------------------------------------------------------------------- /pyisckea/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veesix-networks/pyisckea/c701b965556c5e52a98d53e406d754728d993126/pyisckea/models/__init__.py -------------------------------------------------------------------------------- /pyisckea/models/ctrlagent/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veesix-networks/pyisckea/c701b965556c5e52a98d53e406d754728d993126/pyisckea/models/ctrlagent/__init__.py -------------------------------------------------------------------------------- /pyisckea/models/ctrlagent/config.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.generic.control_socket import ControlSockets 4 | from pyisckea.models.generic.daemon import CommonDaemonConfig 5 | 6 | 7 | class CtrlAgentDaemonConfig(CommonDaemonConfig): 8 | http_host: str 9 | http_port: int 10 | trust_anchor: Optional[str] = None 11 | cert_file: Optional[str] = None 12 | key_file: Optional[str] = None 13 | cert_required: Optional[bool] = None 14 | control_sockets: Optional[ControlSockets] = None 15 | authentication: Optional[dict] = None 16 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp4/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veesix-networks/pyisckea/c701b965556c5e52a98d53e406d754728d993126/pyisckea/models/dhcp4/__init__.py -------------------------------------------------------------------------------- /pyisckea/models/dhcp4/client_class.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.generic.client_class import ClientClass 6 | from pyisckea.models.generic.option_def import OptionDef 7 | 8 | 9 | class ClientClass4(ClientClass): 10 | option_def: Optional[List[OptionDef]] = Field(default_factory=list) 11 | next_server: Optional[str] = None 12 | server_hostname: Optional[str] = None 13 | boot_file_name: Optional[str] = None 14 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp4/config.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.dhcp4.client_class import ClientClass4 6 | from pyisckea.models.dhcp4.reservation import Reservation4 7 | from pyisckea.models.dhcp4.shared_network import SharedNetwork4 8 | from pyisckea.models.dhcp4.subnet import Subnet4 9 | from pyisckea.models.generic.daemon import CommonDhcpDaemonConfig 10 | 11 | 12 | class Dhcp4DaemonConfig(CommonDhcpDaemonConfig): 13 | client_classes: Optional[List[ClientClass4]] = Field(default_factory=list) 14 | shared_networks: Optional[List[SharedNetwork4]] = Field(default_factory=list) 15 | reservations: Optional[List[Reservation4]] = Field(default_factory=list) 16 | subnet4: Optional[List[Subnet4]] = Field(default_factory=list) 17 | echo_client_id: Optional[bool] = None 18 | match_client_id: Optional[bool] = None 19 | authoritative: Optional[bool] = None 20 | next_server: Optional[str] = None 21 | server_hostname: Optional[str] = None 22 | boot_file_name: Optional[str] = None 23 | stash_agent_options: Optional[bool] = None 24 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp4/lease.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.generic.lease import Lease, LeasePage 6 | 7 | 8 | class Lease4(Lease): 9 | pass 10 | 11 | 12 | class Lease4Page(LeasePage): 13 | count: int 14 | leases: Optional[List[Lease4]] = Field(default_factory=list) 15 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp4/reservation.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.generic.reservation import Reservation 4 | 5 | 6 | class Reservation4(Reservation): 7 | client_id: Optional[str] = None 8 | circuit_id: Optional[str] = None 9 | ip_address: str 10 | next_server: Optional[str] = None 11 | server_hostname: Optional[str] = None 12 | boot_file_name: Optional[str] = None 13 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp4/shared_network.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.dhcp4.subnet import Subnet4 6 | from pyisckea.models.generic.shared_network import SharedNetwork 7 | 8 | 9 | class SharedNetwork4(SharedNetwork): 10 | subnet4: Optional[List[Subnet4]] = Field(default_factory=list) 11 | match_client_id: Optional[bool] = None 12 | authoritative: Optional[bool] = None 13 | next_server: Optional[str] = None 14 | server_hostname: Optional[str] = None 15 | boot_file_name: Optional[str] = None 16 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp4/subnet.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, List, Optional 2 | 3 | from pydantic import ConfigDict, Field 4 | 5 | from pyisckea.models.dhcp4.reservation import Reservation4 6 | from pyisckea.models.generic.subnet import Subnet 7 | 8 | 9 | class Subnet4(Subnet): 10 | match_client_id: Optional[bool] = None 11 | authoritative: Optional[bool] = None 12 | next_server: Optional[str] = None 13 | boot_file_name: Optional[str] = None 14 | subnet_4o6_interface: Annotated[Optional[str], Field(alias="4o6-interface")] = None 15 | subnet_4o6_interface_id: Annotated[ 16 | Optional[str], Field(alias="4o6-interface-id") 17 | ] = None 18 | subnet_4o6_subnet: Annotated[Optional[str], Field(alias="4o6-subnet")] = None 19 | reservations: Optional[List[Reservation4]] = Field(default_factory=list) 20 | 21 | model_config = ConfigDict(populate_by_name=True) 22 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp6/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veesix-networks/pyisckea/c701b965556c5e52a98d53e406d754728d993126/pyisckea/models/dhcp6/__init__.py -------------------------------------------------------------------------------- /pyisckea/models/dhcp6/client_class.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.generic.client_class import ClientClass 4 | 5 | 6 | class ClientClass6(ClientClass): 7 | preferred_lifetime: Optional[int] = None 8 | min_preferred_lifetime: Optional[int] = None 9 | max_preferred_lifetime: Optional[int] = None 10 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp6/config.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.dhcp6.client_class import ClientClass6 6 | from pyisckea.models.dhcp6.reservation import Reservation6 7 | from pyisckea.models.dhcp6.server_id import ServerId 8 | from pyisckea.models.dhcp6.shared_network import SharedNetwork6 9 | from pyisckea.models.dhcp6.subnet import Subnet6 10 | from pyisckea.models.generic.daemon import CommonDhcpDaemonConfig 11 | 12 | 13 | class Dhcp6DaemonConfig(CommonDhcpDaemonConfig): 14 | client_classes: Optional[List[ClientClass6]] = Field(default_factory=list) 15 | shared_networks: Optional[List[SharedNetwork6]] = Field(default_factory=list) 16 | reservations: Optional[List[Reservation6]] = Field(default_factory=list) 17 | data_directory: Optional[str] = None 18 | preferred_lifetime: Optional[int] = None 19 | min_preferred_lifetime: Optional[int] = None 20 | max_preferred_lifetime: Optional[int] = None 21 | subnet6: Optional[List[Subnet6]] = Field(default_factory=list) 22 | mac_sources: Optional[List[str]] = Field(default_factory=list) 23 | relay_supplied_options: Optional[List[str]] = Field(default_factory=list) 24 | server_id: ServerId 25 | pd_allocator: Optional[str] = None 26 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp6/lease.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.enums import Lease6TypeEnum 6 | from pyisckea.models.generic.lease import Lease, LeasePage 7 | 8 | 9 | class Lease6(Lease): 10 | duid: str 11 | iaid: int 12 | prefix_len: Optional[int] = None 13 | type: Optional[Lease6TypeEnum] = None 14 | 15 | 16 | class Lease6Page(LeasePage): 17 | count: int 18 | leases: Optional[List[Lease6]] = Field(default_factory=list) 19 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp6/pd_pool.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.generic.base import KeaModel 6 | from pyisckea.models.generic.option_data import OptionData 7 | 8 | 9 | class PDPool(KeaModel): 10 | prefix: str 11 | prefix_len: int 12 | delegated_len: int 13 | option_data: Optional[List[OptionData]] = Field(default_factory=list) 14 | client_class: Optional[str] = None 15 | require_client_classes: Optional[List[str]] = Field(default_factory=list) 16 | excluded_prefix: Optional[str] = None 17 | excluded_prefix_len: Optional[int] = None 18 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp6/reservation.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.generic.reservation import Reservation 6 | 7 | 8 | class Reservation6(Reservation): 9 | ip_addresses: Optional[List[str]] = Field(default_factory=list) 10 | prefixes: Optional[List[str]] = Field(default_factory=list) 11 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp6/server_id.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.enums import ServerIdTypeEnum 4 | from pyisckea.models.generic.config import CommonConfig 5 | 6 | 7 | class ServerId(CommonConfig): 8 | type: ServerIdTypeEnum 9 | htype: int 10 | identifier: Optional[str] = None 11 | time: int 12 | enterprise_id: int 13 | persist: bool 14 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp6/shared_network.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.dhcp6.subnet import Subnet6 6 | from pyisckea.models.generic.shared_network import SharedNetwork 7 | 8 | 9 | class SharedNetwork6(SharedNetwork): 10 | subnet6: Optional[List[Subnet6]] = Field(default_factory=list) 11 | interface_id: Optional[str] = None 12 | min_preferred_lifetime: Optional[int] = None 13 | max_preferred_lifetime: Optional[int] = None 14 | rapid_commit: Optional[bool] = None 15 | -------------------------------------------------------------------------------- /pyisckea/models/dhcp6/subnet.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.dhcp6.pd_pool import PDPool 6 | from pyisckea.models.dhcp6.reservation import Reservation6 7 | from pyisckea.models.generic.subnet import Subnet 8 | 9 | 10 | class Subnet6(Subnet): 11 | preferred_lifetime: Optional[int] = None 12 | min_preferred_lifetime: Optional[int] = None 13 | max_preferred_lifetime: Optional[int] = None 14 | pd_allocator: Optional[str] = None 15 | pd_pools: Optional[List[PDPool]] = Field(default_factory=list) 16 | interface_id: Optional[str] = None 17 | rapid_commit: Optional[bool] = None 18 | reservations: Optional[List[Reservation6]] = Field(default_factory=list) 19 | -------------------------------------------------------------------------------- /pyisckea/models/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class StatusEnum(str, Enum): 5 | ready = "ready" 6 | retrying = "retrying" 7 | failed = "failed" 8 | 9 | 10 | class ReservationMode(str, Enum): 11 | disabled = "disabled" 12 | out_of_pool = ("out-of-pool",) 13 | r_global = "global" 14 | all = "all" 15 | 16 | 17 | class DDNSReplaceClientNameEnum(str, Enum): 18 | when_present = "when-present" 19 | never = "never" 20 | always = "always" 21 | when_not_present = "when-not-present" 22 | 23 | 24 | class Lease6TypeEnum(str, Enum): 25 | iana = "IA_NA" 26 | iapd = "IA_PD" 27 | 28 | 29 | class LoggerLevelEnum(str, Enum): 30 | critical = "CRITICAL" 31 | error = "ERROR" 32 | warning = "WARNING" 33 | info = "INFO" 34 | debug = "DEBUG" 35 | 36 | 37 | class DHCPSocketTypeEnum(str, Enum): 38 | raw = "raw" 39 | udp = "udp" 40 | 41 | 42 | class OutboundInterfaceEnum(str, Enum): 43 | same_as_inbound = "same-as-inbound" 44 | use_routing = "use-routing" 45 | 46 | 47 | class DatabaseTypeEnum(str, Enum): 48 | memfile = "memfile" 49 | mysql = "mysql" 50 | postgresql = "postgresql" 51 | 52 | 53 | class DatabaseOnFailEnum(str, Enum): 54 | stop_retry_exit = "stop-retry-exit" 55 | serve_retry_exit = "serve-retry-exit" 56 | serve_retry_continue = "serve-retry-continue" 57 | 58 | 59 | class HostReservationIdentifierEnum(str, Enum): 60 | duid = "duid" 61 | hw_address = "hw-address" 62 | circuit_id = "circuit-id" 63 | client_id = "client-id" 64 | flex_id = "flex-id" 65 | 66 | 67 | class NCRProtocolEnum(str, Enum): 68 | udp = "UDP" 69 | tcp = "TCP" 70 | 71 | 72 | class NCRFormatEnum(str, Enum): 73 | json = "JSON" 74 | 75 | 76 | class ServerIdTypeEnum(str, Enum): 77 | llt = "LLT" 78 | en = "EN" 79 | ll = "LL" 80 | 81 | 82 | class AuthenticationTypeEnum(str, Enum): 83 | basic = "basic" 84 | 85 | 86 | class RemoteMapTypeEnum(str, Enum): 87 | mysql = "mysql" 88 | postgresql = "postgresql" 89 | 90 | 91 | class HAModeTypeEnum(str, Enum): 92 | load_balancing = "load-balancing" 93 | hot_standby = "hot-standby" 94 | 95 | 96 | class HARoleTypeEnum(str, Enum): 97 | primary = "primary" 98 | secondary = "secondary" 99 | standby = "standby" 100 | 101 | 102 | class HAStateTypeEnum(str, Enum): 103 | backup = "backup" 104 | communication_recovery = "communication-recovery" 105 | hot_standby = "hot-standby" 106 | load_balancing = "load-balancing" 107 | in_maintenance = "in-maintenance" 108 | partner_down = "partner-down" 109 | partner_in_maintenance = "partner-in-maintenance" 110 | passive_backup = "passive-backup" 111 | ready = "ready" 112 | synbcing = "syncing" 113 | terminated = "terminated" 114 | waiting = "waiting" 115 | unavailable = "unavailable" 116 | null = "" 117 | 118 | 119 | class DDNSConflictResolutionModeEnum(str, Enum): 120 | check_with_dhcid = "check-with-dhcid" 121 | no_check_with_dhcid = "no-check-with-dhcid" 122 | check_exists_with_dhcid = "check-exists-with-dhcid" 123 | no_check_without_dhcid = "no-check-without-dhcid" 124 | -------------------------------------------------------------------------------- /pyisckea/models/generic/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["KeaResponse", "Sockets", "StatusGet"] 2 | 3 | from pyisckea.models.generic.api_response import KeaResponse 4 | from pyisckea.models.generic.sockets import Sockets 5 | from pyisckea.models.generic.status import StatusGet 6 | -------------------------------------------------------------------------------- /pyisckea/models/generic/api_response.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class KeaResponse(BaseModel): 7 | result: int 8 | text: Optional[str] = None 9 | arguments: Optional[Union[dict, list]] = None 10 | -------------------------------------------------------------------------------- /pyisckea/models/generic/authentication.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.enums import AuthenticationTypeEnum 6 | from pyisckea.models.generic.config import CommonConfig 7 | 8 | 9 | class AuthenticationClient(CommonConfig): 10 | user: Optional[str] = None 11 | user_file: Optional[str] = None 12 | password: Optional[str] = None 13 | password_file: Optional[str] = None 14 | 15 | 16 | class Authentication(CommonConfig): 17 | type: AuthenticationTypeEnum 18 | realm: str 19 | directory: Optional[str] = None 20 | clients: List[Optional[AuthenticationClient]] = Field(default_factory=list) 21 | -------------------------------------------------------------------------------- /pyisckea/models/generic/base.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, ConfigDict 4 | 5 | 6 | def normalize_keys(string: str) -> str: 7 | return string.replace("_", "-") 8 | 9 | 10 | class KeaBaseModel(BaseModel): 11 | model_config = ConfigDict( 12 | alias_generator=normalize_keys, populate_by_name=True, use_enum_values=True 13 | ) 14 | 15 | 16 | class KeaModel(KeaBaseModel): 17 | user_context: Optional[dict] = None 18 | comment: Optional[str] = None 19 | unknown_map_entry: Optional[str] = None 20 | -------------------------------------------------------------------------------- /pyisckea/models/generic/client_class.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pyisckea.models.generic.config import CommonConfig 4 | from pyisckea.models.generic.option_data import OptionData 5 | 6 | 7 | class ClientClass(CommonConfig): 8 | name: str 9 | test: Optional[str] = None 10 | only_if_required: Optional[bool] = None 11 | option_data: Optional[List[OptionData]] = None 12 | valid_lifetime: Optional[int] = None 13 | min_valid_lifetime: Optional[int] = None 14 | max_valid_lifetime: Optional[int] = None 15 | -------------------------------------------------------------------------------- /pyisckea/models/generic/config.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Union 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.enums import DDNSReplaceClientNameEnum, ReservationMode 6 | from pyisckea.models.generic.base import KeaModel 7 | from pyisckea.models.generic.option_data import OptionData 8 | 9 | 10 | class CommonConfig(KeaModel): 11 | store_extended_info: Optional[bool] = None 12 | 13 | 14 | class CommonDhcpConfig(CommonConfig): 15 | valid_lifetime: Optional[int] = None 16 | min_valid_lifetime: Optional[int] = None 17 | max_valid_lifetime: Optional[int] = None 18 | renew_timer: Optional[int] = None 19 | rebind_timer: Optional[int] = None 20 | option_data: Optional[List[OptionData]] = Field(default_factory=list) 21 | reservation_mode: Optional[ReservationMode] = None 22 | reservations_global: Optional[bool] = None 23 | reservations_in_subnet: Optional[bool] = None 24 | reservations_out_of_pool: Optional[bool] = None 25 | calculate_tee_times: Optional[bool] = None 26 | t1_percent: Optional[float] = None 27 | t2_percent: Optional[float] = None 28 | cache_threshold: Optional[float] = None 29 | cache_max_age: Optional[int] = None 30 | ddns_send_updates: Optional[bool] = None 31 | ddns_override_no_update: Optional[bool] = None 32 | ddns_override_client_update: Optional[bool] = None 33 | ddns_replace_client_name: Optional[Union[DDNSReplaceClientNameEnum, bool]] = None 34 | ddns_generated_prefix: Optional[str] = None 35 | ddns_qualifying_suffix: Optional[str] = None 36 | ddns_update_on_renew: Optional[bool] = None 37 | ddns_use_conflict_resolution: Optional[bool] = None 38 | -------------------------------------------------------------------------------- /pyisckea/models/generic/control_socket.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.generic.base import KeaBaseModel 4 | from pyisckea.models.generic.config import CommonConfig 5 | 6 | 7 | class ControlSocket(CommonConfig): 8 | socket_name: str 9 | socket_type: str 10 | 11 | 12 | class ControlSockets(KeaBaseModel): 13 | dhcp4: Optional[ControlSocket] = None 14 | dhcp6: Optional[ControlSocket] = None 15 | d2: Optional[ControlSocket] = None 16 | unknown_map_entry: Optional[str] = None 17 | -------------------------------------------------------------------------------- /pyisckea/models/generic/daemon.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.enums import ( 6 | DDNSConflictResolutionModeEnum, 7 | DHCPSocketTypeEnum, 8 | HostReservationIdentifierEnum, 9 | OutboundInterfaceEnum, 10 | ) 11 | from pyisckea.models.generic.base import KeaBaseModel 12 | from pyisckea.models.generic.config import CommonDhcpConfig 13 | from pyisckea.models.generic.control_socket import ControlSocket 14 | from pyisckea.models.generic.database import Database 15 | from pyisckea.models.generic.dhcp_ddns import DhcpDdns 16 | from pyisckea.models.generic.dhcp_queue_control import DHCPQueueControl 17 | from pyisckea.models.generic.hook import Hook 18 | from pyisckea.models.generic.logger import Logger 19 | from pyisckea.models.generic.multi_threading import MultiThreading 20 | from pyisckea.models.generic.option_def import OptionDef 21 | from pyisckea.models.generic.sanity_check import SanityCheck 22 | 23 | 24 | class CommonDaemonConfig(CommonDhcpConfig): 25 | hooks_libraries: Optional[List[Hook]] = Field(default_factory=list) 26 | loggers: Optional[List[Logger]] = Field(default_factory=list) 27 | 28 | 29 | class InterfaceListConfig(KeaBaseModel): 30 | interfaces: List[str] = Field(default_factory=list) 31 | dhcp_socket_type: Optional[DHCPSocketTypeEnum] = None 32 | outbound_interface: Optional[OutboundInterfaceEnum] = None 33 | re_detect: Optional[bool] = None 34 | service_sockets_require_all: Optional[bool] = None 35 | service_sockets_retry_wait_time: Optional[int] = None 36 | service_sockets_max_retries: Optional[int] = None 37 | 38 | 39 | class CommonDhcpDaemonConfig(CommonDaemonConfig): 40 | interfaces_config: InterfaceListConfig 41 | lease_database: Optional[Database] = None 42 | hosts_database: Optional[Database] = None 43 | hosts_databases: Optional[List[Database]] = None 44 | host_reservation_identifiers: Optional[List[HostReservationIdentifierEnum]] = Field( 45 | default_factory=list 46 | ) 47 | option_def: Optional[List[OptionDef]] = Field(default_factory=list) 48 | expired_leases_processing: Optional[dict] = None 49 | dhcp4o6_port: Optional[int] = None 50 | control_socket: Optional[ControlSocket] = None 51 | ddns_conflict_resolution_mode: Optional[DDNSConflictResolutionModeEnum] = ( 52 | DDNSConflictResolutionModeEnum.check_with_dhcid 53 | ) 54 | dhcp_queue_control: Optional[DHCPQueueControl] = None 55 | dhcp_ddns: Optional[DhcpDdns] = None 56 | sanity_checks: Optional[SanityCheck] = None 57 | config_control: Optional[dict] = None 58 | server_tag: Optional[str] = None 59 | hostname_char_set: Optional[str] = None 60 | hostname_char_replacement: Optional[str] = None 61 | statistic_default_sample_count: Optional[int] = None 62 | statistic_default_sample_age: Optional[int] = None 63 | multi_threading: Optional[MultiThreading] = None 64 | early_global_reservations_lookup: Optional[bool] = None 65 | ip_reservations_unique: Optional[bool] = None 66 | reservations_lookup_first: Optional[bool] = None 67 | compatibility: Optional[dict] = None 68 | parked_packet_limit: Optional[int] = None 69 | decline_probation_period: Optional[int] = None 70 | allocator: Optional[str] = None 71 | -------------------------------------------------------------------------------- /pyisckea/models/generic/database.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.enums import DatabaseOnFailEnum, DatabaseTypeEnum 4 | from pyisckea.models.generic.base import KeaBaseModel 5 | 6 | 7 | class Database(KeaBaseModel): 8 | type: DatabaseTypeEnum 9 | user: Optional[str] = None 10 | password: Optional[str] = None 11 | host: Optional[str] = None 12 | port: Optional[int] = None 13 | name: Optional[str] = None 14 | persist: Optional[bool] = None 15 | lfc_interval: Optional[int] = None 16 | readonly: Optional[bool] = None 17 | connect_timeout: Optional[int] = None 18 | max_reconnect_tries: Optional[int] = None 19 | on_fail: Optional[DatabaseOnFailEnum] = None 20 | max_row_errors: Optional[int] = None 21 | trust_anchor: Optional[str] = None 22 | cert_file: Optional[str] = None 23 | key_file: Optional[str] = None 24 | cipher_list: Optional[str] = None 25 | unknown_map_entry: Optional[str] = None 26 | -------------------------------------------------------------------------------- /pyisckea/models/generic/dhcp_common.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.generic.config import CommonDhcpConfig 6 | from pyisckea.models.generic.relay import Relay 7 | 8 | 9 | class CommonDHCPParams(CommonDhcpConfig): 10 | """Any param shared between v4/v6 Shared Networks and Subnets""" 11 | 12 | interface: Optional[str] = None 13 | client_class: Optional[str] = None 14 | require_client_classes: Optional[List[str]] = Field(default_factory=list) 15 | relay: Optional[Relay] = None 16 | -------------------------------------------------------------------------------- /pyisckea/models/generic/dhcp_ddns.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.enums import NCRFormatEnum, NCRProtocolEnum 4 | from pyisckea.models.generic.config import CommonConfig 5 | 6 | 7 | class DhcpDdns(CommonConfig): 8 | enable_updates: bool 9 | server_ip: str 10 | server_port: int 11 | sender_ip: str 12 | sender_port: int 13 | max_queue_size: int 14 | ncr_protocol: NCRProtocolEnum 15 | ncr_format: NCRFormatEnum 16 | dep_override_no_update: Optional[bool] = None 17 | dep_override_client_update: Optional[bool] = None 18 | dep_replace_client_name: Optional[str] = None 19 | dep_generated_prefix: Optional[str] = None 20 | dep_qualifying_suffix: Optional[str] = None 21 | dep_hostname_char_set: Optional[str] = None 22 | dep_hostname_char_replacement: Optional[str] = None 23 | 24 | 25 | """ 26 | "enable-updates": false, 27 | "max-queue-size": 1024, 28 | "ncr-format": "JSON", 29 | "ncr-protocol": "UDP", 30 | "sender-ip": "0.0.0.0", 31 | "sender-port": 0, 32 | "server-ip": "127.0.0.1", 33 | "server-port": 53001 34 | """ 35 | -------------------------------------------------------------------------------- /pyisckea/models/generic/dhcp_queue_control.py: -------------------------------------------------------------------------------- 1 | from pyisckea.models.generic.config import CommonConfig 2 | 3 | 4 | class DHCPQueueControl(CommonConfig): 5 | enable_queue: bool 6 | queue_type: str 7 | capacity: int 8 | -------------------------------------------------------------------------------- /pyisckea/models/generic/high_availability.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from pyisckea.models.enums import HAModeTypeEnum, HARoleTypeEnum, HAStateTypeEnum 4 | from pyisckea.models.generic.base import KeaBaseModel 5 | 6 | 7 | class HAServerLocal(KeaBaseModel): 8 | role: HARoleTypeEnum 9 | scopes: List[str] 10 | state: HAStateTypeEnum 11 | 12 | 13 | class HAServerRemote(KeaBaseModel): 14 | age: int 15 | in_touch: bool 16 | last_scopes: List[str] 17 | last_state: Union[None, HAStateTypeEnum] = None 18 | role: HARoleTypeEnum 19 | 20 | 21 | class HAServers(KeaBaseModel): 22 | local: HAServerLocal 23 | remote: HAServerRemote 24 | 25 | 26 | class HighAvailability(KeaBaseModel): 27 | ha_mode: HAModeTypeEnum 28 | ha_servers: HAServers 29 | -------------------------------------------------------------------------------- /pyisckea/models/generic/hook.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.generic.base import KeaBaseModel 4 | 5 | 6 | class Hook(KeaBaseModel): 7 | library: str 8 | parameters: Optional[dict] = None 9 | name: Optional[str] = None 10 | -------------------------------------------------------------------------------- /pyisckea/models/generic/lease.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.generic.base import KeaBaseModel 4 | 5 | 6 | class Lease(KeaBaseModel): 7 | cltt: Optional[int] = None 8 | fqdn_fwd: Optional[bool] = None 9 | fqdn_rev: Optional[bool] = None 10 | hostname: Optional[str] = None 11 | hw_address: Optional[str] = None 12 | ip_address: str 13 | state: Optional[int] = None 14 | subnet_id: Optional[int] = None 15 | valid_lft: Optional[int] = None 16 | 17 | 18 | class LeasePage(KeaBaseModel): 19 | count: int 20 | -------------------------------------------------------------------------------- /pyisckea/models/generic/logger.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | from typing_extensions import Annotated 5 | 6 | from pyisckea.models.enums import LoggerLevelEnum 7 | from pyisckea.models.generic.base import KeaBaseModel, KeaModel 8 | 9 | 10 | class Output(KeaBaseModel): 11 | output: str 12 | flush: bool 13 | maxsize: Optional[int] = None 14 | maxver: Optional[int] = None 15 | pattern: Optional[str] = None 16 | 17 | 18 | class Logger(KeaModel): 19 | name: str 20 | output_options: Optional[List[Output]] = Field(default_factory=list) 21 | debuglevel: Annotated[int, Field(ge=0, le=100)] 22 | severity: Optional[LoggerLevelEnum] = None 23 | -------------------------------------------------------------------------------- /pyisckea/models/generic/multi_threading.py: -------------------------------------------------------------------------------- 1 | from pyisckea.models.generic.config import CommonConfig 2 | 3 | 4 | class MultiThreading(CommonConfig): 5 | enable_multi_threading: bool 6 | thread_pool_size: int 7 | packet_queue_size: int 8 | -------------------------------------------------------------------------------- /pyisckea/models/generic/option_data.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.generic.base import KeaModel 4 | 5 | 6 | class OptionData(KeaModel): 7 | data: str 8 | name: Optional[str] = None 9 | code: Optional[int] = None 10 | space: Optional[str] = None 11 | csv_format: Optional[bool] = None 12 | always_send: Optional[bool] = None 13 | never_send: Optional[bool] = None 14 | -------------------------------------------------------------------------------- /pyisckea/models/generic/option_def.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.generic.config import CommonConfig 4 | 5 | 6 | class OptionDef(CommonConfig): 7 | name: str 8 | code: Optional[int] = None 9 | type: Optional[str] = None 10 | record_types: Optional[str] = None 11 | space: Optional[str] = None 12 | encapsulate: Optional[str] = None 13 | array: Optional[bool] = None 14 | -------------------------------------------------------------------------------- /pyisckea/models/generic/pool.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.generic.base import KeaModel 6 | from pyisckea.models.generic.option_data import OptionData 7 | 8 | 9 | class Pool(KeaModel): 10 | pool: str 11 | option_data: Optional[List[OptionData]] = Field(default_factory=list) 12 | client_class: Optional[str] = None 13 | require_client_classes: Optional[str] = None 14 | -------------------------------------------------------------------------------- /pyisckea/models/generic/relay.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.generic.base import KeaBaseModel 6 | 7 | 8 | class Relay(KeaBaseModel): 9 | ip_addresses: List[str] = Field(default_factory=list) 10 | -------------------------------------------------------------------------------- /pyisckea/models/generic/remote_map.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import Field 4 | from typing_extensions import Annotated 5 | 6 | from pyisckea.models.enums import RemoteMapTypeEnum 7 | from pyisckea.models.generic.base import KeaBaseModel 8 | 9 | 10 | class RemoteMap(KeaBaseModel): 11 | type: Optional[RemoteMapTypeEnum] = None 12 | host: Optional[str] = None 13 | port: Optional[Annotated[int, Field(gt=1, le=65535)]] = None 14 | -------------------------------------------------------------------------------- /pyisckea/models/generic/remote_server.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import StringConstraints 4 | from typing_extensions import Annotated 5 | 6 | from pyisckea.models.generic.base import KeaBaseModel 7 | 8 | 9 | class RemoteServer(KeaBaseModel): 10 | server_tag: Annotated[str, StringConstraints(max_length=256)] 11 | description: Optional[str] = None 12 | -------------------------------------------------------------------------------- /pyisckea/models/generic/reservation.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.generic.base import KeaModel 6 | from pyisckea.models.generic.option_data import OptionData 7 | 8 | 9 | class Reservation(KeaModel): 10 | duid: Optional[str] = None 11 | client_classes: Optional[List[str]] = Field(default_factory=list) 12 | flex_id: Optional[str] = None 13 | hw_address: Optional[str] = None 14 | hostname: Optional[str] = None 15 | option_data: Optional[List[OptionData]] = Field(default_factory=list) 16 | subnet_id: Optional[int] = None # Used for reservation-add 17 | -------------------------------------------------------------------------------- /pyisckea/models/generic/sanity_check.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.generic.base import KeaBaseModel 4 | 5 | 6 | class SanityCheck(KeaBaseModel): 7 | lease_checks: str 8 | extended_info_checks: Optional[str] = None 9 | -------------------------------------------------------------------------------- /pyisckea/models/generic/shared_network.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pyisckea.models.generic.dhcp_common import CommonDHCPParams 4 | 5 | 6 | class SharedNetwork(CommonDHCPParams): 7 | name: str 8 | relay: Optional[dict] = None 9 | -------------------------------------------------------------------------------- /pyisckea/models/generic/sockets.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import BaseModel, ConfigDict, Field 4 | 5 | from pyisckea.models.enums import StatusEnum 6 | 7 | 8 | class Sockets(BaseModel): 9 | errors: Optional[List[str]] = Field(default_factory=list) 10 | status: StatusEnum 11 | model_config = ConfigDict(use_enum_values=True) 12 | -------------------------------------------------------------------------------- /pyisckea/models/generic/status.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | 5 | from pyisckea.models.generic import Sockets 6 | from pyisckea.models.generic.base import KeaBaseModel 7 | from pyisckea.models.generic.high_availability import HighAvailability 8 | 9 | 10 | class StatusGet(KeaBaseModel): 11 | pid: int 12 | uptime: int 13 | reload: int 14 | multi_threading_enabled: Optional[bool] = None 15 | sockets: Optional[Sockets] = None 16 | high_availability: Optional[List[HighAvailability]] = Field(default_factory=list) 17 | -------------------------------------------------------------------------------- /pyisckea/models/generic/subnet.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import Field 4 | from typing_extensions import Annotated 5 | 6 | from pyisckea.models.generic.dhcp_common import CommonDHCPParams 7 | from pyisckea.models.generic.pool import Pool 8 | 9 | 10 | # check whether id is still optional with new versions of kea 11 | class Subnet(CommonDHCPParams): 12 | id: Optional[Annotated[int, Field(gt=0, lt=4294967295)]] = None 13 | pools: Optional[List[Pool]] = Field(default_factory=list) 14 | subnet: str 15 | hostname_char_set: Optional[str] = None 16 | hostname_char_replacement: Optional[str] = None 17 | allocator: Optional[str] = None 18 | -------------------------------------------------------------------------------- /pyisckea/parsers/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["Dhcp4Parser", "Dhcp6Parser", "CtrlAgentParser"] 2 | 3 | from pyisckea.parsers.ctrlagent import CtrlAgentParser 4 | from pyisckea.parsers.dhcp4 import Dhcp4Parser 5 | from pyisckea.parsers.dhcp6 import Dhcp6Parser 6 | -------------------------------------------------------------------------------- /pyisckea/parsers/ctrlagent.py: -------------------------------------------------------------------------------- 1 | from pyisckea.models.ctrlagent.config import CtrlAgentDaemonConfig 2 | from pyisckea.parsers.generic import GenericParser 3 | 4 | 5 | class CtrlAgentParser(GenericParser): 6 | """Parser for the ISC Kea CtrlAgent configuration file. This should ideally 7 | be used with the cached config stored in the Daemon class like this: 8 | 9 | parser = CtrlAgentParser(config=server.ctrlagent.cached_config) 10 | """ 11 | 12 | def __init__(self, config: dict): 13 | self.config = CtrlAgentDaemonConfig.model_validate(config["Control-agent"]) 14 | -------------------------------------------------------------------------------- /pyisckea/parsers/ddns.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veesix-networks/pyisckea/c701b965556c5e52a98d53e406d754728d993126/pyisckea/parsers/ddns.py -------------------------------------------------------------------------------- /pyisckea/parsers/exceptions.py: -------------------------------------------------------------------------------- 1 | class GenericParserError(Exception): 2 | def __init__(self, message: str = "Encountered generic parser error"): 3 | self.message = message 4 | super().__init__(self.message) 5 | 6 | 7 | class ParserSubnetIDAlreadyExistError(GenericParserError): 8 | def __init__(self, id: int): 9 | self.message = f"Subnet with same ID {id} already exist in local configuration" 10 | super().__init__(self.message) 11 | 12 | 13 | class ParserSubnetCIDRAlreadyExistError(GenericParserError): 14 | def __init__(self, cidr: str): 15 | self.message = ( 16 | f"Subnet with same CIDR {cidr} already exist in local configuration" 17 | ) 18 | super().__init__(self.message) 19 | 20 | 21 | class ParserSharedNetworkAlreadyExistError(GenericParserError): 22 | def __init__(self, name: str): 23 | self.message = f"Shared Network with name {name} already exists" 24 | super().__init__(self.message) 25 | 26 | 27 | class ParserSharedNetworkNotFoundError(GenericParserError): 28 | def __init__(self, name: str): 29 | self.message = f"Shared Network with name {name} does not exist" 30 | super().__init__(self.message) 31 | 32 | 33 | class ParserSubnetNotFoundError(GenericParserError): 34 | def __init__(self, id: int): 35 | self.message = f"Subnet with ID {id} does not exist" 36 | super().__init__(self.message) 37 | 38 | 39 | class ParserReservationAlreadyExistError(GenericParserError): 40 | def __init__(self, ip_address: str): 41 | self.message = f"Reservation with IP Address {ip_address} already exist" 42 | super().__init__(self.message) 43 | 44 | 45 | class ParserReservationNotFoundError(GenericParserError): 46 | def __init__(self, ip_address: str): 47 | self.message = f"Reservation with IP Address {ip_address} not found" 48 | super().__init__(self.message) 49 | 50 | 51 | class ParserOptionDataAlreadyExistError(GenericParserError): 52 | def __init__(self, object: str, code: int): 53 | self.message = f"Option {code} already exist on object {object}" 54 | super().__init__(self.message) 55 | 56 | 57 | class ParserSubnetPoolAlreadyExistError(GenericParserError): 58 | def __init__(self, id: int, pool: str): 59 | self.message = f"Pool {pool} already exist in subnet {id}" 60 | super().__init__(self.message) 61 | 62 | 63 | class ParserPoolInvalidAddressError(GenericParserError): 64 | def __init__(self, start: str, end: str): 65 | self.message = ( 66 | f"Start ({start}) or End IP ({end}) for pool is not a valid IP address" 67 | ) 68 | super().__init__(self.message) 69 | 70 | 71 | class ParserPoolAddressNotInSubnetError(GenericParserError): 72 | def __init__(self, address: str, subnet: str): 73 | self.message = ( 74 | f"Pool Start or End address {address} is not within subnet {subnet}" 75 | ) 76 | super().__init__(self.message) 77 | 78 | 79 | class ParserInvalidHostReservationIdentifierError(GenericParserError): 80 | def __init__(self, identifier_type: str): 81 | self.message = f"Identifier Typer {identifier_type} is not a valid type" 82 | super().__init__(self.message) 83 | 84 | 85 | class ParserPDPoolAlreadyExistError(GenericParserError): 86 | def __init__(self, prefix: str, prefix_len: int, id: int): 87 | self.message = ( 88 | f"PD Prefix {prefix}/{prefix_len} already exist in a subnet ({id})" 89 | ) 90 | super().__init__(self.message) 91 | 92 | 93 | class ParserPDPoolNotFoundError(GenericParserError): 94 | def __init__(self, id: int, prefix: str, prefix_len: int): 95 | self.message = f"Unable to find PD Prefix {prefix}/{prefix_len} in subnet {id}" 96 | super().__init__(self.message) 97 | -------------------------------------------------------------------------------- /pyisckea/parsers/generic.py: -------------------------------------------------------------------------------- 1 | class GenericParser: 2 | """A Parser does not interact with the ISC Kea Daemon APIs but essentially builds 3 | similar functionality for a local cached config file. If you do not pay for the 4 | premium hooks to expose the different commands like subnet4-add or network4-del then 5 | the parsers job is to provide similar functionality for the provided daemon configuration 6 | and then you should use the relevant service `config-set` functionality which is implemented 7 | for each service in pyisckea. 8 | 9 | The recommended option is to use the functions within the Daemon class (eg. server.dhcp4) and to 10 | use these parsers as a last resort as tests are not currently performed as extensively vs the API 11 | functionality""" 12 | 13 | def __init__(self, config: dict): 14 | self.config = config 15 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pyisckea" 3 | version = "0.9.1" 4 | description = "Wrapper around requests module to query ISC Kea DHCP API Daemons (ctrlagent, dhcp4, dhcp6, ddns)" 5 | authors = ["Brandon Spendlove "] 6 | license = "Apache 2.0" 7 | readme = "README.md" 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.10" 11 | pydantic = "^2.10.6" 12 | httpx = "^0.28.1" 13 | 14 | 15 | [tool.poetry.group.dev.dependencies] 16 | black = "^24.10.0" 17 | pytest = "^7.4.0" 18 | pytest-order = "^1.1.0" 19 | 20 | 21 | [tool.poetry.group.docs.dependencies] 22 | mkdocs = "^1.6.1" 23 | mkdocs-material = "^9.6.11" 24 | 25 | [build-system] 26 | requires = ["poetry-core"] 27 | build-backend = "poetry.core.masonry.api" 28 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs-material==9.6.11 ; python_version >= "3.10" and python_version < "4.0" -------------------------------------------------------------------------------- /tests/ci/models/test_ci_kea_ctrlagent_config_model.py: -------------------------------------------------------------------------------- 1 | from pyisckea.models.ctrlagent.config import CtrlAgentDaemonConfig 2 | from pyisckea.models.generic.control_socket import ControlSockets 3 | 4 | 5 | def test_ci_kea_ctrlagent_config_model_load(ctrlagent_model: CtrlAgentDaemonConfig): 6 | assert ctrlagent_model 7 | 8 | 9 | def test_ci_kea_ctrlagent_config_model_by_export( 10 | ctrlagent_model: CtrlAgentDaemonConfig, 11 | ): 12 | assert ctrlagent_model.model_dump(exclude_none=True, by_alias=True) 13 | 14 | 15 | def test_ci_kea_ctrlagent_config_model_check_required( 16 | ctrlagent_model: CtrlAgentDaemonConfig, 17 | ): 18 | assert ctrlagent_model.http_host 19 | assert ctrlagent_model.http_port 20 | 21 | 22 | def test_ci_kea_ctrlagent_config_model_control_sockets( 23 | ctrlagent_model: CtrlAgentDaemonConfig, 24 | ): 25 | assert type(ctrlagent_model.control_sockets) is ControlSockets 26 | 27 | assert ctrlagent_model.control_sockets.d2.socket_name 28 | assert ctrlagent_model.control_sockets.d2.socket_type == "unix" 29 | 30 | assert ctrlagent_model.control_sockets.dhcp4.socket_name 31 | assert ctrlagent_model.control_sockets.dhcp4.socket_type == "unix" 32 | 33 | assert ctrlagent_model.control_sockets.dhcp6.socket_name 34 | assert ctrlagent_model.control_sockets.dhcp6.socket_type == "unix" 35 | 36 | 37 | def test_ci_kea_ctrlagent_config_model_loggers( 38 | ctrlagent_model: CtrlAgentDaemonConfig, 39 | ): 40 | assert len(ctrlagent_model.loggers) > 0 41 | -------------------------------------------------------------------------------- /tests/ci/models/test_ci_kea_dhcp4_config_model.py: -------------------------------------------------------------------------------- 1 | from pyisckea.models.dhcp4.config import Dhcp4DaemonConfig 2 | 3 | 4 | def test_ci_kea_dhcp4_config_model_load(dhcp4_model: Dhcp4DaemonConfig): 5 | assert dhcp4_model 6 | 7 | 8 | def test_ci_kea_dhcp4_config_model_export(dhcp4_model: Dhcp4DaemonConfig): 9 | assert dhcp4_model.model_dump(exclude_none=True, by_alias=True) 10 | 11 | 12 | def test_ci_kea_dhcp4_config_model_hooks(dhcp4_model: Dhcp4DaemonConfig): 13 | assert len(dhcp4_model.hooks_libraries) > 0 14 | for hook_library in dhcp4_model.hooks_libraries: 15 | assert hook_library.library 16 | 17 | 18 | def test_ci_kea_dhcp4_config_client_classes(dhcp4_model: Dhcp4DaemonConfig): 19 | assert len(dhcp4_model.client_classes) > 0 20 | for client_class in dhcp4_model.client_classes: 21 | assert client_class.name 22 | assert len(client_class.option_data) > 0 23 | 24 | 25 | def test_ci_kea_dhcp4_config_model_host_reservation_identifiers( 26 | dhcp4_model: Dhcp4DaemonConfig, 27 | ): 28 | identifiers = ["hw-address", "duid", "circuit-id", "client-id"] 29 | assert len(dhcp4_model.host_reservation_identifiers) > 0 30 | for identifier in dhcp4_model.host_reservation_identifiers: 31 | assert identifier in identifiers 32 | 33 | 34 | def test_ci_kea_dhcp4_config_model_interfaces_config(dhcp4_model: Dhcp4DaemonConfig): 35 | assert len(dhcp4_model.interfaces_config.interfaces) == 1 36 | assert dhcp4_model.interfaces_config.interfaces[0] == "eth0" 37 | 38 | 39 | def test_ci_kea_dhcp4_config_model_subnet4(dhcp4_model: Dhcp4DaemonConfig): 40 | assert len(dhcp4_model.subnet4) > 0 41 | for subnet in dhcp4_model.subnet4: 42 | assert subnet.id 43 | assert subnet.subnet 44 | 45 | 46 | def test_ci_kea_dhcp4_config_model_subnet4_option_data(dhcp4_model: Dhcp4DaemonConfig): 47 | for subnet in dhcp4_model.subnet4: 48 | assert len(subnet.option_data) > 0 49 | 50 | for option in subnet.option_data: 51 | assert option.code 52 | assert option.data 53 | 54 | 55 | def test_ci_kea_dhcp4_config_model_subnet4_pool(dhcp4_model: Dhcp4DaemonConfig): 56 | for subnet in dhcp4_model.subnet4: 57 | assert len(subnet.pools) > 0 58 | for pool in subnet.pools: 59 | assert pool.pool 60 | 61 | 62 | def test_ci_kea_dhcp4_config_model_subnet4_reservations(dhcp4_model: Dhcp4DaemonConfig): 63 | for subnet in dhcp4_model.subnet4: 64 | assert len(subnet.reservations) > 0 65 | for reservation in subnet.reservations: 66 | assert reservation.ip_address 67 | -------------------------------------------------------------------------------- /tests/ci/models/test_ci_kea_dhcp6_config_model.py: -------------------------------------------------------------------------------- 1 | from pyisckea.models.dhcp6.config import Dhcp6DaemonConfig 2 | 3 | 4 | def test_ci_kea_dhcp6_config_model_load(dhcp6_model: Dhcp6DaemonConfig): 5 | assert dhcp6_model 6 | 7 | 8 | def test_ci_kea_dhcp6_config_model_export(dhcp6_model: Dhcp6DaemonConfig): 9 | assert dhcp6_model.model_dump(exclude_none=True, by_alias=True) 10 | 11 | 12 | def test_ci_kea_dhcp6_config_model_hooks(dhcp6_model: Dhcp6DaemonConfig): 13 | assert len(dhcp6_model.hooks_libraries) > 0 14 | for hook_library in dhcp6_model.hooks_libraries: 15 | assert hook_library.library 16 | 17 | 18 | def test_ci_kea_dhcp6_config_client_classes(dhcp6_model: Dhcp6DaemonConfig): 19 | assert len(dhcp6_model.client_classes) > 0 20 | for client_class in dhcp6_model.client_classes: 21 | assert client_class.name 22 | assert len(client_class.option_data) > 0 23 | -------------------------------------------------------------------------------- /tests/configs/ctrlagent_api_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Control-agent": { 3 | "cert-file": "/etc/kea/cert.pem", 4 | "cert-required": false, 5 | "control-sockets": { 6 | "d2": { 7 | "socket-name": "/tmp/kea-ddns-ctrl-socket", 8 | "socket-type": "unix" 9 | }, 10 | "dhcp4": { 11 | "socket-name": "/tmp/kea4-ctrl-socket", 12 | "socket-type": "unix" 13 | }, 14 | "dhcp6": { 15 | "socket-name": "/tmp/kea6-ctrl-socket", 16 | "socket-type": "unix" 17 | } 18 | }, 19 | "hooks-libraries": [], 20 | "http-host": "0.0.0.0", 21 | "http-port": 8080, 22 | "key-file": "/etc/kea/key.pem", 23 | "loggers": [ 24 | { 25 | "debuglevel": 0, 26 | "name": "kea-ctrl-agent", 27 | "severity": "INFO" 28 | } 29 | ], 30 | "trust-anchor": "my-ca" 31 | } 32 | } -------------------------------------------------------------------------------- /tests/configs/ctrlagent_parsed_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Control-agent": { 3 | "control-sockets": { 4 | "d2": { 5 | "socket-name": "/tmp/kea-ddns-ctrl-socket", 6 | "socket-type": "unix" 7 | }, 8 | "dhcp4": { 9 | "socket-name": "/tmp/kea4-ctrl-socket", 10 | "socket-type": "unix" 11 | }, 12 | "dhcp6": { 13 | "socket-name": "/tmp/kea6-ctrl-socket", 14 | "socket-type": "unix" 15 | } 16 | }, 17 | "hooks-libraries": [], 18 | "http-host": "0.0.0.0", 19 | "http-port": 8080, 20 | "loggers": [ 21 | { 22 | "debuglevel": 0, 23 | "name": "kea-ctrl-agent", 24 | "severity": "INFO" 25 | } 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /tests/ctrlagent/test_kea_ctrlagent.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | 3 | 4 | def test_kea_ctrlagent_build_report(kea_server: Kea): 5 | response = kea_server.ctrlagent.build_report() 6 | assert response.result == 0 7 | assert response.text 8 | 9 | 10 | def test_kea_ctrlagent_config_get(kea_server: Kea): 11 | response = kea_server.ctrlagent.config_get() 12 | assert response.result == 0 13 | assert response.arguments 14 | 15 | 16 | def test_kea_ctrlagent_config_test(kea_server: Kea): 17 | config = kea_server.ctrlagent.cached_config 18 | if config.get("hash"): # Temp workaround 19 | del config["hash"] 20 | 21 | response = kea_server.ctrlagent.config_test(config=config) 22 | assert response.result == 0 23 | 24 | 25 | def test_kea_ctrlagent_config_set(kea_server: Kea): 26 | config = kea_server.ctrlagent.cached_config 27 | if config.get("hash"): # Temp workaround 28 | del config["hash"] 29 | 30 | response = kea_server.ctrlagent.config_set(config=config) 31 | assert response.result == 0 32 | 33 | 34 | def test_kea_ctrlagent_config_write(kea_server: Kea): 35 | filename = "/usr/local/etc/kea/kea-ctrl-agent.conf" 36 | response = kea_server.ctrlagent.config_write(filename=filename) 37 | assert response.result == 0 38 | assert response.arguments["filename"] == filename 39 | assert response.arguments["size"] > 0 40 | 41 | 42 | def test_kea_ctrlagent_config_reload(kea_server: Kea): 43 | response = kea_server.ctrlagent.config_reload() 44 | assert response.result == 0 45 | assert response.text 46 | 47 | 48 | def test_kea_ctrlagent_list_commands(kea_server: Kea): 49 | response = kea_server.ctrlagent.list_commands() 50 | assert response.result == 0 51 | assert response.arguments 52 | assert "config-get" in response.arguments 53 | 54 | 55 | def test_kea_ctrlagent_status_get(kea_server: Kea): 56 | response = kea_server.ctrlagent.status_get() 57 | assert response.pid 58 | assert response.reload >= 0 59 | assert response.uptime 60 | 61 | 62 | def test_kea_ctrlagent_shutdown(kea_server: Kea): 63 | response = kea_server.ctrlagent.shutdown() 64 | assert response.result == 0 65 | assert response.text == "Control Agent is shutting down" 66 | -------------------------------------------------------------------------------- /tests/ctrlagent/test_kea_ctrlagent_parser.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from pyisckea import Kea 4 | from pyisckea.parsers.ctrlagent import CtrlAgentParser 5 | 6 | 7 | def test_kea_ctrlagent_parser_parse_config(kea_server: Kea): 8 | cached_config = kea_server.ctrlagent.cached_config 9 | parsed = CtrlAgentParser(config=cached_config) 10 | 11 | assert parsed.config.http_host 12 | assert parsed.config.http_port 13 | assert json.dumps( 14 | parsed.config.model_dump(exclude_none=True, by_alias=True), 15 | indent=4, 16 | sort_keys=True, 17 | ) 18 | 19 | 20 | def test_kea_ctrlagent_parser_config_test(kea_server: Kea): 21 | cached_config = kea_server.ctrlagent.cached_config 22 | parsed = CtrlAgentParser(config=cached_config) 23 | config_to_test = { 24 | "Control-agent": parsed.config.model_dump( 25 | exclude_none=True, exclude_unset=True, by_alias=True 26 | ) 27 | } 28 | 29 | test_results = kea_server.ctrlagent.config_test(config=config_to_test) 30 | assert test_results.result == 0 31 | 32 | # Remove hash if exist for now until tests are created to take that into account 33 | if cached_config.get("hash"): 34 | del cached_config["hash"] 35 | 36 | cached_config_json = json.dumps(cached_config, indent=4) 37 | parsed_config_json = json.dumps(config_to_test, indent=4, sort_keys=True) 38 | assert cached_config_json == parsed_config_json 39 | -------------------------------------------------------------------------------- /tests/dhcp4/remote/test_kea_dhcp4_remote.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.generic.remote_server import RemoteServer 3 | 4 | """remote generic process: 5 | remote-prepare (config backend pull test) 6 | remote-server4-set 7 | remote-server4-get 8 | remote-server4-get (non existent) 9 | remote-server4-get-all 10 | remote-server4-del 11 | """ 12 | 13 | 14 | def test_kea_dhcp4_remote_prepare(kea_server: Kea): 15 | response = kea_server.dhcp4.config_backend_pull() 16 | assert response.result == 0 17 | 18 | 19 | def test_kea_dhcp4_remote_server4_set(kea_server: Kea): 20 | response = kea_server.dhcp4.remote_server4_set( 21 | servers=[RemoteServer(server_tag="pyisckea", description="pyisckea-test")] 22 | ) 23 | assert response.result == 0 24 | 25 | 26 | def test_kea_dhcp4_remote_server4_get(kea_server: Kea): 27 | server = kea_server.dhcp4.remote_server4_get(server_tag="pyisckea") 28 | assert server 29 | assert server.server_tag == "pyisckea" 30 | assert server.description == "pyisckea-test" 31 | 32 | 33 | def test_kea_dhcp4_remote_server4_del(kea_server: Kea): 34 | response = kea_server.dhcp4.remote_server4_del(servers=["pyisckea"]) 35 | assert response.result == 0 36 | assert "deleted" in response.text 37 | -------------------------------------------------------------------------------- /tests/dhcp4/remote/test_kea_dhcp4_remote_class4.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaClientClassNotFoundException 5 | from pyisckea.models.dhcp4.client_class import ClientClass4 6 | 7 | """remote-class4 process: 8 | 9 | get (non existent) 10 | set 11 | get 12 | get-all 13 | del 14 | """ 15 | 16 | 17 | def test_kea_dhcp4_remote_class4_get_non_existent(kea_server: Kea): 18 | with pytest.raises(KeaClientClassNotFoundException): 19 | kea_server.dhcp4.remote_class4_get(name="ipxe_efi_x64") 20 | 21 | 22 | def test_kea_dhcp4_remote_class4_set(kea_server: Kea): 23 | client_class = ClientClass4( 24 | name="ipxe_efi_x64", 25 | test="option[93].hex == 0x0009", 26 | next_server="192.0.2.254", 27 | server_hostname="hal9000", 28 | boot_file_name="/dev/null", 29 | ) 30 | response = kea_server.dhcp4.remote_class4_set(client_class=client_class) 31 | assert response.result == 0 32 | assert len(response.arguments.get("client-classes", [])) > 0 33 | 34 | 35 | def test_kea_dhcp4_remote_class4_get(kea_server: Kea): 36 | response = kea_server.dhcp4.remote_class4_get(name="ipxe_efi_x64") 37 | assert response 38 | assert response.name == "ipxe_efi_x64" 39 | 40 | 41 | def test_kea_dhcp4_remote_class4_get_all(kea_server: Kea): 42 | response = kea_server.dhcp4.remote_class4_get_all() 43 | assert response 44 | assert len(response) > 0 45 | 46 | 47 | def test_kea_dhcp4_remote_class4_del(kea_server: Kea): 48 | response = kea_server.dhcp4.remote_class4_del(name="ipxe_efi_x64") 49 | assert response.result == 0 50 | assert response.arguments.get("count") == 1 51 | -------------------------------------------------------------------------------- /tests/dhcp4/remote/test_kea_dhcp4_remote_global_parameter4.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | 3 | """remote-global-parameter4 process: 4 | 5 | get (non existent) 6 | set 7 | get 8 | get-all 9 | del 10 | """ 11 | 12 | 13 | def test_kea_dhcp4_remote_global_parameter4_get_non_existent(kea_server: Kea): 14 | response = kea_server.dhcp4.remote_global_parameter4_get( 15 | parameter="boot-file-name", server_tag="pyisckea-1" 16 | ) 17 | assert response.result == 3 18 | assert response.arguments.get("count") == 0 19 | 20 | 21 | def test_kea_dhcp4_remote_global_parameter4_set(kea_server: Kea): 22 | response = kea_server.dhcp4.remote_global_parameter4_set( 23 | parameters={"boot-file-name": "/dev/null"}, server_tag="all" 24 | ) 25 | assert response.result == 0 26 | assert response.arguments.get("count") == 1 27 | 28 | 29 | def test_kea_dhcp4_remote_global_parameter4_get(kea_server: Kea): 30 | response = kea_server.dhcp4.remote_global_parameter4_get( 31 | parameter="boot-file-name", server_tag="all" 32 | ) 33 | assert response.result == 0 34 | assert response.arguments.get("count") == 1 35 | 36 | 37 | def test_kea_dhcp4_remote_global_parameter4_get_all(kea_server: Kea): 38 | response = kea_server.dhcp4.remote_global_parameter4_get_all(server_tag="all") 39 | assert response.result == 0 40 | assert len(response.arguments.get("parameters")) > 0 41 | 42 | 43 | def test_kea_dhcp4_remote_global_parameter4_del(kea_server: Kea): 44 | response = kea_server.dhcp4.remote_global_parameter4_del( 45 | parameter="boot-file-name", server_tag="all" 46 | ) 47 | assert response.result == 0 48 | 49 | 50 | def test_kea_dhcp4_remote_global_parameter4_del_non_existent(kea_server: Kea): 51 | response = kea_server.dhcp4.remote_global_parameter4_del( 52 | parameter="boot-file-name", server_tag="all" 53 | ) 54 | assert response.result == 3 55 | -------------------------------------------------------------------------------- /tests/dhcp4/remote/test_kea_dhcp4_remote_network4.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaSharedNetworkNotFoundException 5 | from pyisckea.models.dhcp4.shared_network import SharedNetwork4 6 | from pyisckea.models.dhcp4.subnet import Subnet4 7 | 8 | """remote-network4 process: 9 | get (non-existent network) 10 | add 11 | list 12 | get 13 | add and delete subnet (for existing shared-network) 14 | del 15 | del (non-existent) 16 | """ 17 | 18 | 19 | def test_kea_dhcp4_remote_network4_get_non_existent(kea_server: Kea): 20 | name = "pyisckea-pytest" 21 | with pytest.raises(KeaSharedNetworkNotFoundException): 22 | kea_server.dhcp4.remote_network4_get(name=name) 23 | 24 | 25 | def test_kea_dhcp4_remote_network4_add(kea_server: Kea, db_remote_map: dict): 26 | name = "pyisckea-pytest" 27 | data = SharedNetwork4(name=name) 28 | shared_networks = [data] 29 | response = kea_server.dhcp4.remote_network4_set( 30 | shared_networks=shared_networks, server_tags=["all"], remote_map=db_remote_map 31 | ) 32 | 33 | assert response.result == 0 34 | assert len(response.arguments.get("shared-networks", [])) > 0 35 | 36 | 37 | def test_kea_dhcp4_remote_network4_list(kea_server: Kea, db_remote_map: dict): 38 | shared_networks = kea_server.dhcp4.remote_network4_list( 39 | server_tags=["pyisckea-1"], remote_map=db_remote_map 40 | ) 41 | assert shared_networks 42 | assert len(shared_networks) > 0 43 | 44 | 45 | def test_kea_dhcp4_remote_network4_get(kea_server: Kea, db_remote_map: dict): 46 | name = "pyisckea-pytest" 47 | shared_network = kea_server.dhcp4.remote_network4_get( 48 | name=name, remote_map=db_remote_map 49 | ) 50 | assert shared_network 51 | assert shared_network.name == name 52 | 53 | 54 | def test_kea_dhcp4_remote_subnet4_add_subnet(kea_server: Kea, db_remote_map: dict): 55 | name = "pyisckea-pytest" 56 | 57 | # Create Temporary Subnet 58 | subnet = Subnet4(subnet="192.0.2.32/31", id=40123, shared_network_name=name) 59 | 60 | # Create subnet with shared network assosication 61 | response = kea_server.dhcp4.remote_subnet4_set( 62 | subnet=subnet, server_tags=["all"], remote_map=db_remote_map 63 | ) 64 | assert response.result == 0 65 | assert len(response.arguments.get("subnets", [])) > 0 66 | 67 | 68 | def test_kea_dhcp4_remote_subnet4_del_by_id(kea_server: Kea, db_remote_map: dict): 69 | response = kea_server.dhcp4.remote_subnet4_del_by_id( 70 | subnet_id=40123, remote_map=db_remote_map 71 | ) 72 | assert response.result == 0 73 | assert response.arguments.get("count") == 1 74 | 75 | 76 | def test_kea_dhcp4_remote_network4_del(kea_server: Kea, db_remote_map: dict): 77 | response = kea_server.dhcp4.remote_network4_del( 78 | name="pyisckea-pytest", keep_subnets=False, remote_map=db_remote_map 79 | ) 80 | assert response.result == 0 81 | assert response.arguments.get("count") == 1 82 | 83 | 84 | def test_kea_dhcp4_remote_network4_del_non_existent( 85 | kea_server: Kea, db_remote_map: dict 86 | ): 87 | response = kea_server.dhcp4.remote_network4_del( 88 | name="pyisckea-pytest", remote_map=db_remote_map 89 | ) 90 | assert response.result == 3 91 | assert response.arguments["count"] == 0 92 | -------------------------------------------------------------------------------- /tests/dhcp4/remote/test_kea_dhcp4_remote_option4_global.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.generic.option_data import OptionData 3 | 4 | """remote-option-def4 process: 5 | 6 | get (non existent) 7 | set 8 | get 9 | get-all 10 | del 11 | """ 12 | 13 | 14 | def test_kea_dhcp4_remote_option4_global_get_non_existent(kea_server: Kea): 15 | response = kea_server.dhcp4.remote_option4_global_get( 16 | option_code=6, option_space="dhcp4", server_tag="all" 17 | ) 18 | assert response.result == 3 19 | assert response.arguments.get("count") == 0 20 | 21 | 22 | def test_kea_dhcp4_remote_option4_global_set(kea_server: Kea): 23 | option_data = OptionData(name="domain-name-servers", data="192.0.2.111") 24 | response = kea_server.dhcp4.remote_option4_global_set( 25 | option_data=option_data, server_tag="all" 26 | ) 27 | assert response.result == 0 28 | assert len(response.arguments.get("options", [])) > 0 29 | 30 | 31 | def test_kea_dhcp4_remote_option4_global_del(kea_server: Kea): 32 | response = kea_server.dhcp4.remote_option4_global_del( 33 | option_code=6, option_space="dhcp4", server_tag="all" 34 | ) 35 | assert response.result == 0 36 | assert response.arguments.get("count") == 1 37 | 38 | 39 | def test_kea_dhcp4_remote_option4_global_del_non_existent(kea_server: Kea): 40 | response = kea_server.dhcp4.remote_option4_global_del( 41 | option_code=6, option_space="dhcp4", server_tag="all" 42 | ) 43 | assert response.result == 3 44 | assert response.arguments.get("count") == 0 45 | -------------------------------------------------------------------------------- /tests/dhcp4/remote/test_kea_dhcp4_remote_option4_network.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.dhcp4.shared_network import SharedNetwork4 3 | from pyisckea.models.generic.option_data import OptionData 4 | 5 | """remote-option4-network process: 6 | 7 | del (non existent) 8 | set 9 | del 10 | """ 11 | 12 | 13 | def test_kea_dhcp4_remote_option4_network_prepare(kea_server: Kea): 14 | shared_network = SharedNetwork4(name="pyisckea-network") 15 | response = kea_server.dhcp4.remote_network4_set( 16 | shared_networks=[shared_network], server_tags=["all"] 17 | ) 18 | assert response.result == 0 19 | 20 | 21 | def test_kea_dhcp4_remote_option4_network_del_non_existent(kea_server: Kea): 22 | response = kea_server.dhcp4.remote_option4_network_del( 23 | shared_network="pyisckea-network", option_code=6, option_space="dhcp4" 24 | ) 25 | assert response.result == 3 26 | assert response.arguments.get("count") == 0 27 | 28 | 29 | def test_kea_dhcp4_remote_option4_network_set(kea_server: Kea): 30 | option_data = OptionData(name="domain-name-servers", data="192.0.2.111") 31 | response = kea_server.dhcp4.remote_option4_network_set( 32 | shared_network="pyisckea-network", option_data=option_data 33 | ) 34 | assert response.result == 0 35 | assert len(response.arguments.get("options", [])) > 0 36 | 37 | 38 | def test_kea_dhcp4_remote_option4_network_del(kea_server: Kea): 39 | response = kea_server.dhcp4.remote_option4_network_del( 40 | shared_network="pyisckea-network", option_code=6, option_space="dhcp4" 41 | ) 42 | assert response.result == 0 43 | assert response.arguments.get("count") == 1 44 | 45 | 46 | def test_kea_dhcp4_remote_option4_network_cleanup(kea_server: Kea): 47 | response = kea_server.dhcp4.remote_network4_del( 48 | name="pyisckea-network", keep_subnets=False 49 | ) 50 | assert response.result == 0 51 | -------------------------------------------------------------------------------- /tests/dhcp4/remote/test_kea_dhcp4_remote_option4_pool.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.dhcp4.subnet import Subnet4 3 | from pyisckea.models.generic.option_data import OptionData 4 | from pyisckea.models.generic.pool import Pool 5 | 6 | """remote-option4-pool process: 7 | 8 | del (non existent) 9 | set 10 | del 11 | """ 12 | 13 | 14 | def test_kea_dhcp4_remote_option4_pool_prepare(kea_server: Kea): 15 | subnet = Subnet4( 16 | id=40123, subnet="192.0.2.0/24", pools=[Pool(pool="192.0.2.100-192.0.2.200")] 17 | ) 18 | response = kea_server.dhcp4.remote_subnet4_set(subnet=subnet, server_tags=["all"]) 19 | assert response.result == 0 20 | 21 | 22 | def test_kea_dhcp4_remote_option4_pool_del_non_existent(kea_server: Kea): 23 | response = kea_server.dhcp4.remote_option4_pool_del( 24 | pool="192.0.2.100-192.168.0.200", option_code=6, option_space="dhcp4" 25 | ) 26 | assert response.result == 3 27 | assert response.arguments.get("count") == 0 28 | 29 | 30 | def test_kea_dhcp4_remote_option4_pool_set(kea_server: Kea): 31 | option_data = OptionData(name="domain-name-servers", data="192.0.2.111") 32 | response = kea_server.dhcp4.remote_option4_pool_set( 33 | pool="192.0.2.100-192.0.2.200", option_data=option_data 34 | ) 35 | assert response.result == 0 36 | assert len(response.arguments.get("options", [])) > 0 37 | 38 | 39 | def test_kea_dhcp4_remote_option4_pool_del(kea_server: Kea): 40 | response = kea_server.dhcp4.remote_option4_pool_del( 41 | pool="192.0.2.100-192.0.2.200", option_code=6, option_space="dhcp4" 42 | ) 43 | assert response.result == 0 44 | assert response.arguments.get("count") == 1 45 | 46 | 47 | def test_kea_dhcp4_remote_option4_pool_cleanup(kea_server: Kea): 48 | response = kea_server.dhcp4.remote_subnet4_del_by_id(subnet_id=40123) 49 | assert response.result == 0 50 | -------------------------------------------------------------------------------- /tests/dhcp4/remote/test_kea_dhcp4_remote_option4_subnet.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.dhcp4.subnet import Subnet4 3 | from pyisckea.models.generic.option_data import OptionData 4 | 5 | """remote-option4-subnet process: 6 | 7 | del (non existent) 8 | set 9 | del 10 | """ 11 | 12 | 13 | def test_kea_dhcp4_remote_option4_subnet_prepare(kea_server: Kea): 14 | subnet = Subnet4(id=40123, subnet="192.0.2.0/24") 15 | response = kea_server.dhcp4.remote_subnet4_set(subnet=subnet, server_tags=["all"]) 16 | assert response.result == 0 17 | 18 | 19 | def test_kea_dhcp4_remote_option4_subnet_del_non_existent(kea_server: Kea): 20 | response = kea_server.dhcp4.remote_option4_subnet_del( 21 | subnet_id=40123, option_code=6, option_space="dhcp4" 22 | ) 23 | assert response.result == 3 24 | assert response.arguments.get("count") == 0 25 | 26 | 27 | def test_kea_dhcp4_remote_option4_subnet_set(kea_server: Kea): 28 | option_data = OptionData(name="domain-name-servers", data="192.0.2.111") 29 | response = kea_server.dhcp4.remote_option4_subnet_set( 30 | subnet_id=40123, option_data=option_data 31 | ) 32 | assert response.result == 0 33 | assert len(response.arguments.get("options", [])) > 0 34 | 35 | 36 | def test_kea_dhcp4_remote_option4_subnet_del(kea_server: Kea): 37 | response = kea_server.dhcp4.remote_option4_subnet_del( 38 | subnet_id=40123, option_code=6, option_space="dhcp4" 39 | ) 40 | assert response.result == 0 41 | assert response.arguments.get("count") == 1 42 | 43 | 44 | def test_kea_dhcp4_remote_option4_subnet_cleanup(kea_server: Kea): 45 | response = kea_server.dhcp4.remote_subnet4_del_by_id(subnet_id=40123) 46 | assert response.result == 0 47 | -------------------------------------------------------------------------------- /tests/dhcp4/remote/test_kea_dhcp4_remote_option_def4.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.generic.option_def import OptionDef 3 | 4 | """remote-option-def4 process: 5 | 6 | get (non existent) 7 | set 8 | get 9 | get-all 10 | del 11 | """ 12 | 13 | 14 | def test_kea_dhcp4_remote_option_def4_get_non_existent(kea_server: Kea): 15 | response = kea_server.dhcp4.remote_option_def4_get( 16 | option_code=1, option_space="pyisckea", server_tag="all" 17 | ) 18 | assert response.result == 3 19 | assert response.arguments.get("count") == 0 20 | 21 | 22 | def test_kea_dhcp4_remote_option_def4_set(kea_server: Kea): 23 | option_def = OptionDef(name="subopt1", code=1, space="pyisckea", type="string") 24 | response = kea_server.dhcp4.remote_option_def4_set( 25 | option_def=option_def, server_tag="all" 26 | ) 27 | assert response.result == 0 28 | assert len(response.arguments.get("option-defs", [])) > 0 29 | 30 | 31 | def test_kea_dhcp4_remote_option_def4_del(kea_server: Kea): 32 | response = kea_server.dhcp4.remote_option_def4_del( 33 | option_code=1, option_space="pyisckea", server_tag="all" 34 | ) 35 | assert response.result == 0 36 | assert response.arguments.get("count") == 1 37 | 38 | 39 | def test_kea_dhcp4_remote_option_def4_del_non_existent(kea_server: Kea): 40 | response = kea_server.dhcp4.remote_option_def4_del( 41 | option_code=1, option_space="pyisckea", server_tag="all" 42 | ) 43 | assert response.result == 3 44 | assert response.arguments.get("count") == 0 45 | -------------------------------------------------------------------------------- /tests/dhcp4/remote/test_kea_dhcp4_remote_reservation.py: -------------------------------------------------------------------------------- 1 | """The reason why I have created 'remote_reservation' although the API commands are 'reservation- 2 | is because you need a database to use these commands, they don't work with a mem file... Host reservations 3 | must be configured in the global dhcp configuration files as per the documentation: 4 | 5 | https://kea.readthedocs.io/en/kea-2.2.0/arm/hooks.html#hooks-host-cmds 6 | 7 | "To use the commands that change reservation information (i.e. reservation-add and reservation-del), the hosts database 8 | must be specified and it must not operate in read-only mode 9 | (for details, see the hosts-databases descriptions in DHCPv4 Hosts Database Configuration and DHCPv6 Hosts Database Configuration)." 10 | """ 11 | 12 | import pytest 13 | 14 | from pyisckea import Kea 15 | from pyisckea.exceptions import KeaException, KeaReservationNotFoundException 16 | from pyisckea.models.dhcp4.subnet import Subnet4 17 | 18 | """reservation process: 19 | reservation-get non existent 20 | reservation-add 21 | reservation-add existing 22 | reservation-get-all 23 | reservation-get 24 | reservation-get-by-hostname 25 | reservation-get-by-id 26 | reservation-get-page 27 | reservation-del 28 | reservation-del non existent 29 | """ 30 | 31 | 32 | def test_kea_dhcp4_remote_reservation_prepare(kea_server: Kea): 33 | subnet = Subnet4(id=40123, subnet="192.0.2.32/31") 34 | response = kea_server.dhcp4.remote_subnet4_set(subnet=subnet, server_tags=["all"]) 35 | assert response.result == 0 36 | 37 | 38 | def test_kea_dhcp4_remote_reservation_get_non_existent(kea_server: Kea): 39 | with pytest.raises(KeaReservationNotFoundException): 40 | kea_server.dhcp4.reservation_get_by_identifier( 41 | subnet_id=40123, 42 | identifier_type="hw-address", 43 | identifier="aa:bb:cc:dd:ee:ff", 44 | ) 45 | 46 | 47 | def test_kea_dhcp4_remote_reservation_add(kea_server: Kea): 48 | backend_updated = kea_server.dhcp4.config_backend_pull() 49 | assert backend_updated.result == 0 50 | 51 | response = kea_server.dhcp4.reservation_add( 52 | ip_address="192.0.2.33", 53 | hw_address="aa:bb:cc:dd:ee:ff", 54 | hostname="pyisckea-reservation", 55 | subnet_id=40123, 56 | ) 57 | assert response.result == 0 58 | 59 | 60 | def test_kea_dhcp4_remote_reservation_add_existing(kea_server: Kea): 61 | response = kea_server.dhcp4.reservation_add( 62 | ip_address="192.0.2.33", 63 | hw_address="aa:bb:cc:dd:ee:ff", 64 | hostname="pyisckea-reservation", 65 | subnet_id=40123, 66 | ) 67 | 68 | assert response.result == 1 69 | 70 | 71 | def test_kea_dhcp4_remote_reservation_get_all(kea_server: Kea): 72 | reservations = kea_server.dhcp4.reservation_get_all(subnet_id=40123) 73 | assert reservations 74 | assert len(reservations) > 0 75 | 76 | 77 | def test_kea_dhcp4_remote_reservation_get_by_ip(kea_server: Kea): 78 | reservation = kea_server.dhcp4.reservation_get_by_ip_address( 79 | subnet_id=40123, ip_address="192.0.2.33" 80 | ) 81 | assert reservation 82 | assert reservation.hostname == "pyisckea-reservation" 83 | assert reservation.hw_address == "aa:bb:cc:dd:ee:ff" 84 | assert reservation.ip_address == "192.0.2.33" 85 | 86 | 87 | def test_kea_dhcp4_remote_reservation_get_by_hostname(kea_server: Kea): 88 | hostname = "pyisckea-reservation" 89 | reservation = kea_server.dhcp4.reservation_get_by_hostname( 90 | hostname=hostname, subnet_id=40123 91 | ) 92 | assert reservation 93 | assert reservation.hostname == hostname 94 | 95 | 96 | def test_kea_dhcp4_remote_reservation_get_by_identifier(kea_server: Kea): 97 | hw_address = "aa:bb:cc:dd:ee:ff" 98 | reservation = kea_server.dhcp4.reservation_get_by_identifier( 99 | subnet_id=40123, identifier_type="hw-address", identifier=hw_address 100 | ) 101 | assert reservation 102 | assert reservation.hw_address == hw_address 103 | 104 | 105 | def test_kea_dhcp4_remote_reservation_get_page(kea_server: Kea): 106 | reservations = kea_server.dhcp4.reservation_get_page() 107 | assert reservations 108 | assert len(reservations) > 0 109 | 110 | 111 | def test_kea_dhcp4_remote_reservation_get_page_bad_source(kea_server: Kea): 112 | with pytest.raises(KeaException): 113 | kea_server.dhcp4.reservation_get_page(source_index=9999) 114 | 115 | 116 | def test_kea_dhcp4_remote_reservation_del(kea_server: Kea): 117 | response = kea_server.dhcp4.reservation_del_by_ip( 118 | subnet_id=40123, ip_address="192.0.2.33" 119 | ) 120 | assert response.result == 0 121 | 122 | 123 | def test_kea_dhcp4_remote_reservation_cleanup(kea_server: Kea): 124 | response = kea_server.dhcp4.remote_subnet4_del_by_id(subnet_id=40123) 125 | assert response.result == 0 126 | -------------------------------------------------------------------------------- /tests/dhcp4/remote/test_kea_dhcp4_remote_subnet4.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaSubnetNotFoundException 5 | from pyisckea.models.dhcp4.subnet import Subnet4 6 | 7 | """remote-subnet4 process: 8 | get (non-existent subnet) 9 | add 10 | list 11 | get by id 12 | get by prefix 13 | del by id 14 | del non existent by prefix?? 15 | """ 16 | 17 | 18 | def test_kea_dhcp4_remote_subnet4_get_non_existent(kea_server: Kea): 19 | with pytest.raises(KeaSubnetNotFoundException): 20 | kea_server.dhcp4.remote_subnet4_get_by_id(subnet_id=40123) 21 | 22 | 23 | def test_kea_dhcp4_remote_subnet4_add_subnet(kea_server: Kea): 24 | subnet = Subnet4(subnet="192.0.2.32/31", id=40123) 25 | 26 | # Create subnet 27 | response = kea_server.dhcp4.remote_subnet4_set(subnet=subnet, server_tags=["all"]) 28 | assert response.result == 0 29 | assert len(response.arguments.get("subnets", [])) > 0 30 | 31 | 32 | def test_kea_dhcp4_remote_subnet4_list(kea_server: Kea): 33 | subnets = kea_server.dhcp4.remote_subnet4_list(server_tags=["pyisckea-1"]) 34 | assert subnets 35 | assert len(subnets) > 0 36 | 37 | 38 | def test_kea_dhcp4_remote_subnet4_get_by_id(kea_server: Kea): 39 | subnet = kea_server.dhcp4.remote_subnet4_get_by_id(subnet_id=40123) 40 | assert subnet 41 | assert subnet.id == 40123 42 | assert subnet.subnet == "192.0.2.32/31" 43 | 44 | 45 | def test_kea_dhcp4_remote_subnet4_get_by_prefix(kea_server: Kea): 46 | subnet = kea_server.dhcp4.remote_subnet4_get_by_prefix(prefix="192.0.2.32/31") 47 | assert subnet 48 | assert subnet.id == 40123 49 | assert subnet.subnet == "192.0.2.32/31" 50 | 51 | 52 | def test_kea_dhcp4_remote_subnet4_del_by_id(kea_server: Kea): 53 | response = kea_server.dhcp4.remote_subnet4_del_by_id(subnet_id=40123) 54 | assert response.result == 0 55 | assert response.arguments.get("count") == 1 56 | 57 | 58 | def test_kea_dhcp4_remote_subnet4_del_by_prefix_non_existent(kea_server: Kea): 59 | response = kea_server.dhcp4.remote_subnet4_del_by_prefix(prefix="192.0.2.32/31") 60 | assert response.result == 3 61 | assert response.arguments.get("count") == 0 62 | -------------------------------------------------------------------------------- /tests/dhcp4/test_kea_dhcp4.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | 3 | 4 | def test_kea_dhcp4_build_report(kea_server: Kea): 5 | response = kea_server.dhcp4.build_report() 6 | assert response.result == 0 7 | assert response.text 8 | 9 | 10 | def test_kea_dhcp4_config_get(kea_server: Kea): 11 | response = kea_server.dhcp4.config_get() 12 | assert response.result == 0 13 | assert "Dhcp4" in response.arguments 14 | 15 | 16 | def test_kea_dhcp4_config_test(kea_server: Kea): 17 | config = kea_server.dhcp4.cached_config 18 | if config.get("hash"): # Temp workaround 19 | del config["hash"] 20 | 21 | response = kea_server.dhcp4.config_test(config=config) 22 | assert response.result == 0 23 | 24 | 25 | def test_kea_dhcp4_config_set(kea_server: Kea): 26 | config = kea_server.dhcp4.cached_config 27 | if config.get("hash"): # Temp workaround 28 | del config["hash"] 29 | 30 | response = kea_server.dhcp4.config_set(config=config) 31 | assert response.result == 0 32 | 33 | 34 | def test_kea_dhcp4_config_write(kea_server: Kea): 35 | filename = "/usr/local/etc/kea/kea-dhcp4.conf" 36 | response = kea_server.dhcp4.config_write(filename=filename) 37 | assert response.result == 0 38 | assert response.arguments["filename"] == filename 39 | assert response.arguments["size"] > 0 40 | 41 | 42 | def test_kea_dhcp4_config_reload(kea_server: Kea): 43 | response = kea_server.dhcp4.config_reload() 44 | assert response.result == 0 45 | assert response.text == "Configuration successful." 46 | 47 | 48 | def test_kea_dhcp4_dhcp_disable(kea_server: Kea): 49 | response = kea_server.dhcp4.dhcp_disable(max_period=60) 50 | assert response.result == 0 51 | assert response.text == "DHCPv4 service disabled for 60 seconds" 52 | 53 | status = kea_server.dhcp4.status_get() 54 | assert status.reload < 20 55 | 56 | 57 | def test_kea_dhcp4_dhcp_enable(kea_server: Kea): 58 | response = kea_server.dhcp4.dhcp_enable() 59 | assert response.result == 0 60 | assert response.text == "DHCP service successfully enabled" 61 | 62 | 63 | def test_kea_dhcp4_list_commands(kea_server: Kea): 64 | response = kea_server.dhcp4.list_commands() 65 | assert response.result == 0 66 | assert "config-get" in response.arguments 67 | 68 | 69 | def test_kea_dhcp4_status_get(kea_server: Kea): 70 | response = kea_server.dhcp4.status_get() 71 | assert response.pid 72 | assert response.uptime 73 | 74 | 75 | def test_kea_dhcp4_version_get(kea_server: Kea): 76 | response = kea_server.dhcp4.version_get() 77 | assert response.result == 0 78 | 79 | 80 | def test_kea_dhcp4_shutdown(kea_server: Kea): 81 | response = kea_server.dhcp4.shutdown() 82 | assert response == 0 83 | -------------------------------------------------------------------------------- /tests/dhcp4/test_kea_dhcp4_cache.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.dhcp4.reservation import Reservation4 3 | 4 | """cache process: 5 | cache get (none) 6 | cache insert 7 | cache get 8 | cache get_by_id 9 | cache_size 10 | cache remove 11 | cache get_by_id (non existent) 12 | cache_clear 13 | cache_flush 14 | cache_write 15 | cache_load 16 | """ 17 | 18 | 19 | def test_kea_dhcp4_cache_get_none(kea_server: Kea): 20 | response = kea_server.dhcp4.cache_get() 21 | assert len(response) == 0 22 | 23 | 24 | def test_kea_dhcp4_cache_insert(kea_server: Kea): 25 | reservation = Reservation4(ip_address="192.0.2.33", hw_address="aa:bb:cc:dd:ee:ff") 26 | response = kea_server.dhcp4.cache_insert(subnet_id=40123, reservation=reservation) 27 | assert response.result == 0 28 | 29 | 30 | def test_kea_dhcp4_cache_get(kea_server: Kea): 31 | response = kea_server.dhcp4.cache_get() 32 | assert len(response) > 0 33 | 34 | 35 | def test_kea_dhcp4_cache_get_by_id(kea_server: Kea): 36 | response = kea_server.dhcp4.cache_get_by_id( 37 | identifier_type="hw-address", identifier="aa:bb:cc:dd:ee:ff" 38 | ) 39 | assert len(response) > 0 40 | 41 | 42 | def test_kea_dhcp4_cache_size(kea_server: Kea): 43 | response = kea_server.dhcp4.cache_size() 44 | assert response.result == 0 45 | assert response.arguments.get("size") > 0 46 | 47 | 48 | def test_kea_dhcp4_cache_remove(kea_server: Kea): 49 | response = kea_server.dhcp4.cache_remove(subnet_id=40123, ip_address="192.0.2.33") 50 | assert response.result == 0 51 | 52 | 53 | def test_kea_dhcp4_cache_get_by_id_non_existent(kea_server: Kea): 54 | response = kea_server.dhcp4.cache_get_by_id( 55 | identifier_type="hw-address", identifier="aa:bb:cc:dd:ee:ff" 56 | ) 57 | assert len(response) == 0 58 | 59 | 60 | def test_kea_dhcp4_cache_clear(kea_server: Kea): 61 | response = kea_server.dhcp4.cache_clear() 62 | assert response.result == 0 63 | 64 | 65 | def test_kea_dhcp4_cache_flush(kea_server: Kea): 66 | response = kea_server.dhcp4.cache_flush(number=123) 67 | assert response.result == 0 68 | 69 | 70 | def test_kea_dhcp4_cache_write(kea_server: Kea): 71 | response = kea_server.dhcp4.cache_write(filepath="/tmp/kea-host-cache.json") 72 | assert response.result == 0 73 | 74 | 75 | def test_kea_dhcp4_cache_load(kea_server: Kea): 76 | response = kea_server.dhcp4.cache_load(filepath="/tmp/kea-host-cache.json") 77 | assert response.result == 0 78 | -------------------------------------------------------------------------------- /tests/dhcp4/test_kea_dhcp4_class.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaClientClassNotFoundException 5 | from pyisckea.models.dhcp4.client_class import ClientClass4 6 | 7 | """class process: 8 | get (non-existent client-class) 9 | add 10 | add (again to check duplicate client-class) 11 | get 12 | list 13 | update 14 | del 15 | del (non-existent client-class) 16 | """ 17 | 18 | 19 | def test_kea_dhcp4_class_get_non_existent(kea_server: Kea): 20 | with pytest.raises(KeaClientClassNotFoundException): 21 | kea_server.dhcp4.class_get(name="ipxe_efi_x64") 22 | 23 | 24 | def test_kea_dhcp4_class_add(kea_server: Kea): 25 | client_class = ClientClass4( 26 | name="ipxe_efi_x64", 27 | test="option[93].hex == 0x0009", 28 | next_server="192.0.2.254", 29 | server_hostname="hal9000", 30 | boot_file_name="/dev/null", 31 | ) 32 | response = kea_server.dhcp4.class_add(client_class=client_class) 33 | assert response.result == 0 34 | 35 | 36 | def test_kea_dhcp4_class_get(kea_server: Kea): 37 | response = kea_server.dhcp4.class_get(name="ipxe_efi_x64") 38 | assert response 39 | assert response.name == "ipxe_efi_x64" 40 | 41 | 42 | def test_kea_dhcp4_class_list(kea_server: Kea): 43 | response = kea_server.dhcp4.class_list() 44 | assert len(response) > 0 45 | 46 | 47 | def test_kea_dhcp4_class_del(kea_server: Kea): 48 | response = kea_server.dhcp4.class_del(name="ipxe_efi_x64") 49 | assert response.result == 0 50 | 51 | 52 | def test_kea_dhcp4_class_del_non_existent(kea_server: Kea): 53 | response = kea_server.dhcp4.class_del(name="ipxe_efi_x64") 54 | assert response.result == 3 55 | -------------------------------------------------------------------------------- /tests/dhcp4/test_kea_dhcp4_ha.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | 3 | """ha process: 4 | status-get (get HA mode) 5 | """ 6 | 7 | 8 | def test_kea_dhcp4_ha_check_primary(kea_server: Kea): 9 | status = kea_server.dhcp4.status_get() 10 | 11 | assert status.high_availability 12 | assert len(status.high_availability) > 0 13 | 14 | ha_settings = status.high_availability[0] 15 | 16 | assert ha_settings.ha_mode == "hot-standby" 17 | assert ha_settings.ha_servers.local.role == "primary" 18 | assert ha_settings.ha_servers.local.state in ["hot-standby", "waiting"] 19 | assert ha_settings.ha_servers.remote.last_state in ["hot-standby", "ready", ""] 20 | 21 | 22 | def test_kea_dhcp4_ha_continue_not_paused(kea_server: Kea): 23 | response = kea_server.dhcp4.ha_continue() 24 | assert response.result == 0 25 | assert "not paused" in response.text 26 | 27 | 28 | def test_kea_dhcp4_ha_heartbeat(kea_server: Kea): 29 | response = kea_server.dhcp4.ha_heartbeat() 30 | assert response.result == 0 31 | 32 | 33 | def test_kea_dhcp4_ha_maintenance_cancel_wrong_state(kea_server: Kea): 34 | response = kea_server.dhcp4.ha_maintenance_cancel() 35 | assert response.result == 1 36 | assert "not in the partner-in-maintenance state" in response.text 37 | 38 | 39 | def test_kea_dhcp4_ha_maintenance_notify_not_in_maintenance_mode(kea_server: Kea): 40 | response = kea_server.dhcp4.ha_maintenance_notify(cancel=True) 41 | assert response.result == 1 42 | assert "not in the in-maintenance state" in response.text 43 | 44 | 45 | def test_kea_dhcp4_ha_maintenance_start(kea_server: Kea): 46 | response = kea_server.dhcp4.ha_maintenance_start() 47 | assert response.result == 0 48 | 49 | status = kea_server.dhcp4.status_get() 50 | ha_settings = status.high_availability[0] 51 | assert ha_settings.ha_servers.local.state == "partner-in-maintenance" 52 | 53 | 54 | def test_kea_dhcp4_ha_maintenance_cancel(kea_server: Kea): 55 | response = kea_server.dhcp4.ha_maintenance_cancel() 56 | assert response.result == 0 57 | 58 | 59 | def test_kea_dhcp4_ha_maintenance_reset(kea_server: Kea): 60 | response = kea_server.dhcp4.ha_reset() 61 | assert response.result == 0 62 | 63 | 64 | def test_kea_dhcp4_ha_reset_peer(kea_server: Kea): 65 | kea_server.port = ( 66 | 8081 # Need to find a better way to integrate this into pytest as a fixture... 67 | ) 68 | response = kea_server.dhcp4.ha_reset() 69 | assert response.result == 0 70 | 71 | kea_server.dhcp4.ha_maintenance_cancel() 72 | 73 | 74 | def test_kea_dhcp4_ha_sync(kea_server: Kea): 75 | response = kea_server.dhcp4.ha_sync(partner_server="server2", max_period=30) 76 | assert response.result == 0 77 | 78 | 79 | def test_kea_dhcp4_ha_synbc_complete_notify(kea_server: Kea): 80 | response = kea_server.dhcp4.ha_sync_complete_notify() 81 | assert response.result == 0 82 | -------------------------------------------------------------------------------- /tests/dhcp4/test_kea_dhcp4_lease4.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaLeaseNotFoundException 5 | from pyisckea.models.dhcp4.subnet import Subnet4 6 | 7 | """lease4 process: 8 | get (non-existent lease) 9 | add 10 | add (again to check duplicate lease) 11 | get 12 | get-all 13 | get-page 14 | get-by-client-id 15 | get-by-hw-address 16 | update 17 | del 18 | wipe 19 | reclaim 20 | """ 21 | 22 | 23 | def test_kea_dhcp4_lease4_get_non_exsistent(kea_server: Kea): 24 | with pytest.raises(KeaLeaseNotFoundException): 25 | kea_server.dhcp4.lease4_get(ip_address="192.0.2.32") 26 | 27 | 28 | def test_kea_dhcp4_lease4_get_all_none(kea_server: Kea): 29 | with pytest.raises(KeaLeaseNotFoundException): 30 | kea_server.dhcp4.lease4_get_all() 31 | 32 | 33 | def test_kea_dhcp4_lease4_add(kea_server: Kea): 34 | # Add Temporary Subnet 35 | data = Subnet4(id=40123, subnet="192.0.2.32/31") 36 | subnets = [data] 37 | response = kea_server.dhcp4.subnet4_add(subnets=subnets) 38 | assert response.result == 0 39 | 40 | # Add Lease 41 | lease_response = kea_server.dhcp4.lease4_add( 42 | ip_address="192.0.2.32", hw_address="aa:bb:cc:11:22:33" 43 | ) 44 | 45 | assert lease_response.result == 0 46 | 47 | delete_response = kea_server.dhcp4.subnet4_del(subnet_id=40123) 48 | assert delete_response.result == 0 49 | 50 | 51 | def test_kea_dhcp4_lease4_get(kea_server: Kea): 52 | response = kea_server.dhcp4.lease4_get(ip_address="192.0.2.32") 53 | assert response 54 | assert response.ip_address == "192.0.2.32" 55 | assert response.hw_address == "aa:bb:cc:11:22:33" 56 | 57 | 58 | def test_kea_dhcp4_lease4_get_all(kea_server: Kea): 59 | response = kea_server.dhcp4.lease4_get_all() 60 | assert len(response) > 0 61 | 62 | 63 | def test_kea_dhcp4_lease4_get_all_subnets(kea_server: Kea): 64 | response = kea_server.dhcp4.lease4_get_all(subnets=[40123]) 65 | assert len(response) > 0 66 | 67 | 68 | def test_kea_dhcp4_lease4_get_page(kea_server: Kea): 69 | response = kea_server.dhcp4.lease4_get_page(limit=100, search_from="start") 70 | assert response.count > 0 71 | 72 | 73 | def test_kea_dhcp4_lease4_get_by_client_id_non_existent(kea_server: Kea): 74 | with pytest.raises(KeaLeaseNotFoundException): 75 | kea_server.dhcp4.lease4_get_by_client_id(client_id="00:00:11:00:00:22") 76 | 77 | 78 | def test_kea_dhcp4_lease4_get_by_hostname_non_existent(kea_server: Kea): 79 | with pytest.raises(KeaLeaseNotFoundException): 80 | kea_server.dhcp4.lease4_get_by_hostname(hostname="bad-hostname") 81 | 82 | 83 | def test_kea_dhcp4_lease4_get_by_hw_address(kea_server: Kea): 84 | response = kea_server.dhcp4.lease4_get_by_hw_address(hw_address="aa:bb:cc:11:22:33") 85 | assert response.ip_address == "192.0.2.32" 86 | assert response.hw_address == "aa:bb:cc:11:22:33" 87 | assert response.subnet_id == 40123 88 | 89 | 90 | def test_kea_dhcp4_lease4_get_by_hw_address_non_existent(kea_server: Kea): 91 | with pytest.raises(KeaLeaseNotFoundException): 92 | kea_server.dhcp4.lease4_get_by_hw_address(hw_address="00:00:11:00:00:22") 93 | 94 | 95 | def test_kea_dhcp4_lease4_del(kea_server: Kea): 96 | response = kea_server.dhcp4.lease4_del(ip_address="192.0.2.32") 97 | assert response.result == 0 98 | 99 | 100 | def test_kea_dhcp4_lease4_del_non_exsistent(kea_server: Kea): 101 | response = kea_server.dhcp4.lease4_del(ip_address="192.0.2.32") 102 | assert response.result == 3 103 | -------------------------------------------------------------------------------- /tests/dhcp4/test_kea_dhcp4_network4.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaSharedNetworkNotFoundException 5 | from pyisckea.models.dhcp4.shared_network import SharedNetwork4 6 | from pyisckea.models.dhcp4.subnet import Subnet4 7 | 8 | """network4 process: 9 | get (non-existent network) 10 | add 11 | add (again to check duplicate subnets) 12 | get 13 | subnet-add 14 | subnet-del 15 | update (full update without option 3... should disappear) 16 | del 17 | list 18 | """ 19 | 20 | 21 | def test_kea_dhcp4_network4_get_non_existent(kea_server: Kea): 22 | name = "pyisckea-pytest" 23 | with pytest.raises(KeaSharedNetworkNotFoundException): 24 | kea_server.dhcp4.network4_get(name=name) 25 | 26 | 27 | def test_kea_dhcp4_network4_add(kea_server: Kea): 28 | name = "pyisckea-pytest" 29 | data = SharedNetwork4(name=name) 30 | shared_networks = [data] 31 | response = kea_server.dhcp4.network4_add(shared_networks) 32 | assert response.result == 0 33 | 34 | 35 | def test_kea_dhcp4_network4_add_duplicate(kea_server: Kea): 36 | name = "pyisckea-pytest" 37 | data = SharedNetwork4(name=name) 38 | shared_networks = [data] 39 | response = kea_server.dhcp4.network4_add(shared_networks) 40 | assert response.result == 1 41 | 42 | 43 | def test_kea_dhcp4_network4_get(kea_server: Kea): 44 | name = "pyisckea-pytest" 45 | response = kea_server.dhcp4.network4_get(name=name) 46 | assert response 47 | assert response.name == name 48 | 49 | 50 | def test_kea_dhcp4_network4_subnet_add(kea_server: Kea): 51 | name = "pyisckea-pytest" 52 | 53 | # Create Temporary Subnet 54 | data = Subnet4(subnet="192.0.2.32/31", id=40123) 55 | subnets = [data] 56 | subnet = kea_server.dhcp4.subnet4_add(subnets=subnets) 57 | assert subnet.result == 0 58 | 59 | # Assign Subnet to existing shared network 60 | response = kea_server.dhcp4.network4_subnet_add(name=name, subnet_id=data.id) 61 | assert response.result == 0 62 | 63 | # Confirm shared-network has at least 1 subnet 64 | shared_network = kea_server.dhcp4.network4_get(name=name) 65 | assert shared_network 66 | assert len(shared_network.subnet4) == 1 67 | 68 | 69 | def test_kea_dhcp4_network4_subnet_del(kea_server: Kea): 70 | name = "pyisckea-pytest" 71 | 72 | # Delete temporary subnet assosication 73 | response = kea_server.dhcp4.network4_subnet_del(name=name, subnet_id=40123) 74 | assert response.result == 0 75 | 76 | # Delete Temporary Subnet 77 | deleted_subnet = kea_server.dhcp4.subnet4_del(subnet_id=40123) 78 | assert deleted_subnet.result == 0 79 | 80 | # Confirm Shared Network now has 0 subnets 81 | shared_network = kea_server.dhcp4.network4_get(name=name) 82 | assert shared_network 83 | assert len(shared_network.subnet4) == 0 84 | 85 | 86 | def test_kea_dhcp4_network4_del(kea_server: Kea): 87 | name = "pyisckea-pytest" 88 | response = kea_server.dhcp4.network4_del(name=name) 89 | assert response.result == 0 90 | -------------------------------------------------------------------------------- /tests/dhcp4/test_kea_dhcp4_parser.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from pyisckea import Kea 4 | from pyisckea.parsers.dhcp4 import Dhcp4Parser 5 | 6 | 7 | def test_kea_dhcp4_parser_parse_config(kea_server: Kea): 8 | cached_config = kea_server.dhcp4.cached_config 9 | parsed = Dhcp4Parser(config=cached_config) 10 | 11 | assert parsed.config.interfaces_config 12 | assert parsed.config.control_socket 13 | assert json.dumps( 14 | parsed.config.model_dump(exclude_none=True, by_alias=True), 15 | indent=4, 16 | sort_keys=True, 17 | ) 18 | 19 | 20 | def test_kea_dhcp4_parser_config_test(kea_server: Kea): 21 | cached_config = kea_server.dhcp4.cached_config 22 | parsed = Dhcp4Parser(config=cached_config) 23 | config_to_test = { 24 | "Dhcp4": parsed.config.model_dump( 25 | exclude_none=True, exclude_unset=True, by_alias=True 26 | ) 27 | } 28 | 29 | test_results = kea_server.dhcp4.config_test(config=config_to_test) 30 | assert test_results.result == 0 31 | 32 | # Remove hash if exist for now until tests are created to take that into account 33 | if cached_config.get("hash"): 34 | del cached_config["hash"] 35 | 36 | cached_config_json = json.dumps(cached_config, indent=4) 37 | parsed_config_json = json.dumps(config_to_test, indent=4, sort_keys=True) 38 | assert cached_config_json == parsed_config_json 39 | -------------------------------------------------------------------------------- /tests/dhcp4/test_kea_dhcp4_statistics.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | 3 | 4 | def test_kea_dhcp4_statistic_get_all(kea_server: Kea): 5 | response = kea_server.dhcp4.statistic_get_all() 6 | assert response.result == 0 7 | 8 | 9 | def test_kea_dhcp4_statistic_get(kea_server: Kea): 10 | response = kea_server.dhcp4.statistic_get(name="pkt4-received") 11 | assert response.result == 0 12 | assert response.arguments["pkt4-received"] 13 | 14 | 15 | def test_kea_dhcp4_statistic_get_bad_name(kea_server: Kea): 16 | response = kea_server.dhcp4.statistic_get(name="bad-name-argument") 17 | assert response.result == 0 18 | assert response.arguments == {} 19 | -------------------------------------------------------------------------------- /tests/dhcp4/test_kea_dhcp4_subnet4.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaSubnetNotFoundException 5 | from pyisckea.models.dhcp4.subnet import Subnet4 6 | from pyisckea.models.generic.option_data import OptionData 7 | 8 | """subnet4 process: 9 | get (non-existent subnet) 10 | add 11 | add (again to check duplicate subnets) 12 | list 13 | get 14 | delta-add (partial update code 3 option and valid-lifetime) 15 | delta-del (remove valid-lifetime) 16 | update (full update without option 3... should disappear) 17 | del 18 | """ 19 | 20 | 21 | def test_kea_dhcp4_subnet4_get_non_existent(kea_server: Kea): 22 | with pytest.raises(KeaSubnetNotFoundException): 23 | kea_server.dhcp4.subnet4_get(subnet_id=40123) 24 | 25 | 26 | def test_kea_dhcp4_subnet4_add(kea_server: Kea): 27 | option_data = OptionData(data="192.0.2.32", code=3) 28 | data = Subnet4(id=40123, subnet="192.0.2.32/31", option_data=[option_data]) 29 | subnets = [data] 30 | response = kea_server.dhcp4.subnet4_add(subnets=subnets) 31 | assert response.result == 0 32 | 33 | 34 | def test_kea_dhcp4_subnet4_add_existing(kea_server: Kea): 35 | data = Subnet4(id=40123, subnet="192.0.2.32/31") 36 | subnets = [data] 37 | response = kea_server.dhcp4.subnet4_add(subnets=subnets) 38 | assert response.result == 1 39 | 40 | 41 | def test_kea_dhcp4_subnet4_list(kea_server: Kea): 42 | response = kea_server.dhcp4.subnet4_list() 43 | assert response 44 | 45 | 46 | def test_kea_dhcp4_subnet4_get(kea_server: Kea): 47 | response = kea_server.dhcp4.subnet4_get(subnet_id=40123) 48 | assert response 49 | assert response.id == 40123 50 | 51 | 52 | def test_kea_dhcp4_subnet4_delta_add(kea_server: Kea): 53 | data = Subnet4( 54 | id=40123, 55 | subnet="192.0.2.32/31", 56 | min_valid_lifetime=5000, 57 | max_valid_lifetime=7000, 58 | option_data=[{"code": 3, "data": "192.0.2.32"}], 59 | ) 60 | subnets = [data] 61 | 62 | response = kea_server.dhcp4.subnet4_delta_add(subnets=subnets) 63 | assert response.result == 0 64 | 65 | 66 | def test_kea_dhcp4_subnet4_delta_delete(kea_server: Kea): 67 | data = Subnet4( 68 | id=40123, 69 | subnet="192.0.2.32/31", 70 | min_valid_lifetime=5000, 71 | max_valid_lifetime=7000, 72 | option_data=[{"code": 3, "data": "192.0.2.32"}], 73 | ) 74 | subnets = [data] 75 | response = kea_server.dhcp4.subnet4_delta_del(subnets=subnets) 76 | assert response.result == 0 77 | 78 | updated_subnet = kea_server.dhcp4.subnet4_get(subnet_id=40123) 79 | assert updated_subnet 80 | assert updated_subnet.max_valid_lifetime != 7000 81 | 82 | 83 | def test_kea_dhcp4_subnet4_update(kea_server: Kea): 84 | data = Subnet4( 85 | id=40123, 86 | subnet="192.0.2.32/31", 87 | ) 88 | subnets = [data] 89 | response = kea_server.dhcp4.subnet4_update(subnets=subnets) 90 | assert response.result == 0 91 | 92 | updated_subnet = kea_server.dhcp4.subnet4_get(subnet_id=40123) 93 | assert updated_subnet 94 | assert len(updated_subnet.option_data) == 0 95 | 96 | 97 | def test_kea_dhcp4_subnet4_del(kea_server: Kea): 98 | response = kea_server.dhcp4.subnet4_del(subnet_id=40123) 99 | assert response.result == 0 100 | 101 | 102 | def test_kea_dhcp4_subnet4_del_non_existent(kea_server: Kea): 103 | with pytest.raises(KeaSubnetNotFoundException): 104 | kea_server.dhcp4.subnet4_del(subnet_id=40123) 105 | 106 | 107 | def test_kea_dhcp4_subnet4_get_next_available_id(kea_server: Kea): 108 | next_available_id = kea_server.dhcp4.get_next_available_subnet_id() 109 | subnets = kea_server.dhcp4.subnet4_list() 110 | 111 | if subnets: 112 | subnet_ids = [subnet.id for subnet in subnets] 113 | assert next_available_id not in subnet_ids 114 | -------------------------------------------------------------------------------- /tests/dhcp6/remote/test_kea_dhcp6_remote.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.generic.remote_server import RemoteServer 3 | 4 | """remote generic process: 5 | remote-prepare (config backend pull test) 6 | remote-server6-set 7 | remote-server6-get 8 | remote-server6-get (non existent) 9 | remote-server6-get-all 10 | remote-server6-del 11 | """ 12 | 13 | 14 | def test_kea_dhcp6_remote_prepare(kea_server: Kea): 15 | response = kea_server.dhcp6.config_backend_pull() 16 | assert response.result == 0 17 | 18 | 19 | def test_kea_dhcp6_remote_server6_set(kea_server: Kea): 20 | response = kea_server.dhcp6.remote_server6_set( 21 | servers=[RemoteServer(server_tag="pyisckea", description="pyisckea-test")] 22 | ) 23 | assert response.result == 0 24 | 25 | 26 | def test_kea_dhcp6_remote_server6_get(kea_server: Kea): 27 | server = kea_server.dhcp6.remote_server6_get(server_tag="pyisckea") 28 | assert server 29 | assert server.server_tag == "pyisckea" 30 | assert server.description == "pyisckea-test" 31 | 32 | 33 | def test_kea_dhcp6_remote_server6_get_all(kea_server: Kea, db_remote_map: dict): 34 | servers = kea_server.dhcp6.remote_server6_get_all(remote_map=db_remote_map) 35 | assert servers 36 | assert len(servers) > 0 37 | 38 | 39 | def test_kea_dhcp6_remote_server6_del(kea_server: Kea): 40 | response = kea_server.dhcp6.remote_server6_del(servers=["pyisckea"]) 41 | assert response.result == 0 42 | assert "deleted" in response.text 43 | -------------------------------------------------------------------------------- /tests/dhcp6/remote/test_kea_dhcp6_remote_class6.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaClientClassNotFoundException 5 | from pyisckea.models.dhcp6.client_class import ClientClass6 6 | 7 | """remote-class6 process: 8 | 9 | get (non existent) 10 | set 11 | get 12 | get-all 13 | del 14 | """ 15 | 16 | 17 | def test_kea_dhcp6_remote_class6_get_non_existent(kea_server: Kea): 18 | with pytest.raises(KeaClientClassNotFoundException): 19 | kea_server.dhcp6.remote_class6_get(name="ipxe_efi_x64") 20 | 21 | 22 | def test_kea_dhcp6_remote_class6_set(kea_server: Kea): 23 | client_class = ClientClass6( 24 | name="ipxe_efi_x64", 25 | test="option[93].hex == 0x0009", 26 | next_server="2001:db8::111", 27 | server_hostname="hal9000", 28 | boot_file_name="/dev/null", 29 | ) 30 | response = kea_server.dhcp6.remote_class6_set(client_class=client_class) 31 | assert response.result == 0 32 | assert len(response.arguments.get("client-classes", [])) > 0 33 | 34 | 35 | def test_kea_dhcp6_remote_class6_get(kea_server: Kea): 36 | response = kea_server.dhcp6.remote_class6_get(name="ipxe_efi_x64") 37 | assert response 38 | assert response.name == "ipxe_efi_x64" 39 | 40 | 41 | def test_kea_dhcp6_remote_class6_get_all(kea_server: Kea): 42 | response = kea_server.dhcp6.remote_class6_get_all() 43 | assert response 44 | assert len(response) > 0 45 | 46 | 47 | def test_kea_dhcp6_remote_class6_del(kea_server: Kea): 48 | response = kea_server.dhcp6.remote_class6_del(name="ipxe_efi_x64") 49 | assert response.result == 0 50 | assert response.arguments.get("count") == 1 51 | -------------------------------------------------------------------------------- /tests/dhcp6/remote/test_kea_dhcp6_remote_global_parameter6.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | 3 | """remote-global-parameter6 process: 4 | 5 | get (non existent) 6 | set 7 | get 8 | get-all 9 | del 10 | """ 11 | 12 | 13 | def test_kea_dhcp6_remote_global_parameter6_get_non_existent(kea_server: Kea): 14 | response = kea_server.dhcp6.remote_global_parameter6_get( 15 | parameter="t1-percent", server_tag="pyisckea-1" 16 | ) 17 | assert response.result == 3 18 | assert response.arguments.get("count") == 0 19 | 20 | 21 | def test_kea_dhcp6_remote_global_parameter6_set(kea_server: Kea): 22 | response = kea_server.dhcp6.remote_global_parameter6_set( 23 | parameters={"t1-percent": 0.85}, server_tag="all" 24 | ) 25 | assert response.result == 0 26 | assert response.arguments.get("count") == 1 27 | 28 | 29 | def test_kea_dhcp6_remote_global_parameter6_get(kea_server: Kea): 30 | response = kea_server.dhcp6.remote_global_parameter6_get( 31 | parameter="t1-percent", server_tag="all" 32 | ) 33 | assert response.result == 0 34 | assert response.arguments.get("count") == 1 35 | 36 | 37 | def test_kea_dhcp6_remote_global_parameter6_get_all(kea_server: Kea): 38 | response = kea_server.dhcp6.remote_global_parameter6_get_all(server_tag="all") 39 | assert response.result == 0 40 | assert len(response.arguments.get("parameters")) > 0 41 | 42 | 43 | def test_kea_dhcp6_remote_global_parameter6_del(kea_server: Kea): 44 | response = kea_server.dhcp6.remote_global_parameter6_del( 45 | parameter="t1-percent", server_tag="all" 46 | ) 47 | assert response.result == 0 48 | 49 | 50 | def test_kea_dhcp6_remote_global_parameter6_del_non_existent(kea_server: Kea): 51 | response = kea_server.dhcp6.remote_global_parameter6_del( 52 | parameter="t1-percent", server_tag="all" 53 | ) 54 | assert response.result == 3 55 | -------------------------------------------------------------------------------- /tests/dhcp6/remote/test_kea_dhcp6_remote_network6.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaSharedNetworkNotFoundException 5 | from pyisckea.models.dhcp6.shared_network import SharedNetwork6 6 | from pyisckea.models.dhcp6.subnet import Subnet6 7 | 8 | """remote-network6 process: 9 | get (non-existent network) 10 | add 11 | list 12 | get 13 | add and delete subnet (for existing shared-network) 14 | del 15 | del (non-existent) 16 | """ 17 | 18 | 19 | def test_kea_dhcp6_remote_network6_get_non_existent(kea_server: Kea): 20 | name = "pyisckea-pytest" 21 | with pytest.raises(KeaSharedNetworkNotFoundException): 22 | kea_server.dhcp6.remote_network6_get(name=name) 23 | 24 | 25 | def test_kea_dhcp6_remote_network6_add(kea_server: Kea, db_remote_map: dict): 26 | name = "pyisckea-pytest" 27 | data = SharedNetwork6(name=name) 28 | shared_networks = [data] 29 | response = kea_server.dhcp6.remote_network6_set( 30 | shared_networks=shared_networks, server_tags=["all"], remote_map=db_remote_map 31 | ) 32 | 33 | assert response.result == 0 34 | assert len(response.arguments.get("shared-networks", [])) > 0 35 | 36 | 37 | def test_kea_dhcp6_remote_network6_list(kea_server: Kea, db_remote_map: dict): 38 | shared_networks = kea_server.dhcp6.remote_network6_list( 39 | server_tags=["pyisckea-1"], remote_map=db_remote_map 40 | ) 41 | assert shared_networks 42 | assert len(shared_networks) > 0 43 | 44 | 45 | def test_kea_dhcp6_remote_network6_get(kea_server: Kea, db_remote_map: dict): 46 | name = "pyisckea-pytest" 47 | shared_network = kea_server.dhcp6.remote_network6_get( 48 | name=name, remote_map=db_remote_map 49 | ) 50 | assert shared_network 51 | assert shared_network.name == name 52 | 53 | 54 | def test_kea_dhcp6_remote_subnet6_add_subnet(kea_server: Kea, db_remote_map: dict): 55 | name = "pyisckea-pytest" 56 | 57 | # Create Temporary Subnet 58 | subnet = Subnet6(subnet="2001:db8::32/127", id=40123, shared_network_name=name) 59 | 60 | # Create subnet with shared network assosication 61 | response = kea_server.dhcp6.remote_subnet6_set( 62 | subnet=subnet, server_tags=["all"], remote_map=db_remote_map 63 | ) 64 | assert response.result == 0 65 | assert len(response.arguments.get("subnets", [])) > 0 66 | 67 | 68 | def test_kea_dhcp6_remote_subnet6_del_by_id(kea_server: Kea, db_remote_map: dict): 69 | response = kea_server.dhcp6.remote_subnet6_del_by_id( 70 | subnet_id=40123, remote_map=db_remote_map 71 | ) 72 | assert response.result == 0 73 | assert response.arguments.get("count") == 1 74 | 75 | 76 | def test_kea_dhcp6_remote_network6_del(kea_server: Kea, db_remote_map: dict): 77 | response = kea_server.dhcp6.remote_network6_del( 78 | name="pyisckea-pytest", keep_subnets=False, remote_map=db_remote_map 79 | ) 80 | assert response.result == 0 81 | assert response.arguments.get("count") == 1 82 | 83 | 84 | def test_kea_dhcp6_remote_network6_del_non_existent( 85 | kea_server: Kea, db_remote_map: dict 86 | ): 87 | response = kea_server.dhcp6.remote_network6_del( 88 | name="pyisckea-pytest", remote_map=db_remote_map 89 | ) 90 | assert response.result == 3 91 | assert response.arguments["count"] == 0 92 | -------------------------------------------------------------------------------- /tests/dhcp6/remote/test_kea_dhcp6_remote_option6_global.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.generic.option_data import OptionData 3 | 4 | """remote-option-def6 process: 5 | 6 | get (non existent) 7 | set 8 | get 9 | get-all 10 | del 11 | """ 12 | 13 | 14 | def test_kea_dhcp6_remote_option6_global_get_non_existent(kea_server: Kea): 15 | response = kea_server.dhcp6.remote_option6_global_get( 16 | option_code=23, option_space="dhcp6", server_tag="all" 17 | ) 18 | assert response.result == 3 19 | assert response.arguments.get("count") == 0 20 | 21 | 22 | def test_kea_dhcp6_remote_option6_global_set(kea_server: Kea): 23 | option_data = OptionData(name="dns-servers", data="2001:db8::111") 24 | response = kea_server.dhcp6.remote_option6_global_set( 25 | option_data=option_data, server_tag="all" 26 | ) 27 | assert response.result == 0 28 | assert len(response.arguments.get("options", [])) > 0 29 | 30 | 31 | def test_kea_dhcp6_remote_option6_global_del(kea_server: Kea): 32 | response = kea_server.dhcp6.remote_option6_global_del( 33 | option_code=23, option_space="dhcp6", server_tag="all" 34 | ) 35 | assert response.result == 0 36 | assert response.arguments.get("count") == 1 37 | 38 | 39 | def test_kea_dhcp6_remote_option6_global_del_non_existent(kea_server: Kea): 40 | response = kea_server.dhcp6.remote_option6_global_del( 41 | option_code=23, option_space="dhcp6", server_tag="all" 42 | ) 43 | assert response.result == 3 44 | assert response.arguments.get("count") == 0 45 | -------------------------------------------------------------------------------- /tests/dhcp6/remote/test_kea_dhcp6_remote_option6_network.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.dhcp6.shared_network import SharedNetwork6 3 | from pyisckea.models.generic.option_data import OptionData 4 | 5 | """remote-option6-network process: 6 | 7 | del (non existent) 8 | set 9 | del 10 | """ 11 | 12 | 13 | def test_kea_dhcp6_remote_option6_network_prepare(kea_server: Kea): 14 | shared_network = SharedNetwork6(name="pyisckea-network") 15 | response = kea_server.dhcp6.remote_network6_set( 16 | shared_networks=[shared_network], server_tags=["all"] 17 | ) 18 | assert response.result == 0 19 | 20 | 21 | def test_kea_dhcp6_remote_option6_network_del_non_existent(kea_server: Kea): 22 | response = kea_server.dhcp6.remote_option6_network_del( 23 | shared_network="pyisckea-network", option_code=6, option_space="dhcp6" 24 | ) 25 | assert response.result == 3 26 | assert response.arguments.get("count") == 0 27 | 28 | 29 | def test_kea_dhcp6_remote_option6_network_set(kea_server: Kea): 30 | option_data = OptionData(name="dns-servers", data="2001:db8::111") 31 | response = kea_server.dhcp6.remote_option6_network_set( 32 | shared_network="pyisckea-network", option_data=option_data 33 | ) 34 | assert response.result == 0 35 | assert len(response.arguments.get("options", [])) > 0 36 | 37 | 38 | def test_kea_dhcp6_remote_option6_network_del(kea_server: Kea): 39 | response = kea_server.dhcp6.remote_option6_network_del( 40 | shared_network="pyisckea-network", option_code=23, option_space="dhcp6" 41 | ) 42 | assert response.result == 0 43 | assert response.arguments.get("count") == 1 44 | 45 | 46 | def test_kea_dhcp6_remote_option6_network_cleanup(kea_server: Kea): 47 | response = kea_server.dhcp6.remote_network6_del( 48 | name="pyisckea-network", keep_subnets=False 49 | ) 50 | assert response.result == 0 51 | -------------------------------------------------------------------------------- /tests/dhcp6/remote/test_kea_dhcp6_remote_option6_pd_pool.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.dhcp6.pd_pool import PDPool 3 | from pyisckea.models.dhcp6.subnet import Subnet6 4 | from pyisckea.models.generic.option_data import OptionData 5 | 6 | """remote-option6-pd-pool process: 7 | 8 | del (non existent) 9 | set 10 | del 11 | """ 12 | 13 | 14 | def test_kea_dhcp6_remote_option6_pd_pool_prepare(kea_server: Kea): 15 | subnet = Subnet6( 16 | id=40123, 17 | subnet="2001:db8::/64", 18 | pd_pools=[PDPool(prefix="2001:1db8::", prefix_len=38, delegated_len=56)], 19 | ) 20 | response = kea_server.dhcp6.remote_subnet6_set(subnet=subnet, server_tags=["all"]) 21 | assert response.result == 0 22 | 23 | 24 | def test_kea_dhcp6_remote_option6_pd_pool_del_non_existent(kea_server: Kea): 25 | response = kea_server.dhcp6.remote_option6_pd_pool_del( 26 | prefix="2001:1db8::", prefix_len=38, option_code=23, option_space="dhcp6" 27 | ) 28 | assert response.result == 3 29 | assert response.arguments.get("count") == 0 30 | 31 | 32 | def test_kea_dhcp6_remote_option6_pd_pool_set(kea_server: Kea): 33 | option_data = OptionData(name="dns-servers", data="2001:db8::111") 34 | response = kea_server.dhcp6.remote_option6_pd_pool_set( 35 | prefix="2001:1db8::", prefix_len=38, option_data=option_data 36 | ) 37 | assert response.result == 0 38 | assert len(response.arguments.get("options", [])) > 0 39 | 40 | 41 | def test_kea_dhcp6_remote_option6_pd_pool_del(kea_server: Kea): 42 | response = kea_server.dhcp6.remote_option6_pd_pool_del( 43 | prefix="2001:1db8::", prefix_len=38, option_code=23, option_space="dhcp6" 44 | ) 45 | assert response.result == 0 46 | assert response.arguments.get("count") == 1 47 | 48 | 49 | def test_kea_dhcp6_remote_option6_pd_pool_cleanup(kea_server: Kea): 50 | response = kea_server.dhcp6.remote_subnet6_del_by_id(subnet_id=40123) 51 | assert response.result == 0 52 | 53 | 54 | """ 55 | def test_kea_dhcp6_remote_option6_pool_del_non_existent(kea_server: Kea): 56 | response = kea_server.dhcp6.remote_option6_pool_del( 57 | pool="2001:db8::2-2001:db8::ffff", option_code=23, option_space="dhcp6" 58 | ) 59 | assert response.result == 3 60 | assert response.arguments.get("count") == 0 61 | 62 | 63 | def test_kea_dhcp6_remote_option6_pool_set(kea_server: Kea): 64 | option_data = OptionData(name="dns-servers", data="2001:db8::111") 65 | response = kea_server.dhcp6.remote_option6_pool_set( 66 | pool="2001:db8::2-2001:db8::ffff", option_data=option_data 67 | ) 68 | assert response.result == 0 69 | assert len(response.arguments.get("options", [])) > 0 70 | 71 | 72 | def test_kea_dhcp6_remote_option6_pool_del(kea_server: Kea): 73 | response = kea_server.dhcp6.remote_option6_pool_del( 74 | pool="2001:db8::2-2001:db8::ffff", option_code=23, option_space="dhcp6" 75 | ) 76 | assert response.result == 0 77 | assert response.arguments.get("count") == 1 78 | 79 | """ 80 | -------------------------------------------------------------------------------- /tests/dhcp6/remote/test_kea_dhcp6_remote_option6_pool.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.dhcp6.subnet import Subnet6 3 | from pyisckea.models.generic.option_data import OptionData 4 | from pyisckea.models.generic.pool import Pool 5 | 6 | """remote-option6-pool process: 7 | 8 | del (non existent) 9 | set 10 | del 11 | """ 12 | 13 | 14 | def test_kea_dhcp6_remote_option6_pool_prepare(kea_server: Kea): 15 | subnet = Subnet6( 16 | id=40123, 17 | subnet="2001:db8::/64", 18 | pools=[Pool(pool="2001:db8::2-2001:db8::ffff")], 19 | ) 20 | response = kea_server.dhcp6.remote_subnet6_set(subnet=subnet, server_tags=["all"]) 21 | assert response.result == 0 22 | 23 | 24 | def test_kea_dhcp6_remote_option6_pool_del_non_existent(kea_server: Kea): 25 | response = kea_server.dhcp6.remote_option6_pool_del( 26 | pool="2001:db8::2-2001:db8::ffff", option_code=23, option_space="dhcp6" 27 | ) 28 | assert response.result == 3 29 | assert response.arguments.get("count") == 0 30 | 31 | 32 | def test_kea_dhcp6_remote_option6_pool_set(kea_server: Kea): 33 | option_data = OptionData(name="dns-servers", data="2001:db8::111") 34 | response = kea_server.dhcp6.remote_option6_pool_set( 35 | pool="2001:db8::2-2001:db8::ffff", option_data=option_data 36 | ) 37 | assert response.result == 0 38 | assert len(response.arguments.get("options", [])) > 0 39 | 40 | 41 | def test_kea_dhcp6_remote_option6_pool_del(kea_server: Kea): 42 | response = kea_server.dhcp6.remote_option6_pool_del( 43 | pool="2001:db8::2-2001:db8::ffff", option_code=23, option_space="dhcp6" 44 | ) 45 | assert response.result == 0 46 | assert response.arguments.get("count") == 1 47 | 48 | 49 | def test_kea_dhcp6_remote_option6_pool_cleanup(kea_server: Kea): 50 | response = kea_server.dhcp6.remote_subnet6_del_by_id(subnet_id=40123) 51 | assert response.result == 0 52 | -------------------------------------------------------------------------------- /tests/dhcp6/remote/test_kea_dhcp6_remote_option6_subnet.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.dhcp6.subnet import Subnet6 3 | from pyisckea.models.generic.option_data import OptionData 4 | 5 | """remote-option6-subnet process: 6 | 7 | del (non existent) 8 | set 9 | del 10 | """ 11 | 12 | 13 | def test_kea_dhcp6_remote_option6_subnet_prepare(kea_server: Kea): 14 | subnet = Subnet6(id=40123, subnet="2001:db8::/64") 15 | response = kea_server.dhcp6.remote_subnet6_set(subnet=subnet, server_tags=["all"]) 16 | assert response.result == 0 17 | 18 | 19 | def test_kea_dhcp6_remote_option6_subnet_del_non_existent(kea_server: Kea): 20 | response = kea_server.dhcp6.remote_option6_subnet_del( 21 | subnet_id=40123, option_code=23, option_space="dhcp6" 22 | ) 23 | assert response.result == 3 24 | assert response.arguments.get("count") == 0 25 | 26 | 27 | def test_kea_dhcp6_remote_option6_subnet_set(kea_server: Kea): 28 | option_data = OptionData(name="dns-servers", data="2001:db8::111") 29 | response = kea_server.dhcp6.remote_option6_subnet_set( 30 | subnet_id=40123, option_data=option_data 31 | ) 32 | assert response.result == 0 33 | assert len(response.arguments.get("options", [])) > 0 34 | 35 | 36 | def test_kea_dhcp6_remote_option6_subnet_del(kea_server: Kea): 37 | response = kea_server.dhcp6.remote_option6_subnet_del( 38 | subnet_id=40123, option_code=23, option_space="dhcp6" 39 | ) 40 | assert response.result == 0 41 | assert response.arguments.get("count") == 1 42 | 43 | 44 | def test_kea_dhcp6_remote_option6_subnet_cleanup(kea_server: Kea): 45 | response = kea_server.dhcp6.remote_subnet6_del_by_id(subnet_id=40123) 46 | assert response.result == 0 47 | -------------------------------------------------------------------------------- /tests/dhcp6/remote/test_kea_dhcp6_remote_option_def6.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.generic.option_def import OptionDef 3 | 4 | """remote-option-def6 process: 5 | 6 | get (non existent) 7 | set 8 | get 9 | get-all 10 | del 11 | """ 12 | 13 | 14 | def test_kea_dhcp6_remote_option_def6_get_non_existent(kea_server: Kea): 15 | response = kea_server.dhcp6.remote_option_def6_get( 16 | option_code=1, option_space="pyisckea", server_tag="all" 17 | ) 18 | assert response.result == 3 19 | assert response.arguments.get("count") == 0 20 | 21 | 22 | def test_kea_dhcp6_remote_option_def6_set(kea_server: Kea): 23 | option_def = OptionDef(name="subopt1", code=1, space="pyisckea", type="string") 24 | response = kea_server.dhcp6.remote_option_def6_set( 25 | option_def=option_def, server_tag="all" 26 | ) 27 | assert response.result == 0 28 | assert len(response.arguments.get("option-defs", [])) > 0 29 | 30 | 31 | def test_kea_dhcp6_remote_option_def6_del(kea_server: Kea): 32 | response = kea_server.dhcp6.remote_option_def6_del( 33 | option_code=1, option_space="pyisckea", server_tag="all" 34 | ) 35 | assert response.result == 0 36 | assert response.arguments.get("count") == 1 37 | 38 | 39 | def test_kea_dhcp6_remote_option_def6_del_non_existent(kea_server: Kea): 40 | response = kea_server.dhcp6.remote_option_def6_del( 41 | option_code=1, option_space="pyisckea", server_tag="all" 42 | ) 43 | assert response.result == 3 44 | assert response.arguments.get("count") == 0 45 | -------------------------------------------------------------------------------- /tests/dhcp6/remote/test_kea_dhcp6_remote_subnet6.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaSubnetNotFoundException 5 | from pyisckea.models.dhcp6.subnet import Subnet6 6 | 7 | """remote-subnet6 process: 8 | get (non-existent subnet) 9 | add 10 | list 11 | get by id 12 | get by prefix 13 | del by id 14 | del non existent by prefix?? 15 | """ 16 | 17 | 18 | def test_kea_dhcp6_remote_subnet6_get_non_existent(kea_server: Kea): 19 | with pytest.raises(KeaSubnetNotFoundException): 20 | kea_server.dhcp6.remote_subnet6_get_by_id(subnet_id=40123) 21 | 22 | 23 | def test_kea_dhcp6_remote_subnet6_add_subnet(kea_server: Kea): 24 | subnet = Subnet6(subnet="2001:db8::32/127", id=40123) 25 | 26 | # Create subnet 27 | response = kea_server.dhcp6.remote_subnet6_set(subnet=subnet, server_tags=["all"]) 28 | assert response.result == 0 29 | assert len(response.arguments.get("subnets", [])) > 0 30 | 31 | 32 | def test_kea_dhcp6_remote_subnet6_list(kea_server: Kea): 33 | subnets = kea_server.dhcp6.remote_subnet6_list(server_tags=["pyisckea-1"]) 34 | assert subnets 35 | assert len(subnets) > 0 36 | 37 | 38 | def test_kea_dhcp6_remote_subnet6_get_by_id(kea_server: Kea): 39 | subnet = kea_server.dhcp6.remote_subnet6_get_by_id(subnet_id=40123) 40 | assert subnet 41 | assert subnet.id == 40123 42 | assert subnet.subnet == "2001:db8::32/127" 43 | 44 | 45 | def test_kea_dhcp6_remote_subnet6_get_by_prefix(kea_server: Kea): 46 | subnet = kea_server.dhcp6.remote_subnet6_get_by_prefix(prefix="2001:db8::32/127") 47 | assert subnet 48 | assert subnet.id == 40123 49 | assert subnet.subnet == "2001:db8::32/127" 50 | 51 | 52 | def test_kea_dhcp6_remote_subnet6_del_by_id(kea_server: Kea): 53 | response = kea_server.dhcp6.remote_subnet6_del_by_id(subnet_id=40123) 54 | assert response.result == 0 55 | assert response.arguments.get("count") == 1 56 | 57 | 58 | def test_kea_dhcp6_remote_subnet6_del_by_prefix_non_existent(kea_server: Kea): 59 | response = kea_server.dhcp6.remote_subnet6_del_by_prefix(prefix="2001:db8::32/127") 60 | assert response.result == 3 61 | assert response.arguments.get("count") == 0 62 | -------------------------------------------------------------------------------- /tests/dhcp6/test_kea_dhcp6.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | 3 | 4 | def test_kea_dhcp6_build_report(kea_server: Kea): 5 | response = kea_server.dhcp6.build_report() 6 | assert response.result == 0 7 | assert response.text 8 | 9 | 10 | def test_kea_dhcp6_config_get(kea_server: Kea): 11 | response = kea_server.dhcp6.config_get() 12 | assert response.result == 0 13 | assert "Dhcp6" in response.arguments 14 | 15 | 16 | def test_kea_dhcp6_config_test(kea_server: Kea): 17 | config = kea_server.dhcp6.cached_config 18 | if config.get("hash"): # Temp workaround 19 | del config["hash"] 20 | 21 | response = kea_server.dhcp6.config_test(config=config) 22 | assert response.result == 0 23 | 24 | 25 | def test_kea_dhcp6_config_set(kea_server: Kea): 26 | config = kea_server.dhcp6.cached_config 27 | if config.get("hash"): # Temp workaround 28 | del config["hash"] 29 | 30 | response = kea_server.dhcp6.config_set(config=config) 31 | assert response.result == 0 32 | 33 | 34 | def test_kea_dhcp6_config_write(kea_server: Kea): 35 | filename = "/usr/local/etc/kea/kea-dhcp6.conf" 36 | response = kea_server.dhcp6.config_write(filename=filename) 37 | assert response.result == 0 38 | assert response.arguments["filename"] == filename 39 | assert response.arguments["size"] > 0 40 | 41 | 42 | def test_kea_dhcp6_config_reload(kea_server: Kea): 43 | response = kea_server.dhcp6.config_reload() 44 | assert response.result == 0 45 | assert response.text == "Configuration successful." 46 | 47 | 48 | def test_kea_dhcp6_dhcp_disable(kea_server: Kea): 49 | response = kea_server.dhcp6.dhcp_disable(max_period=60) 50 | assert response.result == 0 51 | assert response.text == "DHCPv6 service disabled for 60 seconds" 52 | 53 | 54 | def test_kea_dhcp6_dhcp_enable(kea_server: Kea): 55 | response = kea_server.dhcp6.dhcp_enable() 56 | assert response.result == 0 57 | assert response.text == "DHCP service successfully enabled" 58 | 59 | 60 | def test_kea_dhcp6_list_commands(kea_server: Kea): 61 | response = kea_server.dhcp6.list_commands() 62 | assert response.result == 0 63 | assert "config-get" in response.arguments 64 | 65 | 66 | def test_kea_dhcp6_status_get(kea_server: Kea): 67 | response = kea_server.dhcp6.status_get() 68 | assert response.pid 69 | assert response.uptime 70 | 71 | 72 | def test_kea_dhcp6_version_get(kea_server: Kea): 73 | response = kea_server.dhcp6.version_get() 74 | assert response.result == 0 75 | 76 | 77 | def test_kea_dhcp6_shutdown(kea_server: Kea): 78 | response = kea_server.dhcp6.shutdown() 79 | assert response == 0 80 | -------------------------------------------------------------------------------- /tests/dhcp6/test_kea_dhcp6_cache.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | from pyisckea.models.dhcp6.reservation import Reservation6 3 | 4 | """cache process:Reservation6 5 | cache get (none) 6 | cache insert 7 | cache get 8 | cache get_by_id 9 | cache_size 10 | cache remove 11 | cache get_by_id (non existent) 12 | cache_clear 13 | cache_flush 14 | cache_write 15 | cache_load 16 | """ 17 | 18 | 19 | def test_kea_dhcp6_cache_get_none(kea_server: Kea): 20 | response = kea_server.dhcp6.cache_get() 21 | assert len(response) == 0 22 | 23 | 24 | def test_kea_dhcp6_cache_insert(kea_server: Kea): 25 | reservation = Reservation6( 26 | ip_addresses=["2001:db8::33"], 27 | duid="00:01:00:01:17:96:f9:3a:aa:bb:cc:dd:ee:ff", 28 | ) 29 | response = kea_server.dhcp6.cache_insert(subnet_id=40123, reservation=reservation) 30 | assert response.result == 0 31 | 32 | 33 | def test_kea_dhcp6_cache_get(kea_server: Kea): 34 | response = kea_server.dhcp6.cache_get() 35 | assert len(response) > 0 36 | 37 | 38 | def test_kea_dhcp6_cache_get_by_id(kea_server: Kea): 39 | response = kea_server.dhcp6.cache_get_by_id( 40 | identifier_type="duid", identifier="00:01:00:01:17:96:f9:3a:aa:bb:cc:dd:ee:ff" 41 | ) 42 | assert len(response) > 0 43 | 44 | 45 | def test_kea_dhcp6_cache_size(kea_server: Kea): 46 | response = kea_server.dhcp6.cache_size() 47 | assert response.result == 0 48 | assert response.arguments.get("size") > 0 49 | 50 | 51 | def test_kea_dhcp6_cache_remove(kea_server: Kea): 52 | response = kea_server.dhcp6.cache_remove(subnet_id=40123, ip_address="2001:db8::33") 53 | assert response.result == 0 54 | 55 | 56 | def test_kea_dhcp6_cache_get_by_id_non_existent(kea_server: Kea): 57 | response = kea_server.dhcp6.cache_get_by_id( 58 | identifier_type="duid", identifier="00:01:00:01:17:96:f9:3a:aa:bb:cc:dd:ee:ff" 59 | ) 60 | assert len(response) == 0 61 | 62 | 63 | def test_kea_dhcp6_cache_clear(kea_server: Kea): 64 | response = kea_server.dhcp6.cache_clear() 65 | assert response.result == 0 66 | 67 | 68 | def test_kea_dhcp6_cache_flush(kea_server: Kea): 69 | response = kea_server.dhcp6.cache_flush(number=123) 70 | assert response.result == 0 71 | 72 | 73 | def test_kea_dhcp6_cache_write(kea_server: Kea): 74 | response = kea_server.dhcp6.cache_write(filepath="/tmp/kea-host-cache.json") 75 | assert response.result == 0 76 | 77 | 78 | def test_kea_dhcp6_cache_load(kea_server: Kea): 79 | response = kea_server.dhcp6.cache_load(filepath="/tmp/kea-host-cache.json") 80 | assert response.result == 0 81 | -------------------------------------------------------------------------------- /tests/dhcp6/test_kea_dhcp6_class.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaClientClassNotFoundException 5 | from pyisckea.models.dhcp6.client_class import ClientClass6 6 | 7 | """class process:class_del 8 | get (non-existent client-class) 9 | add 10 | add (again to check duplicate client-class) 11 | get 12 | list 13 | update 14 | del 15 | del (non-existent client-class) 16 | """ 17 | 18 | 19 | def test_kea_dhcp6_class_get_non_existent(kea_server: Kea): 20 | with pytest.raises(KeaClientClassNotFoundException): 21 | kea_server.dhcp6.class_get(name="ipxe_efi_x64") 22 | 23 | 24 | def test_kea_dhcp6_class_add(kea_server: Kea): 25 | client_class = ClientClass6( 26 | name="ipxe_efi_x64", 27 | test="option[93].hex == 0x0009", 28 | next_server="192.0.2.254", 29 | server_hostname="hal9000", 30 | boot_file_name="/dev/null", 31 | ) 32 | response = kea_server.dhcp6.class_add(client_class=client_class) 33 | assert response.result == 0 34 | 35 | 36 | def test_kea_dhcp6_class_get(kea_server: Kea): 37 | response = kea_server.dhcp6.class_get(name="ipxe_efi_x64") 38 | assert response 39 | assert response.name == "ipxe_efi_x64" 40 | 41 | 42 | def test_kea_dhcp6_class_list(kea_server: Kea): 43 | response = kea_server.dhcp6.class_list() 44 | assert len(response) > 0 45 | 46 | 47 | def test_kea_dhcp6_class_del(kea_server: Kea): 48 | response = kea_server.dhcp6.class_del(name="ipxe_efi_x64") 49 | assert response.result == 0 50 | 51 | 52 | def test_kea_dhcp6_class_del_non_existent(kea_server: Kea): 53 | response = kea_server.dhcp6.class_del(name="ipxe_efi_x64") 54 | assert response.result == 3 55 | -------------------------------------------------------------------------------- /tests/dhcp6/test_kea_dhcp6_ha.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | 3 | """ha process: 4 | status-get (get HA mode) 5 | """ 6 | 7 | 8 | def test_kea_dhcp6_ha_check_primary(kea_server: Kea): 9 | status = kea_server.dhcp6.status_get() 10 | 11 | assert status.high_availability 12 | assert len(status.high_availability) > 0 13 | 14 | ha_settings = status.high_availability[0] 15 | 16 | assert ha_settings.ha_mode == "hot-standby" 17 | assert ha_settings.ha_servers.local.role == "primary" 18 | assert ha_settings.ha_servers.local.state in ["hot-standby", "waiting"] 19 | assert ha_settings.ha_servers.remote.last_state in ["hot-standby", "ready", ""] 20 | 21 | 22 | def test_kea_dhcp6_ha_continue_not_paused(kea_server: Kea): 23 | response = kea_server.dhcp6.ha_continue() 24 | assert response.result == 0 25 | assert "not paused" in response.text 26 | 27 | 28 | def test_kea_dhcp6_ha_heartbeat(kea_server: Kea): 29 | response = kea_server.dhcp6.ha_heartbeat() 30 | assert response.result == 0 31 | 32 | 33 | def test_kea_dhcp6_ha_maintenance_cancel_wrong_state(kea_server: Kea): 34 | response = kea_server.dhcp6.ha_maintenance_cancel() 35 | assert response.result == 1 36 | assert "not in the partner-in-maintenance state" in response.text 37 | 38 | 39 | def test_kea_dhcp6_ha_maintenance_notify_not_in_maintenance_mode(kea_server: Kea): 40 | response = kea_server.dhcp6.ha_maintenance_notify(cancel=True) 41 | assert response.result == 1 42 | assert "not in the in-maintenance state" in response.text 43 | 44 | 45 | def test_kea_dhcp6_ha_maintenance_start(kea_server: Kea): 46 | response = kea_server.dhcp6.ha_maintenance_start() 47 | assert response.result == 0 48 | 49 | status = kea_server.dhcp6.status_get() 50 | ha_settings = status.high_availability[0] 51 | assert ha_settings.ha_servers.local.state == "partner-in-maintenance" 52 | 53 | 54 | def test_kea_dhcp6_ha_maintenance_cancel(kea_server: Kea): 55 | response = kea_server.dhcp6.ha_maintenance_cancel() 56 | assert response.result == 0 57 | 58 | 59 | def test_kea_dhcp6_ha_maintenance_reset(kea_server: Kea): 60 | response = kea_server.dhcp6.ha_reset() 61 | assert response.result == 0 62 | 63 | 64 | def test_kea_dhcp6_ha_reset_peer(kea_server: Kea): 65 | kea_server.port = ( 66 | 8081 # Need to find a better way to integrate this into pytest as a fixture... 67 | ) 68 | response = kea_server.dhcp6.ha_reset() 69 | assert response.result == 0 70 | 71 | kea_server.dhcp6.ha_maintenance_cancel() 72 | 73 | 74 | def test_kea_dhcp6_ha_sync(kea_server: Kea): 75 | response = kea_server.dhcp6.ha_sync(partner_server="server2", max_period=30) 76 | assert response.result == 0 77 | 78 | 79 | def test_kea_dhcp6_ha_synbc_complete_notify(kea_server: Kea): 80 | response = kea_server.dhcp6.ha_sync_complete_notify() 81 | assert response.result == 0 82 | -------------------------------------------------------------------------------- /tests/dhcp6/test_kea_dhcp6_lease6.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaLeaseNotFoundException 5 | from pyisckea.models.dhcp6.subnet import Subnet6 6 | 7 | """lease6 process: 8 | get (non-existent lease) 9 | add 10 | add (again to check duplicate lease) 11 | get 12 | get-all 13 | get-page 14 | get-by-client-id 15 | get-by-hw-address 16 | update 17 | del 18 | wipe 19 | reclaim 20 | """ 21 | 22 | 23 | def test_kea_dhcp6_lease6_get_non_exsistent(kea_server: Kea): 24 | with pytest.raises(KeaLeaseNotFoundException): 25 | kea_server.dhcp6.lease6_get(ip_address="2001:db8::32") 26 | 27 | 28 | def test_kea_dhcp4_lease6_get_all_none(kea_server: Kea): 29 | with pytest.raises(KeaLeaseNotFoundException): 30 | kea_server.dhcp6.lease6_get_all() 31 | 32 | 33 | def test_kea_dhcp6_lease6_add(kea_server: Kea): 34 | # Add Temporary Subnet 35 | data = Subnet6(id=40123, subnet="2001:db8::/64") 36 | subnets = [data] 37 | response = kea_server.dhcp6.subnet6_add(subnets=subnets) 38 | assert response.result == 0 39 | 40 | # Add Lease 41 | lease_response = kea_server.dhcp6.lease6_add( 42 | ip_address="2001:db8::32", duid="1a:1b:1c:1d:1e:1f:20:21:22:23:24", iaid=1234 43 | ) 44 | assert lease_response.result == 0 45 | 46 | 47 | def test_kea_dhcp6_lease6_get(kea_server: Kea): 48 | response = kea_server.dhcp6.lease6_get(ip_address="2001:db8::32") 49 | assert response 50 | assert response.ip_address == "2001:db8::32" 51 | assert response.duid == "1a:1b:1c:1d:1e:1f:20:21:22:23:24" 52 | assert response.iaid == 1234 53 | 54 | 55 | def test_kea_dhcp4_lease6_get_all(kea_server: Kea): 56 | response = kea_server.dhcp6.lease6_get_all() 57 | assert len(response) > 0 58 | 59 | 60 | def test_kea_dhcp6_lease6_get_all_subnets(kea_server: Kea): 61 | response = kea_server.dhcp6.lease6_get_all(subnets=[40123]) 62 | assert len(response) > 0 63 | 64 | 65 | def test_kea_dhcp6_lease6_get_page(kea_server: Kea): 66 | response = kea_server.dhcp6.lease6_get_page(limit=100, search_from="start") 67 | assert response.count > 0 68 | 69 | 70 | def test_kea_dhcp6_lease6_get_by_duid(kea_server: Kea): 71 | response = kea_server.dhcp6.lease6_get_by_duid( 72 | duid="1a:1b:1c:1d:1e:1f:20:21:22:23:24" 73 | ) 74 | assert response 75 | assert response.type == "IA_NA" 76 | assert response.ip_address == "2001:db8::32" 77 | assert response.duid == "1a:1b:1c:1d:1e:1f:20:21:22:23:24" 78 | assert response.iaid == 1234 79 | 80 | 81 | def test_kea_dhcp6_lease6_get_by_hostname_non_existent(kea_server: Kea): 82 | with pytest.raises(KeaLeaseNotFoundException): 83 | kea_server.dhcp6.lease6_get_by_hostname(hostname="bad-hostname") 84 | 85 | 86 | def test_kea_dhcp6_lease6_update(kea_server: Kea): 87 | response = kea_server.dhcp6.lease6_update( 88 | ip_address="2001:db8::32", 89 | duid="1a:1b:1c:1d:1e:1f:20:21:22:23:25", 90 | iaid=1234, 91 | hostname="new-hostname", 92 | ) 93 | assert response.result == 0 94 | 95 | updated_lease = kea_server.dhcp6.lease6_get(ip_address="2001:db8::32") 96 | assert updated_lease.hostname == "new-hostname" 97 | 98 | 99 | def test_kea_dhcp6_lease6_del(kea_server: Kea): 100 | response = kea_server.dhcp6.lease6_del(ip_address="2001:db8::32") 101 | assert response.result == 0 102 | 103 | 104 | def test_kea_dhcp6_lease6_del_non_existent(kea_server: Kea): 105 | response = kea_server.dhcp6.lease6_del(ip_address="2001:db8::32") 106 | assert response.result == 3 107 | 108 | 109 | def test_kea_dhcp6_lease6_del_temp_subnet(kea_server: Kea): 110 | delete_response = kea_server.dhcp6.subnet6_del(subnet_id=40123) 111 | assert delete_response.result == 0 112 | -------------------------------------------------------------------------------- /tests/dhcp6/test_kea_dhcp6_network6.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaSharedNetworkNotFoundException 5 | from pyisckea.models.dhcp6.shared_network import SharedNetwork6 6 | from pyisckea.models.dhcp6.subnet import Subnet6 7 | 8 | """network4 process: 9 | get (non-existent network) 10 | add 11 | add (again to check duplicate subnets) 12 | list 13 | get 14 | subnet-add 15 | subnet-del 16 | update (full update without option 3... should disappear) 17 | del 18 | """ 19 | 20 | 21 | def test_kea_dhcp6_network6_get_non_existent(kea_server: Kea): 22 | name = "pyisckea-pytest" 23 | with pytest.raises(KeaSharedNetworkNotFoundException): 24 | kea_server.dhcp6.network6_get(name=name) 25 | 26 | 27 | def test_kea_dhcp6_network6_add(kea_server: Kea): 28 | name = "pyisckea-pytest" 29 | data = SharedNetwork6(name=name) 30 | shared_networks = [data] 31 | response = kea_server.dhcp6.network6_add(shared_networks=shared_networks) 32 | assert response.result == 0 33 | 34 | 35 | def test_kea_dhcp6_network6_add_duplicate(kea_server: Kea): 36 | name = "pyisckea-pytest" 37 | data = SharedNetwork6(name=name) 38 | shared_networks = [data] 39 | response = kea_server.dhcp6.network6_add(shared_networks) 40 | assert response.result == 1 41 | 42 | 43 | def test_kea_dhcp6_network6_get(kea_server: Kea): 44 | name = "pyisckea-pytest" 45 | response = kea_server.dhcp6.network6_get(name=name) 46 | assert response 47 | assert response.name == name 48 | 49 | 50 | def test_kea_dhcp6_network6_list(kea_server: Kea): 51 | networks = kea_server.dhcp6.network6_list() 52 | assert networks 53 | assert len(networks) > 0 54 | 55 | 56 | def test_kea_dhcp6_network6_subnet_add(kea_server: Kea): 57 | name = "pyisckea-pytest" 58 | 59 | # Create Temporary Subnet 60 | data = Subnet6(subnet="2001:db8::/64", id=40123) 61 | subnets = [data] 62 | subnet = kea_server.dhcp6.subnet6_add(subnets=subnets) 63 | assert subnet.result == 0 64 | 65 | # Assign Subnet to existing shared network 66 | response = kea_server.dhcp6.network6_subnet_add(name=name, subnet_id=data.id) 67 | assert response.result == 0 68 | 69 | # Confirm shared-network has at least 1 subnet 70 | shared_network = kea_server.dhcp6.network6_get(name=name) 71 | assert shared_network 72 | assert len(shared_network.subnet6) == 1 73 | 74 | 75 | def test_kea_dhcp6_network6_subnet_del(kea_server: Kea): 76 | name = "pyisckea-pytest" 77 | 78 | # Delete temporary subnet assosication 79 | response = kea_server.dhcp6.network6_subnet_del(name=name, subnet_id=40123) 80 | assert response.result == 0 81 | 82 | # Delete Temporary Subnet 83 | deleted_subnet = kea_server.dhcp6.subnet6_del(subnet_id=40123) 84 | assert deleted_subnet.result == 0 85 | 86 | # Confirm Shared Network now has 0 subnets 87 | shared_network = kea_server.dhcp6.network6_get(name=name) 88 | assert shared_network 89 | assert len(shared_network.subnet6) == 0 90 | 91 | 92 | def test_kea_dhcp6_network6_del(kea_server: Kea): 93 | name = "pyisckea-pytest" 94 | response = kea_server.dhcp6.network6_del(name=name) 95 | assert response.result == 0 96 | -------------------------------------------------------------------------------- /tests/dhcp6/test_kea_dhcp6_parser.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from pyisckea import Kea 4 | from pyisckea.parsers.dhcp6 import Dhcp6Parser 5 | 6 | 7 | def test_kea_dhcp6_parser_parse_config(kea_server: Kea): 8 | cached_config = kea_server.dhcp6.cached_config 9 | parsed = Dhcp6Parser(config=cached_config) 10 | 11 | assert parsed.config.interfaces_config 12 | assert parsed.config.control_socket 13 | assert json.dumps( 14 | parsed.config.model_dump(exclude_none=True, by_alias=True), 15 | indent=4, 16 | sort_keys=True, 17 | ) 18 | 19 | 20 | def test_kea_dhcp6_parser_config_test(kea_server: Kea): 21 | cached_config = kea_server.dhcp6.cached_config 22 | parsed = Dhcp6Parser(config=cached_config) 23 | config_to_test = { 24 | "Dhcp6": parsed.config.model_dump( 25 | exclude_none=True, exclude_unset=True, by_alias=True 26 | ) 27 | } 28 | 29 | test_results = kea_server.dhcp6.config_test(config=config_to_test) 30 | assert test_results.result == 0 31 | 32 | # Remove hash if exist for now until tests are created to take that into account 33 | if cached_config.get("hash"): 34 | del cached_config["hash"] 35 | 36 | cached_config_json = json.dumps(cached_config, indent=4) 37 | parsed_config_json = json.dumps(config_to_test, indent=4, sort_keys=True) 38 | assert cached_config_json == parsed_config_json 39 | -------------------------------------------------------------------------------- /tests/dhcp6/test_kea_dhcp6_statistics.py: -------------------------------------------------------------------------------- 1 | from pyisckea import Kea 2 | 3 | 4 | def test_kea_dhcp6_statistic_get_all(kea_server: Kea): 5 | response = kea_server.dhcp6.statistic_get_all() 6 | assert response.result == 0 7 | 8 | 9 | def test_kea_dhcp6_statistic_get(kea_server: Kea): 10 | response = kea_server.dhcp6.statistic_get(name="pkt6-received") 11 | assert response.result == 0 12 | assert response.arguments["pkt6-received"] 13 | 14 | 15 | def test_kea_dhcp6_statistic_get_bad_name(kea_server: Kea): 16 | response = kea_server.dhcp6.statistic_get(name="bad-name-argument") 17 | assert response.result == 0 18 | assert response.arguments == {} 19 | -------------------------------------------------------------------------------- /tests/dhcp6/test_kea_dhcp6_subnet6.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyisckea import Kea 4 | from pyisckea.exceptions import KeaSubnetNotFoundException 5 | from pyisckea.models.dhcp6.subnet import Subnet6 6 | 7 | """subnet4 process: 8 | get (non-existent subnet) 9 | add 10 | add (again to check duplicate subnets) 11 | list 12 | get 13 | delta-add (partial update code 3 option and valid-lifetime) 14 | delta-del (remove valid-lifetime) 15 | update (full update without option 3... should disappear) 16 | del 17 | """ 18 | 19 | 20 | def test_kea_dhcp6_subnet6_get_non_existent(kea_server: Kea): 21 | with pytest.raises(KeaSubnetNotFoundException): 22 | kea_server.dhcp6.subnet6_get(subnet_id=40123) 23 | 24 | 25 | def test_kea_dhcp6_subnet6_add(kea_server: Kea): 26 | data = Subnet6(id=40123, subnet="2001:db8::/64") 27 | subnets = [data] 28 | response = kea_server.dhcp6.subnet6_add(subnets=subnets) 29 | assert response.result == 0 30 | 31 | 32 | def test_kea_dhcp6_subnet6_add_existing(kea_server: Kea): 33 | data = Subnet6(id=40123, subnet="2001:db8::/64") 34 | subnets = [data] 35 | response = kea_server.dhcp6.subnet6_add(subnets=subnets) 36 | assert response.result == 1 37 | 38 | 39 | def test_kea_dhcp6_subnet6_list(kea_server: Kea): 40 | response = kea_server.dhcp6.subnet6_list() 41 | assert response 42 | 43 | 44 | def test_kea_dhcp6_subnet6_get(kea_server: Kea): 45 | response = kea_server.dhcp6.subnet6_get(subnet_id=40123) 46 | assert response 47 | assert response.id == 40123 48 | 49 | 50 | def test_kea_dhcp6_subnet6_delta_update(kea_server: Kea): 51 | data = Subnet6(id=40123, subnet="2001:db8::/64", comment="pyisckea-test") 52 | subnets = [data] 53 | 54 | response = kea_server.dhcp6.subnet6_delta_add(subnets=subnets) 55 | assert response.result == 0 56 | 57 | 58 | def test_kea_dhcp6_subnet6_delta_del(kea_server: Kea): 59 | data = Subnet6(id=40123, subnet="2001:db8::/64", comment="pyisckea-test") 60 | subnets = [data] 61 | response = kea_server.dhcp6.subnet6_delta_del(subnets=subnets) 62 | assert response.result == 0 63 | 64 | updated_subnet = kea_server.dhcp6.subnet6_get(subnet_id=40123) 65 | assert updated_subnet 66 | assert updated_subnet.comment == "" 67 | 68 | 69 | def test_kea_dhcp6_subnet6_update(kea_server: Kea): 70 | data = Subnet6(id=40123, subnet="2001:db8::/96") 71 | subnets = [data] 72 | response = kea_server.dhcp6.subnet6_update(subnets=subnets) 73 | assert response.result == 0 74 | 75 | updated_subnet = kea_server.dhcp6.subnet6_get(subnet_id=40123) 76 | assert updated_subnet 77 | assert updated_subnet.subnet == "2001:db8::/96" 78 | 79 | 80 | def test_kea_dhcp6_subnet6_del(kea_server: Kea): 81 | response = kea_server.dhcp6.subnet6_del(subnet_id=40123) 82 | assert response.result == 0 83 | 84 | 85 | def test_kea_dhcp6_subnet6_del_non_existent(kea_server: Kea): 86 | with pytest.raises(KeaSubnetNotFoundException): 87 | kea_server.dhcp6.subnet6_del(subnet_id=40123) 88 | -------------------------------------------------------------------------------- /tests/test_infrastructure/Dockerfiles/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG KEA_REPO=public/isc/kea-dev 2 | ARG KEA_VERSION=2.7.7-isc20250326114722 3 | # Indicate if the premium packages should be installed. 4 | # Valid values: "premium" or empty. 5 | ARG KEA_PREMIUM="" 6 | 7 | 8 | FROM debian:12.9-slim AS debian-base 9 | 10 | # Container with a modern Supervisord installled. 11 | FROM debian-base AS supervisor-base 12 | RUN apt-get update \ 13 | && apt-get install \ 14 | -y \ 15 | --no-install-recommends \ 16 | python3.11=3.11.* \ 17 | python3-pip=23.* \ 18 | python3-setuptools=66.* \ 19 | supervisor \ 20 | wget \ 21 | && apt-get clean \ 22 | && rm -rf /var/lib/apt/lists/* \ 23 | && mkdir -p /var/log/supervisor 24 | 25 | # Server containers 26 | FROM supervisor-base AS server 27 | ENTRYPOINT [ "/bin/sh", "-c", \ 28 | "supervisord -c /etc/supervisor/supervisord.conf" ] 29 | EXPOSE 8080 30 | HEALTHCHECK CMD [ "supervisorctl", "status " ] 31 | 32 | FROM supervisor-base AS kea-base 33 | # Install Kea dependencies 34 | RUN apt-get update \ 35 | && apt-get install \ 36 | -y \ 37 | --no-install-recommends \ 38 | curl=7.88.* \ 39 | apt-transport-https=2.6.* \ 40 | gnupg=2.2.* \ 41 | && apt-get clean \ 42 | && rm -rf /var/lib/apt/lists/* 43 | 44 | # Install Kea from Cloudsmith 45 | SHELL [ "/bin/bash", "-o", "pipefail", "-c" ] 46 | ARG KEA_REPO 47 | ARG KEA_VERSION 48 | RUN curl https://dl.cloudsmith.io/${KEA_REPO}/setup.deb.sh | bash \ 49 | && apt-get update \ 50 | && apt-get install \ 51 | --no-install-recommends \ 52 | -y \ 53 | apt-utils \ 54 | isc-kea-ctrl-agent=${KEA_VERSION} \ 55 | isc-kea-dhcp4=${KEA_VERSION} \ 56 | isc-kea-dhcp6=${KEA_VERSION} \ 57 | isc-kea-admin=${KEA_VERSION} \ 58 | isc-kea-common=${KEA_VERSION} \ 59 | isc-kea-hooks=${KEA_VERSION} \ 60 | isc-kea-mysql=${KEA_VERSION} \ 61 | && apt-get clean \ 62 | && rm -rf /var/lib/apt/lists/* \ 63 | && mkdir -p /var/run/kea/ 64 | 65 | # Install premium packages. The KEA_REPO variable must 66 | # be set to the private repository and include an access token. 67 | # Docker ignores this section if the KEA_PREMIUM is empty - thanks 68 | # to this, the image builds correctly when the token is unknown. 69 | FROM kea-base AS keapremium-base 70 | ARG KEA_PREMIUM 71 | ARG KEA_VERSION 72 | # Execute only if the premium is enabled 73 | RUN [ "${KEA_PREMIUM}" != "premium" ] || ( \ 74 | apt-get update \ 75 | && apt-get install \ 76 | --no-install-recommends \ 77 | -y \ 78 | isc-kea-premium-cb-cmds=${KEA_VERSION} \ 79 | && apt-get clean \ 80 | && rm -rf /var/lib/apt/lists/* \ 81 | && mkdir -p /var/run/kea/ \ 82 | && ldconfig \ 83 | ) 84 | 85 | ENTRYPOINT [ "/bin/sh", "-c", \ 86 | "supervisord -c /etc/supervisor/supervisord.conf" ] 87 | EXPOSE 8080 88 | HEALTHCHECK CMD [ "supervisorctl", "status " ] -------------------------------------------------------------------------------- /tests/test_infrastructure/Dockerfiles/Dockerfile.perfdhcp: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN DEBIAN_FRONTEND=noninteractive \ 4 | apt-get update \ 5 | && apt-get install \ 6 | -y \ 7 | --no-install-recommends \ 8 | kea-admin 9 | 10 | RUN perfdhcp -4 -R 100 -B -l eth0 -x ael -g multi -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/ha/primary/kea-ctrl-agent.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Control-agent": { 3 | "http-host": "0.0.0.0", 4 | "http-port": 8000, 5 | "authentication": { 6 | "type": "basic", 7 | "clients": [ 8 | { 9 | "user": "kea", 10 | "password": "secret123" 11 | } 12 | ] 13 | }, 14 | "control-sockets": { 15 | "dhcp4": { 16 | "comment": "socket to DHCP4 server", 17 | "socket-type": "unix", 18 | "socket-name": "/tmp/kea4-ctrl-socket" 19 | }, 20 | "dhcp6": { 21 | "socket-type": "unix", 22 | "socket-name": "/tmp/kea6-ctrl-socket" 23 | }, 24 | "d2": { 25 | "socket-type": "unix", 26 | "socket-name": "/tmp/kea-ddns-ctrl-socket", 27 | "user-context": { 28 | "in-use": false 29 | } 30 | } 31 | }, 32 | "loggers": [ 33 | { 34 | "name": "kea-ctrl-agent", 35 | "output_options": [ 36 | { 37 | "output": "/var/log/kea-ctrl-agent.log", 38 | "flush": true, 39 | "maxsize": 204800, 40 | "maxver": 4, 41 | "pattern": "%d{%y.%m.%d %H:%M:%S.%q} %-5p [%c/%i] %m\n" 42 | } 43 | ], 44 | "severity": "DEBUG", 45 | "debuglevel": 99 46 | } 47 | ] 48 | } 49 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/ha/primary/kea-dhcp4-sql.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": { 3 | "multi-threading": {"enable-multi-threading": false}, 4 | "server-tag": "pyisckea-1", 5 | "interfaces-config": { 6 | "interfaces": [ 7 | "eth0" 8 | ] 9 | }, 10 | "control-socket": { 11 | "socket-type": "unix", 12 | "socket-name": "/tmp/kea4-ctrl-socket" 13 | }, 14 | "lease-database": { 15 | "type": "mysql", 16 | "name": "kea", 17 | "host": "db", 18 | "connect-timeout": 10, 19 | "max-reconnect-tries": 20, 20 | "on-fail": "stop-retry-exit", 21 | "user": "kea", 22 | "password": "secret123" 23 | }, 24 | "hosts-database": { 25 | "type": "mysql", 26 | "name": "kea", 27 | "host": "db", 28 | "connect-timeout": 10, 29 | "max-reconnect-tries": 20, 30 | "on-fail": "stop-retry-exit", 31 | "user": "kea", 32 | "password": "secret123" 33 | }, 34 | "config-control": { 35 | "config-databases": [ 36 | { 37 | "type": "mysql", 38 | "name": "kea", 39 | "user": "kea", 40 | "password": "secret123", 41 | "host": "db" 42 | } 43 | ], 44 | "config-fetch-wait-time": 20 45 | }, 46 | "hooks-libraries": [ 47 | { 48 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so" 49 | }, 50 | { 51 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cmds.so" 52 | }, 53 | { 54 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_mysql.so" 55 | }, 56 | { 57 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_subnet_cmds.so" 58 | }, 59 | { 60 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_class_cmds.so" 61 | }, 62 | { 63 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cache.so" 64 | }, 65 | { 66 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_ha.so", 67 | "parameters": { 68 | "high-availability": [ 69 | { 70 | "this-server-name": "server1", 71 | "mode": "hot-standby", 72 | "send-lease-updates": true, 73 | "sync-leases": true, 74 | "peers": [ 75 | { 76 | "auto-failover": true, 77 | "name": "server1", 78 | "url": "http://192.0.2.150:8000/", 79 | "role": "primary", 80 | "basic-auth-user": "kea", 81 | "basic-auth-password": "secret123" 82 | }, 83 | { 84 | "auto-failover": true, 85 | "name": "server2", 86 | "url": "http://192.0.2.151:8000/", 87 | "role": "standby", 88 | "basic-auth-user": "kea", 89 | "basic-auth-password": "secret123" 90 | } 91 | ] 92 | } 93 | ] 94 | } 95 | } 96 | ], 97 | "loggers": [ 98 | { 99 | "name": "kea-dhcp4", 100 | "output_options": [ 101 | { 102 | "output": "stdout" 103 | } 104 | ], 105 | "severity": "DEBUG", 106 | "debuglevel": 99 107 | } 108 | ] 109 | } 110 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/ha/primary/kea-dhcp6-sql.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp6": { 3 | "multi-threading": {"enable-multi-threading": false}, 4 | "server-tag": "pyisckea-1", 5 | "interfaces-config": { 6 | "interfaces": [ 7 | "eth0" 8 | ] 9 | }, 10 | "control-socket": { 11 | "socket-type": "unix", 12 | "socket-name": "/tmp/kea6-ctrl-socket" 13 | }, 14 | "lease-database": { 15 | "type": "mysql", 16 | "name": "kea", 17 | "host": "db", 18 | "connect-timeout": 10, 19 | "max-reconnect-tries": 3, 20 | "on-fail": "stop-retry-exit", 21 | "user": "kea", 22 | "password": "secret123" 23 | }, 24 | "hosts-database": { 25 | "type": "mysql", 26 | "name": "kea", 27 | "host": "db", 28 | "connect-timeout": 10, 29 | "max-reconnect-tries": 20, 30 | "on-fail": "stop-retry-exit", 31 | "user": "kea", 32 | "password": "secret123" 33 | }, 34 | "config-control": { 35 | "config-databases": [ 36 | { 37 | "type": "mysql", 38 | "name": "kea", 39 | "user": "kea", 40 | "password": "secret123", 41 | "host": "db" 42 | } 43 | ], 44 | "config-fetch-wait-time": 20 45 | }, 46 | "hooks-libraries": [ 47 | { 48 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so" 49 | }, 50 | { 51 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cmds.so" 52 | }, 53 | { 54 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_mysql.so" 55 | }, 56 | { 57 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_subnet_cmds.so" 58 | }, 59 | { 60 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_class_cmds.so" 61 | }, 62 | { 63 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cache.so" 64 | }, 65 | { 66 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_ha.so", 67 | "parameters": { 68 | "high-availability": [ 69 | { 70 | "this-server-name": "server1", 71 | "mode": "hot-standby", 72 | "send-lease-updates": true, 73 | "sync-leases": true, 74 | "peers": [ 75 | { 76 | "auto-failover": true, 77 | "name": "server1", 78 | "url": "http://192.0.2.150:8000/", 79 | "role": "primary", 80 | "basic-auth-user": "kea", 81 | "basic-auth-password": "secret123" 82 | }, 83 | { 84 | "auto-failover": true, 85 | "name": "server2", 86 | "url": "http://192.0.2.151:8000/", 87 | "role": "standby", 88 | "basic-auth-user": "kea", 89 | "basic-auth-password": "secret123" 90 | } 91 | ] 92 | } 93 | ] 94 | } 95 | } 96 | ], 97 | "preferred-lifetime": 3000, 98 | "valid-lifetime": 4000, 99 | "renew-timer": 1000, 100 | "rebind-timer": 2000, 101 | "subnet6": [], 102 | "loggers": [ 103 | { 104 | "name": "kea-dhcp6", 105 | "output_options": [ 106 | { 107 | "output": "stdout" 108 | } 109 | ], 110 | "debuglevel": 0, 111 | "severity": "INFO" 112 | } 113 | ] 114 | } 115 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/ha/standby/kea-ctrl-agent.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Control-agent": { 3 | "http-host": "0.0.0.0", 4 | "http-port": 8000, 5 | "authentication": { 6 | "type": "basic", 7 | "realm": "kea-control-agent", 8 | "clients": [ 9 | { 10 | "user": "kea", 11 | "password": "secret123" 12 | } 13 | ] 14 | }, 15 | "control-sockets": { 16 | "dhcp4": { 17 | "comment": "socket to DHCP4 server", 18 | "socket-type": "unix", 19 | "socket-name": "/tmp/kea4-ctrl-socket" 20 | }, 21 | "dhcp6": { 22 | "socket-type": "unix", 23 | "socket-name": "/tmp/kea6-ctrl-socket" 24 | }, 25 | "d2": { 26 | "socket-type": "unix", 27 | "socket-name": "/tmp/kea-ddns-ctrl-socket", 28 | "user-context": { 29 | "in-use": false 30 | } 31 | } 32 | }, 33 | "loggers": [ 34 | { 35 | "name": "kea-ctrl-agent", 36 | "output_options": [ 37 | { 38 | "output": "/var/log/kea-ctrl-agent.log", 39 | "flush": true, 40 | "maxsize": 204800, 41 | "maxver": 4, 42 | "pattern": "%d{%y.%m.%d %H:%M:%S.%q} %-5p [%c/%i] %m\n" 43 | } 44 | ], 45 | "severity": "DEBUG", 46 | "debuglevel": 99 47 | } 48 | ] 49 | } 50 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/ha/standby/kea-dhcp4-sql.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": { 3 | "multi-threading": {"enable-multi-threading": false}, 4 | "server-tag": "pyisckea-2", 5 | "interfaces-config": { 6 | "interfaces": [ 7 | "eth0" 8 | ] 9 | }, 10 | "control-socket": { 11 | "socket-type": "unix", 12 | "socket-name": "/tmp/kea4-ctrl-socket" 13 | }, 14 | "lease-database": { 15 | "type": "mysql", 16 | "name": "kea", 17 | "host": "db", 18 | "connect-timeout": 10, 19 | "max-reconnect-tries": 3, 20 | "on-fail": "stop-retry-exit", 21 | "user": "kea", 22 | "password": "secret123" 23 | }, 24 | "hosts-database": { 25 | "type": "mysql", 26 | "name": "kea", 27 | "host": "db", 28 | "connect-timeout": 10, 29 | "max-reconnect-tries": 20, 30 | "on-fail": "stop-retry-exit", 31 | "user": "kea", 32 | "password": "secret123" 33 | }, 34 | "config-control": { 35 | "config-databases": [ 36 | { 37 | "type": "mysql", 38 | "name": "kea", 39 | "user": "kea", 40 | "password": "secret123", 41 | "host": "db" 42 | } 43 | ], 44 | "config-fetch-wait-time": 20 45 | }, 46 | "hooks-libraries": [ 47 | { 48 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so" 49 | }, 50 | { 51 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cmds.so" 52 | }, 53 | { 54 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_mysql.so" 55 | }, 56 | { 57 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_subnet_cmds.so" 58 | }, 59 | { 60 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_class_cmds.so" 61 | }, 62 | { 63 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cache.so" 64 | }, 65 | { 66 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_ha.so", 67 | "parameters": { 68 | "high-availability": [ 69 | { 70 | "this-server-name": "server2", 71 | "mode": "hot-standby", 72 | "send-lease-updates": true, 73 | "sync-leases": true, 74 | "peers": [ 75 | { 76 | "auto-failover": true, 77 | "name": "server1", 78 | "url": "http://192.0.2.150:8000/", 79 | "role": "primary", 80 | "basic-auth-user": "kea", 81 | "basic-auth-password": "secret123" 82 | }, 83 | { 84 | "auto-failover": true, 85 | "name": "server2", 86 | "url": "http://192.0.2.151:8000/", 87 | "role": "standby", 88 | "basic-auth-user": "kea", 89 | "basic-auth-password": "secret123" 90 | } 91 | ] 92 | } 93 | ] 94 | } 95 | } 96 | ], 97 | "loggers": [ 98 | { 99 | "name": "kea-dhcp4", 100 | "output_options": [ 101 | { 102 | "output": "stdout" 103 | } 104 | ], 105 | "severity": "DEBUG", 106 | "debuglevel": 99 107 | } 108 | ] 109 | } 110 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/ha/standby/kea-dhcp6-sql.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp6": { 3 | "multi-threading": {"enable-multi-threading": false}, 4 | "server-tag": "pyisckea-2", 5 | "interfaces-config": { 6 | "interfaces": [ 7 | "eth0" 8 | ] 9 | }, 10 | "control-socket": { 11 | "socket-type": "unix", 12 | "socket-name": "/tmp/kea6-ctrl-socket" 13 | }, 14 | "lease-database": { 15 | "type": "mysql", 16 | "name": "kea", 17 | "host": "db", 18 | "connect-timeout": 10, 19 | "max-reconnect-tries": 3, 20 | "on-fail": "stop-retry-exit", 21 | "user": "kea", 22 | "password": "secret123" 23 | }, 24 | "hosts-database": { 25 | "type": "mysql", 26 | "name": "kea", 27 | "host": "db", 28 | "connect-timeout": 10, 29 | "max-reconnect-tries": 20, 30 | "on-fail": "stop-retry-exit", 31 | "user": "kea", 32 | "password": "secret123" 33 | }, 34 | "config-control": { 35 | "config-databases": [ 36 | { 37 | "type": "mysql", 38 | "name": "kea", 39 | "user": "kea", 40 | "password": "secret123", 41 | "host": "db" 42 | } 43 | ], 44 | "config-fetch-wait-time": 20 45 | }, 46 | "hooks-libraries": [ 47 | { 48 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so" 49 | }, 50 | { 51 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cmds.so" 52 | }, 53 | { 54 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_mysql.so" 55 | }, 56 | { 57 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_subnet_cmds.so" 58 | }, 59 | { 60 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_class_cmds.so" 61 | }, 62 | { 63 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cache.so" 64 | }, 65 | { 66 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_ha.so", 67 | "parameters": { 68 | "high-availability": [ 69 | { 70 | "this-server-name": "server2", 71 | "mode": "hot-standby", 72 | "send-lease-updates": true, 73 | "sync-leases": true, 74 | "peers": [ 75 | { 76 | "auto-failover": true, 77 | "name": "server1", 78 | "url": "http://192.0.2.150:8000/", 79 | "role": "primary", 80 | "basic-auth-user": "kea", 81 | "basic-auth-password": "secret123" 82 | }, 83 | { 84 | "auto-failover": true, 85 | "name": "server2", 86 | "url": "http://192.0.2.151:8000/", 87 | "role": "standby", 88 | "basic-auth-user": "kea", 89 | "basic-auth-password": "secret123" 90 | } 91 | ] 92 | } 93 | ] 94 | } 95 | } 96 | ], 97 | "preferred-lifetime": 3000, 98 | "valid-lifetime": 4000, 99 | "renew-timer": 1000, 100 | "rebind-timer": 2000, 101 | "subnet6": [], 102 | "loggers": [ 103 | { 104 | "name": "kea-dhcp6", 105 | "output_options": [ 106 | { 107 | "output": "stdout" 108 | } 109 | ], 110 | "debuglevel": 0, 111 | "severity": "INFO" 112 | } 113 | ] 114 | } 115 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/kea-ctrl-agent.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Control-agent": { 3 | "http-host": "0.0.0.0", 4 | "http-port": 8000, 5 | "authentication": { 6 | "type": "basic", 7 | "realm": "kea-control-agent", 8 | "clients": [ 9 | { 10 | "user": "kea", 11 | "password": "secret123" 12 | } 13 | ] 14 | }, 15 | "control-sockets": { 16 | "dhcp4": { 17 | "comment": "socket to DHCP4 server", 18 | "socket-type": "unix", 19 | "socket-name": "/tmp/kea4-ctrl-socket" 20 | }, 21 | "dhcp6": { 22 | "socket-type": "unix", 23 | "socket-name": "/tmp/kea6-ctrl-socket" 24 | }, 25 | "d2": { 26 | "socket-type": "unix", 27 | "socket-name": "/tmp/kea-ddns-ctrl-socket", 28 | "user-context": { 29 | "in-use": false 30 | } 31 | } 32 | }, 33 | "loggers": [ 34 | { 35 | "name": "kea-ctrl-agent", 36 | "output_options": [ 37 | { 38 | "output": "/var/log/kea-ctrl-agent.log", 39 | "flush": true, 40 | "maxsize": 204800, 41 | "maxver": 4, 42 | "pattern": "%d{%y.%m.%d %H:%M:%S.%q} %-5p [%c/%i] %m\n" 43 | } 44 | ], 45 | "severity": "INFO", 46 | "debuglevel": 0 47 | } 48 | ] 49 | } 50 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/kea-ddns.conf: -------------------------------------------------------------------------------- 1 | { 2 | "DhcpDdns" : 3 | { 4 | "ip-address" : "0.0.0.0", 5 | "port" : 53001, 6 | // "dns-server-timeout" : 100, 7 | // "ncr-protocol" : "UDP" 8 | // "ncr-format" : "JSON" 9 | // "control-socket": 10 | // { 11 | // "socket-type": "unix", 12 | // "socket-name": "/tmp/kea-ddns-ctrl-socket" 13 | // }, 14 | "forward-ddns" : 15 | { 16 | "ddns-domains" : 17 | [ 18 | // { 19 | // "name" : "", 20 | // "key-name" : "", 21 | // "dns-servers" : 22 | // [ 23 | // { 24 | // "ip-address" : "" 25 | // ,"port" : 53 26 | // } 27 | // , 28 | // { 29 | // next DNS server for this DdnsDomain 30 | // } 31 | // : 32 | // ] 33 | // } 34 | // , 35 | // { 36 | // next Forward DdnsDomain 37 | // } 38 | // : 39 | ] 40 | }, 41 | "reverse-ddns" : 42 | { 43 | "ddns-domains" : 44 | [ 45 | // { 46 | // "name" : "", 47 | // "key-name" : "", 48 | // "dns-servers" : 49 | // [ 50 | // { 51 | // "ip-address" : "" 52 | // ,"port" : 53 53 | // } 54 | // , 55 | // { 56 | // next DNS server for this DdnsDomain 57 | // } 58 | // : 59 | // ] 60 | // } 61 | // , 62 | // { 63 | // next Reverse DdnsDomain 64 | // } 65 | // : 66 | ] 67 | }, 68 | "tsig-keys" : 69 | [ 70 | // { 71 | // "name" : "", 72 | // "algorithm" : "", 73 | // Valid values for algorithm are: HMAC-MD5, HMAC-SHA1, 74 | // HMAC-SHA224, HMAC-SHA256, 75 | // HMAC-SHA384, HMAC-SHA512 76 | // "digest-bits" : 256, 77 | // Minimum truncated length in bits. 78 | // Default 0 (means truncation is forbidden). 79 | // "secret" : "" 80 | // } 81 | // , 82 | // { 83 | // next TSIG Key 84 | // } 85 | ] 86 | // ,"loggers": 87 | // [ 88 | // { 89 | // "name": "kea-dhcp-ddns", 90 | // "severity": "info" 91 | // } 92 | // ] 93 | } 94 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/kea-dhcp4-sql.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": { 3 | "server-tag": "pyisckea-1", 4 | "interfaces-config": { 5 | "interfaces": [ 6 | "eth0" 7 | ] 8 | }, 9 | "control-socket": { 10 | "socket-type": "unix", 11 | "socket-name": "/tmp/kea4-ctrl-socket" 12 | }, 13 | "lease-database": { 14 | "type": "mysql", 15 | "name": "kea", 16 | "host": "db", 17 | "connect-timeout": 10, 18 | "max-reconnect-tries": 3, 19 | "on-fail": "stop-retry-exit", 20 | "user": "kea", 21 | "password": "secret123" 22 | }, 23 | "hosts-database": { 24 | "type": "mysql", 25 | "name": "kea", 26 | "host": "db", 27 | "connect-timeout": 10, 28 | "max-reconnect-tries": 20, 29 | "on-fail": "stop-retry-exit", 30 | "user": "kea", 31 | "password": "secret123" 32 | }, 33 | "config-control": { 34 | "config-databases": [ 35 | { 36 | "type": "mysql", 37 | "name": "kea", 38 | "user": "kea", 39 | "password": "secret123", 40 | "host": "db" 41 | } 42 | ], 43 | "config-fetch-wait-time": 20 44 | }, 45 | "hooks-libraries": [ 46 | { 47 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so" 48 | }, 49 | { 50 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cmds.so" 51 | }, 52 | { 53 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_mysql_cb.so" 54 | }, 55 | { 56 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_cb_cmds.so" 57 | }, 58 | { 59 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_subnet_cmds.so" 60 | }, 61 | { 62 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_class_cmds.so" 63 | }, 64 | { 65 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cache.so" 66 | } 67 | ], 68 | "loggers": [ 69 | { 70 | "name": "kea-dhcp4", 71 | "output_options": [ 72 | { 73 | "output": "/var/log/kea-dhcp4.log", 74 | "maxsize": 2048000, 75 | "maxver": 4 76 | } 77 | ], 78 | "severity": "INFO", 79 | "debuglevel": 0 80 | } 81 | ] 82 | } 83 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/kea-dhcp4.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp4": { 3 | "interfaces-config": { 4 | "interfaces": [ 5 | "eth0" 6 | ] 7 | }, 8 | "control-socket": { 9 | "socket-type": "unix", 10 | "socket-name": "/tmp/kea4-ctrl-socket" 11 | }, 12 | "lease-database": { 13 | "type": "memfile" 14 | }, 15 | "valid-lifetime": 43200, 16 | "renew-timer": 21600, 17 | "rebind-timer": 32400, 18 | "expired-leases-processing": { 19 | "reclaim-timer-wait-time": 3600, 20 | "hold-reclaimed-time": 172800, 21 | "max-reclaim-leases": 0, 22 | "max-reclaim-time": 0 23 | }, 24 | "hooks-libraries": [ 25 | { 26 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so" 27 | }, 28 | { 29 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cmds.so" 30 | }, 31 | { 32 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_subnet_cmds.so" 33 | }, 34 | { 35 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_class_cmds.so" 36 | }, 37 | { 38 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cache.so" 39 | } 40 | ], 41 | "subnet4": [ 42 | { 43 | "id": 1, 44 | "subnet": "192.168.1.0/24", 45 | "pools": [ 46 | { 47 | "pool": "192.168.1.100-192.168.1.199" 48 | } 49 | ], 50 | "option-data": [ 51 | { 52 | "name": "routers", 53 | "data": "192.168.1.1" 54 | }, 55 | { 56 | "name": "domain-name-servers", 57 | "data": "1.1.1.1,9.9.9.9" 58 | } 59 | ], 60 | "reservations": [ 61 | { 62 | "hw-address": "1a:1b:1c:1d:1e:1f", 63 | "ip-address": "192.168.1.10" 64 | }, 65 | { 66 | "client-id": "01:11:22:33:44:55:66", 67 | "ip-address": "192.168.1.11" 68 | } 69 | ] 70 | } 71 | ], 72 | "loggers": [ 73 | { 74 | "name": "kea-dhcp4", 75 | "output_options": [ 76 | { 77 | "output": "/var/log/kea-dhcp4.log", 78 | "maxsize": 2048000, 79 | "maxver": 4 80 | } 81 | ], 82 | "severity": "INFO", 83 | "debuglevel": 0 84 | } 85 | ] 86 | } 87 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/kea-dhcp6-sql.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp6": { 3 | "server-tag": "pyisckea-1", 4 | "interfaces-config": { 5 | "interfaces": [ 6 | "eth0" 7 | ] 8 | }, 9 | "control-socket": { 10 | "socket-type": "unix", 11 | "socket-name": "/tmp/kea6-ctrl-socket" 12 | }, 13 | "lease-database": { 14 | "type": "mysql", 15 | "name": "kea", 16 | "host": "db", 17 | "connect-timeout": 10, 18 | "max-reconnect-tries": 3, 19 | "on-fail": "stop-retry-exit", 20 | "user": "kea", 21 | "password": "secret123" 22 | }, 23 | "hosts-database": { 24 | "type": "mysql", 25 | "name": "kea", 26 | "host": "db", 27 | "connect-timeout": 10, 28 | "max-reconnect-tries": 20, 29 | "on-fail": "stop-retry-exit", 30 | "user": "kea", 31 | "password": "secret123" 32 | }, 33 | "config-control": { 34 | "config-databases": [ 35 | { 36 | "type": "mysql", 37 | "name": "kea", 38 | "user": "kea", 39 | "password": "secret123", 40 | "host": "db" 41 | } 42 | ], 43 | "config-fetch-wait-time": 20 44 | }, 45 | "hooks-libraries": [ 46 | { 47 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so" 48 | }, 49 | { 50 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cmds.so" 51 | }, 52 | { 53 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_mysql_cb.so" 54 | }, 55 | { 56 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_cb_cmds.so" 57 | }, 58 | { 59 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_subnet_cmds.so" 60 | }, 61 | { 62 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_class_cmds.so" 63 | }, 64 | { 65 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cache.so" 66 | } 67 | ], 68 | "preferred-lifetime": 3000, 69 | "valid-lifetime": 4000, 70 | "renew-timer": 1000, 71 | "rebind-timer": 2000, 72 | "subnet6": [], 73 | "loggers": [ 74 | { 75 | "name": "kea-dhcp6", 76 | "output_options": [ 77 | { 78 | "output": "stdout" 79 | } 80 | ], 81 | "debuglevel": 0, 82 | "severity": "INFO" 83 | } 84 | ] 85 | } 86 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/kea-dhcp6.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Dhcp6": { 3 | "interfaces-config": { 4 | "interfaces": [ 5 | "eth0" 6 | ] 7 | }, 8 | "lease-database": { 9 | "type": "memfile", 10 | "lfc-interval": 3600 11 | }, 12 | "control-socket": { 13 | "socket-type": "unix", 14 | "socket-name": "/tmp/kea6-ctrl-socket" 15 | }, 16 | "hooks-libraries": [ 17 | { 18 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so" 19 | }, 20 | { 21 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cmds.so" 22 | }, 23 | { 24 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_subnet_cmds.so" 25 | }, 26 | { 27 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_class_cmds.so" 28 | }, 29 | { 30 | "library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_host_cache.so" 31 | } 32 | ], 33 | "preferred-lifetime": 3000, 34 | "valid-lifetime": 4000, 35 | "renew-timer": 1000, 36 | "rebind-timer": 2000, 37 | "subnet6": [ 38 | { 39 | "id": 1, 40 | "pools": [ 41 | { 42 | "pool": "2001:db8:1::/80" 43 | } 44 | ], 45 | "subnet": "2001:db8:1::/64" 46 | } 47 | ], 48 | "loggers": [ 49 | { 50 | "name": "kea-dhcp6", 51 | "output_options": [ 52 | { 53 | "output": "stdout" 54 | } 55 | ], 56 | "debuglevel": 0, 57 | "severity": "INFO" 58 | } 59 | ] 60 | } 61 | } -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/mysqld.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | pid-file = /var/run/mysqld/mysqld.pid 3 | socket = /var/run/mysqld/mysqld.sock 4 | # Where the database files are stored inside the container 5 | datadir = /var/lib/mysql 6 | 7 | # My application special configuration 8 | max_allowed_packet = 32M 9 | sql-mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION' 10 | 11 | # Accept connections from any IP address 12 | bind-address = 0.0.0.0 13 | ssl = 0 14 | host_cache_size = 0 -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/supervisor/conf.d/kea-agent.conf: -------------------------------------------------------------------------------- 1 | [program:kea-agent] 2 | command=/usr/sbin/kea-ctrl-agent -c /etc/kea/kea-ctrl-agent.conf 3 | autostart = true 4 | autorestart = false 5 | stdout_logfile=/dev/stdout 6 | stdout_logfile_maxbytes=0 7 | stderr_logfile=/dev/stderr 8 | stderr_logfile_maxbytes=0 -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/supervisor/conf.d/kea-dhcp4.conf: -------------------------------------------------------------------------------- 1 | [program:kea-dhcp4] 2 | command=/usr/sbin/kea-dhcp4 -c /etc/kea/kea-dhcp4.conf 3 | autostart = true 4 | autorestart = false 5 | stdout_logfile=/dev/stdout 6 | stdout_logfile_maxbytes=0 7 | stderr_logfile=/dev/stderr 8 | stderr_logfile_maxbytes=0 9 | -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/supervisor/conf.d/kea-dhcp6.conf: -------------------------------------------------------------------------------- 1 | [program:kea-dhcp6] 2 | command=/usr/sbin/kea-dhcp6 -c /etc/kea/kea-dhcp6.conf 3 | autostart = true 4 | autorestart = false 5 | stdout_logfile=/dev/stdout 6 | stdout_logfile_maxbytes=0 7 | stderr_logfile=/dev/stderr 8 | stderr_logfile_maxbytes=0 9 | -------------------------------------------------------------------------------- /tests/test_infrastructure/configs/supervisor/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | logfile=/var/log/supervisor/supervisord.log 4 | 5 | [inet_http_server] 6 | port = 0.0.0.0:9001 7 | 8 | [rpcinterface:supervisor] 9 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 10 | 11 | [supervisorctl] 12 | 13 | [include] 14 | files = /etc/supervisor/conf.d/*.conf -------------------------------------------------------------------------------- /tests/test_infrastructure/db-init/02-create-users.sql: -------------------------------------------------------------------------------- 1 | GRANT ALL PRIVILEGES ON kea.* TO 'kea'@'%' WITH GRANT OPTION; -------------------------------------------------------------------------------- /tests/test_infrastructure/docker-compose-ha-sql.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | image: mysql:9.2.0 4 | command: 5 | --bind-address=0.0.0.0 6 | restart: always 7 | environment: 8 | MYSQL_DATABASE: "kea" 9 | MYSQL_ROOT_PASSWORD: "secret123" 10 | MYSQL_ROOT_HOST: "%" 11 | MYSQL_USER: kea 12 | MYSQL_PASSWORD: "secret123" 13 | ports: 14 | - 3306:3306 15 | volumes: 16 | - ./db-init:/docker-entrypoint-initdb.d 17 | networks: 18 | ha_backend: 19 | 20 | keaprimary: 21 | build: 22 | context: . 23 | dockerfile: ./Dockerfiles/Dockerfile 24 | args: 25 | KEA_VERSION: ${KEA_VERSION:-2.7.7-isc20250326114722} 26 | KEA_REPO: ${KEA_REPO:-public/isc/kea-dev} 27 | KEA_PREMIUM: ${KEA_PREMIUM:-""} 28 | ports: 29 | - "8080:8080" # Supervisor API Access 30 | - "8000:8000" # Ctrl Agent Daemon 31 | volumes: 32 | - ./configs/supervisor/supervisord.conf:/etc/supervisor/supervisord.conf:Z 33 | - ./configs/supervisor/conf.d/:/etc/supervisor/conf.d:Z 34 | - ./configs/ha/primary/kea-ctrl-agent.conf:/etc/kea/kea-ctrl-agent.conf:Z 35 | - ./configs/ha/primary/kea-dhcp4-sql.conf:/etc/kea/kea-dhcp4.conf:Z 36 | - ./configs/ha/primary/kea-dhcp6-sql.conf:/etc/kea/kea-dhcp6.conf:Z 37 | tty: true 38 | networks: 39 | ha_backend: 40 | ipv4_address: 192.0.2.150 41 | 42 | keastandby: 43 | build: 44 | context: . 45 | dockerfile: ./Dockerfiles/Dockerfile 46 | args: 47 | KEA_VERSION: ${KEA_VERSION:-2.7.7-isc20250326114722} 48 | KEA_REPO: ${KEA_REPO:-public/isc/kea-dev} 49 | KEA_PREMIUM: ${KEA_PREMIUM:-""} 50 | ports: 51 | - "8081:8080" # Supervisor API Access 52 | - "8001:8000" # Ctrl Agent Daemon 53 | volumes: 54 | - ./configs/supervisor/supervisord.conf:/etc/supervisor/supervisord.conf:Z 55 | - ./configs/supervisor/conf.d/:/etc/supervisor/conf.d:Z 56 | - ./configs/ha/standby/kea-ctrl-agent.conf:/etc/kea/kea-ctrl-agent.conf:Z 57 | - ./configs/ha/standby/kea-dhcp4-sql.conf:/etc/kea/kea-dhcp4.conf:Z 58 | - ./configs/ha/standby/kea-dhcp6-sql.conf:/etc/kea/kea-dhcp6.conf:Z 59 | tty: true 60 | networks: 61 | ha_backend: 62 | ipv4_address: 192.0.2.151 63 | 64 | networks: 65 | ha_backend: 66 | driver: bridge 67 | ipam: 68 | config: 69 | - subnet: 192.0.2.128/25 70 | gateway: 192.0.2.129 -------------------------------------------------------------------------------- /tests/test_infrastructure/docker-compose-sql.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | db: 4 | image: mysql:9.2.0 5 | command: 6 | --bind-address=0.0.0.0 7 | restart: always 8 | environment: 9 | MYSQL_DATABASE: "kea" 10 | MYSQL_ROOT_PASSWORD: "secret123" 11 | MYSQL_ROOT_HOST: "%" 12 | MYSQL_USER: kea 13 | MYSQL_PASSWORD: "secret123" 14 | ports: 15 | - 3306:3306 16 | volumes: 17 | - ./db-init:/docker-entrypoint-initdb.d 18 | 19 | kea: 20 | build: 21 | context: . 22 | dockerfile: ./Dockerfiles/Dockerfile 23 | args: 24 | KEA_VERSION: ${KEA_VERSION} 25 | KEA_REPO: ${KEA_REPO} 26 | KEA_PREMIUM: ${KEA_PREMIUM} 27 | ports: 28 | - "8080:8080" # Supervisor API Access 29 | - "8000:8000" # Ctrl Agent Daemon 30 | - "67:67/udp" # DHCP v4 Daemon 31 | - "547:547/udp" # DHCP v6 Daemon 32 | - "5300:5300/udp" # DDNS Daemon 33 | environment: 34 | KEA_VERSION: "2.6.1-isc20240725093407" 35 | KEA_REPO: "public/isc/kea-2-6" 36 | KEA_PREMIUM: "" 37 | env_file: 38 | - .env 39 | volumes: 40 | - ./configs/supervisor/supervisord.conf:/etc/supervisor/supervisord.conf:Z 41 | - ./configs/supervisor/conf.d/:/etc/supervisor/conf.d:Z 42 | - ./configs/kea-ctrl-agent.conf:/etc/kea/kea-ctrl-agent.conf:Z 43 | - ./configs/kea-dhcp4-sql.conf:/etc/kea/kea-dhcp4.conf:Z 44 | - ./configs/kea-dhcp6-sql.conf:/etc/kea/kea-dhcp6.conf:Z 45 | tty: true -------------------------------------------------------------------------------- /tests/test_infrastructure/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | kea: 4 | build: 5 | context: . 6 | dockerfile: ./Dockerfiles/Dockerfile 7 | args: 8 | KEA_VERSION: ${KEA_VERSION:-2.7.7-isc20250326114722} 9 | KEA_REPO: ${KEA_REPO:-public/isc/kea-dev} 10 | KEA_PREMIUM: ${KEA_PREMIUM:-""} 11 | ports: 12 | - "8080:8080" # Supervisor API Access 13 | - "8000:8000" # Ctrl Agent Daemon 14 | - "67:67/udp" # DHCP v4 Daemon 15 | - "547:547/udp" # DHCP v6 Daemon 16 | - "5300:5300/udp" # DDNS Daemon 17 | volumes: 18 | - ./configs/supervisor/supervisord.conf:/etc/supervisor/supervisord.conf:Z 19 | - ./configs/supervisor/conf.d/:/etc/supervisor/conf.d:Z 20 | - ./configs/kea-ctrl-agent.conf:/etc/kea/kea-ctrl-agent.conf:Z 21 | - ./configs/kea-ddns.conf:/etc/kea/kea-ddns.conf:Z 22 | - ./configs/kea-dhcp4.conf:/etc/kea/kea-dhcp4.conf:Z 23 | - ./configs/kea-dhcp6.conf:/etc/kea/kea-dhcp6.conf:Z 24 | tty: true --------------------------------------------------------------------------------