Source code for lighthouse.service

import collections
import logging

import six

from .configurable import Configurable
from .check import Check


logger = logging.getLogger(__name__)


[docs]class Service(Configurable): """ Class representing a service provided by the current machine. This is a straightforward Configurable subclass, it defines what a valid configuration for a service is and applies them. """ config_subdirectory = "services" def __init__(self): self.host = None self.ports = set() self.configured_ports = None self.discovery = None self.checks = collections.defaultdict(dict) self.check_interval = None self.is_up = collections.defaultdict(lambda: None) self.metadata = {} @classmethod
[docs] def validate_config(cls, config): """ Runs a check on the given config to make sure that `port`/`ports` and `discovery` is defined. """ if "discovery" not in config: raise ValueError("No discovery method defined.") if not any([item in config for item in ["port", "ports"]]): raise ValueError("No port(s) defined.") cls.validate_check_configs(config)
@classmethod
[docs] def validate_check_configs(cls, config): """ Config validation specific to the health check options. Verifies that checks are defined along with an interval, and calls out to the `Check` class to make sure each individual check's config is valid. """ if "checks" not in config: raise ValueError("No checks defined.") if "interval" not in config["checks"]: raise ValueError("No check interval defined.") for check_name, check_config in six.iteritems(config["checks"]): if check_name == "interval": continue Check.from_config(check_name, check_config)
[docs] def apply_config(self, config): """ Takes a given validated config dictionary and sets an instance attribute for each one. For check definitions, a Check instance is is created and a `checks` attribute set to a dictionary keyed off of the checks' names. If the Check instance has some sort of error while being created an error is logged and the check skipped. """ self.host = config.get("host", "127.0.0.1") self.configured_ports = config.get("ports", [config.get("port")]) self.discovery = config["discovery"] self.metadata = config.get("metadata", {}) self.update_ports() self.check_interval = config["checks"]["interval"] self.update_checks(config["checks"])
[docs] def reset_status(self): """ Sets the up/down status of the service ports to the default state. Useful for when the configuration is updated and the checks involved in determining the status might have changed. """ self.is_up = collections.defaultdict(lambda: None)
[docs] def update_ports(self): """ Sets the `ports` attribute to the set of valid port values set in the configuration. """ ports = set() for port in self.configured_ports: try: ports.add(int(port)) except ValueError: logger.error("Invalid port value: %s", port) continue self.ports = ports
[docs] def update_checks(self, check_configs): """ Maintains the values in the `checks` attribute's dictionary. Each key in the dictionary is a port, and each value is a nested dictionary mapping each check's name to the Check instance. This method makes sure the attribute reflects all of the properly configured checks and ports. Removing no-longer-configured ports is left to the `run_checks` method. """ for check_name, check_config in six.iteritems(check_configs): if check_name == "interval": continue for port in self.ports: try: check = Check.from_config(check_name, check_config) check.host = self.host check.port = port self.checks[port][check_name] = check except ValueError as e: logger.error( "Error when configuring check '%s' for service %s: %s", check_name, self.name, str(e) ) continue
[docs] def run_checks(self): """ Iterates over the configured ports and runs the checks on each one. Returns a two-element tuple: the first is the set of ports that transitioned from down to up, the second is the set of ports that transitioned from up to down. Also handles the case where a check for a since-removed port is run, marking the port as down regardless of the check's result and removing the check(s) for the port. """ came_up = set() went_down = set() for port in self.ports: checks = self.checks[port].values() if not checks: logger.warn("No checks defined for self: %s", self.name) for check in checks: check.run() checks_pass = all([check.passing for check in checks]) if self.is_up[port] in (False, None) and checks_pass: came_up.add(port) self.is_up[port] = True elif self.is_up[port] in (True, None) and not checks_pass: went_down.add(port) self.is_up[port] = False for unused_port in set(self.checks.keys()) - self.ports: went_down.add(unused_port) del self.checks[unused_port] return came_up, went_down