Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: retry setup when iDRAC is unreachable during HA boot #21

Merged
merged 4 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions custom_components/idrac_power/__init__.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
}

hass.async_create_task(
hass.config_entries.async_forward_entry_setup(
entry, Platform.SENSOR
)
)
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(
entry, Platform.BINARY_SENSOR
)
)

hass.async_create_task(
hass.config_entries.async_forward_entry_setup(
entry, Platform.BUTTON
hass.config_entries.async_forward_entry_setups(
entry, [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.BUTTON]
)
)
return True
38 changes: 21 additions & 17 deletions custom_components/idrac_power/binary_sensor.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.exceptions import PlatformNotReady

from .const import (DOMAIN, DATA_IDRAC_REST_CLIENT, JSON_MODEL, JSON_MANUFACTURER, JSON_SERIAL_NUMBER,
DATA_IDRAC_FIRMWARE, DATA_IDRAC_INFO)
from .idrac_rest import IdracRest
from .idrac_rest import IdracRest, CannotConnect, RedfishConfig

_LOGGER = logging.getLogger(__name__)

Expand All @@ -25,22 +26,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e
"""Add iDrac power sensor entry"""
rest_client = hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_REST_CLIENT]

if DATA_IDRAC_INFO not in hass.data[DOMAIN][entry.entry_id]:
info = await hass.async_add_executor_job(target=rest_client.get_device_info)
if not info:
_LOGGER.error(f"Could not set up: couldn't reach device.")
return

hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO] = info
else:
info = hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO]

firmware_version = await hass.async_add_executor_job(target=rest_client.get_firmware_version)
if not firmware_version:
if DATA_IDRAC_FIRMWARE in hass.data[DOMAIN][entry.entry_id]:
firmware_version = hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_FIRMWARE]
else:
hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO] = firmware_version
try:
if DATA_IDRAC_INFO not in hass.data[DOMAIN][entry.entry_id]:
info = await hass.async_add_executor_job(target=rest_client.get_device_info)
if not info:
raise PlatformNotReady(f"Could not set up: device didn't return anything.")

hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO] = info
else:
info = hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO]

firmware_version = await hass.async_add_executor_job(target=rest_client.get_firmware_version)
if not firmware_version:
if DATA_IDRAC_FIRMWARE in hass.data[DOMAIN][entry.entry_id]:
firmware_version = hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_FIRMWARE]
else:
hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO] = firmware_version
except (CannotConnect, RedfishConfig) as e:
raise PlatformNotReady(str(e)) from e


model = info[JSON_MODEL]
name = model
Expand Down
37 changes: 20 additions & 17 deletions custom_components/idrac_power/button.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.exceptions import PlatformNotReady

from .const import (DOMAIN, DATA_IDRAC_REST_CLIENT, JSON_MODEL, JSON_MANUFACTURER, JSON_SERIAL_NUMBER, DATA_IDRAC_INFO,
DATA_IDRAC_FIRMWARE)
from .idrac_rest import IdracRest
from .idrac_rest import IdracRest, CannotConnect, RedfishConfig

_LOGGER = logging.getLogger(__name__)

Expand All @@ -19,22 +20,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e
"""Add iDrac power sensor entry"""
rest_client = hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_REST_CLIENT]

if DATA_IDRAC_INFO not in hass.data[DOMAIN][entry.entry_id]:
info = await hass.async_add_executor_job(target=rest_client.get_device_info)
if not info:
_LOGGER.error(f"Could not set up: couldn't reach device.")
return

hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO] = info
else:
info = hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO]

firmware_version = await hass.async_add_executor_job(target=rest_client.get_firmware_version)
if not firmware_version:
if DATA_IDRAC_FIRMWARE in hass.data[DOMAIN][entry.entry_id]:
firmware_version = hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_FIRMWARE]
else:
hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO] = firmware_version
try:
if DATA_IDRAC_INFO not in hass.data[DOMAIN][entry.entry_id]:
info = await hass.async_add_executor_job(target=rest_client.get_device_info)
if not info:
raise PlatformNotReady(f"Could not set up: device didn't return anything.")

hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO] = info
else:
info = hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO]

firmware_version = await hass.async_add_executor_job(target=rest_client.get_firmware_version)
if not firmware_version:
if DATA_IDRAC_FIRMWARE in hass.data[DOMAIN][entry.entry_id]:
firmware_version = hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_FIRMWARE]
else:
hass.data[DOMAIN][entry.entry_id][DATA_IDRAC_INFO] = firmware_version
except (CannotConnect, RedfishConfig) as e:
raise PlatformNotReady(str(e)) from e

