Fix version conflict

This commit is contained in:
Pascal Vizeli 2018-01-07 18:10:03 +01:00
commit 69a2182c04
25 changed files with 276 additions and 37 deletions

39
API.md
View File

@ -86,6 +86,19 @@ Reload addons/version.
Output is the raw docker log. Output is the raw docker log.
- GET `/supervisor/stats`
```json
{
"cpu_percent": 0.0,
"memory_usage": 283123,
"memory_limit": 329392,
"network_tx": 0,
"network_rx": 0,
"blk_read": 0,
"blk_write": 0
}
```
### Security ### Security
- GET `/security/info` - GET `/security/info`
@ -334,6 +347,19 @@ Proxy to real home-assistant instance.
Proxy to real websocket instance. Proxy to real websocket instance.
- GET `/homeassistant/stats`
```json
{
"cpu_percent": 0.0,
"memory_usage": 283123,
"memory_limit": 329392,
"network_tx": 0,
"network_rx": 0,
"blk_read": 0,
"blk_write": 0
}
```
### RESTful for API addons ### RESTful for API addons
- GET `/addons` - GET `/addons`
@ -452,6 +478,19 @@ Only supported for local build addons
Write data to add-on stdin Write data to add-on stdin
- GET `/addons/{addon}/stats`
```json
{
"cpu_percent": 0.0,
"memory_usage": 283123,
"memory_limit": 329392,
"network_tx": 0,
"network_rx": 0,
"blk_read": 0,
"blk_write": 0
}
```
## Host Control ## Host Control
Communicate over UNIX socket with a host daemon. Communicate over UNIX socket with a host daemon.

View File

@ -568,7 +568,7 @@ class Addon(CoreSysAttributes):
last_state = await self.state() last_state = await self.state()
if self.last_version == self.version_installed: if self.last_version == self.version_installed:
_LOGGER.info("No update available for Addon %s", self._id) _LOGGER.warning("No update available for Addon %s", self._id)
return False return False
if not await self.instance.update(self.last_version): if not await self.instance.update(self.last_version):
@ -596,6 +596,14 @@ class Addon(CoreSysAttributes):
""" """
return self.instance.logs() return self.instance.logs()
@check_installed
def stats(self):
"""Return stats of container.
Return a coroutine.
"""
return self.instance.stats()
@check_installed @check_installed
async def rebuild(self): async def rebuild(self):
"""Performe a rebuild of local build addon.""" """Performe a rebuild of local build addon."""

View File

