diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aa4adb5d..a493c71c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,19 @@ env: PIP_DISABLE_PIP_VERSION_CHECK: "1" # Reduce noise in logs jobs: + pre-commit: + env: + SKIP: pytest,pytype,tox,no-commit-to-branch + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 + with: + python-version: 3.11 + - uses: pre-commit/action@v3.0.1 + - uses: pre-commit-ci/lite-action@v1.0.3 + if: always() + test: strategy: # See: https://github.com/xenserver/python-libs/pull/26#discussion_r1179482169 @@ -43,13 +56,13 @@ jobs: env: PYTHON_VERSION_USED_FOR_COVERAGE: ${{ '3.11' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 # Needed by diff-cover to get the changed lines: origin/master..HEAD - name: Set up Python ${{ matrix.python-version }} # Python 3.11 is not supported in the nektos/act container, so we skip this step if: ${{ !(matrix.python-version == '3.11' && github.actor == 'nektos/act') }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ba7cb9cb..1a214b9c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -102,7 +102,7 @@ repos: name: pytest unit tests and static analysis using tox types: [python] # entry: sh -c "pytest -x -rf --new-first --show-capture=all >/dev/tty" - entry: sh -c "tox -e py311-cov-check-pytype-pyright-lint-mdreport >/dev/tty" + entry: sh -c "tox -e py311-cov-check-pytype-pyright-lint-docs-mdreport >/dev/tty" verbose: true language: python require_serial: true @@ -115,6 +115,7 @@ repos: hooks: - id: trailing-whitespace - id: end-of-file-fixer + exclude: docs/source/index.rst - id: check-yaml - id: check-added-large-files args: ['--maxkb=50'] diff --git a/README-Unicode.md b/README-Unicode.md index 3e52c910..8abc2353 100644 --- a/README-Unicode.md +++ b/README-Unicode.md @@ -4,20 +4,20 @@ Python3.6 on XS8 does not have an all-encompassing default UTF-8 mode for I/O. -Newer Python versions have an UTF-8 mode that they even enable by default. -Python3.6 only enabled UTF-8 for I/O when an UTF-8 locale is used. +Newer Python versions have a UTF-8 mode that they even enable by default. +Python3.6 only enabled UTF-8 for I/O when a UTF-8 locale is used. See below for more background info on the UTF-8 mode. For situations where UTF-8 enabled, we have to specify UTF-8 explicitly. -Such sitation happens when LANG or LC_* variables are not set for UTF-8. -XAPI plugins like auto-cert-kit find themself in this situation. +This happens when LANG or LC_* variables are not set for UTF-8. +XAPI plugins like auto-cert-kit find themselves in this situation. Example: For reading UTF-8 files like the `pciids` file, add `encoding="utf-8"`. -This applies especailly to `open()` and `Popen()` when files my contain UTF-8. +This applies especially to `open()` and `Popen()` when files may contain UTF-8. -This also applies when en/decoding to/form `urllib` which uses bytes. +This also applies when en/decoding to/from `urllib` which uses bytes. `urllib` has to use bytes as HTTP data can of course also be binary, e.g. compressed. ## Migrating `subprocess.Popen()` @@ -132,19 +132,9 @@ To fix these issues, `xcp.compat`, provides a wrapper for `open()`. It adds `encoding="utf-8", errors="replace"` to enable UTF-8 conversion and handle encoding errors: - ```py - # xcp/utf8mode.py - if sys.version_info >= (3, 0): - def utf8open(*args, **kwargs): - if len(args) > 1 and "b" in args[1]: - return open(*args, **kwargs) - return open(*args, encoding="utf-8", errors="replace", **kwargs) - else: - utf8open = open - # xcp/{cmd,pci,environ?,logger?}.py tests/test_{pci,biodevname?,...?}.py - + from .utf8mode import utf8open - ... - - open(filename) - + utf8open(filename) - ``` - +```py + def utf8open(*args, **kwargs): + if len(args) > 1 and "b" in args[1]: + return open(*args, **kwargs) + return open(*args, encoding="utf-8", errors="replace", **kwargs) +``` diff --git a/docs/Makefile b/docs/Makefile index d0c3cbf1..bed4efb2 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -17,4 +17,4 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/requirements.txt b/docs/requirements.txt index 6d7e4af0..7c50160d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,6 @@ six sphinx sphinx-autodoc-typehints +sphinxcontrib-mermaid furo myst_parser diff --git a/docs/source/accessor.rst b/docs/source/accessor.rst index 0c700e53..2956fbcb 100644 --- a/docs/source/accessor.rst +++ b/docs/source/accessor.rst @@ -1,6 +1,11 @@ xcp.accessor ============= +.. autoclasstree:: xcp.accessor + :title: Accessor class hierarchy + :caption: (class hierarchy automatically generated from xcp.accessor using Sphinx's autoclasstree directive) + :full: + .. automodule:: xcp.accessor :members: :undoc-members: diff --git a/docs/source/conf.py b/docs/source/conf.py index 5407c091..2ba42162 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -3,6 +3,7 @@ import logging import os import sys +from datetime import datetime # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html @@ -30,12 +31,27 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = "python-libs" -copyright = "2025, Citrix Inc." +copyright = "2025, Citrix Inc." # pylint: disable=redefined-builtin author = "Citrix Inc." -from datetime import datetime - release = datetime.now().strftime("%Y.%m.%d-%H%M") +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html +# Set the favicon and logo to XenServer branding. +html_favicon = "https://xenserver.com/content/dam/xenserver/images/favicon-32x32.png" +html_logo = "https://www.xenserver.com/content/dam/xenserver/images/xenserver-full-color-rgb.svg" + +# -- MyST-Parser configuration ----------------------------------------------- +# https://github.com/mgaitan/sphinxcontrib-mermaid: +# Enables GitHub-style mermaid code blocks in markdown files. +# See https://myst-parser.readthedocs.io/en/latest/syntax/optional.html +# This allows to use mermaid code blocks in markdown files like this: +# ```mermaid +# graph TD; +# A-->B; +# ``` +myst_fence_as_directive = ["mermaid"] + # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration @@ -43,6 +59,7 @@ "sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.githubpages", + "sphinxcontrib.mermaid", "myst_parser", ] @@ -55,4 +72,5 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "furo" -html_static_path = ["_static"] +# No static html source files for now. +# html_static_path = ["_static"] diff --git a/docs/source/include-toplevel-CONTRIBUTING.md b/docs/source/include-toplevel-CONTRIBUTING.md index b007eebc..4d5b2da7 100644 --- a/docs/source/include-toplevel-CONTRIBUTING.md +++ b/docs/source/include-toplevel-CONTRIBUTING.md @@ -1,2 +1,2 @@ ```{include} ../../CONTRIBUTING.md -:parser: myst \ No newline at end of file +:parser: myst diff --git a/docs/source/include-toplevel-DOCUMENTING.md b/docs/source/include-toplevel-DOCUMENTING.md index e56d3fb9..ef536a0e 100644 --- a/docs/source/include-toplevel-DOCUMENTING.md +++ b/docs/source/include-toplevel-DOCUMENTING.md @@ -1,2 +1,2 @@ ```{include} ../../DOCUMENTING.md -:parser: myst \ No newline at end of file +:parser: myst diff --git a/docs/source/include-toplevel-README-Unicode.md b/docs/source/include-toplevel-README-Unicode.md index a64fb4f2..8109f6d3 100644 --- a/docs/source/include-toplevel-README-Unicode.md +++ b/docs/source/include-toplevel-README-Unicode.md @@ -1,2 +1,2 @@ ```{include} ../../README-Unicode.md -:parser: myst \ No newline at end of file +:parser: myst diff --git a/docs/source/include-toplevel-README.md b/docs/source/include-toplevel-README.md index 6c5b587c..8b89a8fd 100644 --- a/docs/source/include-toplevel-README.md +++ b/docs/source/include-toplevel-README.md @@ -1,2 +1,2 @@ ```{include} ../../README.md -:parser: myst \ No newline at end of file +:parser: myst diff --git a/docs/source/index.rst b/docs/source/index.rst index b8def097..b331b134 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -18,6 +18,11 @@ XenServer Python libs for Dom0 .. image:: https://img.shields.io/badge/Sphinx-docs-blue.svg :target: https://python-libs.onrender.com/ +Welcome to the python-libs documentation! + +Each module is documented in its own section. +Select a module from the menu to view its API documentation. + .. toctree:: :maxdepth: 2 :caption: Project Documentation @@ -46,7 +51,20 @@ XenServer Python libs for Dom0 version xmlunwrap -Welcome to the python-libs documentation! +.. toctree:: + :maxdepth: 2 + :caption: XCP.Net Modules + + net/biosdevname + net/ip + net/mac -Each module is documented in its own section. Select a module from the menu to view its API documentation. +.. toctree:: + :maxdepth: 2 + :caption: XCP.Net.Ifrename Modules + net/ifrename/ifrename-util + net/ifrename/ifrename-static + net/ifrename/ifrename-logic + net/ifrename/ifrename-dynamic + net/ifrename/ifrename-macpci diff --git a/docs/source/net/biosdevname.rst b/docs/source/net/biosdevname.rst new file mode 100644 index 00000000..b9647e61 --- /dev/null +++ b/docs/source/net/biosdevname.rst @@ -0,0 +1,7 @@ +xcp.net.biosdevname +=================== + +.. automodule:: xcp.net.biosdevname + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/net/ifrename/ifrename-dynamic.rst b/docs/source/net/ifrename/ifrename-dynamic.rst new file mode 100644 index 00000000..cfcb648b --- /dev/null +++ b/docs/source/net/ifrename/ifrename-dynamic.rst @@ -0,0 +1,7 @@ +xcp.net.ifrename.dynamic +======================== + +.. automodule:: xcp.net.ifrename.dynamic + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/net/ifrename/ifrename-logic.rst b/docs/source/net/ifrename/ifrename-logic.rst new file mode 100644 index 00000000..0606fb6d --- /dev/null +++ b/docs/source/net/ifrename/ifrename-logic.rst @@ -0,0 +1,7 @@ +xcp.net.ifrename.logic +====================== + +.. automodule:: xcp.net.ifrename.logic + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/net/ifrename/ifrename-macpci.rst b/docs/source/net/ifrename/ifrename-macpci.rst new file mode 100644 index 00000000..a6f3f987 --- /dev/null +++ b/docs/source/net/ifrename/ifrename-macpci.rst @@ -0,0 +1,7 @@ +xcp.net.ifrename.macpci +======================= + +.. automodule:: xcp.net.ifrename.macpci + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/net/ifrename/ifrename-static.rst b/docs/source/net/ifrename/ifrename-static.rst new file mode 100644 index 00000000..afc9c7ef --- /dev/null +++ b/docs/source/net/ifrename/ifrename-static.rst @@ -0,0 +1,7 @@ +xcp.net.ifrename.static +======================= + +.. automodule:: xcp.net.ifrename.static + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/net/ifrename/ifrename-util.rst b/docs/source/net/ifrename/ifrename-util.rst new file mode 100644 index 00000000..3b288e6f --- /dev/null +++ b/docs/source/net/ifrename/ifrename-util.rst @@ -0,0 +1,7 @@ +xcp.net.ifrename.util +===================== + +.. automodule:: xcp.net.ifrename.util + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/net/ip.rst b/docs/source/net/ip.rst new file mode 100644 index 00000000..6981fc6b --- /dev/null +++ b/docs/source/net/ip.rst @@ -0,0 +1,7 @@ +xcp.net.ip +========== + +.. automodule:: xcp.net.ip + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/net/mac.rst b/docs/source/net/mac.rst new file mode 100644 index 00000000..414c8320 --- /dev/null +++ b/docs/source/net/mac.rst @@ -0,0 +1,7 @@ +xcp.net.mac +=========== + +.. automodule:: xcp.net.mac + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/repository.rst b/docs/source/repository.rst index 7e4915a0..c70aaaec 100644 --- a/docs/source/repository.rst +++ b/docs/source/repository.rst @@ -1,6 +1,11 @@ xcp.repository ============== +.. autoclasstree:: xcp.repository + :title: Repository class hierarchy + :caption: (class hierarchy automatically generated from xcp.repository using Sphinx's autoclasstree directive) + :full: + .. automodule:: xcp.repository :members: :undoc-members: diff --git a/stubs/pytest_httpserver.pyi b/stubs/pytest_httpserver.pyi deleted file mode 100644 index 0ee27850..00000000 --- a/stubs/pytest_httpserver.pyi +++ /dev/null @@ -1,134 +0,0 @@ -import abc -from enum import Enum -from ssl import SSLContext -from typing import Any, Callable, Iterable, Mapping, MutableMapping, Optional, Pattern, Tuple, Union - -# pylint: disable=import-error, no-name-in-module, super-init-not-called, multiple-statements, too-few-public-methods, invalid-name, line-too-long -from _typeshed import Incomplete - -from werkzeug.wrappers import Request, Response - -URI_DEFAULT: str -METHOD_ALL: str -HEADERS_T = Union[Mapping[str, Union[str, Iterable[str]]], Iterable[Tuple[str, str]]] -HVMATCHER_T = Callable[[str, Optional[str], str], bool] - -class Error(Exception): ... -class NoHandlerError(Error): ... -class HTTPServerError(Error): ... -class NoMethodFoundForMatchingHeaderValueError(Error): ... - -class WaitingSettings: - raise_assertions: Incomplete - stop_on_nohandler: Incomplete - timeout: Incomplete - def __init__(self, raise_assertions: bool = ..., stop_on_nohandler: bool = ..., timeout: float = ...) -> None: ... - -class Waiting: - def __init__(self) -> None: ... - def complete(self, result: bool): ... - @property - def result(self) -> bool: ... - @property - def elapsed_time(self) -> float: ... - -class HeaderValueMatcher: - DEFAULT_MATCHERS: MutableMapping[str, Callable[[Optional[str], str], bool]] - matchers: Incomplete - def __init__(self, matchers: Optional[Mapping[str, Callable[[Optional[str], str], bool]]] = ...) -> None: ... - @staticmethod - def authorization_header_value_matcher(actual: Optional[str], expected: str) -> bool: ... - @staticmethod - def default_header_value_matcher(actual: Optional[str], expected: str) -> bool: ... - def __call__(self, header_name: str, actual: Optional[str], expected: str) -> bool: ... - -class URIPattern(abc.ABC, metaclass=abc.ABCMeta): - @abc.abstractmethod - def match(self, uri: str) -> bool: ... - -class RequestMatcher: - uri: Incomplete - method: Incomplete - query_string: Incomplete - query_matcher: Incomplete - json: Incomplete - headers: Incomplete - data: Incomplete - data_encoding: Incomplete - header_value_matcher: Incomplete - # def __init__(self) - def match_data(self, request: Request) -> bool: ... - def match_uri(self, request: Request) -> bool: ... - def match_json(self, request: Request) -> bool: ... - def match(self, request: Request) -> bool: ... - -class RequestHandlerBase(abc.ABC, metaclass=abc.ABCMeta): - def respond_with_json(self, response_json, status: int = ..., headers: Optional[Mapping[str, str]] = ..., content_type: str = ...): ... - def respond_with_data(self, response_data: Union[str, bytes] = ..., status: int = ..., headers: Optional[HEADERS_T] = ..., mimetype: Optional[str] = ..., content_type: Optional[str] = ...): ... - @abc.abstractmethod - def respond_with_response(self, response: Response): ... - -class RequestHandler(RequestHandlerBase): - matcher: Incomplete - request_handler: Incomplete - def __init__(self, matcher: RequestMatcher) -> None: ... - def respond(self, request: Request) -> Response: ... - def respond_with_handler(self, func: Callable[[Request], Response]): ... - def respond_with_response(self, response: Response): ... - -class HandlerType(Enum): - PERMANENT: str - ONESHOT: str - ORDERED: str - -class HTTPServerBase(abc.ABC, metaclass=abc.ABCMeta): - host: Incomplete - port: Incomplete - server: Incomplete - server_thread: Incomplete - assertions: Incomplete - handler_errors: Incomplete - log: Incomplete - ssl_context: Incomplete - no_handler_status_code: int - def __init__(self, host: str, port: int, ssl_context: Optional[SSLContext] = ...) -> None: ... - def clear(self) -> None: ... - def clear_assertions(self) -> None: ... - def clear_handler_errors(self) -> None: ... - def clear_log(self) -> None: ... - def url_for(self, suffix: str): ... - def create_matcher(self, *args, **kwargs) -> RequestMatcher: ... - def thread_target(self) -> None: ... - def is_running(self) -> bool: ... - def start(self) -> None: ... - def stop(self) -> None: ... - def add_assertion(self, obj) -> None: ... - def check(self) -> None: ... - def check_assertions(self) -> None: ... - def check_handler_errors(self) -> None: ... - def respond_nohandler(self, request: Request, extra_message: str = ...): ... - @abc.abstractmethod - def dispatch(self, request: Request) -> Response: ... - def application(self, request: Request): ... - def __enter__(self): ... - def __exit__(self, *args, **kwargs) -> None: ... - @staticmethod - def format_host(host): ... - -class HTTPServer(HTTPServerBase): - DEFAULT_LISTEN_HOST: str - DEFAULT_LISTEN_PORT: int - ordered_handlers: Incomplete - oneshot_handlers: Incomplete - handlers: Incomplete - permanently_failed: bool - default_waiting_settings: Incomplete - def __init__(self, host=..., port=..., ssl_context: Optional[SSLContext] = ..., default_waiting_settings: Optional[WaitingSettings] = ...) -> None: ... - def clear(self) -> None: ... - def clear_all_handlers(self) -> None: ... - def expect_request(self, uri: Union[str, URIPattern, Pattern[str]], method: str = ..., data: Union[str, bytes, None] = ..., data_encoding: str = ..., header_value_matcher: Optional[HVMATCHER_T] = ..., handler_type: HandlerType = ..., json: Any = ...) -> RequestHandler: ... - def format_matchers(self) -> str: ... - def respond_nohandler(self, request: Request, extra_message: str = ...): ... - def respond_permanent_failure(self): ... - def dispatch(self, request: Request) -> Response: ... - def wait(self, raise_assertions: Optional[bool] = ..., stop_on_nohandler: Optional[bool] = ..., timeout: Optional[float] = ...): ... diff --git a/tox.ini b/tox.ini index bcfdeab4..109d2828 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ min_version = 4.6 # .github/workflows/main.yml is set up to test with 3.11, 3.12 and 3.13 in parallel. # Therefore, use three environments: One with 3.11, one with 3.12 and one with 3.13: # -envlist = py311-covcp-check-pytype-mdreport, py312-cov, py313-cov-lint-pyright +envlist = py311-covcp-check-pytype-mdreport, py312-cov-docs, py313-cov-lint-pyright isolated_build = true skip_missing_interpreters = true requires = @@ -37,6 +37,7 @@ commands = description = Run in a {basepython} virtualenv: cov: {[cov]description} covcp: Copy the generated .converage and coverage.xml to the UPLOAD_DIR dir + docs: {[docs]description} fox: {[fox]description} lint: {[lint]description} mdreport: Make a test report (which is shown in the GitHub Actions Summary Page) @@ -56,12 +57,14 @@ deps = {cov,covcp,fox}: coverage[toml] {cov,covcp,fox}: diff-cover {lint,fox}: {[lint]deps} + docs: {[docs]deps} pyright: pyright allowlist_externals = {cov,covcp,fox,check,lint,test,mdreport}: echo {cov,covcp,fox,check,lint,test,mdreport}: sh {cov,covcp,fox}: cp check: cat + docs: make fox: firefox passenv = {pytype,lint,test}: GITHUB_STEP_SUMMARY @@ -99,6 +102,7 @@ setenv = lint: ENVLOGDIR={envlogdir} {[cov]setenv} commands = + docs: {[docs]commands} lint: {[lint]commands} pyright: {[pyright]commands} check: {[check]commands} @@ -126,6 +130,13 @@ commands = --html-report {envlogdir}/coverage-diff.html \ {envlogdir}/coverage.xml + +[docs] +description = Build the documentation to check for errors +deps = -r docs/requirements.txt +commands = make -C docs html SPHINXOPTS="--fail-on-warning" + + [lint] description = Run pylint and fail on warnings remaining on lines in the diff to master deps = pylint diff --git a/xcp/net/ifrename/logic.py b/xcp/net/ifrename/logic.py index 476dc2d7..3d3325e0 100644 --- a/xcp/net/ifrename/logic.py +++ b/xcp/net/ifrename/logic.py @@ -33,12 +33,16 @@ list of MACPCI objects in form ethXXX|side-XXX-ethXX->(mac, pci) [in] last_state - Last boot state (post rename) of network cards on the machine list of MACPCI objects in form (mac, pci)->ethXX -[in] old_state - Any older nics which have disappeared in the meantime +[in] old_state - Any older NICs which have disappeared in the meantime list of MACPCI objects in form (mac, pci)->ethXX [out] transactions list of string tuples as source and destination names for "ip link set name" + +Abbreviations used in this file: + kname: The kernel name of the network interface (the original name assigned by the kernel). + tname: The temporary name of the interface, used while renaming interfaces to avoid name conflicts. """ from __future__ import unicode_literals @@ -70,11 +74,11 @@ class LogicError(RuntimeError): def __rename_nic(nic, name, transactions, cur_state): """ - Rename a specified nic to name. - It checkes in possibly_aliased for nics which currently have name, and - renames them sideways. - The caller should ensure that no nics in cur_state have already been renamed - to name, and that name is a valid nic name + Rename a specified NIC to the given name. + It looks at possibly aliased NICs which currently have name, and + renames them sideways if necessary. + The caller should ensure that no NICs in cur_state have already been renamed + to name, and that name is a valid NIC name """ # Assert that name is valid @@ -89,7 +93,7 @@ def __rename_nic(nic, name, transactions, cur_state): if aliased is None: # Using this rule will not alias another currently present NIC - LOG.debug("Renaming unaliased nic '%s' to '%s'" % (nic, name)) + LOG.debug("Renaming unaliased NIC '%s' to '%s'" % (nic, name)) nic.tname = name transactions.append((nic.kname, name)) @@ -122,24 +126,40 @@ def __rename_nic(nic, name, transactions, cur_state): transactions.append((nic.kname, name)) -def rename_logic( static_rules, - cur_state, - last_state, - old_state ): +def rename_logic( + static_rules, + cur_state, + last_state, + old_state, +): # type: (list[MACPCI], list[MACPCI], list[MACPCI], list[MACPCI]) -> list[tuple[str, str]] """ Core logic of renaming the current state based on the rules and past state. + This function assumes all inputs have been suitably sanitised. - @param static_rules + + Parameters + ---------- + static_rules : list[MACPCI] List of MACPCI objects representing rules - @param cur_state + cur_state : list[MACPCI] List of MACPCI objects representing the current state - @param last_state + last_state : list[MACPCI] List of MACPCI objects representing the last boot state - @param old_state + old_state : list[MACPCI] List of MACPCI objects representing the old state - @returns List of tuples... - @throws AssertionError (Should not be thrown, but better to know about logic - errors if they occur) + + Returns + ------- + list[tuple[str, str]] + List of (source_name, destination_name) tuples, where each tuple + represents a name transaction for "ip link set name". + The first element is the current interface name (source), + and the second is the new interface name (destination). + + Raises + ------ + AssertionError + If the current state contains invalid entries. """ transactions = [] @@ -365,26 +385,56 @@ def rename_logic( static_rules, util.niceformat(cur_state))) return transactions -def rename( static_rules, - cur_state, - last_state, - old_state ): +def rename( + static_rules, + cur_state, + last_state, + old_state, +): # type: (list[MACPCI], list[MACPCI], list[MACPCI], list[MACPCI]) -> list[tuple[str, str]] """ Rename current state based on the rules and past state. - This function sanitises the input and delegates the renaming logic to - __rename. - @param static_rules + + This function: + - Sanitises the input + - Delegates the renaming logic to rename_logic() + + Parameters + ---------- + static_rules : list[MACPCI] List of MACPCI objects representing rules - @param cur_state + cur_state : list[MACPCI] List of MACPCI objects representing the current state - @param last_state + last_state : list[MACPCI] List of MACPCI objects representing the last boot state - @param old_state + old_state : list[MACPCI] List of MACPCI objects representing the old state - @throws StaticRuleError, CurrentStateError, LastStateError, TypeError - - @returns list of tuples of name changes required + Returns + ------- + list[tuple[str, str]] + List of (source_name, destination_name) tuples, where each tuple + represents a name transaction for "ip link set name". + The first element is the current interface name (source), + and the second is the new interface name (destination). + + Raises + ------ + OldStateError + Raised if any of the following conditions are met: + - An old state has a kernel name. + - An old state has a tname not starting with 'eth'. + StaticRuleError + Raised if any of the following conditions are met: + - A static rule has a kernel name. + - A static rule has a tname not starting with 'eth'. + - Duplicate eth names are present in static rules. + - Duplicate MAC addresses are present in static rules. + CurrentStateError + If the current state contains invalid entries. + LastStateError + If the last state contains invalid entries. + TypeError + If any of the input lists contain objects that are not MACPCI instances. """ if len(static_rules): diff --git a/xcp/net/ifrename/static.py b/xcp/net/ifrename/static.py index 76ab3723..2b4b9124 100644 --- a/xcp/net/ifrename/static.py +++ b/xcp/net/ifrename/static.py @@ -25,17 +25,16 @@ Object for manipulating static rules. Rules are of the form: - : = "value" + : = "value" -target name must be in the form eth* -id methods are: - mac: value should be the mac address of a device (e.g. DE:AD:C0:DE:00:00) - pci: value should be the pci bus location of the device, optionally with an index (e.g. 0000:01:01.1[0]) - ppn: value should be the result of the biosdevname physical naming policy of a device (e.g. p1p1) - label: value should be the SMBios label of a device (for SMBios 2.6 or above) - -Any line starting with '#' is considered to be a comment + target name must be in the form eth* + id methods are: + - `mac`: value should be the MAC address of a device (e.g. DE:AD:C0:DE:00:00) + - `pci`: value should be the PCI bus location of the device, optionally with an index (e.g. 0000:01:01.1[0]) + - `ppn`: value should be the result of the biosdevname physical naming policy of a device (e.g. p1p1) + - `label`: value should be the SMBIOS label of a device (for SMBIOS 2.6 or above) + Any line starting with '#' is considered to be a comment """ from __future__ import unicode_literals @@ -71,10 +70,10 @@ # target name must be in the form eth* # id methods are: -# mac: value should be the mac address of a device (e.g. DE:AD:C0:DE:00:00) -# pci: value should be the pci bus location of the device (e.g. 0000:01:01.1[0]) +# mac: value should be the MAC address of a device (e.g. DE:AD:C0:DE:00:00) +# pci: value should be the PCI bus location of the device (e.g. 0000:01:01.1[0]) # ppn: value should be the result of the biosdevname physical naming policy of a device (e.g. p1p1) -# label: value should be the SMBios label of a device (for SMBios 2.6 or above) +# label: value should be the SMBIOS label of a device (for SMBIOS 2.6 or above) """