model = info[JSON_MODEL]
name = model
Expand Down
Empty file modified custom_components/idrac_power/config_flow.py
100644 → 100755
Empty file.
Empty file modified custom_components/idrac_power/const.py
100644 → 100755
Empty file.
42 changes: 22 additions & 20 deletions custom_components/idrac_power/idrac_rest.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import urllib3
from homeassistant.exceptions import HomeAssistantError
from requests import Response
from requests.exceptions import RequestException, JSONDecodeError

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

Expand All @@ -28,7 +29,11 @@ def handle_error(result):
raise InvalidAuth()

if result.status_code == 404:
error = result.json()['error']
try:
error = result.json()['error']
except JSONDecodeError:
# start of iDRAC can cause 404 error, ignore it
raise CannotConnect(f"iDRAC responed with 404, but no JSON present:\n{result.text}")
if error['code'] == 'Base.1.0.GeneralError' and 'RedFish attribute is disabled' in \
error['@Message.ExtendedInfo'][0]['Message']:
raise RedfishConfig()
Expand All @@ -54,9 +59,8 @@ def __init__(self, host, username, password, interval):
def get_device_info(self) -> dict | None:
try:
result = self.get_path(drac_chassis_path)
except ConnectionError:
_LOGGER.warning(f"Could not get device info from {self.host}")
return None
except RequestException:
raise CannotConnect(f"Cannot connect to {self.host}")

handle_error(result)

Expand All @@ -71,25 +75,23 @@ def get_device_info(self) -> dict | None:
def get_firmware_version(self) -> str | None:
try:
result = self.get_path(drac_managers_path)
except ConnectionError:
_LOGGER.warning(f"Could not get firmware version of {self.host}")
return None
except RequestException:
raise CannotConnect(f"Could not get firmware version of {self.host}")

handle_error(result)

manager_results = result.json()
return manager_results[JSON_FIRMWARE_VERSION]

def get_path(self, path):
return requests.get(protocol + self.host + path, auth=self.auth, verify=False)
return requests.get(protocol + self.host + path, auth=self.auth, verify=False, timeout=300)

def power_on(self) -> Response | None:
try:
result = requests.post(protocol + self.host + drac_powerON_path, auth=self.auth, verify=False,
json={"ResetType": "On"})
except ConnectionError as e:
_LOGGER.error(f"Could power on {self.host}: {e}")
return None
json={"ResetType": "On"}, timeout=300)
except RequestException as e:
raise CannotConnect(f"Could not power on {self.host}: {e}")

json = result.json()
if result.status_code == 401:
Expand All @@ -101,7 +103,7 @@ def power_on(self) -> Response | None:
error['@Message.ExtendedInfo'][0]['Message']:
raise RedfishConfig()
if "error" in json:
_LOGGER.error("Idrac power on failed: %s", json["error"]["@Message.ExtendedInfo"][0]["Message"])
_LOGGER.error("iDRAC power on failed: %s", json["error"]["@Message.ExtendedInfo"][0]["Message"])

return result

Expand All @@ -120,8 +122,8 @@ def update_thermals(self) -> dict:
handle_error(req)
new_thermals = req.json()

except ConnectionError as e:
_LOGGER.warning(f"Couldn't update {self.host} thermals: {e}")
except (RequestException, RedfishConfig, CannotConnect) as e:
_LOGGER.debug(f"Couldn't update {self.host} thermals: {e}")
new_thermals = None

if new_thermals != self.thermal_values:
Expand All @@ -141,9 +143,9 @@ def update_status(self):
except:
new_status = None

except ConnectionError as e:
_LOGGER.warning(f"Couldn't update {self.host} status: {e}")
new_status = False
except (RequestException, RedfishConfig, CannotConnect) as e:
_LOGGER.debug(f"Couldn't update {self.host} status: {e}")
new_status = None

if new_status != self.status:
self.status = new_status
Expand All @@ -155,8 +157,8 @@ def update_power_usage(self):
result = self.get_path(drac_powercontrol_path)
handle_error(result)
power_values = result.json()
except ConnectionError as e:
_LOGGER.warning(f"Couldn't update {self.host} thermals: {e}")
except (RequestException, RedfishConfig, CannotConnect) as e:
_LOGGER.debug(f"Couldn't update {self.host} power usage: {e}")
for callback in self.callback_power_usage:
callback(None)
return
Expand Down
Empty file modified custom_components/idrac_power/manifest.json
100644 → 100755
Empty file.
Loading
Loading