mirror of
https://github.com/home-assistant/core.git
synced 2025-07-03 19:37:10 +00:00
222 lines
7.0 KiB
Python
222 lines
7.0 KiB
Python
"""Support for Proxmox VE."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import timedelta
|
|
from typing import Any
|
|
|
|
from proxmoxer import AuthenticationError, ProxmoxAPI
|
|
import requests.exceptions
|
|
from requests.exceptions import ConnectTimeout, SSLError
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.const import (
|
|
CONF_HOST,
|
|
CONF_PASSWORD,
|
|
CONF_PORT,
|
|
CONF_USERNAME,
|
|
CONF_VERIFY_SSL,
|
|
Platform,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers import config_validation as cv
|
|
from homeassistant.helpers.discovery import async_load_platform
|
|
from homeassistant.helpers.typing import ConfigType
|
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
|
|
|
from .common import ProxmoxClient, call_api_container_vm, parse_api_container_vm
|
|
from .const import (
|
|
_LOGGER,
|
|
CONF_CONTAINERS,
|
|
CONF_NODE,
|
|
CONF_NODES,
|
|
CONF_REALM,
|
|
CONF_VMS,
|
|
COORDINATORS,
|
|
DEFAULT_PORT,
|
|
DEFAULT_REALM,
|
|
DEFAULT_VERIFY_SSL,
|
|
DOMAIN,
|
|
PROXMOX_CLIENTS,
|
|
TYPE_CONTAINER,
|
|
TYPE_VM,
|
|
UPDATE_INTERVAL,
|
|
)
|
|
|
|
PLATFORMS = [Platform.BINARY_SENSOR]
|
|
|
|
CONFIG_SCHEMA = vol.Schema(
|
|
{
|
|
DOMAIN: vol.All(
|
|
cv.ensure_list,
|
|
[
|
|
vol.Schema(
|
|
{
|
|
vol.Required(CONF_HOST): cv.string,
|
|
vol.Required(CONF_USERNAME): cv.string,
|
|
vol.Required(CONF_PASSWORD): cv.string,
|
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
|
vol.Optional(CONF_REALM, default=DEFAULT_REALM): cv.string,
|
|
vol.Optional(
|
|
CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL
|
|
): cv.boolean,
|
|
vol.Required(CONF_NODES): vol.All(
|
|
cv.ensure_list,
|
|
[
|
|
vol.Schema(
|
|
{
|
|
vol.Required(CONF_NODE): cv.string,
|
|
vol.Optional(CONF_VMS, default=[]): [
|
|
cv.positive_int
|
|
],
|
|
vol.Optional(CONF_CONTAINERS, default=[]): [
|
|
cv.positive_int
|
|
],
|
|
}
|
|
)
|
|
],
|
|
),
|
|
}
|
|
)
|
|
],
|
|
)
|
|
},
|
|
extra=vol.ALLOW_EXTRA,
|
|
)
|
|
|
|
|
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|
"""Set up the platform."""
|
|
hass.data.setdefault(DOMAIN, {})
|
|
|
|
def build_client() -> ProxmoxAPI:
|
|
"""Build the Proxmox client connection."""
|
|
hass.data[PROXMOX_CLIENTS] = {}
|
|
|
|
for entry in config[DOMAIN]:
|
|
host = entry[CONF_HOST]
|
|
port = entry[CONF_PORT]
|
|
user = entry[CONF_USERNAME]
|
|
realm = entry[CONF_REALM]
|
|
password = entry[CONF_PASSWORD]
|
|
verify_ssl = entry[CONF_VERIFY_SSL]
|
|
|
|
hass.data[PROXMOX_CLIENTS][host] = None
|
|
|
|
try:
|
|
# Construct an API client with the given data for the given host
|
|
proxmox_client = ProxmoxClient(
|
|
host, port, user, realm, password, verify_ssl
|
|
)
|
|
proxmox_client.build_client()
|
|
except AuthenticationError:
|
|
_LOGGER.warning(
|
|
"Invalid credentials for proxmox instance %s:%d", host, port
|
|
)
|
|
continue
|
|
except SSLError:
|
|
_LOGGER.error(
|
|
(
|
|
"Unable to verify proxmox server SSL. "
|
|
'Try using "verify_ssl: false" for proxmox instance %s:%d'
|
|
),
|
|
host,
|
|
port,
|
|
)
|
|
continue
|
|
except ConnectTimeout:
|
|
_LOGGER.warning("Connection to host %s timed out during setup", host)
|
|
continue
|
|
except requests.exceptions.ConnectionError:
|
|
_LOGGER.warning("Host %s is not reachable", host)
|
|
continue
|
|
|
|
hass.data[PROXMOX_CLIENTS][host] = proxmox_client
|
|
|
|
await hass.async_add_executor_job(build_client)
|
|
|
|
coordinators: dict[
|
|
str, dict[str, dict[int, DataUpdateCoordinator[dict[str, Any] | None]]]
|
|
] = {}
|
|
hass.data[DOMAIN][COORDINATORS] = coordinators
|
|
|
|
# Create a coordinator for each vm/container
|
|
for host_config in config[DOMAIN]:
|
|
host_name = host_config["host"]
|
|
coordinators[host_name] = {}
|
|
|
|
proxmox_client = hass.data[PROXMOX_CLIENTS][host_name]
|
|
|
|
# Skip invalid hosts
|
|
if proxmox_client is None:
|
|
continue
|
|
|
|
proxmox = proxmox_client.get_api_client()
|
|
|
|
for node_config in host_config["nodes"]:
|
|
node_name = node_config["node"]
|
|
node_coordinators = coordinators[host_name][node_name] = {}
|
|
|
|
for vm_id in node_config["vms"]:
|
|
coordinator = create_coordinator_container_vm(
|
|
hass, proxmox, host_name, node_name, vm_id, TYPE_VM
|
|
)
|
|
|
|
# Fetch initial data
|
|
await coordinator.async_refresh()
|
|
|
|
node_coordinators[vm_id] = coordinator
|
|
|
|
for container_id in node_config["containers"]:
|
|
coordinator = create_coordinator_container_vm(
|
|
hass, proxmox, host_name, node_name, container_id, TYPE_CONTAINER
|
|
)
|
|
|
|
# Fetch initial data
|
|
await coordinator.async_refresh()
|
|
|
|
node_coordinators[container_id] = coordinator
|
|
|
|
for component in PLATFORMS:
|
|
await hass.async_create_task(
|
|
async_load_platform(hass, component, DOMAIN, {"config": config}, config)
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
def create_coordinator_container_vm(
|
|
hass: HomeAssistant,
|
|
proxmox: ProxmoxAPI,
|
|
host_name: str,
|
|
node_name: str,
|
|
vm_id: int,
|
|
vm_type: int,
|
|
) -> DataUpdateCoordinator[dict[str, Any] | None]:
|
|
"""Create and return a DataUpdateCoordinator for a vm/container."""
|
|
|
|
async def async_update_data() -> dict[str, Any] | None:
|
|
"""Call the api and handle the response."""
|
|
|
|
def poll_api() -> dict[str, Any] | None:
|
|
"""Call the api."""
|
|
return call_api_container_vm(proxmox, node_name, vm_id, vm_type)
|
|
|
|
vm_status = await hass.async_add_executor_job(poll_api)
|
|
|
|
if vm_status is None:
|
|
_LOGGER.warning(
|
|
"Vm/Container %s unable to be found in node %s", vm_id, node_name
|
|
)
|
|
return None
|
|
|
|
return parse_api_container_vm(vm_status)
|
|
|
|
return DataUpdateCoordinator(
|
|
hass,
|
|
_LOGGER,
|
|
name=f"proxmox_coordinator_{host_name}_{node_name}_{vm_id}",
|
|
update_method=async_update_data,
|
|
update_interval=timedelta(seconds=UPDATE_INTERVAL),
|
|
)
|