mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 08:17:08 +00:00
Add Proxmox VE integration (#27315)
* Added the Proxmox VE integration * Fixed code as described in PR #27315 * Fixed small linting error * Fix code as described in PR #27315 code review * Improve code as described in PR #27315
This commit is contained in:
parent
c35f9ee35f
commit
829e0a7c42
@ -526,6 +526,7 @@ omit =
|
|||||||
homeassistant/components/proliphix/climate.py
|
homeassistant/components/proliphix/climate.py
|
||||||
homeassistant/components/prometheus/*
|
homeassistant/components/prometheus/*
|
||||||
homeassistant/components/prowl/notify.py
|
homeassistant/components/prowl/notify.py
|
||||||
|
homeassistant/components/proxmoxve/*
|
||||||
homeassistant/components/proxy/camera.py
|
homeassistant/components/proxy/camera.py
|
||||||
homeassistant/components/ptvsd/*
|
homeassistant/components/ptvsd/*
|
||||||
homeassistant/components/pulseaudio_loopback/switch.py
|
homeassistant/components/pulseaudio_loopback/switch.py
|
||||||
|
@ -239,6 +239,7 @@ homeassistant/components/plant/* @ChristianKuehnel
|
|||||||
homeassistant/components/plex/* @jjlawren
|
homeassistant/components/plex/* @jjlawren
|
||||||
homeassistant/components/plugwise/* @laetificat @CoMPaTech @bouwew
|
homeassistant/components/plugwise/* @laetificat @CoMPaTech @bouwew
|
||||||
homeassistant/components/point/* @fredrike
|
homeassistant/components/point/* @fredrike
|
||||||
|
homeassistant/components/proxmoxve/* @k4ds3
|
||||||
homeassistant/components/ps4/* @ktnrg45
|
homeassistant/components/ps4/* @ktnrg45
|
||||||
homeassistant/components/ptvsd/* @swamp-ig
|
homeassistant/components/ptvsd/* @swamp-ig
|
||||||
homeassistant/components/push/* @dgomes
|
homeassistant/components/push/* @dgomes
|
||||||
|
154
homeassistant/components/proxmoxve/__init__.py
Normal file
154
homeassistant/components/proxmoxve/__init__.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
"""Support for Proxmox VE."""
|
||||||
|
from enum import Enum
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from proxmoxer import ProxmoxAPI
|
||||||
|
from proxmoxer.backends.https import AuthenticationError
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_USERNAME,
|
||||||
|
CONF_VERIFY_SSL,
|
||||||
|
)
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOMAIN = "proxmoxve"
|
||||||
|
PROXMOX_CLIENTS = "proxmox_clients"
|
||||||
|
CONF_REALM = "realm"
|
||||||
|
CONF_NODE = "node"
|
||||||
|
CONF_NODES = "nodes"
|
||||||
|
CONF_VMS = "vms"
|
||||||
|
CONF_CONTAINERS = "containers"
|
||||||
|
|
||||||
|
DEFAULT_PORT = 8006
|
||||||
|
DEFAULT_REALM = "pam"
|
||||||
|
DEFAULT_VERIFY_SSL = True
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(hass, config):
|
||||||
|
"""Set up the component."""
|
||||||
|
|
||||||
|
# Create API Clients for later use
|
||||||
|
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]
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
hass.data[PROXMOX_CLIENTS][f"{host}:{port}"] = proxmox_client
|
||||||
|
|
||||||
|
if hass.data[PROXMOX_CLIENTS]:
|
||||||
|
hass.helpers.discovery.load_platform(
|
||||||
|
"binary_sensor", DOMAIN, {"entries": config[DOMAIN]}, config
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class ProxmoxItemType(Enum):
|
||||||
|
"""Represents the different types of machines in Proxmox."""
|
||||||
|
|
||||||
|
qemu = 0
|
||||||
|
lxc = 1
|
||||||
|
|
||||||
|
|
||||||
|
class ProxmoxClient:
|
||||||
|
"""A wrapper for the proxmoxer ProxmoxAPI client."""
|
||||||
|
|
||||||
|
def __init__(self, host, port, user, realm, password, verify_ssl):
|
||||||
|
"""Initialize the ProxmoxClient."""
|
||||||
|
|
||||||
|
self._host = host
|
||||||
|
self._port = port
|
||||||
|
self._user = user
|
||||||
|
self._realm = realm
|
||||||
|
self._password = password
|
||||||
|
self._verify_ssl = verify_ssl
|
||||||
|
|
||||||
|
self._proxmox = None
|
||||||
|
self._connection_start_time = None
|
||||||
|
|
||||||
|
def build_client(self):
|
||||||
|
"""Construct the ProxmoxAPI client."""
|
||||||
|
|
||||||
|
self._proxmox = ProxmoxAPI(
|
||||||
|
self._host,
|
||||||
|
port=self._port,
|
||||||
|
user=f"{self._user}@{self._realm}",
|
||||||
|
password=self._password,
|
||||||
|
verify_ssl=self._verify_ssl,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._connection_start_time = time.time()
|
||||||
|
|
||||||
|
def get_api_client(self):
|
||||||
|
"""Return the ProxmoxAPI client and rebuild it if necessary."""
|
||||||
|
|
||||||
|
connection_age = time.time() - self._connection_start_time
|
||||||
|
|
||||||
|
# Workaround for the Proxmoxer bug where the connection stops working after some time
|
||||||
|
if connection_age > 30 * 60:
|
||||||
|
self.build_client()
|
||||||
|
|
||||||
|
return self._proxmox
|
112
homeassistant/components/proxmoxve/binary_sensor.py
Normal file
112
homeassistant/components/proxmoxve/binary_sensor.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
"""Binary sensor to read Proxmox VE data."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
|
from homeassistant.const import ATTR_ATTRIBUTION, CONF_HOST, CONF_PORT
|
||||||
|
|
||||||
|
from . import CONF_CONTAINERS, CONF_NODES, CONF_VMS, PROXMOX_CLIENTS, ProxmoxItemType
|
||||||
|
|
||||||
|
ATTRIBUTION = "Data provided by Proxmox VE"
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
"""Set up the sensor platform."""
|
||||||
|
|
||||||
|
sensors = []
|
||||||
|
|
||||||
|
for entry in discovery_info["entries"]:
|
||||||
|
port = entry[CONF_PORT]
|
||||||
|
|
||||||
|
for node in entry[CONF_NODES]:
|
||||||
|
for virtual_machine in node[CONF_VMS]:
|
||||||
|
sensors.append(
|
||||||
|
ProxmoxBinarySensor(
|
||||||
|
hass.data[PROXMOX_CLIENTS][f"{entry[CONF_HOST]}:{port}"],
|
||||||
|
node["node"],
|
||||||
|
ProxmoxItemType.qemu,
|
||||||
|
virtual_machine,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for container in node[CONF_CONTAINERS]:
|
||||||
|
sensors.append(
|
||||||
|
ProxmoxBinarySensor(
|
||||||
|
hass.data[PROXMOX_CLIENTS][f"{entry[CONF_HOST]}:{port}"],
|
||||||
|
node["node"],
|
||||||
|
ProxmoxItemType.lxc,
|
||||||
|
container,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
add_entities(sensors, True)
|
||||||
|
|
||||||
|
|
||||||
|
class ProxmoxBinarySensor(BinarySensorDevice):
|
||||||
|
"""A binary sensor for reading Proxmox VE data."""
|
||||||
|
|
||||||
|
def __init__(self, proxmox_client, item_node, item_type, item_id):
|
||||||
|
"""Initialize the binary sensor."""
|
||||||
|
self._proxmox_client = proxmox_client
|
||||||
|
self._item_node = item_node
|
||||||
|
self._item_type = item_type
|
||||||
|
self._item_id = item_id
|
||||||
|
|
||||||
|
self._vmname = None
|
||||||
|
self._name = None
|
||||||
|
|
||||||
|
self._state = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the entity."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self):
|
||||||
|
"""Return true if VM/container is running."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return device attributes of the entity."""
|
||||||
|
return {
|
||||||
|
"node": self._item_node,
|
||||||
|
"vmid": self._item_id,
|
||||||
|
"vmname": self._vmname,
|
||||||
|
"type": self._item_type.name,
|
||||||
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||||
|
}
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Check if the VM/Container is running."""
|
||||||
|
item = self.poll_item()
|
||||||
|
|
||||||
|
if item is None:
|
||||||
|
_LOGGER.warning("Failed to poll VM/container %s", self._item_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._state = item["status"] == "running"
|
||||||
|
|
||||||
|
def poll_item(self):
|
||||||
|
"""Find the VM/Container with the set item_id."""
|
||||||
|
items = (
|
||||||
|
self._proxmox_client.get_api_client()
|
||||||
|
.nodes(self._item_node)
|
||||||
|
.get(self._item_type.name)
|
||||||
|
)
|
||||||
|
item = next(
|
||||||
|
(item for item in items if item["vmid"] == str(self._item_id)), None
|
||||||
|
)
|
||||||
|
|
||||||
|
if item is None:
|
||||||
|
_LOGGER.warning("Couldn't find VM/Container with the ID %s", self._item_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self._vmname is None:
|
||||||
|
self._vmname = item["name"]
|
||||||
|
|
||||||
|
if self._name is None:
|
||||||
|
self._name = f"{self._item_node} {self._vmname} running"
|
||||||
|
|
||||||
|
return item
|
8
homeassistant/components/proxmoxve/manifest.json
Normal file
8
homeassistant/components/proxmoxve/manifest.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"domain": "proxmoxve",
|
||||||
|
"name": "Proxmox VE",
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/proxmoxve",
|
||||||
|
"dependencies": [],
|
||||||
|
"codeowners": ["@k4ds3"],
|
||||||
|
"requirements": ["proxmoxer==1.0.3"]
|
||||||
|
}
|
@ -1023,6 +1023,9 @@ prometheus_client==0.7.1
|
|||||||
# homeassistant.components.tensorflow
|
# homeassistant.components.tensorflow
|
||||||
protobuf==3.6.1
|
protobuf==3.6.1
|
||||||
|
|
||||||
|
# homeassistant.components.proxmoxve
|
||||||
|
proxmoxer==1.0.3
|
||||||
|
|
||||||
# homeassistant.components.systemmonitor
|
# homeassistant.components.systemmonitor
|
||||||
psutil==5.6.5
|
psutil==5.6.5
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user