mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Create DataUpdateCoordinator for each proxmoxve vm/container (#45171)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
f538ea1827
commit
eac1041277
@ -367,7 +367,7 @@ homeassistant/components/powerwall/* @bdraco @jrester
|
|||||||
homeassistant/components/profiler/* @bdraco
|
homeassistant/components/profiler/* @bdraco
|
||||||
homeassistant/components/progettihwsw/* @ardaseremet
|
homeassistant/components/progettihwsw/* @ardaseremet
|
||||||
homeassistant/components/prometheus/* @knyar
|
homeassistant/components/prometheus/* @knyar
|
||||||
homeassistant/components/proxmoxve/* @k4ds3 @jhollowe
|
homeassistant/components/proxmoxve/* @k4ds3 @jhollowe @Corbeno
|
||||||
homeassistant/components/ps4/* @ktnrg45
|
homeassistant/components/ps4/* @ktnrg45
|
||||||
homeassistant/components/push/* @dgomes
|
homeassistant/components/push/* @dgomes
|
||||||
homeassistant/components/pvoutput/* @fabaff
|
homeassistant/components/pvoutput/* @fabaff
|
||||||
|
@ -5,7 +5,8 @@ import logging
|
|||||||
from proxmoxer import ProxmoxAPI
|
from proxmoxer import ProxmoxAPI
|
||||||
from proxmoxer.backends.https import AuthenticationError
|
from proxmoxer.backends.https import AuthenticationError
|
||||||
from proxmoxer.core import ResourceException
|
from proxmoxer.core import ResourceException
|
||||||
from requests.exceptions import SSLError
|
import requests.exceptions
|
||||||
|
from requests.exceptions import ConnectTimeout, SSLError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -31,7 +32,7 @@ CONF_NODES = "nodes"
|
|||||||
CONF_VMS = "vms"
|
CONF_VMS = "vms"
|
||||||
CONF_CONTAINERS = "containers"
|
CONF_CONTAINERS = "containers"
|
||||||
|
|
||||||
COORDINATOR = "coordinator"
|
COORDINATORS = "coordinators"
|
||||||
API_DATA = "api_data"
|
API_DATA = "api_data"
|
||||||
|
|
||||||
DEFAULT_PORT = 8006
|
DEFAULT_PORT = 8006
|
||||||
@ -90,6 +91,7 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
|||||||
def build_client() -> ProxmoxAPI:
|
def build_client() -> ProxmoxAPI:
|
||||||
"""Build the Proxmox client connection."""
|
"""Build the Proxmox client connection."""
|
||||||
hass.data[PROXMOX_CLIENTS] = {}
|
hass.data[PROXMOX_CLIENTS] = {}
|
||||||
|
|
||||||
for entry in config[DOMAIN]:
|
for entry in config[DOMAIN]:
|
||||||
host = entry[CONF_HOST]
|
host = entry[CONF_HOST]
|
||||||
port = entry[CONF_PORT]
|
port = entry[CONF_PORT]
|
||||||
@ -98,6 +100,8 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
|||||||
password = entry[CONF_PASSWORD]
|
password = entry[CONF_PASSWORD]
|
||||||
verify_ssl = entry[CONF_VERIFY_SSL]
|
verify_ssl = entry[CONF_VERIFY_SSL]
|
||||||
|
|
||||||
|
hass.data[PROXMOX_CLIENTS][host] = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Construct an API client with the given data for the given host
|
# Construct an API client with the given data for the given host
|
||||||
proxmox_client = ProxmoxClient(
|
proxmox_client = ProxmoxClient(
|
||||||
@ -111,92 +115,101 @@ async def async_setup(hass: HomeAssistant, config: dict):
|
|||||||
continue
|
continue
|
||||||
except SSLError:
|
except SSLError:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'Unable to verify proxmox server SSL. Try using "verify_ssl: false"'
|
"Unable to verify proxmox server SSL. "
|
||||||
|
'Try using "verify_ssl: false" for proxmox instance %s:%d',
|
||||||
|
host,
|
||||||
|
port,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
except ConnectTimeout:
|
||||||
|
_LOGGER.warning("Connection to host %s timed out during setup", host)
|
||||||
|
continue
|
||||||
|
|
||||||
return proxmox_client
|
hass.data[PROXMOX_CLIENTS][host] = proxmox_client
|
||||||
|
|
||||||
proxmox_client = await hass.async_add_executor_job(build_client)
|
await hass.async_add_executor_job(build_client)
|
||||||
|
|
||||||
async def async_update_data() -> dict:
|
coordinators = hass.data[DOMAIN][COORDINATORS] = {}
|
||||||
"""Fetch data from API endpoint."""
|
|
||||||
|
# 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()
|
proxmox = proxmox_client.get_api_client()
|
||||||
|
|
||||||
def poll_api() -> dict:
|
for node_config in host_config["nodes"]:
|
||||||
data = {}
|
node_name = node_config["node"]
|
||||||
|
node_coordinators = coordinators[host_name][node_name] = {}
|
||||||
|
|
||||||
for host_config in config[DOMAIN]:
|
for vm_id in node_config["vms"]:
|
||||||
host_name = host_config["host"]
|
coordinator = create_coordinator_container_vm(
|
||||||
|
hass, proxmox, host_name, node_name, vm_id, TYPE_VM
|
||||||
|
)
|
||||||
|
|
||||||
data[host_name] = {}
|
# Fetch initial data
|
||||||
|
await coordinator.async_refresh()
|
||||||
|
|
||||||
for node_config in host_config["nodes"]:
|
node_coordinators[vm_id] = coordinator
|
||||||
node_name = node_config["node"]
|
|
||||||
data[host_name][node_name] = {}
|
|
||||||
|
|
||||||
for vm_id in node_config["vms"]:
|
for container_id in node_config["containers"]:
|
||||||
data[host_name][node_name][vm_id] = {}
|
coordinator = create_coordinator_container_vm(
|
||||||
|
hass, proxmox, host_name, node_name, container_id, TYPE_CONTAINER
|
||||||
|
)
|
||||||
|
|
||||||
vm_status = call_api_container_vm(
|
# Fetch initial data
|
||||||
proxmox, node_name, vm_id, TYPE_VM
|
await coordinator.async_refresh()
|
||||||
)
|
|
||||||
|
|
||||||
if vm_status is None:
|
node_coordinators[container_id] = coordinator
|
||||||
_LOGGER.warning("Vm/Container %s unable to be found", vm_id)
|
|
||||||
data[host_name][node_name][vm_id] = None
|
|
||||||
continue
|
|
||||||
|
|
||||||
data[host_name][node_name][vm_id] = parse_api_container_vm(
|
for component in PLATFORMS:
|
||||||
vm_status
|
|
||||||
)
|
|
||||||
|
|
||||||
for container_id in node_config["containers"]:
|
|
||||||
data[host_name][node_name][container_id] = {}
|
|
||||||
|
|
||||||
container_status = call_api_container_vm(
|
|
||||||
proxmox, node_name, container_id, TYPE_CONTAINER
|
|
||||||
)
|
|
||||||
|
|
||||||
if container_status is None:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Vm/Container %s unable to be found", container_id
|
|
||||||
)
|
|
||||||
data[host_name][node_name][container_id] = None
|
|
||||||
continue
|
|
||||||
|
|
||||||
data[host_name][node_name][
|
|
||||||
container_id
|
|
||||||
] = parse_api_container_vm(container_status)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
return await hass.async_add_executor_job(poll_api)
|
|
||||||
|
|
||||||
coordinator = DataUpdateCoordinator(
|
|
||||||
hass,
|
|
||||||
_LOGGER,
|
|
||||||
name="proxmox_coordinator",
|
|
||||||
update_method=async_update_data,
|
|
||||||
update_interval=timedelta(seconds=UPDATE_INTERVAL),
|
|
||||||
)
|
|
||||||
|
|
||||||
hass.data[DOMAIN][COORDINATOR] = coordinator
|
|
||||||
|
|
||||||
# Fetch initial data
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
|
||||||
|
|
||||||
for platform in PLATFORMS:
|
|
||||||
await hass.async_create_task(
|
await hass.async_create_task(
|
||||||
hass.helpers.discovery.async_load_platform(
|
hass.helpers.discovery.async_load_platform(
|
||||||
platform, DOMAIN, {"config": config}, config
|
component, DOMAIN, {"config": config}, config
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def create_coordinator_container_vm(
|
||||||
|
hass, proxmox, host_name, node_name, vm_id, vm_type
|
||||||
|
):
|
||||||
|
"""Create and return a DataUpdateCoordinator for a vm/container."""
|
||||||
|
|
||||||
|
async def async_update_data():
|
||||||
|
"""Call the api and handle the response."""
|
||||||
|
|
||||||
|
def poll_api():
|
||||||
|
"""Call the api."""
|
||||||
|
vm_status = call_api_container_vm(proxmox, node_name, vm_id, vm_type)
|
||||||
|
return vm_status
|
||||||
|
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_api_container_vm(status):
|
def parse_api_container_vm(status):
|
||||||
"""Get the container or vm api data and return it formatted in a dictionary.
|
"""Get the container or vm api data and return it formatted in a dictionary.
|
||||||
|
|
||||||
@ -216,7 +229,7 @@ def call_api_container_vm(proxmox, node_name, vm_id, machine_type):
|
|||||||
status = proxmox.nodes(node_name).qemu(vm_id).status.current.get()
|
status = proxmox.nodes(node_name).qemu(vm_id).status.current.get()
|
||||||
elif machine_type == TYPE_CONTAINER:
|
elif machine_type == TYPE_CONTAINER:
|
||||||
status = proxmox.nodes(node_name).lxc(vm_id).status.current.get()
|
status = proxmox.nodes(node_name).lxc(vm_id).status.current.get()
|
||||||
except ResourceException:
|
except (ResourceException, requests.exceptions.ConnectionError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
"""Binary sensor to read Proxmox VE data."""
|
"""Binary sensor to read Proxmox VE data."""
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON
|
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from . import COORDINATOR, DOMAIN, ProxmoxEntity
|
from . import COORDINATORS, DOMAIN, PROXMOX_CLIENTS, ProxmoxEntity
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
@ -10,41 +11,45 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
if discovery_info is None:
|
if discovery_info is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
coordinator = hass.data[DOMAIN][COORDINATOR]
|
|
||||||
|
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
for host_config in discovery_info["config"][DOMAIN]:
|
for host_config in discovery_info["config"][DOMAIN]:
|
||||||
host_name = host_config["host"]
|
host_name = host_config["host"]
|
||||||
|
host_name_coordinators = hass.data[DOMAIN][COORDINATORS][host_name]
|
||||||
|
|
||||||
|
if hass.data[PROXMOX_CLIENTS][host_name] is None:
|
||||||
|
continue
|
||||||
|
|
||||||
for node_config in host_config["nodes"]:
|
for node_config in host_config["nodes"]:
|
||||||
node_name = node_config["node"]
|
node_name = node_config["node"]
|
||||||
|
|
||||||
for vm_id in node_config["vms"]:
|
for vm_id in node_config["vms"]:
|
||||||
coordinator_data = coordinator.data[host_name][node_name][vm_id]
|
coordinator = host_name_coordinators[node_name][vm_id]
|
||||||
|
coordinator_data = coordinator.data
|
||||||
|
|
||||||
# unfound vm case
|
# unfound vm case
|
||||||
if coordinator_data is None:
|
if coordinator_data is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
vm_name = coordinator_data["name"]
|
vm_name = coordinator_data["name"]
|
||||||
vm_status = create_binary_sensor(
|
vm_sensor = create_binary_sensor(
|
||||||
coordinator, host_name, node_name, vm_id, vm_name
|
coordinator, host_name, node_name, vm_id, vm_name
|
||||||
)
|
)
|
||||||
sensors.append(vm_status)
|
sensors.append(vm_sensor)
|
||||||
|
|
||||||
for container_id in node_config["containers"]:
|
for container_id in node_config["containers"]:
|
||||||
coordinator_data = coordinator.data[host_name][node_name][container_id]
|
coordinator = host_name_coordinators[node_name][container_id]
|
||||||
|
coordinator_data = coordinator.data
|
||||||
|
|
||||||
# unfound container case
|
# unfound container case
|
||||||
if coordinator_data is None:
|
if coordinator_data is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
container_name = coordinator_data["name"]
|
container_name = coordinator_data["name"]
|
||||||
container_status = create_binary_sensor(
|
container_sensor = create_binary_sensor(
|
||||||
coordinator, host_name, node_name, container_id, container_name
|
coordinator, host_name, node_name, container_id, container_name
|
||||||
)
|
)
|
||||||
sensors.append(container_status)
|
sensors.append(container_sensor)
|
||||||
|
|
||||||
add_entities(sensors)
|
add_entities(sensors)
|
||||||
|
|
||||||
@ -62,7 +67,7 @@ def create_binary_sensor(coordinator, host_name, node_name, vm_id, name):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProxmoxBinarySensor(ProxmoxEntity):
|
class ProxmoxBinarySensor(ProxmoxEntity, BinarySensorEntity):
|
||||||
"""A binary sensor for reading Proxmox VE data."""
|
"""A binary sensor for reading Proxmox VE data."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -80,12 +85,18 @@ class ProxmoxBinarySensor(ProxmoxEntity):
|
|||||||
coordinator, unique_id, name, icon, host_name, node_name, vm_id
|
coordinator, unique_id, name, icon, host_name, node_name, vm_id
|
||||||
)
|
)
|
||||||
|
|
||||||
self._state = None
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return the state of the binary sensor."""
|
||||||
|
data = self.coordinator.data
|
||||||
|
|
||||||
|
if data is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return data["status"] == "running"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def available(self):
|
||||||
"""Return the state of the binary sensor."""
|
"""Return sensor availability."""
|
||||||
data = self.coordinator.data[self._host_name][self._node_name][self._vm_id]
|
|
||||||
if data["status"] == "running":
|
return super().available and self.coordinator.data is not None
|
||||||
return STATE_ON
|
|
||||||
return STATE_OFF
|
|
||||||
|
@ -2,6 +2,6 @@
|
|||||||
"domain": "proxmoxve",
|
"domain": "proxmoxve",
|
||||||
"name": "Proxmox VE",
|
"name": "Proxmox VE",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/proxmoxve",
|
"documentation": "https://www.home-assistant.io/integrations/proxmoxve",
|
||||||
"codeowners": ["@k4ds3", "@jhollowe"],
|
"codeowners": ["@k4ds3", "@jhollowe", "@Corbeno"],
|
||||||
"requirements": ["proxmoxer==1.1.1"]
|
"requirements": ["proxmoxer==1.1.1"]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user