@ -69,6 +69,7 @@ class RestAPI(CoreSysAttributes):
self.webapp.router.add_get('/supervisor/ping', api_supervisor.ping) self.webapp.router.add_get('/supervisor/ping', api_supervisor.ping)
self.webapp.router.add_get('/supervisor/info', api_supervisor.info) self.webapp.router.add_get('/supervisor/info', api_supervisor.info)
self.webapp.router.add_get('/supervisor/stats', api_supervisor.stats)
self.webapp.router.add_post( self.webapp.router.add_post(
'/supervisor/update', api_supervisor.update) '/supervisor/update', api_supervisor.update)
self.webapp.router.add_post( self.webapp.router.add_post(
@ -84,6 +85,7 @@ class RestAPI(CoreSysAttributes):
self.webapp.router.add_get('/homeassistant/info', api_hass.info) self.webapp.router.add_get('/homeassistant/info', api_hass.info)
self.webapp.router.add_get('/homeassistant/logs', api_hass.logs) self.webapp.router.add_get('/homeassistant/logs', api_hass.logs)
self.webapp.router.add_get('/homeassistant/stats', api_hass.stats)
self.webapp.router.add_post('/homeassistant/options', api_hass.options) self.webapp.router.add_post('/homeassistant/options', api_hass.options)
self.webapp.router.add_post('/homeassistant/update', api_hass.update) self.webapp.router.add_post('/homeassistant/update', api_hass.update)
self.webapp.router.add_post('/homeassistant/restart', api_hass.restart) self.webapp.router.add_post('/homeassistant/restart', api_hass.restart)
@ -114,7 +116,6 @@ class RestAPI(CoreSysAttributes):
self.webapp.router.add_get('/addons', api_addons.list) self.webapp.router.add_get('/addons', api_addons.list)
self.webapp.router.add_post('/addons/reload', api_addons.reload) self.webapp.router.add_post('/addons/reload', api_addons.reload)
self.webapp.router.add_get('/addons/{addon}/info', api_addons.info) self.webapp.router.add_get('/addons/{addon}/info', api_addons.info)
self.webapp.router.add_post( self.webapp.router.add_post(
'/addons/{addon}/install', api_addons.install) '/addons/{addon}/install', api_addons.install)
@ -135,6 +136,7 @@ class RestAPI(CoreSysAttributes):
self.webapp.router.add_get( self.webapp.router.add_get(
'/addons/{addon}/changelog', api_addons.changelog) '/addons/{addon}/changelog', api_addons.changelog)
self.webapp.router.add_post('/addons/{addon}/stdin', api_addons.stdin) self.webapp.router.add_post('/addons/{addon}/stdin', api_addons.stdin)
self.webapp.router.add_get('/addons/{addon}/stats', api_addons.stats)
def _register_security(self): def _register_security(self):
"""Register security function.""" """Register security function."""

View File

@ -15,6 +15,8 @@ from ..const import (
ATTR_AUDIO, ATTR_AUDIO_INPUT, ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API, ATTR_AUDIO, ATTR_AUDIO_INPUT, ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API,
ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, BOOT_AUTO, BOOT_MANUAL, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, BOOT_AUTO, BOOT_MANUAL,
ATTR_CHANGELOG, ATTR_HOST_IPC, ATTR_HOST_DBUS, ATTR_LONG_DESCRIPTION, ATTR_CHANGELOG, ATTR_HOST_IPC, ATTR_HOST_DBUS, ATTR_LONG_DESCRIPTION,
ATTR_CPU_PERCENT, ATTR_MEMORY_LIMIT, ATTR_MEMORY_USAGE, ATTR_NETWORK_TX,
ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE,
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT) CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..validate import DOCKER_PORTS from ..validate import DOCKER_PORTS
@ -158,6 +160,25 @@ class APIAddons(CoreSysAttributes):
return True return True
@api_process
async def stats(self, request):
"""Return resource information."""
addon = self._extract_addon(request)
stats = await addon.stats()
if not stats:
raise RuntimeError("No stats available")
return {
ATTR_CPU_PERCENT: stats.cpu_percent,
ATTR_MEMORY_USAGE: stats.memory_usage,
ATTR_MEMORY_LIMIT: stats.memory_limit,
ATTR_NETWORK_RX: stats.network_rx,
ATTR_NETWORK_TX: stats.network_tx,
ATTR_BLK_READ: stats.blk_read,
ATTR_BLK_WRITE: stats.blk_write,
}
@api_process @api_process
def install(self, request): def install(self, request):
"""Install addon.""" """Install addon."""

View File

@ -7,7 +7,9 @@ import voluptuous as vol
from .utils import api_process, api_process_raw, api_validate from .utils import api_process, api_process_raw, api_validate
from ..const import ( from ..const import (
ATTR_VERSION, ATTR_LAST_VERSION, ATTR_IMAGE, ATTR_CUSTOM, ATTR_BOOT, ATTR_VERSION, ATTR_LAST_VERSION, ATTR_IMAGE, ATTR_CUSTOM, ATTR_BOOT,
ATTR_PORT, ATTR_PASSWORD, ATTR_SSL, ATTR_WATCHDOG, CONTENT_TYPE_BINARY) ATTR_PORT, ATTR_PASSWORD, ATTR_SSL, ATTR_WATCHDOG, ATTR_CPU_PERCENT,
ATTR_MEMORY_USAGE, ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX,
ATTR_BLK_READ, ATTR_BLK_WRITE, CONTENT_TYPE_BINARY)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..validate import NETWORK_PORT from ..validate import NETWORK_PORT
@ -76,6 +78,23 @@ class APIHomeAssistant(CoreSysAttributes):
self._homeassistant.save() self._homeassistant.save()
return True return True
@api_process
async def stats(self, request):
"""Return resource information."""
stats = await self._homeassistant.stats()
if not stats:
raise RuntimeError("No stats available")
return {
ATTR_CPU_PERCENT: stats.cpu_percent,
ATTR_MEMORY_USAGE: stats.memory_usage,
ATTR_MEMORY_LIMIT: stats.memory_limit,
ATTR_NETWORK_RX: stats.network_rx,
ATTR_NETWORK_TX: stats.network_tx,
ATTR_BLK_READ: stats.blk_read,
ATTR_BLK_WRITE: stats.blk_write,
}
@api_process @api_process
async def update(self, request): async def update(self, request):
"""Update homeassistant.""" """Update homeassistant."""

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,9 @@ from ..const import (
ATTR_ADDONS, ATTR_VERSION, ATTR_LAST_VERSION, ATTR_BETA_CHANNEL, ATTR_ARCH, ATTR_ADDONS, ATTR_VERSION, ATTR_LAST_VERSION, ATTR_BETA_CHANNEL, ATTR_ARCH,
HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES, ATTR_LOGO, ATTR_REPOSITORY, HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES, ATTR_LOGO, ATTR_REPOSITORY,
ATTR_DESCRIPTON, ATTR_NAME, ATTR_SLUG, ATTR_INSTALLED, ATTR_TIMEZONE, ATTR_DESCRIPTON, ATTR_NAME, ATTR_SLUG, ATTR_INSTALLED, ATTR_TIMEZONE,
ATTR_STATE, ATTR_WAIT_BOOT, CONTENT_TYPE_BINARY) ATTR_STATE, ATTR_WAIT_BOOT, ATTR_CPU_PERCENT, ATTR_MEMORY_USAGE,
ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_BLK_READ,
ATTR_BLK_WRITE, CONTENT_TYPE_BINARY)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..validate import validate_timezone, WAIT_BOOT from ..validate import validate_timezone, WAIT_BOOT
@ -86,6 +88,23 @@ class APISupervisor(CoreSysAttributes):
self._config.save() self._config.save()
return True return True
@api_process
async def stats(self, request):
"""Return resource information."""
stats = await self._supervisor.stats()
if not stats:
raise RuntimeError("No stats available")
return {
ATTR_CPU_PERCENT: stats.cpu_percent,
ATTR_MEMORY_USAGE: stats.memory_usage,
ATTR_MEMORY_LIMIT: stats.memory_limit,
ATTR_NETWORK_RX: stats.network_rx,
ATTR_NETWORK_TX: stats.network_tx,
ATTR_BLK_READ: stats.blk_read,
ATTR_BLK_WRITE: stats.blk_write,
}
@api_process @api_process
async def update(self, request): async def update(self, request):
"""Update supervisor OS.""" """Update supervisor OS."""

View File

@ -2,7 +2,7 @@
from pathlib import Path from pathlib import Path
from ipaddress import ip_network from ipaddress import ip_network
HASSIO_VERSION = '0.79' HASSIO_VERSION = '0.80'
URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/' URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/'
'hassio/{}/version.json') 'hassio/{}/version.json')
@ -128,6 +128,13 @@ ATTR_SQUASH = 'squash'
ATTR_GPIO = 'gpio' ATTR_GPIO = 'gpio'
ATTR_LEGACY = 'legacy' ATTR_LEGACY = 'legacy'
ATTR_ADDONS_CUSTOM_LIST = 'addons_custom_list' ATTR_ADDONS_CUSTOM_LIST = 'addons_custom_list'
ATTR_CPU_PERCENT = 'cpu_percent'
ATTR_NETWORK_RX = 'network_rx'
ATTR_NETWORK_TX = 'network_tx'
ATTR_MEMORY_LIMIT = 'memory_limit'
ATTR_MEMORY_USAGE = 'memory_usage'
ATTR_BLK_READ = 'blk_read'
ATTR_BLK_WRITE = 'blk_write'
STARTUP_INITIALIZE = 'initialize' STARTUP_INITIALIZE = 'initialize'
STARTUP_SYSTEM = 'system' STARTUP_SYSTEM = 'system'

