Skip to content

Commit 87cddec

Browse files
committed
feat(linstor): add LinstorManager class
Signed-off-by: Ronan Abhamon <ronan.abhamon@vates.tech>
1 parent d7cd0b1 commit 87cddec

25 files changed

+4249
-0
lines changed

xcp-storage/pyproject.toml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
[tool.mypy]
2+
mypy_path = "stubs"
3+
4+
disallow_untyped_defs = true
5+
enable_error_code = ["explicit-override"]
6+
ignore_missing_imports = false
7+
implicit_optional = false
8+
9+
[[tool.mypy.overrides]]
10+
module = "setuptools.*"
11+
ignore_missing_imports = true
12+
13+
# To generate stubs, run the following commands in the linstor source folder:
14+
# ./setup.py build
15+
# stubgen --include-private build/lib/linstor/ -o <XCP_STORAGE_PATH>/stubs/
16+
# echo "from . import sharedconsts as consts" >> <XCP_STORAGE_PATH>/stubs/linstor/__init__.pyi
17+
[[tool.mypy.overrides]]
18+
module = "linstor.*"
19+
20+
disallow_untyped_defs = false
21+
disable_error_code = [
22+
"arg-type",
23+
"attr-defined",
24+
"explicit-override",
25+
"name-defined",
26+
"no-redef",
27+
"override"
28+
]
29+
30+
[tool.ruff]
31+
line-length = 120
32+
33+
[tool.ruff.lint]
34+
exclude = ["stubs/*"]
35+
ignore = [
36+
"UP006", # pyupgrade: non-pep585-annotation
37+
"UP007", # pyupgrade: non-pep604-annotation-union
38+
"UP045" # pyupgrade: non-pep604-annotation-optional
39+
]
40+
select = [
41+
"ARG", # flake8-unused-arguments
42+
"F", # Pyflakes
43+
"I", # isort
44+
"N", # pep8-naming
45+
"RUF013", # ruff: implicit-optional
46+
"RUF059", # ruff: unused-unpacked-variable
47+
"SIM", # flake8-simplify
48+
"SLF", # flake8-self
49+
"T20", # flake8-print
50+
"UP" # pyupgrade
51+
]
52+
typing-modules = ["xcp_storage.typing"]
53+
54+
[tool.ruff.lint.isort.sections]
55+
"typing" = ["xcp_storage.typing"]
56+
57+
[tool.ruff.lint.isort]
58+
case-sensitive = false
59+
combine-as-imports = true
60+
force-sort-within-sections = true
61+
known-first-party = ["xcp_storage"]
62+
known-third-party = ["linstor"]
63+
lines-after-imports = 1
64+
order-by-type = false
65+
section-order = [
66+
"future",
67+
"standard-library",
68+
"third-party",
69+
"first-party",
70+
"local-folder",
71+
"typing"
72+
]

xcp-storage/setup.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (C) 2026 Vates SAS
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
16+
from setuptools import find_packages, setup
17+
18+
setup(
19+
name="xcp-storage",
20+
version="1.0.0",
21+
description="XCP storage layer, scripts and plugins",
22+
author="Ronan Abhamon <ronan.abhamon@vates.tech>",
23+
author_email="ronan.abhamon@vates.tech",
24+
url="https://vates.tech",
25+
license="GPLv3",
26+
packages=find_packages(
27+
where="src",
28+
),
29+
python_requires=">=3.6",
30+
package_dir={"": "src"},
31+
scripts=[]
32+
)

xcp-storage/src/xcp_storage/__init__.py

Whitespace-only changes.

xcp-storage/src/xcp_storage/backends/__init__.py

Whitespace-only changes.

xcp-storage/src/xcp_storage/backends/linstor/__init__.py

Whitespace-only changes.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (C) 2026 Vates SAS
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
import contextlib
18+
import json
19+
20+
import xcp_storage.log as log
21+
from xcp_storage.utils.process import run_command
22+
23+
from xcp_storage.typing import (
24+
Any,
25+
Dict,
26+
Iterator,
27+
)
28+
29+
# ==============================================================================
30+
31+
@contextlib.contextmanager
32+
def _handle_drbd_json_error() -> Iterator[None]:
33+
try:
34+
yield
35+
except KeyError as e:
36+
log.error(
37+
f"The key `{e}` could not be found in the DRBD configuration. The JSON format may have changed.",
38+
exc_info=True
39+
)
40+
except Exception as e:
41+
log.error(f"Failed to parse DRBD configuration: `{e}`. The JSON format may have changed.", exc_info=True)
42+
43+
def _get_drbd_status(resource_name: str) -> Dict[str, Any]:
44+
stdout, _stderr, ret_code = run_command(["drbdsetup", "status", resource_name, "--json"], simple=False)
45+
if ret_code != 0:
46+
return {}
47+
48+
try:
49+
status = json.loads(stdout)
50+
except Exception as e:
51+
log.error(f"Failed to read DRBD status as JSON: `{e}`.")
52+
return {}
53+
54+
with _handle_drbd_json_error():
55+
return status[0]
56+
return {}
57+
58+
# ------------------------------------------------------------------------------
59+
60+
def get_drbd_connection_address(resource_name: str, node_name: str) -> str:
61+
status = _get_drbd_status(resource_name)
62+
if not status:
63+
return ""
64+
65+
with _handle_drbd_json_error():
66+
for connection in status["connections"]:
67+
if connection["name"] == node_name:
68+
return connection["paths"][0]["remote_host"]["address"]
69+
return ""
70+
71+
def get_drbd_primary_address(resource_name: str) -> str:
72+
status = _get_drbd_status(resource_name)
73+
if not status:
74+
return ""
75+
76+
with _handle_drbd_json_error():
77+
if status["role"] == "Primary":
78+
return status["connections"][0]["paths"][0]["this_host"]["address"]
79+
80+
for connection in status["connections"]:
81+
if connection["peer-role"] == "Primary":
82+
return connection["paths"][0]["remote_host"]["address"]
83+
84+
return ""

0 commit comments

Comments
 (0)