├── .version ├── debian ├── compat ├── source │ ├── format │ └── options ├── pvc-daemon-common.install ├── pvc-daemon-api.prerm ├── pvc-daemon-health.prerm ├── pvc-daemon-worker.prerm ├── pvc-daemon-node.prerm ├── pvc-client-cli.prerm ├── pvc-daemon-health.install ├── pvc-daemon-worker.install ├── pvc-daemon-api.preinst ├── pvc-daemon-common.preinst ├── pvc-daemon-node.preinst ├── pvc-daemon-worker.preinst ├── pvc-daemon-node.install ├── pvc-daemon-api.install ├── pvc-daemon-health.preinst ├── pvc-client-cli.postinst ├── pvc-daemon-health.postinst ├── pvc-daemon-worker.postinst ├── pvc-daemon-api.postinst ├── pvc-daemon-node.postinst ├── copyright ├── rules └── control ├── client-cli ├── pvc │ ├── __init__.py │ ├── lib │ │ ├── __init__.py │ │ ├── ansiprint.py │ │ ├── zkhandler.py │ │ ├── faults.py │ │ └── cluster.py │ └── cli │ │ ├── parsers.py │ │ └── waiters.py ├── pyproject.toml └── pvc.py ├── daemon-common ├── __init__.py ├── migrations │ └── versions │ │ ├── 0.json │ │ ├── 1.json │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ ├── 5.json │ │ ├── 6.json │ │ ├── 7.json │ │ ├── 8.json │ │ ├── 10.json │ │ ├── 9.json │ │ ├── 11.json │ │ ├── 12.json │ │ ├── 13.json │ │ ├── 14.json │ │ └── 15.json ├── celery.py └── libvirt_schema.py ├── api-daemon ├── pvcapid │ ├── __init__.py │ └── Daemon.py ├── daemon_lib ├── migrations │ ├── README │ ├── script.py.mako │ ├── versions │ │ ├── 3efe890e1d87_pvc_version_0_9_0.py │ │ ├── bae4d5a77c74_pvc_version_0_9_18.py │ │ ├── 5c2109dbbeae_pvc_version_0_9_37.py │ │ ├── 977e7b4d3497_pvc_version_0_9_89.py │ │ ├── 3bc6117ea44d_pvc_version_0_7.py │ │ ├── 88fa0d88a9f8_pvc_version_0_9_55.py │ │ ├── 88c8514684f7_pvc_version_0_7.py │ │ └── 2d1daa722a0a_pvc_version_0_6.py │ ├── alembic.ini │ └── env.py ├── provisioner │ └── examples │ │ └── userdata │ │ ├── multipart-userdata.yaml │ │ └── userdata.yaml ├── pvc-api-db-upgrade ├── pvcapid.service ├── pvcapid-manage-zk.py ├── pvcapid-manage-flask.py └── pvcapid.py ├── node-daemon ├── pvcnoded │ ├── __init__.py │ ├── util │ │ ├── __init__.py │ │ ├── libvirt.py │ │ └── services.py │ └── objects │ │ ├── __init__.py │ │ └── VMConsoleWatcherInstance.py ├── daemon_lib ├── pvc.target ├── pvcnoded.service ├── pvcautoready.service └── pvcnoded.py ├── health-daemon ├── pvchealthd │ ├── __init__.py │ ├── util │ │ └── __init__.py │ ├── objects │ │ └── __init__.py │ └── Daemon.py ├── daemon_lib ├── pvchealthd.service ├── pvchealthd.py └── plugins │ ├── kydb │ ├── lbvt │ ├── zkpr │ ├── edac │ ├── load │ └── ipmi ├── worker-daemon ├── pvcworkerd │ └── __init__.py ├── daemon_lib ├── pvcworkerd.service ├── pvcworkerd.py └── pvcworkerd.sh ├── .github └── FUNDING.yml ├── images ├── 5-vm-details.png ├── 10-provisioner.png ├── pvc_logo_black.png ├── 0-integrated-help.png ├── 4-vm-information.png ├── 3-node-information.png ├── 8-vm-and-node-logs.png ├── 11-prometheus-grafana.png ├── 6-network-information.png ├── 7-storage-information.png ├── 9-vm-and-worker-tasks.png ├── 1-connection-management.png └── 2-cluster-details-and-output-formats.png ├── monitoring ├── checkmk │ ├── pvc │ └── pvc.py ├── prometheus │ ├── prometheus.yml │ ├── targets-pvc_cluster.json │ └── README.md ├── README.md └── munin │ └── pvc ├── .bbuilder-tasks.yaml ├── .gitignore ├── gen-zk-migrations ├── lint ├── .hooks └── pre-commit ├── format ├── gen-api-migrations ├── .flake8 ├── gen-api-doc ├── .file-header └── bump-version /.version: -------------------------------------------------------------------------------- 1 | 1.0.1 2 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 13 2 | -------------------------------------------------------------------------------- /client-cli/pvc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /daemon-common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 1.0 2 | -------------------------------------------------------------------------------- /api-daemon/pvcapid/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client-cli/pvc/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /node-daemon/pvcnoded/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api-daemon/daemon_lib: -------------------------------------------------------------------------------- 1 | ../daemon-common -------------------------------------------------------------------------------- /health-daemon/pvchealthd/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /node-daemon/daemon_lib: -------------------------------------------------------------------------------- 1 | ../daemon-common -------------------------------------------------------------------------------- /node-daemon/pvcnoded/util/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /worker-daemon/pvcworkerd/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /health-daemon/daemon_lib: -------------------------------------------------------------------------------- 1 | ../daemon-common -------------------------------------------------------------------------------- /health-daemon/pvchealthd/util/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /node-daemon/pvcnoded/objects/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /worker-daemon/daemon_lib: -------------------------------------------------------------------------------- 1 | ../daemon-common -------------------------------------------------------------------------------- /health-daemon/pvchealthd/objects/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api-daemon/migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /debian/source/options: -------------------------------------------------------------------------------- 1 | extend-diff-ignore = "^[^/]*[.]egg-info/" 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [joshuaboniface] 2 | patreon: [joshuaboniface] 3 | -------------------------------------------------------------------------------- /debian/pvc-daemon-common.install: -------------------------------------------------------------------------------- 1 | daemon-common/* usr/share/pvc/daemon_lib 2 | -------------------------------------------------------------------------------- /debian/pvc-daemon-api.prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Remove client binary symlink 4 | rm -f /usr/bin/pvcapid 5 | -------------------------------------------------------------------------------- /images/5-vm-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/5-vm-details.png -------------------------------------------------------------------------------- /images/10-provisioner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/10-provisioner.png -------------------------------------------------------------------------------- /images/pvc_logo_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/pvc_logo_black.png -------------------------------------------------------------------------------- /debian/pvc-daemon-health.prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Disable the services 4 | systemctl disable pvchealthd.service 5 | -------------------------------------------------------------------------------- /debian/pvc-daemon-worker.prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Disable the services 4 | systemctl disable pvcworkerd.service 5 | -------------------------------------------------------------------------------- /images/0-integrated-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/0-integrated-help.png -------------------------------------------------------------------------------- /images/4-vm-information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/4-vm-information.png -------------------------------------------------------------------------------- /images/3-node-information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/3-node-information.png -------------------------------------------------------------------------------- /images/8-vm-and-node-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/8-vm-and-node-logs.png -------------------------------------------------------------------------------- /images/11-prometheus-grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/11-prometheus-grafana.png -------------------------------------------------------------------------------- /images/6-network-information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/6-network-information.png -------------------------------------------------------------------------------- /images/7-storage-information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/7-storage-information.png -------------------------------------------------------------------------------- /images/9-vm-and-worker-tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/9-vm-and-worker-tasks.png -------------------------------------------------------------------------------- /images/1-connection-management.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/1-connection-management.png -------------------------------------------------------------------------------- /debian/pvc-daemon-node.prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Disable the services 4 | systemctl disable pvcnoded.service 5 | systemctl disable pvc.target 6 | -------------------------------------------------------------------------------- /images/2-cluster-details-and-output-formats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parallelvirtualcluster/pvc/HEAD/images/2-cluster-details-and-output-formats.png -------------------------------------------------------------------------------- /monitoring/checkmk/pvc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # PVC cluster status check for Check_MK (agent-side) 4 | 5 | echo "<<>>" 6 | hostname -s 7 | pvc --quiet cluster status --format json 8 | -------------------------------------------------------------------------------- /debian/pvc-client-cli.prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Remove the bash completion 4 | if [ -f /etc/bash_completion.d/pvc ]; then 5 | rm -f /etc/bash_completion.d/pvc 6 | fi 7 | 8 | exit 0 9 | -------------------------------------------------------------------------------- /debian/pvc-daemon-health.install: -------------------------------------------------------------------------------- 1 | health-daemon/pvchealthd.py usr/share/pvc 2 | health-daemon/pvchealthd usr/share/pvc 3 | health-daemon/pvchealthd.service lib/systemd/system 4 | health-daemon/plugins usr/share/pvc 5 | -------------------------------------------------------------------------------- /debian/pvc-daemon-worker.install: -------------------------------------------------------------------------------- 1 | worker-daemon/pvcworkerd.sh usr/share/pvc 2 | worker-daemon/pvcworkerd.py usr/share/pvc 3 | worker-daemon/pvcworkerd usr/share/pvc 4 | worker-daemon/pvcworkerd.service lib/systemd/system 5 | -------------------------------------------------------------------------------- /.bbuilder-tasks.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | bbuilder: 3 | release: 4 | published: 5 | - git submodule update --init 6 | - /bin/bash build-stable-deb.sh 7 | - sudo /usr/local/bin/deploy-package -C pvc -D bookworm 8 | -------------------------------------------------------------------------------- /node-daemon/pvc.target: -------------------------------------------------------------------------------- 1 | # Parallel Virtual Cluster startup target 2 | 3 | [Unit] 4 | Description = Parallel Virtual Cluster target for managing all services collectively 5 | 6 | [Install] 7 | WantedBy = multi-user.target 8 | -------------------------------------------------------------------------------- /debian/pvc-daemon-api.preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Remove any cached CPython directories or files 4 | echo "Cleaning up CPython caches" 5 | find /usr/share/pvc/pvcapid -type d -name "__pycache__" -exec rm -fr {} + &>/dev/null || true 6 | -------------------------------------------------------------------------------- /debian/pvc-daemon-common.preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Remove any cached CPython directories or files 4 | echo "Cleaning up CPython caches" 5 | find /usr/share/pvc/daemon_lib -type d -name "__pycache__" -exec rm -fr {} + &>/dev/null || true 6 | -------------------------------------------------------------------------------- /debian/pvc-daemon-node.preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Remove any cached CPython directories or files 4 | echo "Cleaning up CPython caches" 5 | find /usr/share/pvc/pvcnoded -type d -name "__pycache__" -exec rm -fr {} + &>/dev/null || true 6 | -------------------------------------------------------------------------------- /debian/pvc-daemon-worker.preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Remove any cached CPython directories or files 4 | echo "Cleaning up CPython caches" 5 | find /usr/share/pvc/pvcworkerd -type d -name "__pycache__" -exec rm -fr {} + &>/dev/null || true 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.tmp 3 | *.swp 4 | # Ignore swagger output (for pvc-docs instead) 5 | swagger.json 6 | # Ignore build artifacts 7 | debian/pvc-*/ 8 | debian/*.log 9 | debian/*.substvars 10 | debian/files 11 | client-cli/build/ 12 | -------------------------------------------------------------------------------- /gen-zk-migrations: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Generate the Zookeeper migration files 4 | 5 | pushd $( git rev-parse --show-toplevel ) &>/dev/null 6 | pushd api-daemon &>/dev/null 7 | ./pvcapid-manage-zk.py 8 | popd &>/dev/null 9 | popd &>/dev/null 10 | -------------------------------------------------------------------------------- /monitoring/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | # Other configuration omitted 2 | scrape_configs: 3 | - job_name: "pvc_cluster" 4 | metrics_path: /api/v1/metrics 5 | scheme: "http" 6 | file_sd_configs: 7 | - files: 8 | - 'targets-pvc_cluster.json' 9 | -------------------------------------------------------------------------------- /debian/pvc-daemon-node.install: -------------------------------------------------------------------------------- 1 | node-daemon/pvcnoded.py usr/share/pvc 2 | node-daemon/pvcnoded usr/share/pvc 3 | node-daemon/pvcnoded.service lib/systemd/system 4 | node-daemon/pvc.target lib/systemd/system 5 | node-daemon/pvcautoready.service lib/systemd/system 6 | monitoring usr/share/pvc 7 | -------------------------------------------------------------------------------- /debian/pvc-daemon-api.install: -------------------------------------------------------------------------------- 1 | api-daemon/pvcapid.py usr/share/pvc 2 | api-daemon/pvcapid-manage*.py usr/share/pvc 3 | api-daemon/pvc-api-db-upgrade usr/share/pvc 4 | api-daemon/pvcapid usr/share/pvc 5 | api-daemon/pvcapid.service lib/systemd/system 6 | api-daemon/provisioner usr/share/pvc 7 | api-daemon/migrations usr/share/pvc 8 | -------------------------------------------------------------------------------- /debian/pvc-daemon-health.preinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Remove any cached CPython directories or files 4 | echo "Cleaning up CPython caches" 5 | find /usr/share/pvc/pvchealthd -type d -name "__pycache__" -exec rm -fr {} + &>/dev/null || true 6 | find /usr/share/pvc/plugins -type d -name "__pycache__" -exec rm -fr {} + &>/dev/null || true 7 | -------------------------------------------------------------------------------- /lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if ! which flake8 &>/dev/null; then 4 | echo "Flake8 is required to lint this project" 5 | exit 1 6 | fi 7 | 8 | pushd $( git rev-parse --show-toplevel ) &>/dev/null 9 | 10 | echo ">>> Linting..." 11 | flake8 12 | ret=$? 13 | if [[ $ret -eq 0 ]]; then 14 | echo "No linting issues found!" 15 | fi 16 | 17 | popd &>/dev/null 18 | exit $ret 19 | -------------------------------------------------------------------------------- /api-daemon/provisioner/examples/userdata/multipart-userdata.yaml: -------------------------------------------------------------------------------- 1 | Content-Type: multipart/mixed; boundary="==BOUNDARY==" 2 | MIME-Version: 1.0 3 | 4 | --==BOUNDARY== 5 | Content-Type: text/cloud-config; charset="us-ascii" 6 | 7 | users: 8 | - blah 9 | 10 | --==BOUNDARY== 11 | Content-Type: text/x-shellscript; charset="us-ascii" 12 | 13 | #!/bin/bash 14 | echo "pvc is grand" >> /etc/motd 15 | 16 | --==BOUNDARY==-- 17 | -------------------------------------------------------------------------------- /.hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pushd $( git rev-parse --show-toplevel ) &>/dev/null 4 | 5 | ex=0 6 | 7 | ./format check 8 | if [[ $? -ne 0 ]]; then 9 | ./format 10 | echo "Black formatting change detected; review and recommit" 11 | ex=1 12 | fi 13 | 14 | ./lint 15 | if [[ $? -ne 0 ]]; then 16 | echo "Linting error detected; correct and recommit" 17 | ex=1 18 | fi 19 | 20 | echo 21 | popd &>/dev/null 22 | exit $ex 23 | -------------------------------------------------------------------------------- /client-cli/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pvc" 7 | version = "1.0.1" 8 | dependencies = [ 9 | "Click", 10 | "PyYAML", 11 | "lxml", 12 | "colorama", 13 | "requests", 14 | "requests-toolbelt", 15 | ] 16 | 17 | [tool.setuptools] 18 | packages = ["pvc.cli", "pvc.lib"] 19 | 20 | [project.scripts] 21 | pvc = "pvc.cli.cli:cli" 22 | -------------------------------------------------------------------------------- /api-daemon/pvc-api-db-upgrade: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Apply PVC database migrations 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | 6 | export PVC_CONFIG_FILE="/etc/pvc/pvc.conf" 7 | 8 | if [[ ! -f ${PVC_CONFIG_FILE} ]]; then 9 | echo "Create a configuration file at ${PVC_CONFIG_FILE} before upgrading the database." 10 | exit 1 11 | fi 12 | 13 | pushd /usr/share/pvc 14 | 15 | export FLASK_APP=./pvcapid-manage-flask.py 16 | flask db upgrade 17 | 18 | popd 19 | -------------------------------------------------------------------------------- /api-daemon/pvcapid.service: -------------------------------------------------------------------------------- 1 | # Parallel Virtual Cluster API client daemon unit file 2 | 3 | [Unit] 4 | Description = Parallel Virtual Cluster API client daemon 5 | After = network-online.target 6 | 7 | [Service] 8 | Type = simple 9 | WorkingDirectory = /usr/share/pvc 10 | Environment = PYTHONUNBUFFERED=true 11 | Environment = PVC_CONFIG_FILE=/etc/pvc/pvc.conf 12 | ExecStart = /usr/share/pvc/pvcapid.py 13 | Restart = on-failure 14 | 15 | [Install] 16 | WantedBy = multi-user.target 17 | -------------------------------------------------------------------------------- /debian/pvc-client-cli.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Generate the bash completion configuration 4 | if [ -d /etc/bash_completion.d ]; then 5 | echo "Installing BASH completion configuration" 6 | _PVC_COMPLETE=source_bash pvc > /etc/bash_completion.d/pvc 7 | fi 8 | 9 | # Remove any cached CPython directories or files 10 | echo "Cleaning up CPython caches" 11 | find /usr/lib/python3/dist-packages/pvc -type d -name "__pycache__" -exec rm -fr {} + &>/dev/null || true 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /format: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if ! which black &>/dev/null; then 4 | echo "Black is required to format this project" 5 | exit 1 6 | fi 7 | 8 | if [[ $1 == "check" ]]; then 9 | check="--check" 10 | fi 11 | 12 | pushd $( git rev-parse --show-toplevel ) &>/dev/null 13 | 14 | echo ">>> Formatting..." 15 | black --safe ${check} --exclude api-daemon/migrations . 16 | ret=$? 17 | if [[ $ret -eq 0 ]]; then 18 | echo "Successfully formatted project!" 19 | fi 20 | 21 | popd &>/dev/null 22 | exit $ret 23 | -------------------------------------------------------------------------------- /monitoring/prometheus/targets-pvc_cluster.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "targets": [ 4 | "pvc.upstream.floating.address.1.tld:7370" 5 | ], 6 | "labels": { 7 | "pvc_cluster_id": "cluster1", 8 | "pvc_cluster_name": "cluster1: My First Cluster" 9 | } 10 | }, 11 | { 12 | "targets": [ 13 | "pvc.upstream.floating.address.2.tld:7370" 14 | ], 15 | "labels": { 16 | "pvc_cluster_id": "cluster2", 17 | "pvc_cluster_name": "cluster2: My Second Cluster" 18 | } 19 | } 20 | ] 21 | 22 | -------------------------------------------------------------------------------- /node-daemon/pvcnoded.service: -------------------------------------------------------------------------------- 1 | # Parallel Virtual Cluster node daemon unit file 2 | 3 | [Unit] 4 | Description = Parallel Virtual Cluster node daemon 5 | After = network.target 6 | Wants = network-online.target 7 | PartOf = pvc.target 8 | 9 | [Service] 10 | Type = simple 11 | WorkingDirectory = /usr/share/pvc 12 | Environment = PYTHONUNBUFFERED=true 13 | Environment = PVC_CONFIG_FILE=/etc/pvc/pvc.conf 14 | ExecStartPre = /bin/sleep 2 15 | ExecStart = /usr/share/pvc/pvcnoded.py 16 | ExecStopPost = /bin/sleep 2 17 | Restart = on-failure 18 | 19 | [Install] 20 | WantedBy = pvc.target 21 | -------------------------------------------------------------------------------- /gen-api-migrations: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Generate the database migration files 4 | 5 | set -o xtrace 6 | 7 | VERSION="$( head -1 debian/changelog | awk -F'[()-]' '{ print $2 }' )" 8 | 9 | sudo ip addr add 10.0.1.250/32 dev lo 10 | 11 | pushd $( git rev-parse --show-toplevel ) &>/dev/null 12 | pushd api-daemon &>/dev/null 13 | export PVC_CONFIG_FILE="../pvc.sample.conf" 14 | export FLASK_APP=./pvcapid-manage_flask.py 15 | flask db migrate -m "PVC version ${VERSION}" 16 | flask db upgrade 17 | popd &>/dev/null 18 | popd &>/dev/null 19 | 20 | sudo ip addr del 10.0.1.250/32 dev lo 21 | -------------------------------------------------------------------------------- /health-daemon/pvchealthd.service: -------------------------------------------------------------------------------- 1 | # Parallel Virtual Cluster health daemon unit file 2 | 3 | [Unit] 4 | Description = Parallel Virtual Cluster health daemon 5 | After = network.target 6 | Wants = network-online.target 7 | PartOf = pvc.target 8 | 9 | [Service] 10 | Type = simple 11 | WorkingDirectory = /usr/share/pvc 12 | Environment = PYTHONUNBUFFERED=true 13 | Environment = PVC_CONFIG_FILE=/etc/pvc/pvc.conf 14 | ExecStartPre = /bin/sleep 2 15 | ExecStart = /usr/share/pvc/pvchealthd.py 16 | ExecStopPost = /bin/sleep 2 17 | Restart = on-failure 18 | 19 | [Install] 20 | WantedBy = pvc.target 21 | -------------------------------------------------------------------------------- /worker-daemon/pvcworkerd.service: -------------------------------------------------------------------------------- 1 | # Parallel Virtual Cluster worker daemon unit file 2 | 3 | [Unit] 4 | Description = Parallel Virtual Cluster worker daemon 5 | After = network.target 6 | Wants = network-online.target 7 | PartOf = pvc.target 8 | 9 | [Service] 10 | Type = simple 11 | WorkingDirectory = /usr/share/pvc 12 | Environment = PYTHONUNBUFFERED=true 13 | Environment = PVC_CONFIG_FILE=/etc/pvc/pvc.conf 14 | ExecStartPre = /bin/sleep 2 15 | ExecStart = /usr/share/pvc/pvcworkerd.sh 16 | ExecStopPost = /bin/sleep 2 17 | Restart = on-failure 18 | 19 | [Install] 20 | WantedBy = pvc.target 21 | -------------------------------------------------------------------------------- /api-daemon/migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /debian/pvc-daemon-health.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Reload systemd's view of the units 4 | systemctl daemon-reload 5 | 6 | # Enable the service and target 7 | systemctl enable /lib/systemd/system/pvchealthd.service 8 | 9 | # Inform administrator of the service restart/startup not occurring automatically 10 | if systemctl is-active --quiet pvchealthd.service; then 11 | echo "NOTE: The PVC health daemon (pvchealthd.service) has not been restarted; this is up to the administrator." 12 | else 13 | echo "NOTE: The PVC health daemon (pvchealthd.service) has not been started; create a config file at /etc/pvc/pvc.conf then start it." 14 | fi 15 | -------------------------------------------------------------------------------- /debian/pvc-daemon-worker.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Reload systemd's view of the units 4 | systemctl daemon-reload 5 | 6 | # Enable the service and target 7 | systemctl enable /lib/systemd/system/pvcworkerd.service 8 | 9 | # Inform administrator of the service restart/startup not occurring automatically 10 | if systemctl is-active --quiet pvcworkerd.service; then 11 | echo "NOTE: The PVC worker daemon (pvcworkerd.service) has not been restarted; this is up to the administrator." 12 | else 13 | echo "NOTE: The PVC worker daemon (pvcworkerd.service) has not been started; create a config file at /etc/pvc/pvc.conf then start it." 14 | fi 15 | -------------------------------------------------------------------------------- /node-daemon/pvcautoready.service: -------------------------------------------------------------------------------- 1 | # Parallel Virtual Cluster autoready oneshot 2 | 3 | [Unit] 4 | Description = Parallel Virtual Cluster autoready oneshot 5 | After = pvcnoded.service pvcapid.service zookeeper.service libvirtd.service ssh.service ceph.target network-online.target 6 | Wants = pvcnoded.service pvcapid.service 7 | PartOf = pvc.target 8 | ConditionPathExists=/etc/pvc/autoready 9 | 10 | [Service] 11 | Type = oneshot 12 | RemainAfterExit = false 13 | WorkingDirectory = /usr/share/pvc 14 | TimeoutSec = 31min 15 | ExecStartPre = /bin/sleep 60 16 | ExecStart = /usr/bin/pvc -c local node ready --wait 17 | 18 | [Install] 19 | WantedBy = pvc.target 20 | -------------------------------------------------------------------------------- /api-daemon/provisioner/examples/userdata/userdata.yaml: -------------------------------------------------------------------------------- 1 | Content-Type: text/cloud-config; charset="us-ascii" 2 | MIME-Version: 1.0 3 | 4 | #cloud-config 5 | # Example user-data file. It will: 6 | # 1. Generate locales for us 7 | # 2. Update packages and install OpenSSH and sudo 8 | # 3. Disable the manually-enabled cloud-init target (see debootstrap_script.py) 9 | # 4. Reboot the system 10 | # You can, of course, do anything you want in here which cloud-init normally supports. 11 | bootcmd: 12 | - "locale-gen" 13 | package_update: true 14 | packages: 15 | - openssh-server 16 | - sudo 17 | runcmd: 18 | - "systemctl disable cloud-init.target" 19 | - "reboot" 20 | -------------------------------------------------------------------------------- /debian/pvc-daemon-api.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Reload systemd's view of the units 4 | systemctl daemon-reload 5 | 6 | # Restart the main daemon and apply database migrations (or warn on first install) 7 | if systemctl is-active --quiet pvcapid.service; then 8 | systemctl stop pvcapid.service 9 | /usr/share/pvc/pvc-api-db-upgrade 10 | systemctl start pvcapid.service 11 | fi 12 | 13 | if [ ! -f /etc/pvc/pvc.conf ]; then 14 | echo "NOTE: The PVC client API daemon (pvcapid.service) and the PVC Worker daemon (pvcworkerd.service) have not been started; create a config file at /etc/pvc/pvc.conf, then run the database configuration (/usr/share/pvc/pvc-api-db-upgrade) and start them manually." 15 | fi 16 | -------------------------------------------------------------------------------- /debian/pvc-daemon-node.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Reload systemd's view of the units 4 | systemctl daemon-reload 5 | 6 | # Enable the service and target 7 | systemctl enable /lib/systemd/system/pvcnoded.service 8 | systemctl enable /lib/systemd/system/pvcautoready.service 9 | systemctl enable /lib/systemd/system/pvc.target 10 | 11 | # Inform administrator of the service restart/startup not occurring automatically 12 | if systemctl is-active --quiet pvcnoded.service; then 13 | echo "NOTE: The PVC node daemon (pvcnoded.service) has not been restarted; this is up to the administrator." 14 | else 15 | echo "NOTE: The PVC node daemon (pvcnoded.service) has not been started; create a config file at /etc/pvc/pvc.conf then start it." 16 | fi 17 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # We ignore the following errors: 3 | # * W503 (line break before binary operator): Black moves these to new lines 4 | # * E501 (line too long): Long lines are a fact of life in comment blocks; Black handles active instances of this 5 | # * E203 (whitespace before ':'): Black recommends this as disabled 6 | # * F403 (import * used; unable to detect undefined names): We use a wildcard for helpers 7 | # * F405 (possibly undefined name): We use a wildcard for helpers 8 | ignore = W503, E501, F403, F405 9 | extend-ignore = E203 10 | # We exclude the Debian, migrations, and provisioner examples 11 | exclude = debian,monitoring,api-daemon/migrations/versions,api-daemon/provisioner/examples 12 | # Set the max line length to 88 for Black 13 | max-line-length = 88 14 | 15 | -------------------------------------------------------------------------------- /gen-api-doc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # gen-doc.py - Generate a Swagger JSON document for the API 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | 6 | from flask_swagger import swagger 7 | import os 8 | import sys 9 | import json 10 | 11 | os.environ['PVC_CONFIG_FILE'] = "./pvc.sample.conf" 12 | 13 | sys.path.append('api-daemon') 14 | 15 | import pvcapid.flaskapi as pvc_api 16 | 17 | swagger_file = "swagger.json" 18 | swagger_data = swagger(pvc_api.app) 19 | swagger_data['info']['version'] = "1.0" 20 | swagger_data['info']['title'] = "PVC Client and Provisioner API" 21 | swagger_data['host'] = "pvc.local:7370" 22 | 23 | with open(swagger_file, 'w') as fd: 24 | fd.write(json.dumps(swagger_data, sort_keys=True, indent=4)) 25 | 26 | print(f"Swagger file output to {swagger_file}; add it to the PVC documentation repo.") 27 | -------------------------------------------------------------------------------- /api-daemon/migrations/versions/3efe890e1d87_pvc_version_0_9_0.py: -------------------------------------------------------------------------------- 1 | """PVC version 0.9.0 2 | 3 | Revision ID: 3efe890e1d87 4 | Revises: 3bc6117ea44d 5 | Create Date: 2020-10-29 11:49:58.756626 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '3efe890e1d87' 14 | down_revision = '3bc6117ea44d' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('system_template', sa.Column('migration_method', sa.Text(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('system_template', 'migration_method') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /api-daemon/migrations/versions/bae4d5a77c74_pvc_version_0_9_18.py: -------------------------------------------------------------------------------- 1 | """PVC version 0.9.18 2 | 3 | Revision ID: bae4d5a77c74 4 | Revises: 3efe890e1d87 5 | Create Date: 2021-06-02 15:41:40.061806 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'bae4d5a77c74' 14 | down_revision = '3efe890e1d87' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.execute('ALTER TABLE network ALTER COLUMN vni TYPE TEXT') 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.execute('ALTER TABLE network ALTER COLUMN vni TYPE INTEGER USING vni::integer') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /api-daemon/migrations/versions/5c2109dbbeae_pvc_version_0_9_37.py: -------------------------------------------------------------------------------- 1 | """PVC version 0.9.37 2 | 3 | Revision ID: 5c2109dbbeae 4 | Revises: bae4d5a77c74 5 | Create Date: 2021-10-02 00:47:29.693579 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '5c2109dbbeae' 14 | down_revision = 'bae4d5a77c74' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('storage_benchmarks', sa.Column('test_format', sa.Integer(), server_default='0', nullable=False)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('storage_benchmarks', 'test_format') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /api-daemon/migrations/versions/977e7b4d3497_pvc_version_0_9_89.py: -------------------------------------------------------------------------------- 1 | """PVC version 0.9.89 2 | 3 | Revision ID: 977e7b4d3497 4 | Revises: 88fa0d88a9f8 5 | Create Date: 2024-01-10 16:09:44.659027 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '977e7b4d3497' 14 | down_revision = '88fa0d88a9f8' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('system_template', sa.Column('migration_max_downtime', sa.Integer(), default="300", server_default="300", nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('system_template', 'migration_max_downtime') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /.file-header: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # - 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | -------------------------------------------------------------------------------- /api-daemon/migrations/versions/3bc6117ea44d_pvc_version_0_7.py: -------------------------------------------------------------------------------- 1 | """PVC version 0.7 2 | 3 | Revision ID: 3bc6117ea44d 4 | Revises: 88c8514684f7 5 | Create Date: 2020-08-24 14:34:36.919308 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '3bc6117ea44d' 14 | down_revision = '88c8514684f7' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('storage_benchmarks', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('job', sa.Text(), nullable=False), 24 | sa.Column('result', sa.Text(), nullable=False), 25 | sa.PrimaryKeyConstraint('id') 26 | ) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade(): 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.drop_table('storage_benchmarks') 33 | # ### end Alembic commands ### 34 | -------------------------------------------------------------------------------- /api-daemon/migrations/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | script_location = . 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [handler_console] 38 | class = StreamHandler 39 | args = (sys.stderr,) 40 | level = NOTSET 41 | formatter = generic 42 | 43 | [formatter_generic] 44 | format = %(levelname)-5.5s [%(name)s] %(message)s 45 | datefmt = %H:%M:%S 46 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: pvc 3 | Source: https://github.com/parallelvirtualcluster/pvc 4 | 5 | Files: * 6 | Copyright: 2018-2021 Joshua Boniface 7 | License: GPL-3 8 | This package is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, version 3. 11 | . 12 | This package is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | . 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see 19 | . 20 | On Debian systems, the complete text of the GNU General 21 | Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". 22 | -------------------------------------------------------------------------------- /health-daemon/pvchealthd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pvchealthd.py - Health daemon startup stub 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import pvchealthd.Daemon # noqa: F401 23 | 24 | pvchealthd.Daemon.entrypoint() 25 | -------------------------------------------------------------------------------- /worker-daemon/pvcworkerd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pvcworkerd.py - Health daemon startup stub 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import pvcworkerd.Daemon # noqa: F401 23 | 24 | pvcworkerd.Daemon.entrypoint() 25 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # See debhelper(7) (uncomment to enable) 3 | # output every command that modifies files on the build system. 4 | export DH_VERBOSE = 1 5 | 6 | %: 7 | dh $@ --with python3 8 | 9 | override_dh_python3: 10 | cd $(CURDIR)/client-cli; pybuild --system=pyproject --dest-dir=../debian/pvc-client-cli/ 11 | mkdir -p debian/pvc-client-cli/usr/lib/python3 12 | mv debian/pvc-client-cli/usr/lib/python3*/* debian/pvc-client-cli/usr/lib/python3/ 13 | rm -r $(CURDIR)/client-cli/.pybuild $(CURDIR)/client-cli/pvc.egg-info 14 | 15 | override_dh_auto_clean: 16 | find $(CURDIR) -name "__pycache__" -o -name ".pybuild" -exec rm -fr {} + || true 17 | rm -r $(CURDIR)/client-cli/build || true 18 | 19 | # If you need to rebuild the Sphinx documentation 20 | # Add spinxdoc to the dh --with line 21 | #override_dh_auto_build: 22 | # dh_auto_build 23 | # PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bhtml docs/ build/html # HTML generator 24 | # PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bman docs/ build/man # Manpage generator 25 | 26 | -------------------------------------------------------------------------------- /api-daemon/pvcapid-manage-zk.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pvcapid-manage-zk.py - PVC Zookeeper migration generator 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | from daemon_lib.zkhandler import ZKSchema 23 | 24 | schema = ZKSchema(root_path=".") 25 | schema.write() 26 | -------------------------------------------------------------------------------- /node-daemon/pvcnoded.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pvcnoded.py - Node daemon startup stub 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | from sys import argv 23 | import pvcnoded.Daemon # noqa: F401 24 | 25 | if "--version" in argv: 26 | print(pvcnoded.Daemon.version) 27 | exit(0) 28 | 29 | pvcnoded.Daemon.entrypoint() 30 | -------------------------------------------------------------------------------- /client-cli/pvc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pvc.py - PVC client command-line interface (stub testing interface) 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | from pvc.cli.cli import cli 23 | 24 | 25 | # 26 | # Main entry point 27 | # 28 | def main(): 29 | return cli(obj={}) 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /api-daemon/migrations/versions/88fa0d88a9f8_pvc_version_0_9_55.py: -------------------------------------------------------------------------------- 1 | """PVC version 0.9.55 2 | 3 | Revision ID: 88fa0d88a9f8 4 | Revises: 5c2109dbbeae 5 | Create Date: 2022-10-06 10:33:38.784497 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '88fa0d88a9f8' 14 | down_revision = '5c2109dbbeae' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.alter_column('profile', 'script', 22 | existing_type=sa.INTEGER(), 23 | nullable=False) 24 | op.alter_column('profile', 'system_template', 25 | existing_type=sa.INTEGER(), 26 | nullable=False) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade(): 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.alter_column('profile', 'system_template', 33 | existing_type=sa.INTEGER(), 34 | nullable=True) 35 | op.alter_column('profile', 'script', 36 | existing_type=sa.INTEGER(), 37 | nullable=True) 38 | # ### end Alembic commands ### 39 | -------------------------------------------------------------------------------- /api-daemon/pvcapid-manage-flask.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pvcapid-manage_flask.py - PVC Database management tasks (via Flask CLI) 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | from pvcapid.flaskapi import app, db 23 | from pvcapid.models import * # noqa F401,F403 24 | 25 | from flask_migrate import Migrate 26 | 27 | migrate = Migrate(app, db) 28 | 29 | # Call flask --app /usr/share/pvc/pvcapid-manage_flask.py db upgrade 30 | -------------------------------------------------------------------------------- /api-daemon/pvcapid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # pvcapid.py - API daemon startup stub 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import sys 23 | from os import path 24 | 25 | # Ensure current directory (/usr/share/pvc) is in the system path for Gunicorn 26 | current_dir = path.dirname(path.abspath(__file__)) 27 | sys.path.append(current_dir) 28 | 29 | import pvcapid.Daemon # noqa: F401, E402 30 | 31 | pvcapid.Daemon.entrypoint() 32 | -------------------------------------------------------------------------------- /node-daemon/pvcnoded/util/libvirt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # libvirt.py - Utility functions for pvcnoded libvirt 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import libvirt 23 | 24 | 25 | def validate_libvirtd(logger, config): 26 | if config["enable_hypervisor"]: 27 | libvirt_check_name = f'qemu+tcp://{config["node_hostname"]}/system' 28 | logger.out(f"Connecting to Libvirt daemon at {libvirt_check_name}", state="i") 29 | try: 30 | lv_conn = libvirt.open(libvirt_check_name) 31 | lv_conn.close() 32 | except Exception as e: 33 | logger.out(f"Failed to connect to Libvirt daemon: {e}", state="e") 34 | return False 35 | 36 | return True 37 | -------------------------------------------------------------------------------- /worker-daemon/pvcworkerd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # pvcworkerd.py - API Celery worker daemon startup stub 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | CELERY_BIN="$( which celery )" 23 | 24 | # This absolute hackery is needed because Celery got the bright idea to change how their 25 | # app arguments work in a non-backwards-compatible way with Celery 5. 26 | case "$( cat /etc/debian_version )" in 27 | 10.*) 28 | CELERY_ARGS="worker --app pvcworkerd.Daemon.celery --events --concurrency 3 --hostname pvcworkerd@$(hostname -s) --queues $(hostname -s) --loglevel INFO" 29 | ;; 30 | *) 31 | CELERY_ARGS="--app pvcworkerd.Daemon.celery worker --events --concurrency 3 --hostname pvcworkerd@$(hostname -s) --queues $(hostname -s) --loglevel INFO" 32 | ;; 33 | esac 34 | 35 | ${CELERY_BIN} ${CELERY_ARGS} 36 | exit $? 37 | -------------------------------------------------------------------------------- /monitoring/prometheus/README.md: -------------------------------------------------------------------------------- 1 | # Prometheus Monitoring for PVC 2 | 3 | This example contains a Prometheus config snippit, an example `file_sd_configs` file, and a Grafana dashboard for monitoring a PVC cluster using the inbuilt metrics (`/api/v1/metrics`). 4 | 5 | ## `prometheus.yml` 6 | 7 | This snippit shows how to set up a scrape config leveraging the `file_sd_configs` file. 8 | 9 | This example uses `http` transport; if you use HTTPS for PVC API traffic (e.g. if it traverses the Internet), use `https` here. You can optionally disable certificate checking like so: 10 | 11 | ``` 12 | [...] 13 | scheme: "https" 14 | tls_config: 15 | insecure_skip_verify: true 16 | file_sd_configs: 17 | [...] 18 | ``` 19 | 20 | Note that due to the limitations of the Prometheus configuration, this is a global option for all service discovery entries specified; it cannot be toggled for each connection individually. 21 | 22 | ## `targets-pvc_cluster.json` 23 | 24 | This JSON-based config shows two example clusters as two discrete entries. This is required for proper labeling. 25 | 26 | Each entry must contain: 27 | 28 | * A single `targets` entry, pointing at the API address and port of the PVC cluster. 29 | 30 | * Two `labels` which are leveraged by the Grafana dashboard: 31 | 32 | * `pvc_cluster_id`: An identifier for the cluster. Likely, the `Name` in your `pvc connection list` entry for the cluster. 33 | 34 | * `pvc_cluster_name`: A nicer, more human-readable description of the cluster. Likely, the `Description` in your `pvc connection list` entry for the cluster. 35 | 36 | This file can be autogenerated from the list of configured PVC clusters in a PVC CLI client using the `pvc connection list --format json-prometheus` option. 37 | 38 | ## `grafana-pvc-cluster-dashboard.json` 39 | 40 | This JSON-based Grafana dashboard allows for a nice presentation of the Cluster and Node metrics collected by the above Prometheus pollers. The cluster can be selected (based on the `pvc_cluster_name` value) and useful information about the cluster is then displayed. 41 | 42 | ## `grafana-pvc-vms-dashboard.json` 43 | 44 | This JSON-based Grafana dashboard allows for a nice presentation of the VM metrics collected by the above Prometheus pollers. The cluster can be selected (based on the `pvc_cluster_name` value) and useful information about a given VM is then displayed. 45 | -------------------------------------------------------------------------------- /client-cli/pvc/lib/ansiprint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ansiprint.py - Printing function for formatted messages 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import datetime 23 | 24 | 25 | # ANSII colours for output 26 | def red(): 27 | return "\033[91m" 28 | 29 | 30 | def blue(): 31 | return "\033[94m" 32 | 33 | 34 | def cyan(): 35 | return "\033[96m" 36 | 37 | 38 | def green(): 39 | return "\033[92m" 40 | 41 | 42 | def yellow(): 43 | return "\033[93m" 44 | 45 | 46 | def purple(): 47 | return "\033[95m" 48 | 49 | 50 | def bold(): 51 | return "\033[1m" 52 | 53 | 54 | def end(): 55 | return "\033[0m" 56 | 57 | 58 | # Print function 59 | def echo(message, prefix, state): 60 | # Get the date 61 | date = "{} - ".format(datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S.%f")) 62 | endc = end() 63 | 64 | # Continuation 65 | if state == "c": 66 | date = "" 67 | colour = "" 68 | prompt = " " 69 | # OK 70 | elif state == "o": 71 | colour = green() 72 | prompt = ">>> " 73 | # Error 74 | elif state == "e": 75 | colour = red() 76 | prompt = ">>> " 77 | # Warning 78 | elif state == "w": 79 | colour = yellow() 80 | prompt = ">>> " 81 | # Tick 82 | elif state == "t": 83 | colour = purple() 84 | prompt = ">>> " 85 | # Information 86 | elif state == "i": 87 | colour = blue() 88 | prompt = ">>> " 89 | else: 90 | colour = bold() 91 | prompt = ">>> " 92 | 93 | # Append space to prefix 94 | if prefix != "": 95 | prefix = prefix + " " 96 | 97 | print(colour + prompt + endc + date + prefix + message) 98 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: pvc 2 | Section: main 3 | Priority: optional 4 | Maintainer: Joshua Boniface 5 | Standards-Version: 3.9.8 6 | Homepage: https://www.boniface.me 7 | X-Python3-Version: >= 3.7 8 | Build-Depends: pybuild-plugin-pyproject, dh-python 9 | 10 | Package: pvc-daemon-node 11 | Architecture: all 12 | Depends: systemd, pvc-daemon-common, pvc-daemon-health, pvc-daemon-worker, python3-kazoo, python3-psutil, python3-apscheduler, python3-libvirt, python3-psycopg2, python3-dnspython, python3-yaml, python3-distutils, python3-rados, python3-gevent, ipmitool, libvirt-daemon-system, arping, vlan, bridge-utils, dnsmasq, nftables, pdns-server, pdns-backend-pgsql 13 | Description: Parallel Virtual Cluster node daemon 14 | A KVM/Zookeeper/Ceph-based VM and private cloud manager 15 | . 16 | This package installs the PVC node daemon 17 | 18 | Package: pvc-daemon-health 19 | Architecture: all 20 | Depends: systemd, pvc-daemon-common, python3-kazoo, python3-psutil, python3-apscheduler, python3-yaml 21 | Description: Parallel Virtual Cluster health daemon 22 | A KVM/Zookeeper/Ceph-based VM and private cloud manager 23 | . 24 | This package installs the PVC health monitoring daemon 25 | 26 | Package: pvc-daemon-worker 27 | Architecture: all 28 | Depends: systemd, pvc-daemon-common, python3-kazoo, python3-celery, python3-redis, python3-yaml, python-celery-common, fio 29 | Description: Parallel Virtual Cluster worker daemon 30 | A KVM/Zookeeper/Ceph-based VM and private cloud manager 31 | . 32 | This package installs the PVC Celery task worker daemon 33 | 34 | Package: pvc-daemon-api 35 | Architecture: all 36 | Depends: systemd, pvc-daemon-common, gunicorn, python3-gunicorn, python3-yaml, python3-flask, python3-flask-restful, python3-celery, python3-distutils, python3-redis, python3-lxml, python3-flask-migrate 37 | Description: Parallel Virtual Cluster API daemon 38 | A KVM/Zookeeper/Ceph-based VM and private cloud manager 39 | . 40 | This package installs the PVC API daemon 41 | 42 | Package: pvc-daemon-common 43 | Architecture: all 44 | Depends: python3-kazoo, python3-psutil, python3-click, python3-lxml 45 | Description: Parallel Virtual Cluster common libraries 46 | A KVM/Zookeeper/Ceph-based VM and private cloud manager 47 | . 48 | This package installs the common libraries for the daemon and API 49 | 50 | Package: pvc-client-cli 51 | Architecture: all 52 | Depends: python3-requests, python3-requests-toolbelt, python3-yaml, python3-lxml, python3-click 53 | Description: Parallel Virtual Cluster CLI client 54 | A KVM/Zookeeper/Ceph-based VM and private cloud manager 55 | . 56 | This package installs the PVC API command-line client 57 | -------------------------------------------------------------------------------- /bump-version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | 5 | new_version="${1}" 6 | if [[ -z ${new_version} ]]; then 7 | exit 1 8 | fi 9 | 10 | pushd $( git rev-parse --show-toplevel ) &>/dev/null 11 | 12 | current_version="$( cat .version )" 13 | echo "${current_version} -> ${new_version}" 14 | 15 | changelog_file=$( mktemp ) 16 | echo "# Write the changelog below; comments will be ignored" >> ${changelog_file} 17 | $EDITOR ${changelog_file} 18 | 19 | changelog="$( cat ${changelog_file} | grep -v '^#' | sed 's/^*/ */' )" 20 | 21 | sed -i "s,version = \"${current_version}\",version = \"${new_version}\"," node-daemon/pvcnoded/Daemon.py 22 | sed -i "s,version = \"${current_version}\",version = \"${new_version}\"," health-daemon/pvchealthd/Daemon.py 23 | sed -i "s,version = \"${current_version}\",version = \"${new_version}\"," worker-daemon/pvcworkerd/Daemon.py 24 | sed -i "s,version = \"${current_version}\",version = \"${new_version}\"," api-daemon/pvcapid/Daemon.py 25 | sed -i "s,version = \"${current_version}\",version = \"${new_version}\"," client-cli/pyproject.toml 26 | sed -i "s,VERSION = \"${current_version}\",VERSION = \"${new_version}\"," client-cli/pvc/cli/helpers.py 27 | echo ${new_version} > .version 28 | 29 | changelog_tmpdir=$( mktemp -d ) 30 | cp CHANGELOG.md ${changelog_tmpdir}/ 31 | pushd ${changelog_tmpdir} &>/dev/null 32 | 33 | echo -e "\n###### [v${new_version}](https://github.com/parallelvirtualcluster/pvc/releases/tag/v${new_version})\n\n${changelog}" >> middle 34 | 35 | csplit CHANGELOG.md "/## PVC Changelog/1" &>/dev/null 36 | cat xx00 middle xx01 > CHANGELOG.md 37 | rm xx00 xx01 38 | 39 | popd &>/dev/null 40 | mv ${changelog_tmpdir}/CHANGELOG.md CHANGELOG.md 41 | rm -r ${changelog_tmpdir} 42 | 43 | deb_changelog_orig="$( cat debian/changelog )" 44 | deb_changelog_new="pvc (${new_version}-0) unstable; urgency=high\n\n${changelog}\n\n -- $( git config --get user.name ) <$( git config --get user.email )> $( date --rfc-email )\n" 45 | 46 | deb_changelog_file=$( mktemp ) 47 | echo -e "${deb_changelog_new}" >> ${deb_changelog_file} 48 | echo -e "${deb_changelog_orig}" >> ${deb_changelog_file} 49 | mv ${deb_changelog_file} debian/changelog 50 | 51 | git add node-daemon/pvcnoded/Daemon.py health-daemon/pvchealthd/Daemon.py worker-daemon/pvcworkerd/Daemon.py api-daemon/pvcapid/Daemon.py client-cli/pvc/cli/helpers.py client-cli/pyproject.toml debian/changelog CHANGELOG.md .version 52 | git commit -v 53 | 54 | popd &>/dev/null 55 | 56 | rm ${changelog_file} 57 | 58 | echo 59 | echo "Release message:" 60 | echo 61 | echo "# Parallel Virtual Cluster version ${new_version}" 62 | echo 63 | echo -e "${changelog}" | sed 's/^ \*/*/' 64 | echo 65 | -------------------------------------------------------------------------------- /daemon-common/migrations/versions/0.json: -------------------------------------------------------------------------------- 1 | {"version": "0", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "cmd": "/cmd", "cmd.node": "/cmd/nodes", "cmd.domain": "/cmd/domains", "cmd.ceph": "/cmd/ceph", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "migrate.sync_lock": "/migrate_sync_lock"}, "network": {"vni": "", "type": "/nettype", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /monitoring/README.md: -------------------------------------------------------------------------------- 1 | # PVC Node Monitoring Resources 2 | 3 | This directory contains several monitoring resources that can be used with various monitoring systems to track and alert on a PVC cluster system. 4 | 5 | ## Prometheus + Grafana 6 | 7 | The included example Prometheus configuration and Grafana dashboard can be used to query the PVC API for Prometheus data and display it with a consistent dashboard. 8 | 9 | See the README in the `prometheus` folder for more details. 10 | 11 | ## Munin 12 | 13 | The included Munin plugins can be activated by linking to them from `/etc/munin/plugins/`. Two plugins are provided: 14 | 15 | * `pvc`: Checks the PVC cluster and node health, as well as their status (OK/Warning/Critical, based on maintenance status), providing 4 graphs. 16 | 17 | * `ceph_utilization`: Checks the Ceph cluster statistics, providing multiple graphs. Note that this plugin is independent of PVC itself, and makes local calls to various Ceph commands itself. 18 | 19 | The `pvc` plugin provides no configuration; the status is hardcoded such that <=90% health is warning, <=50% health is critical, and maintenance state forces OK. The alerting is provided by two separate graphs from the health graph so that actual health state is logged regardless of alerting. 20 | 21 | The `ceph_utilization` plugin provides no configuration; only the cluster utilization graph alerts such that >80% used is warning and >90% used is critical. Ceph itself begins warning above 80% as well. 22 | 23 | ## CheckMK 24 | 25 | The included CheckMK plugin is divided into two parts: the agent plugin, and the monitoring server plugin. This monitoring server plugin requires CheckMK version 2.0 or higher. The two parts can be installed as follows: 26 | 27 | * `pvc`: Place this file in the `/usr/lib/check_mk_agent/plugins/` directory on each node. 28 | 29 | * `pvc.py`: Place this file in the `~/local/lib/python3/cmk/base/plugins/agent_based/` directory on the CheckMK monitoring host for each monitoring site. 30 | 31 | The plugin provides no configuration: the status is hardcoded such that <=90% health is warning, <=50% health is critical, and maintenance state forces OK. 32 | 33 | With both the agent and server plugins installed, you can then run `cmk -II ` (or use WATO) to inventory each node, which should produce two new checks: 34 | 35 | * `PVC Cluster`: Provides the cluster-wide health. Note that this will be identical for all nodes in the cluster (i.e. if the cluster health drops, all nodes in the cluster will alert this check). 36 | 37 | * `PVC Node `: Provides the per-node health. 38 | 39 | The "Summary" text, shown in the check lists, will be simplistic, only showing the current health percentage. 40 | 41 | The "Details" text, found in the specific check details, will show the full list of problem(s) the check finds, as shown by `pvc status` itself. 42 | -------------------------------------------------------------------------------- /api-daemon/migrations/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | from alembic import context 3 | from sqlalchemy import engine_from_config, pool 4 | from logging.config import fileConfig 5 | from flask import current_app 6 | import logging 7 | 8 | # this is the Alembic Config object, which provides 9 | # access to the values within the .ini file in use. 10 | config = context.config 11 | 12 | # Interpret the config file for Python logging. 13 | # This line sets up loggers basically. 14 | fileConfig(config.config_file_name) 15 | logger = logging.getLogger('alembic.env') 16 | 17 | # add your model's MetaData object here 18 | # for 'autogenerate' support 19 | # from myapp import mymodel 20 | # target_metadata = mymodel.Base.metadata 21 | config.set_main_option('sqlalchemy.url', 22 | current_app.config.get('SQLALCHEMY_DATABASE_URI')) 23 | target_metadata = current_app.extensions['migrate'].db.metadata 24 | 25 | # other values from the config, defined by the needs of env.py, 26 | # can be acquired: 27 | # my_important_option = config.get_main_option("my_important_option") 28 | # ... etc. 29 | 30 | 31 | def run_migrations_offline(): 32 | """Run migrations in 'offline' mode. 33 | 34 | This configures the context with just a URL 35 | and not an Engine, though an Engine is acceptable 36 | here as well. By skipping the Engine creation 37 | we don't even need a DBAPI to be available. 38 | 39 | Calls to context.execute() here emit the given string to the 40 | script output. 41 | 42 | """ 43 | url = config.get_main_option("sqlalchemy.url") 44 | context.configure(url=url) 45 | 46 | with context.begin_transaction(): 47 | context.run_migrations() 48 | 49 | 50 | def run_migrations_online(): 51 | """Run migrations in 'online' mode. 52 | 53 | In this scenario we need to create an Engine 54 | and associate a connection with the context. 55 | 56 | """ 57 | 58 | # this callback is used to prevent an auto-migration from being generated 59 | # when there are no changes to the schema 60 | # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html 61 | def process_revision_directives(context, revision, directives): 62 | if getattr(config.cmd_opts, 'autogenerate', False): 63 | script = directives[0] 64 | if script.upgrade_ops.is_empty(): 65 | directives[:] = [] 66 | logger.info('No changes in schema detected.') 67 | 68 | engine = engine_from_config(config.get_section(config.config_ini_section), 69 | prefix='sqlalchemy.', 70 | poolclass=pool.NullPool) 71 | 72 | connection = engine.connect() 73 | context.configure(connection=connection, 74 | target_metadata=target_metadata, 75 | process_revision_directives=process_revision_directives, 76 | **current_app.extensions['migrate'].configure_args) 77 | 78 | try: 79 | with context.begin_transaction(): 80 | context.run_migrations() 81 | finally: 82 | connection.close() 83 | 84 | 85 | if context.is_offline_mode(): 86 | run_migrations_offline() 87 | else: 88 | run_migrations_online() 89 | -------------------------------------------------------------------------------- /api-daemon/migrations/versions/88c8514684f7_pvc_version_0_7.py: -------------------------------------------------------------------------------- 1 | """PVC version 0.7 2 | 3 | Revision ID: 88c8514684f7 4 | Revises: 2d1daa722a0a 5 | Create Date: 2020-02-16 19:49:50.126265 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '88c8514684f7' 14 | down_revision = '2d1daa722a0a' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('ova', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('name', sa.Text(), nullable=False), 24 | sa.Column('ovf', sa.Text(), nullable=False), 25 | sa.PrimaryKeyConstraint('id'), 26 | sa.UniqueConstraint('name') 27 | ) 28 | op.create_table('ova_volume', 29 | sa.Column('id', sa.Integer(), nullable=False), 30 | sa.Column('ova', sa.Integer(), nullable=False), 31 | sa.Column('pool', sa.Text(), nullable=False), 32 | sa.Column('volume_name', sa.Text(), nullable=False), 33 | sa.Column('volume_format', sa.Text(), nullable=False), 34 | sa.Column('disk_id', sa.Text(), nullable=False), 35 | sa.Column('disk_size_gb', sa.Integer(), nullable=False), 36 | sa.ForeignKeyConstraint(['ova'], ['ova.id'], ), 37 | sa.PrimaryKeyConstraint('id') 38 | ) 39 | op.alter_column('network', 'network_template', 40 | existing_type=sa.INTEGER(), 41 | nullable=False) 42 | op.add_column('network_template', sa.Column('ova', sa.Integer(), nullable=True)) 43 | op.create_foreign_key(None, 'network_template', 'ova', ['ova'], ['id']) 44 | op.add_column('profile', sa.Column('ova', sa.Integer(), nullable=True)) 45 | op.add_column('profile', sa.Column('profile_type', sa.Text(), nullable=False)) 46 | op.create_foreign_key(None, 'profile', 'ova', ['ova'], ['id']) 47 | op.alter_column('storage', 'storage_template', 48 | existing_type=sa.INTEGER(), 49 | nullable=False) 50 | op.add_column('storage_template', sa.Column('ova', sa.Integer(), nullable=True)) 51 | op.create_foreign_key(None, 'storage_template', 'ova', ['ova'], ['id']) 52 | op.add_column('system_template', sa.Column('ova', sa.Integer(), nullable=True)) 53 | op.create_foreign_key(None, 'system_template', 'ova', ['ova'], ['id']) 54 | # ### end Alembic commands ### 55 | 56 | 57 | def downgrade(): 58 | # ### commands auto generated by Alembic - please adjust! ### 59 | op.drop_constraint(None, 'system_template', type_='foreignkey') 60 | op.drop_column('system_template', 'ova') 61 | op.drop_constraint(None, 'storage_template', type_='foreignkey') 62 | op.drop_column('storage_template', 'ova') 63 | op.alter_column('storage', 'storage_template', 64 | existing_type=sa.INTEGER(), 65 | nullable=True) 66 | op.drop_constraint(None, 'profile', type_='foreignkey') 67 | op.drop_column('profile', 'profile_type') 68 | op.drop_column('profile', 'ova') 69 | op.drop_constraint(None, 'network_template', type_='foreignkey') 70 | op.drop_column('network_template', 'ova') 71 | op.alter_column('network', 'network_template', 72 | existing_type=sa.INTEGER(), 73 | nullable=True) 74 | op.drop_table('ova_volume') 75 | op.drop_table('ova') 76 | # ### end Alembic commands ### 77 | -------------------------------------------------------------------------------- /client-cli/pvc/lib/zkhandler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # zkhandler.py - Secure versioned ZooKeeper updates 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import uuid 23 | 24 | 25 | # Exists function 26 | def exists(zk_conn, key): 27 | stat = zk_conn.exists(key) 28 | if stat: 29 | return True 30 | else: 31 | return False 32 | 33 | 34 | # Child list function 35 | def listchildren(zk_conn, key): 36 | children = zk_conn.get_children(key) 37 | return children 38 | 39 | 40 | # Delete key function 41 | def deletekey(zk_conn, key, recursive=True): 42 | zk_conn.delete(key, recursive=recursive) 43 | 44 | 45 | # Data read function 46 | def readdata(zk_conn, key): 47 | data_raw = zk_conn.get(key) 48 | data = data_raw[0].decode("utf8") 49 | return data 50 | 51 | 52 | # Data write function 53 | def writedata(zk_conn, kv): 54 | # Start up a transaction 55 | zk_transaction = zk_conn.transaction() 56 | 57 | # Proceed one KV pair at a time 58 | for key in sorted(kv): 59 | data = kv[key] 60 | 61 | # Check if this key already exists or not 62 | if not zk_conn.exists(key): 63 | # We're creating a new key 64 | zk_transaction.create(key, str(data).encode("utf8")) 65 | else: 66 | # We're updating a key with version validation 67 | orig_data = zk_conn.get(key) 68 | version = orig_data[1].version 69 | 70 | # Set what we expect the new version to be 71 | new_version = version + 1 72 | 73 | # Update the data 74 | zk_transaction.set_data(key, str(data).encode("utf8")) 75 | 76 | # Set up the check 77 | try: 78 | zk_transaction.check(key, new_version) 79 | except TypeError: 80 | print('Zookeeper key "{}" does not match expected version'.format(key)) 81 | return False 82 | 83 | # Commit the transaction 84 | try: 85 | zk_transaction.commit() 86 | return True 87 | except Exception: 88 | return False 89 | 90 | 91 | # Write lock function 92 | def writelock(zk_conn, key): 93 | lock_id = str(uuid.uuid1()) 94 | lock = zk_conn.WriteLock("{}".format(key), lock_id) 95 | return lock 96 | 97 | 98 | # Read lock function 99 | def readlock(zk_conn, key): 100 | lock_id = str(uuid.uuid1()) 101 | lock = zk_conn.ReadLock("{}".format(key), lock_id) 102 | return lock 103 | -------------------------------------------------------------------------------- /daemon-common/migrations/versions/1.json: -------------------------------------------------------------------------------- 1 | {"version": "1", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "cmd": "/cmd", "cmd.node": "/cmd/nodes", "cmd.domain": "/cmd/domains", "cmd.ceph": "/cmd/ceph", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "migrate.sync_lock": "/migrate_sync_lock"}, "network": {"vni": "", "type": "/nettype", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /daemon-common/migrations/versions/2.json: -------------------------------------------------------------------------------- 1 | {"version": "2", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "cmd": "/cmd", "cmd.node": "/cmd/nodes", "cmd.domain": "/cmd/domains", "cmd.ceph": "/cmd/ceph", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "migrate.sync_lock": "/migrate_sync_lock"}, "network": {"vni": "", "type": "/nettype", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /daemon-common/celery.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # celery.py - PVC client function library, Celery helper fuctions 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | 23 | import sys 24 | 25 | from inspect import stack 26 | from logging import getLogger 27 | from time import sleep 28 | 29 | 30 | class TaskFailure(Exception): 31 | pass 32 | 33 | 34 | def start(celery, msg, current=0, total=1): 35 | logger = getLogger(__name__) 36 | caller_name = stack()[1].function 37 | logger.info(f"Start {caller_name} {current}/{total}: {msg}") 38 | if celery is None: 39 | return 40 | celery.update_state( 41 | state="RUNNING", meta={"current": current, "total": total, "status": msg} 42 | ) 43 | sleep(1) 44 | 45 | 46 | def fail(celery, msg, exception=None, current=1, total=1): 47 | caller_name = stack()[1].function 48 | if exception is None: 49 | exception = TaskFailure 50 | 51 | msg = f"{type(exception()).__name__}: {msg}" 52 | 53 | logger = getLogger(__name__) 54 | logger.error(f"Fail {caller_name} {current}/{total}: {msg}") 55 | 56 | sys.tracebacklimit = 0 57 | raise exception(msg) 58 | 59 | 60 | def log_info(celery, msg): 61 | logger = getLogger(__name__) 62 | caller_name = stack()[1].function 63 | logger.info(f"Log {caller_name}: {msg}") 64 | 65 | 66 | def log_warn(celery, msg): 67 | logger = getLogger(__name__) 68 | caller_name = stack()[1].function 69 | logger.warning(f"Log {caller_name}: {msg}") 70 | 71 | 72 | def log_err(celery, msg): 73 | logger = getLogger(__name__) 74 | caller_name = stack()[1].function 75 | logger.error(f"Log {caller_name}: {msg}") 76 | 77 | 78 | def update(celery, msg, current=1, total=2): 79 | logger = getLogger(__name__) 80 | caller_name = stack()[1].function 81 | logger.info(f"Update {caller_name} {current}/{total}: {msg}") 82 | if celery is None: 83 | return 84 | celery.update_state( 85 | state="RUNNING", meta={"current": current, "total": total, "status": msg} 86 | ) 87 | sleep(1) 88 | 89 | 90 | def finish(celery, msg, current=2, total=2): 91 | logger = getLogger(__name__) 92 | caller_name = stack()[1].function 93 | logger.info(f"Update {caller_name} {current}/{total}: Finishing up") 94 | if celery is None: 95 | return 96 | celery.update_state( 97 | state="RUNNING", 98 | meta={"current": current, "total": total, "status": "Finishing up"}, 99 | ) 100 | sleep(1) 101 | logger.info(f"Success {caller_name} {current}/{total}: {msg}") 102 | return {"status": msg, "current": current, "total": total} 103 | -------------------------------------------------------------------------------- /monitoring/checkmk/pvc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Check_MK PVC plugin 4 | # 5 | # Copyright 2017-2021, Joshua Boniface 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | from cmk.agent_based.v2 import * 21 | from time import time 22 | from json import loads 23 | 24 | 25 | def parse_pvc(string_table): 26 | hostname = string_table[0][0] 27 | data = loads(" ".join(string_table[1])) 28 | parsed = (hostname, data) 29 | return parsed 30 | 31 | 32 | def discover_pvc(section): 33 | my_node, _ = section 34 | yield Service(item=f"PVC Node {my_node}") 35 | yield Service(item="PVC Cluster") 36 | 37 | 38 | def check_pvc(item, params, section): 39 | my_node, data = section 40 | state = State.OK 41 | summary = "" 42 | details = None 43 | 44 | maintenance_map = { 45 | "true": "on", 46 | "false": "off", 47 | } 48 | maintenance = maintenance_map[data["maintenance"]] 49 | 50 | # Node check 51 | if item == f"PVC Node {my_node}": 52 | node_health = data["node_health"][my_node]["health"] 53 | node_messages = data["node_health"][my_node]["messages"] 54 | 55 | summary = f"Node health is {node_health}% (maintenance {maintenance})" 56 | 57 | if len(node_messages) > 0: 58 | details = ", ".join(node_messages) 59 | 60 | if node_health <= 50 and maintenance == "off": 61 | state = State.CRIT 62 | elif node_health <= 90 and maintenance == "off": 63 | state = State.WARN 64 | else: 65 | state = State.OK 66 | 67 | yield Metric(name="node-health", value=node_health) 68 | 69 | # Cluster check 70 | elif item == "PVC Cluster": 71 | cluster_health = data["cluster_health"]["health"] 72 | cluster_messages = data["cluster_health"]["messages"] 73 | 74 | summary = f"Cluster health is {cluster_health}% (maintenance {maintenance})" 75 | 76 | if len(cluster_messages) > 0: 77 | details = ", ".join([m["text"] for m in cluster_messages]) 78 | 79 | if cluster_health <= 50 and maintenance == "off": 80 | state = State.CRIT 81 | elif cluster_health <= 90 and maintenance == "off": 82 | state = State.WARN 83 | else: 84 | state = State.OK 85 | 86 | yield Metric(name="cluster-health", value=cluster_health) 87 | 88 | yield Result(state=state, summary=summary, details=details) 89 | return 90 | 91 | 92 | agent_section_pvc = AgentSection( 93 | name="pvc", 94 | parse_function=parse_pvc, 95 | ) 96 | 97 | check_plugin_pvc = CheckPlugin( 98 | name="pvc", 99 | service_name="%s", 100 | check_ruleset_name="pvc", 101 | discovery_function=discover_pvc, 102 | check_function=check_pvc, 103 | check_default_parameters={}, 104 | ) 105 | -------------------------------------------------------------------------------- /daemon-common/migrations/versions/3.json: -------------------------------------------------------------------------------- 1 | {"version": "3", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "cmd": "/cmd", "cmd.node": "/cmd/nodes", "cmd.domain": "/cmd/domains", "cmd.ceph": "/cmd/ceph", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /daemon-common/migrations/versions/4.json: -------------------------------------------------------------------------------- 1 | {"version": "4", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "cmd": "/cmd", "cmd.node": "/cmd/nodes", "cmd.domain": "/cmd/domains", "cmd.ceph": "/cmd/ceph", "logs": "/logs", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /daemon-common/migrations/versions/5.json: -------------------------------------------------------------------------------- 1 | {"version": "5", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "cmd": "/cmd", "cmd.node": "/cmd/nodes", "cmd.domain": "/cmd/domains", "cmd.ceph": "/cmd/ceph", "logs": "/logs", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "db_device": "/db_device", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /daemon-common/migrations/versions/6.json: -------------------------------------------------------------------------------- 1 | {"version": "6", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "cmd": "/cmd", "cmd.node": "/cmd/nodes", "cmd.domain": "/cmd/domains", "cmd.ceph": "/cmd/ceph", "logs": "/logs", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "mtu": "/mtu", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "db_device": "/db_device", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /daemon-common/migrations/versions/7.json: -------------------------------------------------------------------------------- 1 | {"version": "7", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "cmd": "/cmd", "cmd.node": "/cmd/nodes", "cmd.domain": "/cmd/domains", "cmd.ceph": "/cmd/ceph", "logs": "/logs", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "mtu": "/mtu", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "db_device": "/db_device", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "tier": "/tier", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /daemon-common/migrations/versions/8.json: -------------------------------------------------------------------------------- 1 | {"version": "8", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "cmd": "/cmd", "cmd.node": "/cmd/nodes", "cmd.domain": "/cmd/domains", "cmd.ceph": "/cmd/ceph", "logs": "/logs", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "mtu": "/mtu", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "db_device": "/db_device", "fsid": "/fsid", "ofsid": "/fsid/osd", "cfsid": "/fsid/cluster", "lvm": "/lvm", "vg": "/lvm/vg", "lv": "/lvm/lv", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "tier": "/tier", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /daemon-common/migrations/versions/10.json: -------------------------------------------------------------------------------- 1 | {"version": "10", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "logs": "/logs", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.health": "/ceph/health", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf", "monitoring.plugins": "/monitoring_plugins", "monitoring.data": "/monitoring_data", "monitoring.health": "/monitoring_health"}, "monitoring_plugin": {"name": "", "last_run": "/last_run", "health_delta": "/health_delta", "message": "/message", "data": "/data", "runtime": "/runtime"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "mtu": "/mtu", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "db_device": "/db_device", "fsid": "/fsid", "ofsid": "/fsid/osd", "cfsid": "/fsid/cluster", "lvm": "/lvm", "vg": "/lvm/vg", "lv": "/lvm/lv", "is_split": "/is_split", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "tier": "/tier", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /daemon-common/migrations/versions/9.json: -------------------------------------------------------------------------------- 1 | {"version": "9", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "cmd": "/cmd", "cmd.node": "/cmd/nodes", "cmd.domain": "/cmd/domains", "cmd.ceph": "/cmd/ceph", "logs": "/logs", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.health": "/ceph/health", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf", "monitoring.plugins": "/monitoring_plugins", "monitoring.data": "/monitoring_data", "monitoring.health": "/monitoring_health"}, "monitoring_plugin": {"name": "", "last_run": "/last_run", "health_delta": "/health_delta", "message": "/message", "data": "/data", "runtime": "/runtime"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "mtu": "/mtu", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "db_device": "/db_device", "fsid": "/fsid", "ofsid": "/fsid/osd", "cfsid": "/fsid/cluster", "lvm": "/lvm", "vg": "/lvm/vg", "lv": "/lvm/lv", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "tier": "/tier", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /daemon-common/migrations/versions/11.json: -------------------------------------------------------------------------------- 1 | {"version": "11", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "logs": "/logs", "faults": "/faults", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.health": "/ceph/health", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "faults": {"id": "", "last_time": "/last_time", "first_time": "/first_time", "ack_time": "/ack_time", "status": "/status", "delta": "/delta", "message": "/message"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf", "monitoring.plugins": "/monitoring_plugins", "monitoring.data": "/monitoring_data", "monitoring.health": "/monitoring_health"}, "monitoring_plugin": {"name": "", "last_run": "/last_run", "health_delta": "/health_delta", "message": "/message", "data": "/data", "runtime": "/runtime"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "mtu": "/mtu", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "db_device": "/db_device", "fsid": "/fsid", "ofsid": "/fsid/osd", "cfsid": "/fsid/cluster", "lvm": "/lvm", "vg": "/lvm/vg", "lv": "/lvm/lv", "is_split": "/is_split", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "tier": "/tier", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /daemon-common/migrations/versions/12.json: -------------------------------------------------------------------------------- 1 | {"version": "12", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "logs": "/logs", "faults": "/faults", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.health": "/ceph/health", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "faults": {"id": "", "last_time": "/last_time", "first_time": "/first_time", "ack_time": "/ack_time", "status": "/status", "delta": "/delta", "message": "/message"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf", "monitoring.plugins": "/monitoring_plugins", "monitoring.data": "/monitoring_data", "monitoring.health": "/monitoring_health", "network.stats": "/network_stats"}, "monitoring_plugin": {"name": "", "last_run": "/last_run", "health_delta": "/health_delta", "message": "/message", "data": "/data", "runtime": "/runtime"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "mtu": "/mtu", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "db_device": "/db_device", "fsid": "/fsid", "ofsid": "/fsid/osd", "cfsid": "/fsid/cluster", "lvm": "/lvm", "vg": "/lvm/vg", "lv": "/lvm/lv", "is_split": "/is_split", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "tier": "/tier", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /client-cli/pvc/lib/faults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # faults.py - PVC CLI client function library, faults management 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | from pvc.lib.common import call_api 23 | 24 | 25 | def get_list(config, limit=None, sort_key="last_reported"): 26 | """ 27 | Get list of PVC faults 28 | 29 | API endpoint: GET /api/v1/faults 30 | API arguments: sort_key={sort_key} 31 | API schema: {json_data_object} 32 | """ 33 | if limit is not None: 34 | params = {} 35 | endpoint = f"/faults/{limit}" 36 | else: 37 | params = {"sort_key": sort_key} 38 | endpoint = "/faults" 39 | 40 | response = call_api(config, "get", endpoint, params=params) 41 | 42 | if response.status_code == 200: 43 | return True, response.json() 44 | else: 45 | return False, response.json().get("message", "") 46 | 47 | 48 | def acknowledge(config, faults): 49 | """ 50 | Acknowledge one or more PVC faults 51 | 52 | API endpoint: PUT /api/v1/faults/ for fault_id in faults 53 | API arguments: 54 | API schema: {json_message} 55 | """ 56 | status_codes = list() 57 | bad_msgs = list() 58 | for fault_id in faults: 59 | response = call_api(config, "put", f"/faults/{fault_id}") 60 | 61 | if response.status_code == 200: 62 | status_codes.append(True) 63 | else: 64 | status_codes.append(False) 65 | bad_msgs.append(response.json().get("message", "")) 66 | 67 | if all(status_codes): 68 | return True, f"Successfully acknowledged fault(s) {', '.join(faults)}" 69 | else: 70 | return False, ", ".join(bad_msgs) 71 | 72 | 73 | def acknowledge_all(config): 74 | """ 75 | Acknowledge all PVC faults 76 | 77 | API endpoint: PUT /api/v1/faults 78 | API arguments: 79 | API schema: {json_message} 80 | """ 81 | response = call_api(config, "put", "/faults") 82 | 83 | if response.status_code == 200: 84 | return True, response.json().get("message", "") 85 | else: 86 | return False, response.json().get("message", "") 87 | 88 | 89 | def delete(config, faults): 90 | """ 91 | Delete one or more PVC faults 92 | 93 | API endpoint: DELETE /api/v1/faults/ for fault_id in faults 94 | API arguments: 95 | API schema: {json_message} 96 | """ 97 | status_codes = list() 98 | bad_msgs = list() 99 | for fault_id in faults: 100 | response = call_api(config, "delete", f"/faults/{fault_id}") 101 | 102 | if response.status_code == 200: 103 | status_codes.append(True) 104 | else: 105 | status_codes.append(False) 106 | bad_msgs.append(response.json().get("message", "")) 107 | 108 | if all(status_codes): 109 | return True, f"Successfully deleted fault(s) {', '.join(faults)}" 110 | else: 111 | return False, ", ".join(bad_msgs) 112 | 113 | 114 | def delete_all(config): 115 | """ 116 | Delete all PVC faults 117 | 118 | API endpoint: DELETE /api/v1/faults 119 | API arguments: 120 | API schema: {json_message} 121 | """ 122 | response = call_api(config, "delete", "/faults") 123 | 124 | if response.status_code == 200: 125 | return True, response.json().get("message", "") 126 | else: 127 | return False, response.json().get("message", "") 128 | -------------------------------------------------------------------------------- /daemon-common/migrations/versions/13.json: -------------------------------------------------------------------------------- 1 | {"version": "13", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "logs": "/logs", "faults": "/faults", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.health": "/ceph/health", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "faults": {"id": "", "last_time": "/last_time", "first_time": "/first_time", "ack_time": "/ack_time", "status": "/status", "delta": "/delta", "message": "/message"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf", "monitoring.plugins": "/monitoring_plugins", "monitoring.data": "/monitoring_data", "monitoring.health": "/monitoring_health", "network.stats": "/network_stats"}, "monitoring_plugin": {"name": "", "last_run": "/last_run", "health_delta": "/health_delta", "message": "/message", "data": "/data", "runtime": "/runtime"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.migrate_max_downtime": "/migration_max_downtime", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "network": {"vni": "", "type": "/nettype", "mtu": "/mtu", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "db_device": "/db_device", "fsid": "/fsid", "ofsid": "/fsid/osd", "cfsid": "/fsid/cluster", "lvm": "/lvm", "vg": "/lvm/vg", "lv": "/lvm/lv", "is_split": "/is_split", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "tier": "/tier", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /health-daemon/plugins/kydb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # kydb.py - PVC Monitoring example plugin for KeyDB/Redis 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | # This script provides an example of a PVC monitoring plugin script. It will create 23 | # a simple plugin to check the Libvirt daemon instance on the node for operation. 24 | 25 | # This script can thus be used as an example or reference implementation of a 26 | # PVC monitoring pluginscript and expanded upon as required. 27 | 28 | # A monitoring plugin script must implement the class "MonitoringPluginScript" which 29 | # extends "MonitoringPlugin", providing the 3 functions indicated. Detailed explanation 30 | # of the role of each function is provided in context of the example; see the other 31 | # examples for more potential uses. 32 | 33 | # WARNING: 34 | # 35 | # This script will run in the context of the node daemon keepalives as root. 36 | # DO NOT install untrusted, unvetted plugins under any circumstances. 37 | 38 | 39 | # This import is always required here, as MonitoringPlugin is used by the 40 | # MonitoringPluginScript class 41 | from pvchealthd.objects.MonitoringInstance import MonitoringPlugin 42 | 43 | 44 | # A monitoring plugin script must always expose its nice name, which must be identical to 45 | # the file name 46 | PLUGIN_NAME = "kydb" 47 | 48 | 49 | # The MonitoringPluginScript class must be named as such, and extend MonitoringPlugin. 50 | class MonitoringPluginScript(MonitoringPlugin): 51 | def setup(self): 52 | """ 53 | setup(): Perform special setup steps during node daemon startup 54 | 55 | This step is optional and should be used sparingly. 56 | 57 | If you wish for the plugin to not kydb in certain conditions, do any checks here 58 | and return a non-None failure message to indicate the error. 59 | """ 60 | 61 | pass 62 | 63 | def run(self, coordinator_state=None): 64 | """ 65 | run(): Perform the check actions and return a PluginResult object 66 | 67 | The {coordinator_state} can be used to check if this is a "primary" coordinator, "secondary" coordinator, or "client" (non-coordinator) 68 | """ 69 | 70 | # Run any imports first 71 | from redis import Redis 72 | 73 | rd_conn = None 74 | 75 | # Set the health delta to 0 (no change) 76 | health_delta = 0 77 | # Craft a message that can be used by the clients 78 | message = "Successfully connected to KeyDB/Redis on localhost" 79 | 80 | # Check the Zookeeper connection 81 | try: 82 | rd_conn = Redis(host='localhost', port=6379, decode_responses=True) 83 | data = rd_conn.info() 84 | except Exception as e: 85 | health_delta = 50 86 | message = f"Failed to connect to KeyDB/Redis: {e}" 87 | finally: 88 | del rd_conn 89 | 90 | # Set the health delta in our local PluginResult object 91 | self.plugin_result.set_health_delta(health_delta) 92 | 93 | # Set the message in our local PluginResult object 94 | self.plugin_result.set_message(message) 95 | 96 | # Return our local PluginResult object 97 | return self.plugin_result 98 | 99 | def cleanup(self): 100 | """ 101 | cleanup(): Perform special cleanup steps during node daemon termination 102 | 103 | This step is optional and should be used sparingly. 104 | """ 105 | 106 | pass 107 | -------------------------------------------------------------------------------- /client-cli/pvc/lib/cluster.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # cluster.py - PVC CLI client function library, cluster management 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import json 23 | 24 | from time import sleep 25 | 26 | from pvc.lib.common import call_api 27 | 28 | 29 | def initialize(config, overwrite=False): 30 | """ 31 | Initialize the PVC cluster 32 | 33 | API endpoint: GET /api/v1/initialize 34 | API arguments: overwrite, yes-i-really-mean-it 35 | API schema: {json_data_object} 36 | """ 37 | params = {"yes-i-really-mean-it": "yes", "overwrite": overwrite} 38 | response = call_api(config, "post", "/initialize", params=params) 39 | 40 | if response.status_code == 200: 41 | retstatus = True 42 | else: 43 | retstatus = False 44 | 45 | return retstatus, response.json().get("message", "") 46 | 47 | 48 | def backup(config): 49 | """ 50 | Get a JSON backup of the cluster 51 | 52 | API endpoint: GET /api/v1/backup 53 | API arguments: 54 | API schema: {json_data_object} 55 | """ 56 | response = call_api(config, "get", "/backup") 57 | 58 | if response.status_code == 200: 59 | return True, response.json() 60 | else: 61 | return False, response.json().get("message", "") 62 | 63 | 64 | def restore(config, cluster_data): 65 | """ 66 | Restore a JSON backup to the cluster 67 | 68 | API endpoint: POST /api/v1/restore 69 | API arguments: yes-i-really-mean-it 70 | API schema: {json_data_object} 71 | """ 72 | cluster_data_json = json.dumps(cluster_data) 73 | 74 | params = {"yes-i-really-mean-it": "yes"} 75 | data = {"cluster_data": cluster_data_json} 76 | response = call_api(config, "post", "/restore", params=params, data=data) 77 | 78 | if response.status_code == 200: 79 | retstatus = True 80 | else: 81 | retstatus = False 82 | 83 | return retstatus, response.json().get("message", "") 84 | 85 | 86 | def maintenance_mode(config, state): 87 | """ 88 | Enable or disable PVC cluster maintenance mode 89 | 90 | API endpoint: POST /api/v1/status 91 | API arguments: {state}={state} 92 | API schema: {json_data_object} 93 | """ 94 | params = {"state": state} 95 | response = call_api(config, "post", "/status", params=params) 96 | 97 | if response.status_code == 200: 98 | retstatus = True 99 | else: 100 | retstatus = False 101 | 102 | return retstatus, response.json().get("message", "") 103 | 104 | 105 | def get_info(config): 106 | """ 107 | Get status of the PVC cluster 108 | 109 | API endpoint: GET /api/v1/status 110 | API arguments: 111 | API schema: {json_data_object} 112 | """ 113 | response = call_api(config, "get", "/status") 114 | 115 | if response.status_code == 200: 116 | return True, response.json() 117 | else: 118 | return False, response.json().get("message", "") 119 | 120 | 121 | def get_primary_node(config): 122 | """ 123 | Get the current primary node of the PVC cluster 124 | 125 | API endpoint: GET /api/v1/status/primary_node 126 | API arguments: 127 | API schema: {json_data_object} 128 | """ 129 | while True: 130 | response = call_api(config, "get", "/status/primary_node") 131 | resp_code = response.status_code 132 | if resp_code == 200: 133 | break 134 | else: 135 | sleep(1) 136 | 137 | return True, response.json()["primary_node"] 138 | -------------------------------------------------------------------------------- /health-daemon/plugins/lbvt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # lbvt.py - PVC Monitoring example plugin for Libvirtd 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | # This script provides an example of a PVC monitoring plugin script. It will create 23 | # a simple plugin to check the Libvirt daemon instance on the node for operation. 24 | 25 | # This script can thus be used as an example or reference implementation of a 26 | # PVC monitoring pluginscript and expanded upon as required. 27 | 28 | # A monitoring plugin script must implement the class "MonitoringPluginScript" which 29 | # extends "MonitoringPlugin", providing the 3 functions indicated. Detailed explanation 30 | # of the role of each function is provided in context of the example; see the other 31 | # examples for more potential uses. 32 | 33 | # WARNING: 34 | # 35 | # This script will run in the context of the node daemon keepalives as root. 36 | # DO NOT install untrusted, unvetted plugins under any circumstances. 37 | 38 | 39 | # This import is always required here, as MonitoringPlugin is used by the 40 | # MonitoringPluginScript class 41 | from pvchealthd.objects.MonitoringInstance import MonitoringPlugin 42 | 43 | 44 | # A monitoring plugin script must always expose its nice name, which must be identical to 45 | # the file name 46 | PLUGIN_NAME = "lbvt" 47 | 48 | 49 | # The MonitoringPluginScript class must be named as such, and extend MonitoringPlugin. 50 | class MonitoringPluginScript(MonitoringPlugin): 51 | def setup(self): 52 | """ 53 | setup(): Perform special setup steps during node daemon startup 54 | 55 | This step is optional and should be used sparingly. 56 | 57 | If you wish for the plugin to not lbvt in certain conditions, do any checks here 58 | and return a non-None failure message to indicate the error. 59 | """ 60 | 61 | pass 62 | 63 | def run(self, coordinator_state=None): 64 | """ 65 | run(): Perform the check actions and return a PluginResult object 66 | 67 | The {coordinator_state} can be used to check if this is a "primary" coordinator, "secondary" coordinator, or "client" (non-coordinator) 68 | """ 69 | 70 | # Run any imports first 71 | from libvirt import openReadOnly as lvopen 72 | 73 | lv_conn = None 74 | 75 | # Set the health delta to 0 (no change) 76 | health_delta = 0 77 | # Craft a message that can be used by the clients 78 | message = "Successfully connected to Libvirtd on localhost" 79 | 80 | # Check the Zookeeper connection 81 | try: 82 | lv_conn = lvopen(f"qemu+tcp://{self.this_node.name}/system") 83 | data = lv_conn.getHostname() 84 | except Exception as e: 85 | health_delta = 50 86 | message = f"Failed to connect to Libvirtd: {e}" 87 | finally: 88 | if lv_conn is not None: 89 | lv_conn.close() 90 | 91 | # Set the health delta in our local PluginResult object 92 | self.plugin_result.set_health_delta(health_delta) 93 | 94 | # Set the message in our local PluginResult object 95 | self.plugin_result.set_message(message) 96 | 97 | # Return our local PluginResult object 98 | return self.plugin_result 99 | 100 | def cleanup(self): 101 | """ 102 | cleanup(): Perform special cleanup steps during node daemon termination 103 | 104 | This step is optional and should be used sparingly. 105 | """ 106 | 107 | pass 108 | -------------------------------------------------------------------------------- /daemon-common/migrations/versions/14.json: -------------------------------------------------------------------------------- 1 | {"version": "14", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "logs": "/logs", "faults": "/faults", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.health": "/ceph/health", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "faults": {"id": "", "last_time": "/last_time", "first_time": "/first_time", "ack_time": "/ack_time", "status": "/status", "delta": "/delta", "message": "/message"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf", "monitoring.plugins": "/monitoring_plugins", "monitoring.data": "/monitoring_data", "monitoring.health": "/monitoring_health", "network.stats": "/network_stats"}, "monitoring_plugin": {"name": "", "last_run": "/last_run", "health_delta": "/health_delta", "message": "/message", "data": "/data", "runtime": "/runtime"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.migrate_max_downtime": "/migration_max_downtime", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock", "snapshots": "/snapshots"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "domain_snapshot": {"name": "", "timestamp": "/timestamp", "xml": "/xml", "rbd_snapshots": "/rbdsnaplist"}, "network": {"vni": "", "type": "/nettype", "mtu": "/mtu", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "db_device": "/db_device", "fsid": "/fsid", "ofsid": "/fsid/osd", "cfsid": "/fsid/cluster", "lvm": "/lvm", "vg": "/lvm/vg", "lv": "/lvm/lv", "is_split": "/is_split", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "tier": "/tier", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /node-daemon/pvcnoded/objects/VMConsoleWatcherInstance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # VMConsoleWatcherInstance.py - Class implementing a console log watcher for PVC domains 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import os 23 | import time 24 | 25 | from threading import Thread, Event 26 | from collections import deque 27 | 28 | 29 | class VMConsoleWatcherInstance(object): 30 | # Initialization function 31 | def __init__(self, domuuid, domname, zkhandler, config, logger, this_node): 32 | self.domuuid = domuuid 33 | self.domname = domname 34 | self.zkhandler = zkhandler 35 | self.config = config 36 | self.logfile = "{}/{}.log".format(config["console_log_directory"], self.domname) 37 | self.console_log_lines = config["console_log_lines"] 38 | self.logger = logger 39 | self.this_node = this_node 40 | 41 | # Try to append (create) the logfile and set its permissions 42 | open(self.logfile, "a").close() 43 | os.chmod(self.logfile, 0o600) 44 | 45 | try: 46 | self.logdeque = deque(open(self.logfile), self.console_log_lines) 47 | except UnicodeDecodeError: 48 | # There is corruption in the log file; overwrite it 49 | self.logger.out( 50 | "Failed to decode console log file; clearing existing file", 51 | state="w", 52 | prefix="Domain {}".format(self.domuuid), 53 | ) 54 | with open(self.logfile, "w") as lfh: 55 | lfh.write("\n") 56 | self.logdeque = deque(open(self.logfile), self.console_log_lines) 57 | 58 | self.stamp = None 59 | self.cached_stamp = None 60 | 61 | # Set up the deque with the current contents of the log 62 | self.last_loglines = None 63 | self.loglines = None 64 | 65 | # Thread options 66 | self.thread = None 67 | self.thread_stopper = Event() 68 | 69 | # Start execution thread 70 | def start(self): 71 | self.thread_stopper.clear() 72 | self.thread = Thread(target=self.run, args=(), kwargs={}) 73 | self.logger.out( 74 | "Starting VM log parser", state="i", prefix="Domain {}".format(self.domuuid) 75 | ) 76 | self.thread.start() 77 | 78 | # Stop execution thread 79 | def stop(self): 80 | if self.thread and self.thread.is_alive(): 81 | self.logger.out( 82 | "Stopping VM log parser", 83 | state="i", 84 | prefix="Domain {}".format(self.domuuid), 85 | ) 86 | self.thread_stopper.set() 87 | # Do one final flush 88 | self.update() 89 | 90 | # Main entrypoint 91 | def run(self): 92 | # Main loop 93 | while not self.thread_stopper.is_set(): 94 | self.update() 95 | time.sleep(0.5) 96 | 97 | def update(self): 98 | self.stamp = os.stat(self.logfile).st_mtime 99 | if self.stamp != self.cached_stamp: 100 | self.cached_stamp = self.stamp 101 | self.fetch_lines() 102 | # Update Zookeeper with the new loglines if they changed 103 | if self.loglines != self.last_loglines: 104 | self.zkhandler.write( 105 | [(("domain.console.log", self.domuuid), self.loglines)] 106 | ) 107 | self.last_loglines = self.loglines 108 | 109 | def fetch_lines(self): 110 | self.logdeque = deque(open(self.logfile), self.console_log_lines) 111 | self.loglines = "".join(self.logdeque) 112 | -------------------------------------------------------------------------------- /daemon-common/migrations/versions/15.json: -------------------------------------------------------------------------------- 1 | {"version": "15", "root": "", "base": {"root": "", "schema": "/schema", "schema.version": "/schema/version", "config": "/config", "config.maintenance": "/config/maintenance", "config.fence_lock": "/config/fence_lock", "config.primary_node": "/config/primary_node", "config.primary_node.sync_lock": "/config/primary_node/sync_lock", "config.upstream_ip": "/config/upstream_ip", "config.migration_target_selector": "/config/migration_target_selector", "logs": "/logs", "faults": "/faults", "node": "/nodes", "domain": "/domains", "network": "/networks", "storage": "/ceph", "storage.health": "/ceph/health", "storage.util": "/ceph/util", "osd": "/ceph/osds", "pool": "/ceph/pools", "volume": "/ceph/volumes", "snapshot": "/ceph/snapshots"}, "logs": {"node": "", "messages": "/messages"}, "faults": {"id": "", "last_time": "/last_time", "first_time": "/first_time", "ack_time": "/ack_time", "status": "/status", "delta": "/delta", "message": "/message"}, "node": {"name": "", "keepalive": "/keepalive", "mode": "/daemonmode", "data.active_schema": "/activeschema", "data.latest_schema": "/latestschema", "data.static": "/staticdata", "data.pvc_version": "/pvcversion", "running_domains": "/runningdomains", "count.provisioned_domains": "/domainscount", "count.networks": "/networkscount", "state.daemon": "/daemonstate", "state.router": "/routerstate", "state.domain": "/domainstate", "cpu.load": "/cpuload", "vcpu.allocated": "/vcpualloc", "memory.total": "/memtotal", "memory.used": "/memused", "memory.free": "/memfree", "memory.allocated": "/memalloc", "memory.provisioned": "/memprov", "ipmi.hostname": "/ipmihostname", "ipmi.username": "/ipmiusername", "ipmi.password": "/ipmipassword", "sriov": "/sriov", "sriov.pf": "/sriov/pf", "sriov.vf": "/sriov/vf", "monitoring.plugins": "/monitoring_plugins", "monitoring.data": "/monitoring_data", "monitoring.health": "/monitoring_health", "network.stats": "/network_stats"}, "monitoring_plugin": {"name": "", "last_run": "/last_run", "health_delta": "/health_delta", "message": "/message", "data": "/data", "runtime": "/runtime"}, "sriov_pf": {"phy": "", "mtu": "/mtu", "vfcount": "/vfcount"}, "sriov_vf": {"phy": "", "pf": "/pf", "mtu": "/mtu", "mac": "/mac", "phy_mac": "/phy_mac", "config": "/config", "config.vlan_id": "/config/vlan_id", "config.vlan_qos": "/config/vlan_qos", "config.tx_rate_min": "/config/tx_rate_min", "config.tx_rate_max": "/config/tx_rate_max", "config.spoof_check": "/config/spoof_check", "config.link_state": "/config/link_state", "config.trust": "/config/trust", "config.query_rss": "/config/query_rss", "pci": "/pci", "pci.domain": "/pci/domain", "pci.bus": "/pci/bus", "pci.slot": "/pci/slot", "pci.function": "/pci/function", "used": "/used", "used_by": "/used_by"}, "domain": {"name": "", "xml": "/xml", "state": "/state", "profile": "/profile", "stats": "/stats", "node": "/node", "last_node": "/lastnode", "failed_reason": "/failedreason", "storage.volumes": "/rbdlist", "console.log": "/consolelog", "console.vnc": "/vnc", "meta.autostart": "/node_autostart", "meta.migrate_method": "/migration_method", "meta.migrate_max_downtime": "/migration_max_downtime", "meta.node_selector": "/node_selector", "meta.node_limit": "/node_limit", "meta.tags": "/tags", "migrate.sync_lock": "/migrate_sync_lock", "snapshots": "/snapshots"}, "tag": {"name": "", "type": "/type", "protected": "/protected"}, "domain_snapshot": {"name": "", "timestamp": "/timestamp", "xml": "/xml", "rbd_snapshots": "/rbdsnaplist"}, "network": {"vni": "", "type": "/nettype", "mtu": "/mtu", "rule": "/firewall_rules", "rule.in": "/firewall_rules/in", "rule.out": "/firewall_rules/out", "nameservers": "/name_servers", "domain": "/domain", "reservation": "/dhcp4_reservations", "lease": "/dhcp4_leases", "ip4.gateway": "/ip4_gateway", "ip4.network": "/ip4_network", "ip4.dhcp": "/dhcp4_flag", "ip4.dhcp_start": "/dhcp4_start", "ip4.dhcp_end": "/dhcp4_end", "ip6.gateway": "/ip6_gateway", "ip6.network": "/ip6_network", "ip6.dhcp": "/dhcp6_flag"}, "reservation": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname"}, "lease": {"mac": "", "ip": "/ipaddr", "hostname": "/hostname", "expiry": "/expiry", "client_id": "/clientid"}, "rule": {"description": "", "rule": "/rule", "order": "/order"}, "osd": {"id": "", "node": "/node", "device": "/device", "db_device": "/db_device", "fsid": "/fsid", "ofsid": "/fsid/osd", "cfsid": "/fsid/cluster", "lvm": "/lvm", "vg": "/lvm/vg", "lv": "/lvm/lv", "is_split": "/is_split", "stats": "/stats"}, "pool": {"name": "", "pgs": "/pgs", "tier": "/tier", "stats": "/stats"}, "volume": {"name": "", "stats": "/stats"}, "snapshot": {"name": "", "stats": "/stats"}} -------------------------------------------------------------------------------- /health-daemon/plugins/zkpr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # zkpr.py - PVC Monitoring example plugin for Zookeeper 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | # This script provides an example of a PVC monitoring plugin script. It will create 23 | # a simple plugin to check the Zookeeper instance on the node for operation. 24 | 25 | # This script can thus be used as an example or reference implementation of a 26 | # PVC monitoring pluginscript and expanded upon as required. 27 | 28 | # A monitoring plugin script must implement the class "MonitoringPluginScript" which 29 | # extends "MonitoringPlugin", providing the 3 functions indicated. Detailed explanation 30 | # of the role of each function is provided in context of the example; see the other 31 | # examples for more potential uses. 32 | 33 | # WARNING: 34 | # 35 | # This script will run in the context of the node daemon keepalives as root. 36 | # DO NOT install untrusted, unvetted plugins under any circumstances. 37 | 38 | 39 | # This import is always required here, as MonitoringPlugin is used by the 40 | # MonitoringPluginScript class 41 | from pvchealthd.objects.MonitoringInstance import MonitoringPlugin 42 | 43 | 44 | # A monitoring plugin script must always expose its nice name, which must be identical to 45 | # the file name 46 | PLUGIN_NAME = "zkpr" 47 | 48 | 49 | # The MonitoringPluginScript class must be named as such, and extend MonitoringPlugin. 50 | class MonitoringPluginScript(MonitoringPlugin): 51 | def setup(self): 52 | """ 53 | setup(): Perform special setup steps during node daemon startup 54 | 55 | This step is optional and should be used sparingly. 56 | 57 | If you wish for the plugin to not zkpr in certain conditions, do any checks here 58 | and return a non-None failure message to indicate the error. 59 | """ 60 | 61 | pass 62 | 63 | def run(self, coordinator_state=None): 64 | """ 65 | run(): Perform the check actions and return a PluginResult object 66 | 67 | The {coordinator_state} can be used to check if this is a "primary" coordinator, "secondary" coordinator, or "client" (non-coordinator) 68 | """ 69 | 70 | # Run any imports first 71 | from kazoo.client import KazooClient, KazooState 72 | 73 | zk_conn = None 74 | 75 | # Set the health delta to 0 (no change) 76 | health_delta = 0 77 | # Craft a message that can be used by the clients 78 | message = "Successfully connected to Zookeeper on localhost" 79 | 80 | # Check the Zookeeper connection 81 | try: 82 | zk_conn = KazooClient(hosts=[f"{self.this_node.name}:2181"], timeout=1, read_only=True) 83 | zk_conn.start(timeout=1) 84 | data = zk_conn.get('/schema/version') 85 | except Exception as e: 86 | health_delta = 50 87 | message = f"Failed to connect to Zookeeper: {e}" 88 | finally: 89 | if zk_conn is not None: 90 | zk_conn.stop() 91 | zk_conn.close() 92 | 93 | # Set the health delta in our local PluginResult object 94 | self.plugin_result.set_health_delta(health_delta) 95 | 96 | # Set the message in our local PluginResult object 97 | self.plugin_result.set_message(message) 98 | 99 | # Return our local PluginResult object 100 | return self.plugin_result 101 | 102 | def cleanup(self): 103 | """ 104 | cleanup(): Perform special cleanup steps during node daemon termination 105 | 106 | This step is optional and should be used sparingly. 107 | """ 108 | 109 | pass 110 | -------------------------------------------------------------------------------- /health-daemon/plugins/edac: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # edac.py - PVC Monitoring example plugin for EDAC 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | # This script provides an example of a PVC monitoring plugin script. It will create 23 | # a simple plugin to check the system's EDAC registers and report any failures. 24 | 25 | # This script can thus be used as an example or reference implementation of a 26 | # PVC monitoring pluginscript and expanded upon as required. 27 | 28 | # A monitoring plugin script must implement the class "MonitoringPluginScript" which 29 | # extends "MonitoringPlugin", providing the 3 functions indicated. Detailed explanation 30 | # of the role of each function is provided in context of the example; see the other 31 | # examples for more potential uses. 32 | 33 | # WARNING: 34 | # 35 | # This script will run in the context of the node daemon keepalives as root. 36 | # DO NOT install untrusted, unvetted plugins under any circumstances. 37 | 38 | 39 | # This import is always required here, as MonitoringPlugin is used by the 40 | # MonitoringPluginScript class 41 | from pvchealthd.objects.MonitoringInstance import MonitoringPlugin 42 | 43 | 44 | # A monitoring plugin script must always expose its nice name, which must be identical to 45 | # the file name 46 | PLUGIN_NAME = "edac" 47 | 48 | 49 | # The MonitoringPluginScript class must be named as such, and extend MonitoringPlugin. 50 | class MonitoringPluginScript(MonitoringPlugin): 51 | def setup(self): 52 | """ 53 | setup(): Perform special setup steps during node daemon startup 54 | 55 | This step is optional and should be used sparingly. 56 | 57 | If you wish for the plugin to not load in certain conditions, do any checks here 58 | and return a non-None failure message to indicate the error. 59 | """ 60 | 61 | pass 62 | 63 | def run(self, coordinator_state=None): 64 | """ 65 | run(): Perform the check actions and return a PluginResult object 66 | 67 | The {coordinator_state} can be used to check if this is a "primary" coordinator, "secondary" coordinator, or "client" (non-coordinator) 68 | """ 69 | 70 | # Run any imports first 71 | import daemon_lib.common as common 72 | from re import match, search 73 | 74 | # Get edac-util output 75 | retcode, stdout, stderr = common.run_os_command('/usr/bin/edac-util') 76 | 77 | # If there's no errors, we're OK 78 | if match(r'^edac-util: No errors to report.', stdout): 79 | health_delta = 0 80 | message = "EDAC reports no errors" 81 | else: 82 | health_delta = 0 83 | message = "EDAC reports errors: " 84 | errors = list() 85 | for line in stdout.split('\n'): 86 | if match(r'^mc[0-9]: csrow', line): 87 | if 'Uncorrected' in line: 88 | health_delta = 50 89 | errors.append(' '.join(line.split()[2:])) 90 | message += ', '.join(errors) 91 | 92 | # Set the health delta in our local PluginResult object 93 | self.plugin_result.set_health_delta(health_delta) 94 | 95 | # Set the message in our local PluginResult object 96 | self.plugin_result.set_message(message) 97 | 98 | # Return our local PluginResult object 99 | return self.plugin_result 100 | 101 | def cleanup(self): 102 | """ 103 | cleanup(): Perform special cleanup steps during node daemon termination 104 | 105 | This step is optional and should be used sparingly. 106 | """ 107 | 108 | pass 109 | -------------------------------------------------------------------------------- /health-daemon/plugins/load: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # load.py - PVC Monitoring example plugin for load 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | # This script provides an example of a PVC monitoring plugin script. It will create 23 | # a simple plugin to check the system load against the total number of CPU cores. 24 | 25 | # This script can thus be used as an example or reference implementation of a 26 | # PVC monitoring pluginscript and expanded upon as required. 27 | 28 | # A monitoring plugin script must implement the class "MonitoringPluginScript" which 29 | # extends "MonitoringPlugin", providing the 3 functions indicated. Detailed explanation 30 | # of the role of each function is provided in context of the example; see the other 31 | # examples for more potential uses. 32 | 33 | # WARNING: 34 | # 35 | # This script will run in the context of the node daemon keepalives as root. 36 | # DO NOT install untrusted, unvetted plugins under any circumstances. 37 | 38 | 39 | # This import is always required here, as MonitoringPlugin is used by the 40 | # MonitoringPluginScript class 41 | from pvchealthd.objects.MonitoringInstance import MonitoringPlugin 42 | 43 | 44 | # A monitoring plugin script must always expose its nice name, which must be identical to 45 | # the file name 46 | PLUGIN_NAME = "load" 47 | 48 | 49 | # The MonitoringPluginScript class must be named as such, and extend MonitoringPlugin. 50 | class MonitoringPluginScript(MonitoringPlugin): 51 | def setup(self): 52 | """ 53 | setup(): Perform special setup steps during node daemon startup 54 | 55 | This step is optional and should be used sparingly. 56 | 57 | If you wish for the plugin to not load in certain conditions, do any checks here 58 | and return a non-None failure message to indicate the error. 59 | """ 60 | 61 | pass 62 | 63 | def run(self, coordinator_state=None): 64 | """ 65 | run(): Perform the check actions and return a PluginResult object 66 | 67 | The {coordinator_state} can be used to check if this is a "primary" coordinator, "secondary" coordinator, or "client" (non-coordinator) 68 | """ 69 | 70 | # Run any imports first 71 | from os import getloadavg 72 | from psutil import cpu_count 73 | 74 | # Get the current 1-minute system load average 75 | load_average = float(round(getloadavg()[0], 2)) 76 | 77 | # Get the number of CPU cores 78 | cpu_cores = cpu_count() 79 | 80 | # Check that the load average is greater or equal to the cpu count 81 | if load_average > float(cpu_cores): 82 | # Set the health delta to 10 (subtract 10 from the total of 100) 83 | health_delta = 50 84 | # Craft a message that can be used by the clients 85 | message = f"Current load is {load_average} out of {cpu_cores} CPU cores" 86 | 87 | else: 88 | # Set the health delta to 0 (no change) 89 | health_delta = 0 90 | # Craft a message that can be used by the clients 91 | message = f"Current load is {load_average} out of {cpu_cores} CPU cores" 92 | 93 | # Set the health delta in our local PluginResult object 94 | self.plugin_result.set_health_delta(health_delta) 95 | 96 | # Set the message in our local PluginResult object 97 | self.plugin_result.set_message(message) 98 | 99 | # Return our local PluginResult object 100 | return self.plugin_result 101 | 102 | def cleanup(self): 103 | """ 104 | cleanup(): Perform special cleanup steps during node daemon termination 105 | 106 | This step is optional and should be used sparingly. 107 | """ 108 | 109 | pass 110 | -------------------------------------------------------------------------------- /daemon-common/libvirt_schema.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # libvirt_schema.py - Libvirt schema elements 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | # File header, containing default values for various non-device components 23 | # Variables: 24 | # * vm_name 25 | # * vm_uuid 26 | # * vm_description 27 | # * vm_memory 28 | # * vm_vcpus 29 | # * vm_architecture 30 | libvirt_header = """ 31 | {vm_name} 32 | {vm_uuid} 33 | {vm_description} 34 | {vm_memory} 35 | {vm_vcpus} 36 | 37 | 38 | 39 | 40 | hvm 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | destroy 53 | restart 54 | restart 55 | 56 | 57 | """ 58 | 59 | # File footer, closing devices and domain elements 60 | libvirt_footer = """ 61 | """ 62 | 63 | # Default devices for all VMs 64 | devices_default = """ /usr/bin/kvm 65 | 66 | 67 | 68 | 69 | /dev/random 70 | 71 | 72 | 73 | 74 | """ 75 | 76 | # Serial device 77 | # Variables: 78 | # * vm_name 79 | devices_serial = """ 80 | 81 | 82 | """ 83 | 84 | # VNC device 85 | # Variables: 86 | # * vm_vncport 87 | # * vm_vnc_autoport 88 | # * vm_vnc_bind 89 | devices_vnc = """ 90 | """ 91 | 92 | # VirtIO SCSI device 93 | devices_scsi_controller = """ 94 | """ 95 | 96 | # Disk device header 97 | # Variables: 98 | # * ceph_storage_secret 99 | # * disk_pool 100 | # * vm_name 101 | # * disk_id 102 | devices_disk_header = """ 103 | 104 | 105 | 106 | 107 | 108 | 109 | """ 110 | 111 | # Disk device coordinator element 112 | # Variables: 113 | # * coordinator_name 114 | # * coordinator_ceph_mon_port 115 | devices_disk_coordinator = """ 116 | """ 117 | 118 | # Disk device footer 119 | devices_disk_footer = """ 120 | 121 | """ 122 | 123 | # vhostmd virtualization passthrough device 124 | devices_vhostmd = """ 125 | 126 | 127 | 128 | 129 | 130 | """ 131 | 132 | # Network interface device 133 | # Variables: 134 | # * eth_macaddr 135 | # * eth_bridge 136 | devices_net_interface = """ 137 | 138 | 139 | 140 | 141 | """ 142 | -------------------------------------------------------------------------------- /node-daemon/pvcnoded/util/services.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # services.py - Utility functions for pvcnoded external services 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import daemon_lib.common as common 23 | from time import sleep 24 | 25 | 26 | def start_zookeeper(logger, config): 27 | if config["daemon_mode"] == "coordinator": 28 | logger.out("Starting Zookeeper daemon", state="i") 29 | # TODO: Move our handling out of Systemd and integrate it directly as a subprocess? 30 | common.run_os_command("systemctl start zookeeper.service") 31 | 32 | 33 | def start_libvirtd(logger, config): 34 | if config["enable_hypervisor"]: 35 | logger.out("Starting Libvirt daemon", state="i") 36 | # TODO: Move our handling out of Systemd and integrate it directly as a subprocess? 37 | common.run_os_command("systemctl start libvirtd.service") 38 | 39 | 40 | def start_patroni(logger, config): 41 | if config["enable_networking"] and config["daemon_mode"] == "coordinator": 42 | logger.out("Starting Patroni daemon", state="i") 43 | # TODO: Move our handling out of Systemd and integrate it directly as a subprocess? 44 | common.run_os_command("systemctl start patroni.service") 45 | 46 | 47 | def start_frrouting(logger, config): 48 | if config["enable_networking"] and config["daemon_mode"] == "coordinator": 49 | logger.out("Starting FRRouting daemon", state="i") 50 | # TODO: Move our handling out of Systemd and integrate it directly as a subprocess? 51 | common.run_os_command("systemctl start frr.service") 52 | 53 | 54 | def start_ceph_mon(logger, config): 55 | if config["enable_storage"] and config["daemon_mode"] == "coordinator": 56 | logger.out("Starting Ceph Monitor daemon", state="i") 57 | # TODO: Move our handling out of Systemd and integrate it directly as a subprocess? 58 | common.run_os_command( 59 | f'systemctl start ceph-mon@{config["node_hostname"]}.service' 60 | ) 61 | 62 | 63 | def start_ceph_mgr(logger, config): 64 | if config["enable_storage"] and config["daemon_mode"] == "coordinator": 65 | logger.out("Starting Ceph Manager daemon", state="i") 66 | # TODO: Move our handling out of Systemd and integrate it directly as a subprocess? 67 | common.run_os_command( 68 | f'systemctl start ceph-mgr@{config["node_hostname"]}.service' 69 | ) 70 | 71 | 72 | def start_keydb(logger, config): 73 | if (config["enable_api"] or config["enable_worker"]) and config[ 74 | "daemon_mode" 75 | ] == "coordinator": 76 | logger.out("Starting KeyDB daemon", state="i") 77 | # TODO: Move our handling out of Systemd and integrate it directly as a subprocess? 78 | common.run_os_command("systemctl start keydb-server.service") 79 | 80 | 81 | def start_workerd(logger, config): 82 | if config["enable_worker"]: 83 | logger.out("Starting Celery Worker daemon", state="i") 84 | # TODO: Move our handling out of Systemd and integrate it directly as a subprocess? 85 | common.run_os_command("systemctl start pvcworkerd.service") 86 | 87 | 88 | def start_healthd(logger, config): 89 | logger.out("Starting Health Monitoring daemon", state="i") 90 | # TODO: Move our handling out of Systemd and integrate it directly as a subprocess? 91 | common.run_os_command("systemctl start pvchealthd.service") 92 | 93 | 94 | def start_system_services(logger, config): 95 | start_zookeeper(logger, config) 96 | start_libvirtd(logger, config) 97 | start_patroni(logger, config) 98 | start_frrouting(logger, config) 99 | start_ceph_mon(logger, config) 100 | start_ceph_mgr(logger, config) 101 | start_keydb(logger, config) 102 | start_workerd(logger, config) 103 | start_healthd(logger, config) 104 | 105 | logger.out("Waiting 10 seconds for daemons to start", state="s") 106 | sleep(10) 107 | -------------------------------------------------------------------------------- /api-daemon/migrations/versions/2d1daa722a0a_pvc_version_0_6.py: -------------------------------------------------------------------------------- 1 | """PVC version 0.6 2 | 3 | Revision ID: 2d1daa722a0a 4 | Revises: 5 | Create Date: 2020-02-15 23:14:14.733134 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '2d1daa722a0a' 14 | down_revision = None 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('network_template', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('name', sa.Text(), nullable=False), 24 | sa.Column('mac_template', sa.Text(), nullable=True), 25 | sa.PrimaryKeyConstraint('id'), 26 | sa.UniqueConstraint('name') 27 | ) 28 | op.create_table('script', 29 | sa.Column('id', sa.Integer(), nullable=False), 30 | sa.Column('name', sa.Text(), nullable=False), 31 | sa.Column('script', sa.Text(), nullable=False), 32 | sa.PrimaryKeyConstraint('id'), 33 | sa.UniqueConstraint('name') 34 | ) 35 | op.create_table('storage_template', 36 | sa.Column('id', sa.Integer(), nullable=False), 37 | sa.Column('name', sa.Text(), nullable=False), 38 | sa.PrimaryKeyConstraint('id'), 39 | sa.UniqueConstraint('name') 40 | ) 41 | op.create_table('system_template', 42 | sa.Column('id', sa.Integer(), nullable=False), 43 | sa.Column('name', sa.Text(), nullable=False), 44 | sa.Column('vcpu_count', sa.Integer(), nullable=False), 45 | sa.Column('vram_mb', sa.Integer(), nullable=False), 46 | sa.Column('serial', sa.Boolean(), nullable=False), 47 | sa.Column('vnc', sa.Boolean(), nullable=False), 48 | sa.Column('vnc_bind', sa.Text(), nullable=True), 49 | sa.Column('node_limit', sa.Text(), nullable=True), 50 | sa.Column('node_selector', sa.Text(), nullable=True), 51 | sa.Column('node_autostart', sa.Boolean(), nullable=False), 52 | sa.PrimaryKeyConstraint('id'), 53 | sa.UniqueConstraint('name') 54 | ) 55 | op.create_table('userdata', 56 | sa.Column('id', sa.Integer(), nullable=False), 57 | sa.Column('name', sa.Text(), nullable=False), 58 | sa.Column('userdata', sa.Text(), nullable=False), 59 | sa.PrimaryKeyConstraint('id'), 60 | sa.UniqueConstraint('name') 61 | ) 62 | op.create_table('network', 63 | sa.Column('id', sa.Integer(), nullable=False), 64 | sa.Column('network_template', sa.Integer(), nullable=True), 65 | sa.Column('vni', sa.Integer(), nullable=False), 66 | sa.ForeignKeyConstraint(['network_template'], ['network_template.id'], ), 67 | sa.PrimaryKeyConstraint('id') 68 | ) 69 | op.create_table('profile', 70 | sa.Column('id', sa.Integer(), nullable=False), 71 | sa.Column('name', sa.Text(), nullable=False), 72 | sa.Column('system_template', sa.Integer(), nullable=True), 73 | sa.Column('network_template', sa.Integer(), nullable=True), 74 | sa.Column('storage_template', sa.Integer(), nullable=True), 75 | sa.Column('userdata', sa.Integer(), nullable=True), 76 | sa.Column('script', sa.Integer(), nullable=True), 77 | sa.Column('arguments', sa.Text(), nullable=True), 78 | sa.ForeignKeyConstraint(['network_template'], ['network_template.id'], ), 79 | sa.ForeignKeyConstraint(['script'], ['script.id'], ), 80 | sa.ForeignKeyConstraint(['storage_template'], ['storage_template.id'], ), 81 | sa.ForeignKeyConstraint(['system_template'], ['system_template.id'], ), 82 | sa.ForeignKeyConstraint(['userdata'], ['userdata.id'], ), 83 | sa.PrimaryKeyConstraint('id'), 84 | sa.UniqueConstraint('name') 85 | ) 86 | op.create_table('storage', 87 | sa.Column('id', sa.Integer(), nullable=False), 88 | sa.Column('storage_template', sa.Integer(), nullable=True), 89 | sa.Column('pool', sa.Text(), nullable=False), 90 | sa.Column('disk_id', sa.Text(), nullable=False), 91 | sa.Column('source_volume', sa.Text(), nullable=True), 92 | sa.Column('disk_size_gb', sa.Integer(), nullable=True), 93 | sa.Column('mountpoint', sa.Text(), nullable=True), 94 | sa.Column('filesystem', sa.Text(), nullable=True), 95 | sa.Column('filesystem_args', sa.Text(), nullable=True), 96 | sa.ForeignKeyConstraint(['storage_template'], ['storage_template.id'], ), 97 | sa.PrimaryKeyConstraint('id') 98 | ) 99 | # ### end Alembic commands ### 100 | 101 | 102 | def downgrade(): 103 | # ### commands auto generated by Alembic - please adjust! ### 104 | op.drop_table('storage') 105 | op.drop_table('profile') 106 | op.drop_table('network') 107 | op.drop_table('userdata') 108 | op.drop_table('system_template') 109 | op.drop_table('storage_template') 110 | op.drop_table('script') 111 | op.drop_table('network_template') 112 | # ### end Alembic commands ### 113 | -------------------------------------------------------------------------------- /health-daemon/plugins/ipmi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ipmi.py - PVC Monitoring example plugin for IPMI 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | # This script provides an example of a PVC monitoring plugin script. It will create 23 | # a simple plugin to check whether the system IPMI is reachable. 24 | 25 | # This script can thus be used as an example or reference implementation of a 26 | # PVC monitoring pluginscript and expanded upon as required. 27 | 28 | # A monitoring plugin script must implement the class "MonitoringPluginScript" which 29 | # extends "MonitoringPlugin", providing the 3 functions indicated. Detailed explanation 30 | # of the role of each function is provided in context of the example; see the other 31 | # examples for more potential uses. 32 | 33 | # WARNING: 34 | # 35 | # This script will run in the context of the node daemon keepalives as root. 36 | # DO NOT install untrusted, unvetted plugins under any circumstances. 37 | 38 | 39 | # This import is always required here, as MonitoringPlugin is used by the 40 | # MonitoringPluginScript class 41 | from pvchealthd.objects.MonitoringInstance import MonitoringPlugin 42 | 43 | 44 | # A monitoring plugin script must always expose its nice name, which must be identical to 45 | # the file name 46 | PLUGIN_NAME = "ipmi" 47 | 48 | 49 | # The MonitoringPluginScript class must be named as such, and extend MonitoringPlugin. 50 | class MonitoringPluginScript(MonitoringPlugin): 51 | def setup(self): 52 | """ 53 | setup(): Perform special setup steps during node daemon startup 54 | 55 | This step is optional and should be used sparingly. 56 | 57 | If you wish for the plugin to not ipmi in certain conditions, do any checks here 58 | and return a non-None failure message to indicate the error. 59 | """ 60 | 61 | pass 62 | 63 | def run(self, coordinator_state=None): 64 | """ 65 | run(): Perform the check actions and return a PluginResult object 66 | 67 | The {coordinator_state} can be used to check if this is a "primary" coordinator, "secondary" coordinator, or "client" (non-coordinator) 68 | """ 69 | 70 | # Run any imports first 71 | from daemon_lib.common import run_os_command 72 | from time import sleep 73 | 74 | # Check the node's IPMI interface 75 | ipmi_hostname = self.config["ipmi_hostname"] 76 | ipmi_username = self.config["ipmi_username"] 77 | ipmi_password = self.config["ipmi_password"] 78 | retcode = 1 79 | trycount = 0 80 | while retcode > 0 and trycount < 3: 81 | retcode, _, _ = run_os_command( 82 | f"/usr/bin/ipmitool -I lanplus -H {ipmi_hostname} -U {ipmi_username} -P {ipmi_password} chassis power status", 83 | timeout=2 84 | ) 85 | trycount += 1 86 | if retcode > 0 and trycount < 3: 87 | sleep(trycount) 88 | 89 | if retcode > 0: 90 | # Set the health delta to 10 (subtract 10 from the total of 100) 91 | health_delta = 10 92 | # Craft a message that can be used by the clients 93 | message = f"IPMI via {ipmi_username}@{ipmi_hostname} is NOT responding after 3 attempts" 94 | else: 95 | # Set the health delta to 0 (no change) 96 | health_delta = 0 97 | # Craft a message that can be used by the clients 98 | message = f"IPMI via {ipmi_username}@{ipmi_hostname} is responding after {trycount} attempts" 99 | 100 | # Set the health delta in our local PluginResult object 101 | self.plugin_result.set_health_delta(health_delta) 102 | 103 | # Set the message in our local PluginResult object 104 | self.plugin_result.set_message(message) 105 | 106 | # Return our local PluginResult object 107 | return self.plugin_result 108 | 109 | def cleanup(self): 110 | """ 111 | cleanup(): Perform special cleanup steps during node daemon termination 112 | 113 | This step is optional and should be used sparingly. 114 | """ 115 | 116 | pass 117 | -------------------------------------------------------------------------------- /client-cli/pvc/cli/parsers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # parsers.py - PVC Click CLI data parser function library 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | from os import path 23 | from re import sub 24 | 25 | from pvc.cli.helpers import read_config_from_yaml, get_config 26 | 27 | import pvc.lib.cluster 28 | 29 | 30 | def cli_connection_list_parser(connections_config, show_keys_flag): 31 | """ 32 | Parse connections_config into formatable data for cli_connection_list 33 | """ 34 | 35 | connections_data = list() 36 | 37 | for connection, details in connections_config.items(): 38 | if details.get("cfgfile", None) is not None: 39 | if path.isfile(details.get("cfgfile")): 40 | description, address, port, scheme, api_key = read_config_from_yaml( 41 | details.get("cfgfile") 42 | ) 43 | else: 44 | continue 45 | if not show_keys_flag and api_key is not None: 46 | api_key = sub(r"[a-z0-9]", "x", api_key) 47 | connections_data.append( 48 | { 49 | "name": connection, 50 | "description": description, 51 | "address": address, 52 | "port": port, 53 | "scheme": scheme, 54 | "api_key": api_key, 55 | } 56 | ) 57 | else: 58 | if not show_keys_flag: 59 | details["api_key"] = sub(r"[a-z0-9]", "x", details["api_key"]) 60 | connections_data.append( 61 | { 62 | "name": connection, 63 | "description": details["description"], 64 | "address": details["host"], 65 | "port": details["port"], 66 | "scheme": details["scheme"], 67 | "api_key": details["api_key"], 68 | } 69 | ) 70 | 71 | # Return, ensuring local is always first 72 | return sorted(connections_data, key=lambda x: (x.get("name") != "local")) 73 | 74 | 75 | def cli_connection_detail_parser(connections_config): 76 | """ 77 | Parse connections_config into formatable data for cli_connection_detail 78 | """ 79 | connections_data = list() 80 | for connection, details in connections_config.items(): 81 | cluster_config = get_config(connections_config, connection=connection) 82 | if cluster_config.get("badcfg", False): 83 | continue 84 | # Connect to each API and gather cluster status 85 | retcode, retdata = pvc.lib.cluster.get_info(cluster_config) 86 | if retcode == 0: 87 | # Create dummy data of N/A for all fields 88 | connections_data.append( 89 | { 90 | "name": cluster_config["connection"], 91 | "description": cluster_config["description"], 92 | "health": "N/A", 93 | "maintenance": "N/A", 94 | "primary_node": "N/A", 95 | "pvc_version": "N/A", 96 | "nodes": "N/A", 97 | "vms": "N/A", 98 | "networks": "N/A", 99 | "osds": "N/A", 100 | "pools": "N/A", 101 | "volumes": "N/A", 102 | "snapshots": "N/A", 103 | } 104 | ) 105 | else: 106 | # Normalize data into nice formattable version 107 | connections_data.append( 108 | { 109 | "name": cluster_config["connection"], 110 | "description": cluster_config["description"], 111 | "health": retdata.get("cluster_health", {}).get("health", "N/A"), 112 | "maintenance": retdata.get("maintenance", "N/A"), 113 | "primary_node": retdata.get("primary_node", "N/A"), 114 | "pvc_version": retdata.get("pvc_version", "N/A"), 115 | "nodes": retdata.get("nodes", {}).get("total", "N/A"), 116 | "vms": retdata.get("vms", {}).get("total", "N/A"), 117 | "networks": retdata.get("networks", "N/A"), 118 | "osds": retdata.get("osds", {}).get("total", "N/A"), 119 | "pools": retdata.get("pools", "N/A"), 120 | "volumes": retdata.get("volumes", "N/A"), 121 | "snapshots": retdata.get("snapshots", "N/A"), 122 | } 123 | ) 124 | 125 | # Return, ensuring local is always first 126 | return sorted(connections_data, key=lambda x: (x.get("name") != "local")) 127 | -------------------------------------------------------------------------------- /client-cli/pvc/cli/waiters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # waiters.py - PVC Click CLI output waiters library 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import sys 23 | 24 | from click import progressbar 25 | from time import sleep, time 26 | 27 | from pvc.cli.helpers import echo 28 | 29 | import pvc.lib.node 30 | 31 | 32 | def cli_node_waiter(config, node, state_field, state_value): 33 | """ 34 | Wait for state transitions for cli_node tasks 35 | 36 | {node} is the name of the node 37 | {state_field} is the node_info field to check for {state_value} 38 | {state_value} is the TRANSITIONAL value that, when no longer set, will terminate waiting 39 | """ 40 | 41 | # Sleep for this long between API polls 42 | sleep_time = 1 43 | 44 | # Print a dot after this many {sleep_time}s 45 | dot_time = 5 46 | 47 | t_start = time() 48 | 49 | echo(config, "Waiting...", newline=False) 50 | sleep(sleep_time) 51 | 52 | count = 0 53 | while True: 54 | count += 1 55 | try: 56 | _retcode, _retdata = pvc.lib.node.node_info(config, node) 57 | if _retdata[state_field] != state_value: 58 | break 59 | else: 60 | raise ValueError 61 | except Exception: 62 | sleep(sleep_time) 63 | if count % dot_time == 0: 64 | echo(config, ".", newline=False) 65 | 66 | t_end = time() 67 | echo(config, f" done. [{int(t_end - t_start)}s]") 68 | 69 | 70 | def wait_for_celery_task(CLI_CONFIG, task_detail, start_late=False): 71 | """ 72 | Wait for a Celery task to complete 73 | """ 74 | 75 | task_id = task_detail["task_id"] 76 | task_name = task_detail["task_name"] 77 | 78 | if not start_late: 79 | run_on = task_detail["run_on"] 80 | 81 | echo(CLI_CONFIG, f"Task ID: {task_id} ({task_name}) assigned to node {run_on}") 82 | echo(CLI_CONFIG, "") 83 | 84 | # Wait for the task to start 85 | echo(CLI_CONFIG, "Waiting for task to start...", newline=False) 86 | while True: 87 | sleep(0.5) 88 | task_status = pvc.lib.common.task_status( 89 | CLI_CONFIG, task_id=task_id, is_watching=True 90 | ) 91 | if task_status.get("state") != "PENDING": 92 | break 93 | echo(CLI_CONFIG, ".", newline=False) 94 | echo(CLI_CONFIG, " done.") 95 | echo(CLI_CONFIG, "") 96 | 97 | echo( 98 | CLI_CONFIG, 99 | task_status.get("status") + ":", 100 | ) 101 | else: 102 | task_status = pvc.lib.common.task_status( 103 | CLI_CONFIG, task_id=task_id, is_watching=True 104 | ) 105 | 106 | echo(CLI_CONFIG, f"Watching existing task {task_id} ({task_name}):") 107 | 108 | # Start following the task state, updating progress as we go 109 | total_task = task_status.get("total") 110 | with progressbar(length=total_task, width=20, show_eta=False) as bar: 111 | last_task = 0 112 | maxlen = 21 113 | echo( 114 | CLI_CONFIG, 115 | " " + "Gathering information", 116 | newline=False, 117 | ) 118 | while True: 119 | sleep(0.5) 120 | 121 | task_status = pvc.lib.common.task_status( 122 | CLI_CONFIG, task_id=task_id, is_watching=True 123 | ) 124 | 125 | if isinstance(task_status, tuple): 126 | continue 127 | if task_status.get("state") != "RUNNING": 128 | break 129 | if task_status.get("current") == 0: 130 | continue 131 | 132 | current_task = int(task_status.get("current")) 133 | total_task = int(task_status.get("total")) 134 | bar.length = total_task 135 | 136 | if current_task > last_task: 137 | bar.update(current_task - last_task) 138 | last_task = current_task 139 | 140 | curlen = len(str(task_status.get("status"))) 141 | if curlen > maxlen: 142 | maxlen = curlen 143 | lendiff = maxlen - curlen 144 | overwrite_whitespace = " " * lendiff 145 | 146 | percent_complete = (current_task / total_task) * 100 147 | bar_output = f"[{bar.format_bar()}] {percent_complete:3.0f}%" 148 | sys.stdout.write( 149 | f"\r {bar_output} {task_status['status']}{overwrite_whitespace}" 150 | ) 151 | sys.stdout.flush() 152 | 153 | if task_status.get("state") == "SUCCESS": 154 | bar.update(total_task - last_task) 155 | 156 | echo(CLI_CONFIG, "") 157 | retdata = task_status.get("state") + ": " + task_status.get("status") 158 | 159 | return retdata 160 | -------------------------------------------------------------------------------- /monitoring/munin/pvc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # -*- sh -*- 3 | 4 | : << =cut 5 | 6 | =head1 NAME 7 | 8 | pvc - Plugin to monitor a PVC cluster. 9 | 10 | =head1 AUTHOR 11 | 12 | Joshua Boniface 13 | 14 | =head1 LICENSE 15 | 16 | GPLv3 17 | 18 | =head1 BUGS 19 | 20 | =back 21 | 22 | =head1 MAGIC MARKERS 23 | 24 | #%# family=auto 25 | #%# capabilities=autoconf 26 | 27 | =cut 28 | 29 | . "$MUNIN_LIBDIR/plugins/plugin.sh" 30 | 31 | is_multigraph 32 | 33 | warning=0.99 34 | critical=1.99 35 | 36 | export PVC_CLIENT_DIR="/run/shm/munin-pvc" 37 | PVC_CMD="/usr/bin/sudo -E /usr/bin/pvc --quiet cluster status --format json-pretty" 38 | JQ_CMD="/usr/bin/jq" 39 | 40 | output_usage() { 41 | echo "This plugin outputs information about a PVC cluster and node" 42 | exit 0 43 | } 44 | 45 | output_autoconf() { 46 | $PVC_CMD &>/dev/null 47 | pvc_ret=$? 48 | $JQ_CMD --version &>/dev/null 49 | jq_ret=$? 50 | 51 | if [[ ${pvc_ret} -eq 0 && ${jq_ret} -eq 0 ]]; then 52 | echo "yes" 53 | elif [[ ${pvc_ret} -ne 0 ]]; then 54 | echo "no (no 'pvc' command found or local cluster not usable)" 55 | elif [[ ${jq_ret} -ne 0 ]]; then 56 | echo "no (no 'jq' command found)" 57 | else 58 | echo "no (generic failure)" 59 | fi 60 | } 61 | 62 | output_config() { 63 | echo 'multigraph pvc_cluster_health' 64 | echo 'graph_title PVC Cluster Health' 65 | echo 'graph_args --base 1000' 66 | echo 'graph_vlabel Health%' 67 | echo 'graph_category pvc' 68 | echo 'graph_info Health of the PVC cluster' 69 | 70 | echo 'pvc_cluster_health.label Cluster Health' 71 | echo 'pvc_cluster_health.type GAUGE' 72 | echo 'pvc_cluster_health.max 100' 73 | echo 'pvc_cluster_health.min 0' 74 | echo 'pvc_cluster_health.info Health of the PVC cluster in %' 75 | 76 | echo 'multigraph pvc_cluster_alert' 77 | echo 'graph_title PVC Cluster Alerting' 78 | echo 'graph_args --base 1000' 79 | echo 'graph_vlabel State' 80 | echo 'graph_category pvc' 81 | echo 'graph_info Alerting state of the PVC cluster health' 82 | 83 | echo 'pvc_cluster_alert.label Cluster Health State' 84 | echo 'pvc_cluster_alert.type GAUGE' 85 | echo 'pvc_cluster_alert.max 2' 86 | echo 'pvc_cluster_alert.min 0' 87 | echo 'pvc_cluster_alert.info Alerting state of the PVC cluster health' 88 | print_warning pvc_cluster_alert 89 | print_critical pvc_cluster_alert 90 | 91 | echo 'multigraph pvc_node_health' 92 | echo 'graph_title PVC Node Health' 93 | echo 'graph_args --base 1000' 94 | echo 'graph_vlabel Health%' 95 | echo 'graph_category pvc' 96 | echo 'graph_info Health of the PVC node' 97 | 98 | echo 'pvc_node_health.label Node Health' 99 | echo 'pvc_node_health.type GAUGE' 100 | echo 'pvc_node_health.max 100' 101 | echo 'pvc_node_health.min 0' 102 | echo 'pvc_node_health.info Health of the PVC node in %' 103 | 104 | echo 'multigraph pvc_node_alert' 105 | echo 'graph_title PVC Node Alerting' 106 | echo 'graph_args --base 1000' 107 | echo 'graph_vlabel State' 108 | echo 'graph_category pvc' 109 | echo 'graph_info Alerting state of the PVC node health' 110 | 111 | echo 'pvc_node_alert.label Node Health State' 112 | echo 'pvc_node_alert.type GAUGE' 113 | echo 'pvc_node_alert.max 2' 114 | echo 'pvc_node_alert.min 0' 115 | echo 'pvc_node_alert.info Alerting state of the PVC node health' 116 | print_warning pvc_node_alert 117 | print_critical pvc_node_alert 118 | 119 | exit 0 120 | } 121 | 122 | output_values() { 123 | PVC_OUTPUT="$( $PVC_CMD )" 124 | HOST="$( hostname --short )" 125 | 126 | is_maintenance="$( $JQ_CMD ".maintenance" <<<"${PVC_OUTPUT}" | tr -d '"' )" 127 | 128 | cluster_health="$( $JQ_CMD ".cluster_health.health" <<<"${PVC_OUTPUT}" | tr -d '"' )" 129 | cluster_health_messages="$( $JQ_CMD -r ".cluster_health.messages | map(.text) | join(\", \")" <<<"${PVC_OUTPUT}" )" 130 | echo 'multigraph pvc_cluster_health' 131 | echo "pvc_cluster_health.value ${cluster_health}" 132 | echo "pvc_cluster_health.extinfo ${cluster_health_messages}" 133 | 134 | if [[ ${cluster_health} -le 50 && ${is_maintenance} == "false" ]]; then 135 | cluster_health_alert=2 136 | elif [[ ${cluster_health} -le 90 && ${is_maintenance} == "false" ]]; then 137 | cluster_health_alert=1 138 | else 139 | cluster_health_alert=0 140 | fi 141 | echo 'multigraph pvc_cluster_alert' 142 | echo "pvc_cluster_alert.value ${cluster_health_alert}" 143 | 144 | node_health="$( $JQ_CMD ".node_health.${HOST}.health" <<<"${PVC_OUTPUT}" | tr -d '"' )" 145 | node_health_messages="$( $JQ_CMD -r ".node_health.${HOST}.messages | join(\", \")" <<<"${PVC_OUTPUT}" )" 146 | echo 'multigraph pvc_node_health' 147 | echo "pvc_node_health.value ${node_health}" 148 | echo "pvc_node_health.extinfo ${node_health_messages}" 149 | 150 | if [[ ${node_health} -le 50 && ${is_maintenance} != "true" ]]; then 151 | node_health_alert=2 152 | elif [[ ${node_health} -le 90 && ${is_maintenance} != "true" ]]; then 153 | node_health_alert=1 154 | else 155 | node_health_alert=0 156 | fi 157 | echo 'multigraph pvc_node_alert' 158 | echo "pvc_node_alert.value ${node_health_alert}" 159 | } 160 | 161 | case $# in 162 | 0) 163 | output_values 164 | ;; 165 | 1) 166 | case $1 in 167 | autoconf) 168 | output_autoconf 169 | ;; 170 | config) 171 | output_config 172 | ;; 173 | *) 174 | output_usage 175 | exit 1 176 | ;; 177 | esac 178 | ;; 179 | *) 180 | output_usage 181 | exit 1 182 | esac 183 | -------------------------------------------------------------------------------- /api-daemon/pvcapid/Daemon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Daemon.py - PVC HTTP API daemon 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import subprocess 23 | from ssl import SSLContext, TLSVersion 24 | from distutils.util import strtobool as dustrtobool 25 | import daemon_lib.config as cfg 26 | 27 | # Daemon version 28 | version = "0.9.100~git-73c0834f" 29 | 30 | # API version 31 | API_VERSION = 1.0 32 | 33 | 34 | ########################################################## 35 | # Helper Functions 36 | ########################################################## 37 | 38 | 39 | def strtobool(stringv): 40 | if stringv is None: 41 | return False 42 | if isinstance(stringv, bool): 43 | return bool(stringv) 44 | try: 45 | return bool(dustrtobool(stringv)) 46 | except Exception: 47 | return False 48 | 49 | 50 | ########################################################## 51 | # Configuration Parsing 52 | ########################################################## 53 | 54 | # Get our configuration 55 | config = cfg.get_configuration() 56 | config["daemon_name"] = "pvcapid" 57 | config["daemon_version"] = version 58 | 59 | 60 | ########################################################## 61 | # Flask App Creation for Gunicorn 62 | ########################################################## 63 | 64 | 65 | def create_app(): 66 | """ 67 | Create and return the Flask app and SSL context if necessary. 68 | """ 69 | # Import the Flask app from pvcapid.flaskapi after adjusting the path 70 | import pvcapid.flaskapi as pvc_api 71 | 72 | # Print our startup messages 73 | print("") 74 | print("|--------------------------------------------------------------|") 75 | print("| |") 76 | print("| ███████████ ▜█▙ ▟█▛ █████ █ █ █ |") 77 | print("| ██ ▜█▙ ▟█▛ ██ |") 78 | print("| ███████████ ▜█▙ ▟█▛ ██ |") 79 | print("| ██ ▜█▙▟█▛ ███████████ |") 80 | print("| |") 81 | print("|--------------------------------------------------------------|") 82 | print("| Parallel Virtual Cluster API daemon v{0: <23} |".format(version)) 83 | print("| Debug: {0: <53} |".format(str(config["debug"]))) 84 | print("| Cluster: {0: <51} |".format(config["cluster_name"])) 85 | print("| API version: v{0: <46} |".format(API_VERSION)) 86 | print( 87 | "| Listen: {0: <52} |".format( 88 | "{}:{}".format(config["api_listen_address"], config["api_listen_port"]) 89 | ) 90 | ) 91 | print("| SSL: {0: <55} |".format(str(config["api_ssl_enabled"]))) 92 | print("| Authentication: {0: <44} |".format(str(config["api_auth_enabled"]))) 93 | print("|--------------------------------------------------------------|") 94 | print("") 95 | 96 | pvc_api.celery_startup() 97 | 98 | return pvc_api.app 99 | 100 | 101 | ########################################################## 102 | # Entrypoint 103 | ########################################################## 104 | 105 | 106 | def entrypoint(): 107 | if config["debug"]: 108 | app = create_app() 109 | 110 | if config["api_ssl_enabled"]: 111 | ssl_context = SSLContext() 112 | ssl_context.minimum_version = TLSVersion.TLSv1 113 | ssl_context.get_ca_certs() 114 | ssl_context.load_cert_chain( 115 | config["api_ssl_cert_file"], keyfile=config["api_ssl_key_file"] 116 | ) 117 | else: 118 | ssl_context = None 119 | 120 | app.run( 121 | config["api_listen_address"], 122 | config["api_listen_port"], 123 | threaded=True, 124 | ssl_context=ssl_context, 125 | ) 126 | else: 127 | # Build the command to run Gunicorn 128 | gunicorn_cmd = [ 129 | "gunicorn", 130 | "--workers", 131 | "1", 132 | "--threads", 133 | "8", 134 | "--timeout", 135 | "86400", 136 | "--bind", 137 | "{}:{}".format(config["api_listen_address"], config["api_listen_port"]), 138 | "pvcapid.Daemon:create_app()", 139 | "--log-level", 140 | "info", 141 | "--access-logfile", 142 | "-", 143 | "--error-logfile", 144 | "-", 145 | ] 146 | 147 | if config["api_ssl_enabled"]: 148 | gunicorn_cmd += [ 149 | "--certfile", 150 | config["api_ssl_cert_file"], 151 | "--keyfile", 152 | config["api_ssl_key_file"], 153 | ] 154 | 155 | # Run Gunicorn 156 | try: 157 | subprocess.run(gunicorn_cmd) 158 | except KeyboardInterrupt: 159 | exit(0) 160 | except Exception as e: 161 | print(e) 162 | exit(1) 163 | -------------------------------------------------------------------------------- /health-daemon/pvchealthd/Daemon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Daemon.py - Health daemon main entrypoing 4 | # Part of the Parallel Virtual Cluster (PVC) system 5 | # 6 | # Copyright (C) 2018-2024 Joshua M. Boniface 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, version 3. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | 22 | import pvchealthd.util.zookeeper 23 | 24 | import pvchealthd.objects.MonitoringInstance as MonitoringInstance 25 | import pvchealthd.objects.NodeInstance as NodeInstance 26 | 27 | import daemon_lib.config as cfg 28 | import daemon_lib.log as log 29 | 30 | from time import sleep 31 | 32 | import os 33 | import signal 34 | 35 | # Daemon version 36 | version = "1.0.1" 37 | 38 | 39 | ########################################################## 40 | # Entrypoint 41 | ########################################################## 42 | 43 | 44 | def entrypoint(): 45 | monitoring_instance = None 46 | 47 | # Get our configuration 48 | config = cfg.get_configuration() 49 | config["daemon_name"] = "pvchealthd" 50 | config["daemon_version"] = version 51 | 52 | # Set up the logger instance 53 | logger = log.Logger(config) 54 | 55 | # Print our startup message 56 | logger.out("") 57 | logger.out("|--------------------------------------------------------------|") 58 | logger.out("| |") 59 | logger.out("| ███████████ ▜█▙ ▟█▛ █████ █ █ █ |") 60 | logger.out("| ██ ▜█▙ ▟█▛ ██ |") 61 | logger.out("| ███████████ ▜█▙ ▟█▛ ██ |") 62 | logger.out("| ██ ▜█▙▟█▛ ███████████ |") 63 | logger.out("| |") 64 | logger.out("|--------------------------------------------------------------|") 65 | logger.out("| Parallel Virtual Cluster health daemon v{0: <20} |".format(version)) 66 | logger.out("| Debug: {0: <53} |".format(str(config["debug"]))) 67 | logger.out("| Cluster: {0: <51} |".format(config["cluster_name"])) 68 | logger.out("| FQDN: {0: <54} |".format(config["node_fqdn"])) 69 | logger.out("| Host: {0: <54} |".format(config["node_hostname"])) 70 | logger.out("| ID: {0: <56} |".format(config["node_id"])) 71 | logger.out("| IPMI hostname: {0: <45} |".format(config["ipmi_hostname"])) 72 | logger.out("| Machine details: |") 73 | logger.out("| CPUs: {0: <52} |".format(config["static_data"][0])) 74 | logger.out("| Arch: {0: <52} |".format(config["static_data"][3])) 75 | logger.out("| OS: {0: <54} |".format(config["static_data"][2])) 76 | logger.out("| Kernel: {0: <50} |".format(config["static_data"][1])) 77 | logger.out("|--------------------------------------------------------------|") 78 | logger.out("") 79 | logger.out(f'Starting pvchealthd on host {config["node_fqdn"]}', state="s") 80 | 81 | # Connect to Zookeeper and return our handler and current schema version 82 | zkhandler, _ = pvchealthd.util.zookeeper.connect(logger, config) 83 | 84 | logger.out("Waiting for node daemon to be operating", state="s") 85 | while zkhandler.read(("node.state.daemon", config["node_hostname"])) != "run": 86 | sleep(5) 87 | logger.out("Node daemon in run state, continuing health daemon startup", state="s") 88 | 89 | # Define a cleanup function 90 | def cleanup(failure=False): 91 | nonlocal logger, zkhandler, monitoring_instance 92 | 93 | logger.out("Terminating pvchealthd and cleaning up", state="s") 94 | 95 | # Shut down the monitoring system 96 | try: 97 | logger.out("Shutting down monitoring subsystem", state="s") 98 | monitoring_instance.shutdown() 99 | except Exception: 100 | pass 101 | 102 | # Close the Zookeeper connection 103 | try: 104 | zkhandler.disconnect(persistent=True) 105 | del zkhandler 106 | except Exception: 107 | pass 108 | 109 | logger.out("Terminated health daemon", state="s") 110 | logger.terminate() 111 | 112 | if failure: 113 | retcode = 1 114 | else: 115 | retcode = 0 116 | 117 | os._exit(retcode) 118 | 119 | # Termination function 120 | def term(signum="", frame=""): 121 | cleanup(failure=False) 122 | 123 | # Hangup (logrotate) function 124 | def hup(signum="", frame=""): 125 | if config["file_logging"]: 126 | logger.hup() 127 | 128 | # Handle signals gracefully 129 | signal.signal(signal.SIGTERM, term) 130 | signal.signal(signal.SIGINT, term) 131 | signal.signal(signal.SIGQUIT, term) 132 | signal.signal(signal.SIGHUP, hup) 133 | 134 | this_node = NodeInstance.NodeInstance( 135 | config["node_hostname"], 136 | zkhandler, 137 | config, 138 | logger, 139 | ) 140 | 141 | # Set up the node monitoring instance and thread 142 | monitoring_instance = MonitoringInstance.MonitoringInstance( 143 | zkhandler, config, logger, this_node 144 | ) 145 | 146 | # Tick loop; does nothing since everything is async 147 | while True: 148 | try: 149 | sleep(1) 150 | except Exception: 151 | break 152 | --------------------------------------------------------------------------------