# pylint: disable=too-many-instance-attributes
import asyncio
import collections
import logging
import aiopollen
import caspia.node
from caspia.gateway import errors, services
from caspia.gateway.rules import RuleActivationContext, activate_rules
from caspia.node import components
from caspia.node import utils as node_utils
from caspia.toolbox import monitor
logger = logging.getLogger(__name__)
[docs]class Configurator:
def __init__(self, hwid_map, network, lookup, loop, work_dir, rules_filter):
self.loop = loop
self.network = network
self.hwid_map = hwid_map
self.lookup = lookup
self.component_dependencies = dict()
self.requested_rules = []
self.active_rules = []
self.active_rules_context = None
self.work_dir = work_dir
self.lock = asyncio.Lock(loop=self.loop)
self.rules_filter = rules_filter
self._nodes_configured = False
@property
def nodes_configured(self):
return self._nodes_configured
@property
def gateway_services(self):
return (s for s in self.lookup.services.values() if isinstance(s, services.GatewayService))
[docs] async def prepare_nodes(self):
"""Make sure all nodes are reachable through known CID."""
for name in self.hwid_map:
try:
await self._prepare_node(name)
except Exception as e: # pylint: disable=broad-except
monitor.record_metric('caspia-gateway:nodes-err', 1)
logger.error('Failed to prepare node %r: %s', name, repr(e))
async def _prepare_node(self, name):
hwid = self.hwid_map[name]
logger.debug('Looking for node %s', name)
node = self.network.get_node(name)
real_hwid = None
async def check_node():
nonlocal real_hwid
real_hwid = await node.system.read_hwid()
if real_hwid == hwid:
logger.debug('Node %s is ready with can id %03X', name, node.can_id)
else:
msg = ('Node %s could not be reached, because node'
' with different HWID has its CAN ID')
logger.warning(msg, name)
raise errors.CanIDReservationError(msg % name)
try:
await check_node()
except aiopollen.errors.TimeoutError:
logger.info('Node %s is not responding. Going to find it by hwid.', name)
await node_utils.assign_can_id_by_hwid(hwid, node.can_id, self.network.pollen_client)
logger.info('New CAN ID was assigned to %s, checking now.', name)
await check_node()
except errors.CanIDReservationError:
logger.info('Trying to recover: going to change HWID of the'
' conflicting node to 0x1FF and then retry')
await node_utils.assign_can_id_by_hwid(real_hwid, 0x1FF, self.network.pollen_client)
await node_utils.assign_can_id_by_hwid(hwid, node.can_id, self.network.pollen_client)
logger.info('New CAN ID was assigned to %s, checking now.', name)
await check_node()
async def _configure_base(self):
"""Configure base of all nodes."""
logger.debug('Preparing base configuration')
self.network.reset_configuration()
for name, node in self.network.nodes.items():
node.drop_components()
node.system.config = components.System.Config(self.network.cidmng.cid_of(name))
for service in self.gateway_services:
service.configure()
self._update_component_dependencies()
logger.debug('Base configuration is ready')
async def _configure_rules(self):
"""Add extra configurations (like events on button press etc)."""
if self.active_rules_context:
await self.active_rules_context.cleanup()
self.active_rules_context = RuleActivationContext(self.loop,
self,
rules_filter=self.rules_filter)
self.active_rules = activate_rules(self.requested_rules, self.active_rules_context)
logger.debug('Active rules: %s', self.active_rules)
async def _apply_configuration(self):
"""Upload configuration to the nodes."""
for name, node in self.network.nodes.items():
try:
was_needed = await node.configure()
if was_needed:
logger.info('Configuration of %s was updated', name)
else:
logger.debug('Configuration of %s was up-to-date', name)
except caspia.node.errors.InvalidConfigurationError:
monitor.record_metric('caspia-gateway:nodes-err', 1)
logger.exception('Invalid configuration of %r', name)
except aiopollen.errors.TimeoutError:
monitor.record_metric('caspia-gateway:nodes-err', 1)
logger.exception('Failed to configure %s, node is not responding.', name)
except Exception as e: # pylint: disable=broad-except
monitor.record_metric('caspia-gateway:nodes-err', 1)
logger.exception('Failed to configure %r: %s', name, repr(e))
def _update_component_dependencies(self):
component_dependencies = collections.defaultdict(set)
for service in self.gateway_services:
for component in service.dependant_components:
component_dependencies[component].add(service)
self.component_dependencies = component_dependencies