Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .cqfd/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ RUN set -x \
python3 \
python3-flake8 \
python3-setuptools \
python3-sphinx \
python3-pip \
openjdk-8-jre-headless \
unzip \
wget \
&& rm -rf /var/lib/apt/lists/
&& rm -rf /var/lib/apt/lists/ \
&& pip3 install --no-cache-dir sphinx-argparse

ARG sonar_version=4.7.0.2747
ARG sonar_repo=https://binaries.sonarsource.com/Distribution/sonar-scanner-cli
Expand Down
5 changes: 4 additions & 1 deletion .cqfdrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
org='rte'
name='vm_manager'

flavors='check sonar check_format format flake'
flavors='check sonar check_format format flake docs'

[build]
command='/usr/bin/pip install --root-user-action=ignore --prefix=. .'
Expand All @@ -27,3 +27,6 @@ command='black -l 79 -t py38 .'

[flake]
command='python3 -m flake8 --ignore=E501,W503'

[docs]
command='sphinx-build -b html docs docs/_build/html'
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ jobs:

- name: Run tests
run: sg libvirt -c "pytest tests/ -v --tb=short"

- name: Install documentation dependencies
run: pip install ".[docs]"

- name: Build documentation
run: sphinx-build -b html docs/ docs/_build/html
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ __pycache__/
# Ignore IDE files
/.idea/

# Ignore generated documentation
/docs/_build/

# Ignore sonar files
/.scannerwork/
/.sonar/
Expand Down
16 changes: 16 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ cqfd -b flake # flake8
cqfd -b check # pylint
```

## Documentation

```bash
# Generate HTML docs locally (output: docs/_build/html/)
pip install .[docs]
sphinx-build -b html docs docs/_build/html

# Via cqfd
cqfd init
cqfd -b docs
```

Docs structure: `docs/overview.rst` (project overview), `docs/cli.rst`
(CLI reference via sphinx-argparse), `docs/api.rst` (Python API via autodoc).
sphinx-argparse requires `get_parser()` functions in both CLI modules.

## Tests

Tests are integration scripts requiring a real Ceph/Pacemaker cluster. Run individually:
Expand Down
31 changes: 31 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Python API Reference
====================

Standalone mode
---------------

.. automodule:: vm_manager.vm_manager_libvirt
:members:
:undoc-members:

Cluster mode
------------

.. automodule:: vm_manager.vm_manager_cluster
:members:
:undoc-members:

Helpers
-------

.. automodule:: vm_manager.helpers.libvirt
:members:
:undoc-members:

.. automodule:: vm_manager.helpers.pacemaker
:members:
:undoc-members:

.. automodule:: vm_manager.helpers.rbd_manager
:members:
:undoc-members:
23 changes: 23 additions & 0 deletions docs/cli.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CLI Reference
=============

vm_manager_cmd
--------------

Main CLI tool for VM management. Available subcommands depend on the
operating mode (standalone or cluster) detected at runtime.

.. argparse::
:module: vm_manager.vm_manager_cmd
:func: get_parser
:prog: vm_manager_cmd

libvirt_cmd
-----------

Lower-level CLI for direct libvirt operations.

.. argparse::
:module: vm_manager.helpers.libvirt_cmd
:func: get_parser
:prog: libvirt_cmd
28 changes: 28 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright (C) 2026 Savoir-faire Linux Inc.
# SPDX-License-Identifier: Apache-2.0

import os
import sys

sys.path.insert(0, os.path.abspath(".."))

project = "vm_manager"
copyright = "2026, Savoir-faire Linux Inc, RTE (http://www.rte-france.com) and contributors"
author = "Mathieu Dupré"

extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.viewcode",
"sphinxarg.ext",
]

# Mock imports for optional dependencies unavailable at doc build time
autodoc_mock_imports = [
"libvirt",
"rados",
"rbd",
"flask",
"flask_wtf",
]

html_theme = "alabaster"
16 changes: 16 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
vm_manager documentation
========================

.. toctree::
:maxdepth: 2
:caption: Contents

overview
cli
api

Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
52 changes: 52 additions & 0 deletions docs/overview.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
Overview
========

**vm_manager** is a Python tool for managing Virtual Machines on the
`SEAPATH <https://github.com/seapath>`_ platform.

Operating modes
---------------

The mode is auto-detected at import time based on available dependencies:

- **Standalone mode** (``vm_manager_libvirt.py``): manages VMs via KVM/libvirt
only. Used when Pacemaker and Ceph RBD dependencies are not present.

- **Cluster mode** (``vm_manager_cluster.py``): manages VMs on a Pacemaker
HA cluster with Ceph RBD storage. Activated when both ``rados``/``rbd``
and ``crm`` (Pacemaker) are available.