View File

@ -4,10 +4,10 @@ import aiohttp
from .config import CoreConfig from .config import CoreConfig
from .docker import DockerAPI from .docker import DockerAPI
from .dns import DNSForward from .misc.dns import DNSForward
from .hardware import Hardware from .misc.hardware import Hardware
from .host_control import HostControl from .misc.host_control import HostControl
from .scheduler import Scheduler from .misc.scheduler import Scheduler
class CoreSys(object): class CoreSys(object):

View File

@ -6,6 +6,7 @@ import logging
import docker import docker
from .utils import docker_process from .utils import docker_process
from .stats import DockerStats
from ..const import LABEL_VERSION, LABEL_ARCH from ..const import LABEL_VERSION, LABEL_ARCH
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
@ -325,3 +326,24 @@ class DockerInterface(CoreSysAttributes):
Need run inside executor. Need run inside executor.
""" """
raise NotImplementedError() raise NotImplementedError()
def stats(self):
"""Read and return stats from container."""
return self._loop.run_in_executor(None, self._stats)
def _stats(self):
"""Create a temporary container and run command.
Need run inside executor.
"""
try:
container = self._docker.containers.get(self.name)
except docker.errors.DockerException:
return None
try:
stats = container.stats(stream=False)
return DockerStats(stats)
except docker.errors.DockerException as err:
_LOGGER.error("Can't read stats from %s: %s", self.name, err)
return None

