diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e10a07e7..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 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/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/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) """