Entry points
------------

- ``vm_manager_cmd`` — main CLI exposing all VM operations as subcommands
- ``libvirt_cmd`` — lower-level CLI for direct libvirt operations
- ``vm_manager_api`` — Flask REST API (``/``, ``/status/<guest>``,
``/stop/<guest>``, ``/start/<guest>``)

Architecture
------------

Helper classes (used as context managers):

- :class:`~vm_manager.helpers.libvirt.LibVirtManager` — wraps
``libvirt-python`` for domain management
- :class:`~vm_manager.helpers.pacemaker.Pacemaker` — wraps the ``crm`` CLI
via ``subprocess``
- :class:`~vm_manager.helpers.rbd_manager.RbdManager` — wraps Ceph
``rados``/``rbd`` Python bindings

Installation
------------

.. code-block:: bash

pip install .

# or with documentation dependencies
pip install .[docs]

License
-------

Apache-2.0
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ readme = "README.md"

[project.optional-dependencies]
test = ["pytest>=7.0"]
docs = ["sphinx>=4.0", "sphinx-argparse"]

[tool.setuptools]
packages = ["vm_manager", "vm_manager.helpers", "vm_manager.helpers.tests.pacemaker", "vm_manager.helpers.tests.rbd_manager"]
Expand Down
17 changes: 14 additions & 3 deletions vm_manager/helpers/libvirt.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ def close(self):
def list(self):
"""
List all VM
:return the name list of all defined libvirt domain

:return: the name list of all defined libvirt domain
"""
return [x.name() for x in self._conn.listAllDomains()]

def get_virsh_secrets(self):
"""
Get the virsh secrets

:return: a dictionary of virsh secrets
"""
secrets = {}
Expand All @@ -66,6 +68,7 @@ def define(self, xml):
Validate and create a VM from xml configuration

Raise an error if the XML is not valid.

:param xml: the libvirt XML string
"""
try:
Expand All @@ -77,6 +80,7 @@ def define(self, xml):
def undefine(self, vm_name):
"""
Remove a VM

:param vm_name: the VM to undefined
"""
domain = self._conn.lookupByName(vm_name)
Expand All @@ -85,6 +89,7 @@ def undefine(self, vm_name):
def set_autostart(self, vm_name, enabled):
"""
Set the autostart flag on a VM

:param vm_name: the VM name
:param enabled: True to enable autostart, False to disable
"""
Expand All @@ -93,30 +98,34 @@ def set_autostart(self, vm_name, enabled):
def start(self, vm_name):
"""
Start a VM

:param vm_name: the VM to start
"""
self._conn.lookupByName(vm_name).create()

def stop(self, vm_name):
"""
Stop a VM

:param vm_name: the VM to be stopped
"""
self._conn.lookupByName(vm_name).shutdown()

def force_stop(self, vm_name):
"""
Forces a VM to stop

:param vm_name: the VM to be stopped
"""
self._conn.lookupByName(vm_name).destroy()

def status(self, vm_name):
"""
Get the VM status

:param vm_name: the VM for which the status must be checked
:return: the status of the VM, among Starting, Started, Paused,
Stopped, Stopping, Undefined and FAILED
:return: the status of the VM, among Starting, Started, Paused,
Stopped, Stopping, Undefined and FAILED
"""
if vm_name not in self.list():
logger.info(vm_name + "does not exist")
Expand Down Expand Up @@ -145,6 +154,7 @@ def status(self, vm_name):
def console(self, vm_name):
"""
Open a console on a VM

:param vm_name: the VM to open the console
"""
uri = self._conn.getURI()
Expand All @@ -165,6 +175,7 @@ def console(self, vm_name):
def export_configuration(domain, xml_path):
"""
Dump the libvirt XML configuration

:param domain: the domain whose the configuration is exported
:param xml_path: the path where the XML configuration will be exported
"""
Expand Down
8 changes: 7 additions & 1 deletion vm_manager/helpers/libvirt_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from vm_manager.helpers.libvirt import LibVirtManager


def main():
def get_parser():
"""Return the argument parser for libvirt_cmd."""
parser = argparse.ArgumentParser(description="libvirt helper cli wrapper")
subparsers = parser.add_subparsers(
help="command", dest="command", required=True, metavar="command"
Expand All @@ -32,6 +33,11 @@ def main():
help="full path where the configuration is exported",
)
define_parser.add_argument("xml", type=str, help="The XML file path")
return parser


def main():
parser = get_parser()
args = parser.parse_args()
if args.command == "list":
with LibVirtManager() as libvirt_manager:
Expand Down
Loading