90
hassio/docker/stats.py Normal file
View File

@ -0,0 +1,90 @@
"""Calc & represent docker stats data."""
from contextlib import suppress
class DockerStats(object):
"""Hold stats data from container inside."""
def __init__(self, stats):
"""Initialize docker stats."""
self._cpu = 0.0
self._network_rx = 0
self._network_tx = 0
self._blk_read = 0
self._blk_write = 0
try:
self._memory_usage = stats['memory_stats']['usage']
self._memory_limit = stats['memory_stats']['limit']
except KeyError:
self._memory_usage = 0
self._memory_limit = 0
with suppress(KeyError):
self._calc_cpu_percent(stats)
with suppress(KeyError):
self._calc_network(stats['networks'])
with suppress(KeyError):
self._calc_block_io(stats['blkio_stats'])
def _calc_cpu_percent(self, stats):
"""Calculate CPU percent."""
cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - \
stats['precpu_stats']['cpu_usage']['total_usage']
system_delta = stats['cpu_stats']['system_cpu_usage'] - \
stats['precpu_stats']['system_cpu_usage']
if system_delta > 0.0 and cpu_delta > 0.0:
self._cpu = (cpu_delta / system_delta) * \
len(stats['cpu_stats']['cpu_usage']['percpu_usage']) * 100.0
def _calc_network(self, networks):
"""Calculate Network IO stats."""
for _, stats in networks.items():
self._network_rx += stats['rx_bytes']
self._network_tx += stats['tx_bytes']
def _calc_block_io(self, blkio):
"""Calculate block IO stats."""
for stats in blkio['io_service_bytes_recursive']:
if stats['op'] == 'Read':
self._blk_read += stats['value']
elif stats['op'] == 'Write':
self._blk_write += stats['value']
@property
def cpu_percent(self):
"""Return CPU percent."""
return self._cpu
@property
def memory_usage(self):
"""Return memory usage."""
return self._memory_usage
@property
def memory_limit(self):
"""Return memory limit."""
return self._memory_limit
@property
def network_rx(self):
"""Return network rx stats."""
return self._network_rx
@property
def network_tx(self):
"""Return network rx stats."""
return self._network_tx
@property
def blk_read(self):
"""Return block IO read stats."""
return self._blk_read
@property
def blk_write(self):
"""Return block IO write stats."""
return self._blk_write

View File

@ -219,6 +219,13 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
""" """
return self.instance.logs() return self.instance.logs()
def stats(self):
"""Return stats of HomeAssistant.
Return a coroutine.
"""
return self.instance.stats()
def is_running(self): def is_running(self):
"""Return True if docker container is running. """Return True if docker container is running.

1
hassio/misc/__init__.py Normal file
View File

@ -0,0 +1 @@
"""Special object and tools for Hass.io."""

View File

@ -6,7 +6,7 @@ import re
import pyudev import pyudev
from .const import ATTR_NAME, ATTR_TYPE, ATTR_DEVICES from ..const import ATTR_NAME, ATTR_TYPE, ATTR_DEVICES
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -5,7 +5,7 @@ import logging
import async_timeout import async_timeout
from .const import ( from ..const import (
SOCKET_HC, ATTR_LAST_VERSION, ATTR_VERSION, ATTR_TYPE, ATTR_FEATURES, SOCKET_HC, ATTR_LAST_VERSION, ATTR_VERSION, ATTR_TYPE, ATTR_FEATURES,
ATTR_HOSTNAME, ATTR_OS) ATTR_HOSTNAME, ATTR_OS)

View File

@ -46,14 +46,16 @@ class Supervisor(CoreSysAttributes):
version = version or self.last_version version = version or self.last_version
if version == self._supervisor.version: if version == self._supervisor.version:
_LOGGER.info("Version %s is already installed", version) _LOGGER.warning("Version %s is already installed", version)
return return
_LOGGER.info("Update supervisor to version %s", version) _LOGGER.info("Update supervisor to version %s", version)
if await self.instance.install(version): if await self.instance.install(version):
self._loop.call_later(1, self._loop.stop) self._loop.call_later(1, self._loop.stop)
else: return True
_LOGGER.error("Update of hass.io fails!") _LOGGER.error("Update of hass.io fails!")
return False
@property @property
def in_progress(self): def in_progress(self):
@ -66,3 +68,10 @@ class Supervisor(CoreSysAttributes):
Return a coroutine. Return a coroutine.
""" """
return self.instance.logs() return self.instance.logs()
def stats(self):
"""Return stats of Supervisor.
Return a coroutine.
"""
return self.instance.stats()

@ -1 +1 @@
Subproject commit 9b9cba86c28bf7710a84ac00e065e0165c61754f Subproject commit ea16ebd4f0fe90ef3c4623875ddc0b53f79ce3ae

View File

@ -31,10 +31,11 @@ setup(
platforms='any', platforms='any',
packages=[ packages=[
'hassio', 'hassio',
'hassio.utils',
'hassio.docker', 'hassio.docker',
'hassio.api',
'hassio.addons', 'hassio.addons',
'hassio.api',
'hassio.misc',
'hassio.utils',
'hassio.snapshots' 'hassio.snapshots'
], ],
include_package_data=True, include_package_data=True,

View File

@ -10,5 +10,5 @@ deps =
basepython = python3 basepython = python3
ignore_errors = True ignore_errors = True
commands = commands =
flake8 flake8 hassio
pylint --rcfile pylintrc hassio pylint --rcfile pylintrc hassio

View File

@ -1,5 +1,5 @@
{ {
"hassio": "0.79", "hassio": "0.80",
"homeassistant": "0.60.1", "homeassistant": "0.60.1",
"resinos": "1.1", "resinos": "1.1",
"resinhup": "0.3", "resinhup": "0.3",