def delete(self, probe_name): """ Delete mapped services from probe. """ session = config.session() try: probe = session.query(Probe).filter(Probe.name == probe_name).one() logging.info("Deleting service mapping where id in %s" % ",".join(request.args.getlist("id"))) for service in session.query(MappedService)\ .join(Service, Service.id == MappedService.probe_service_id)\ .filter(MappedService.id.in_(request.args.getlist("id")))\ .filter(Service.probe_id == probe.id).all(): session.delete(service) session.commit() return {"status": "OK"} except: session.rollback() raise finally: session.close()
def get(self, name): """ Return probe configuration. """ session = config.session() try: probe = session.query(entity.Probe).filter_by(name=name).one() return { "name": probe.name, "services": [{ "name": service.name, "description": service.description, "deleted": service.deleted, "options": [{ "identifier": option.identifier, "name": option.name, "type": option.data_type, "description": option.description, "required": option.required } for option in service.options] } for service in probe.services] } finally: session.commit()
def load_errors(self): """ Load error causes from database. """ session = config.session() try: errors = session.query(ErrorCause).all() self._errors = {} for error in errors: self._errors[error.id] = error.name self._errors[error.name] = error.id finally: session.close()
def load_status(self): """ Load statuses from database. """ session = config.session() try: statuses = session.query(Status).all() self._status = {} for status in statuses: self._status[status.id] = status.name self._status[status.name] = status.id finally: session.close()
def load_service_status(self): """ Load service statuses. """ session = config.session() try: statuses = session.query(ServiceStatus).order_by(ServiceStatus.id).all() self._service_status = {} for status in statuses: self._service_status[status.id] = status.name self._service_status[status.name] = status.id finally: session.close()
def get(self): """ List of probes. """ session = config.session() try: query = select(session, request.args.getlist("show"), self.SELECT_COLUMN_MAPPING, self.SELECT_JOIN_MAPPING, self.TABLE_JOINS).order_by(entity.Probe.name) result = [probe._asdict() for probe in query.all()] logging.info(result) return result finally: session.commit()
def put(self, probe_name): """ Put new reading to database. :param probe_name: Name of probe which sent the reading. """ session = config.session() try: probe = session.query(Probe).filter(Probe.name == probe_name).one() # Load all active service IDs to be able to verify if posted service can be updated. active_service_ids = [] active_services = {} for service in session.query(MappedService)\ .select_from(MappedService)\ .join(Service, MappedService.probe_service_id == Service.id)\ .filter(Service.probe_id == probe.id)\ .filter(MappedService.status_id == const.status["active"])\ .all(): active_service_ids.append(service.id) active_services[service.id] = service readings_by_service = {} valid_service_ids = [ value["service"] for value in request.json if value["service"] in active_service_ids ] # Construct thresholds[mapping_id][reading_name] = [threshold1, threshold2, ...]. thresholds_for_mapping = {} for threshold, mapped_service_id in session.query(ServiceThreshold, MappedService.id)\ .select_from(MappedService)\ .join(Service, Service.id == MappedService.probe_service_id)\ .join(ServiceThreshold, ServiceThreshold.probe_service_id == Service.id)\ .order_by(MappedService.id, ServiceThreshold.service_status_id)\ .filter(MappedService.id.in_(valid_service_ids)).all(): thresholds_for_mapping\ .setdefault(mapped_service_id, {})\ .setdefault(threshold.reading, [])\ .append(threshold) for reading in session.query(Reading)\ .filter(Reading.mapped_service_id.in_(active_service_ids))\ .all(): readings_by_service.setdefault(reading.mapped_service_id, {})[reading.name] = reading for value in request.json: if value["service"] not in active_service_ids: logging.warning( "Received reading for unknown service %s. Maybe it was removed or deactivated. " "Ignoring.") continue # Test if reading value already exists db_reading = readings_by_service.setdefault( value["service"], {}).get(value["reading"]) if db_reading is None: db_reading = Reading(mapped_service_id=value["service"], name=value["reading"]) readings_by_service[value["service"]][ value["reading"]] = db_reading logging.debug("Create new reading %s." % (value["reading"], )) db_reading.values.append( ReadingValue(datetime=value["timestamp"], value=value["value"])) logging.debug("Store value %s=%s." % (db_reading.name, value["value"])) # TDetermine whether service changes status and write that to database. db_service = active_services[value["service"]] if value["service"] in thresholds_for_mapping: # Determine what is current status of service. thresholds = thresholds_for_mapping[value["service"]] combined_thresholds = {} # Determine possible keys and sort them by priority. for reading, status_thresholds in thresholds.items(): if fnmatch(value["reading"], reading): # Determine match priority. Be dummy here and think, that longer pattern is more accurate. priority = len(reading) for threshold in status_thresholds: if threshold.service_status_id not in combined_thresholds or \ combined_thresholds[threshold.service_status_id][0] < priority: combined_thresholds[ threshold. service_status_id] = priority, threshold # OK is default status. current_status = const.service_status["ok"] for key in sorted(combined_thresholds.keys()): threshold = combined_thresholds[key][1] if (threshold.min is not None and value["value"] < threshold.min) or ( threshold.max is not None and value["value"] > threshold.max): current_status = threshold.service_status_id if db_service.current_status != current_status: db_service.current_status = current_status db_service.current_status_from = now() # Create history entry. session.add( ServiceStatusHistory( mapped_service_id=db_service.id, service_status_id=current_status, timestamp=now())) session.add(db_service) else: if db_service.current_status is not None: db_service.current_status = None db_service.current_status_from = None # Create history entry. session.add( ServiceStatusHistory( mapped_service_id=db_service.id, service_status_id=None, timestamp=now())) session.add(db_service) session.add(db_reading) session.commit() return {"status": "OK"} except: session.rollback() raise finally: session.close()
def put(self): """ Create/update probe data. """ data = request.json session = config.session() try: probe = session.query( entity.Probe).filter_by(name=data["name"]).one() services_by_name = {} for service in probe.services: services_by_name[service.name] = service reported_services_names = [] for service in data.get("services", []): if service["name"] not in services_by_name: db_service = entity.Service(name=service["name"], description=service.get( "description", "")) probe.services.append(db_service) services_by_name[db_service.name] = db_service else: db_service = services_by_name[service["name"]] db_service.description = service.get("description", "") db_service.deleted = False reported_services_names.append(service["name"]) options_by_identifier = {} reported_option_identifiers = [] for option in db_service.options: options_by_identifier[option.identifier] = option required_options = [] new_required_option = False for option in service.get("options", []): reported_option_identifiers.append(option["identifier"]) if option["identifier"] not in options_by_identifier: db_option = entity.ServiceOption( identifier=option["identifier"], name=option.get("name", option["identifier"]), description=option.get("description", ""), data_type=option.get("type", "string"), required=option.get("required", False)) db_service.options.append(db_option) # Set all mapped services nonfunctional if new required option is introduced. if db_option.required and db_service.id: new_required_option = True else: db_option = options_by_identifier[option["identifier"]] db_option.name = option.get("name", option["identifier"]) db_option.description = option.get("description", "") db_option.data_type = option.get("type", "string") db_option.required = option.get("required", False) if db_option.required and db_service.id: required_options.append(db_option.id) # Set service error if there is new required option. First case is when there is whole new option, # second is for situations, when option becomes required. if new_required_option or db_service.deleted: session.execute( """UPDATE mapped_services SET status_id = :status_id, error_cause_id = :error_cause_id WHERE probe_service_id = :service_id""", { "status_id": const.status["error"], "error_cause_id": (const.error_cause["ERROR_MISSING_REQUIRED_OPTION"] if new_required_option else const.error_cause["ERROR_SERVICE_UNAVAILABLE"]), "service_id": db_service.id }) elif required_options: session.execute( """UPDATE mapped_services m LEFT JOIN mapped_service_options o ON (o.mapped_service_id = m.id AND o.option_id IN (""" + ",".join(map(str, required_options)) + """)) SET m.status_id = :status_id, m.error_cause_id = :error_cause_id WHERE o.id IS NULL""", { "status_id": const.status["error"], "error_cause_id": const.error_cause["ERROR_MISSING_REQUIRED_OPTION"], "service_id": db_service.id }) # Delete no longer known options. for option_identifier, option in options_by_identifier.items(): if option_identifier not in reported_option_identifiers: session.delete(option) # Update / create thresholds. known_thresholds = [] for threshold in db_service.thresholds: known_thresholds.append(threshold) for name, limits in service.get("thresholds", {}).items(): found = False for db_threshold in known_thresholds: if db_threshold.reading == name and \ const.service_status[db_threshold.service_status_id] == limits["status"]: found = True # Update only if the limits come from service and are not overwritten by the configuration. if db_threshold.source == "service": db_threshold.min = limits.get("min", None) db_threshold.max = limits.get("max", None) break if not found: db_service.thresholds.append( entity.ServiceThreshold( service_status_id=const.service_status[ limits["status"]], reading=name, min=limits.get("min", None), max=limits.get("max", None), source="service")) # Delete no longer known services for service_name, service in services_by_name.items(): if service_name not in reported_services_names: service.deleted = True session.add(probe) except Exception: session.rollback() raise finally: session.commit() return {"status": "OK"}
def get(self, probe_name): """ List mapped services of probe. :param probe_name: Probe name """ session = config.session() try: probe = session.query(Probe).filter_by(name=probe_name).one() allowed_statuses = [] for status in request.args.getlist("status"): if status == "all": allowed_statuses = None break if status in const.status.keys(): allowed_statuses.append(const.status[status]) else: raise BadRequest( "Bad status value: '%s'. Must be one of %s." % (status, ",".join(const.status.keys()))) if not allowed_statuses and allowed_statuses is not None: allowed_statuses.append(const.status["active"]) show = request.args.getlist("show") show_options = [] for column in show: if column.startswith("options."): show_options.append(column[len("options."):]) for column in show_options: show.remove("options.%s" % (column, )) mapped_services = select(session, show, self.SHOW_COLUMNS)\ .select_from(MappedService)\ .add_column(MappedService.id.label("id"))\ .add_column(MappedService.probe_service_id.label("probe_service_id"))\ .join(Service, MappedService.probe_service_id == Service.id)\ .join(Status, MappedService.status_id == Status.id)\ .outerjoin(ErrorCause, MappedService.error_cause_id == ErrorCause.id)\ .filter((Service.probe_id == probe.id))\ .order_by(MappedService.name) if allowed_statuses: mapped_services = mapped_services.filter( MappedService.status_id.in_(allowed_statuses)) ids = request.args.getlist("id") if ids: mapped_services = mapped_services.filter( MappedService.id.in_(ids)) services = [] service_ids = set() mapped_ids = set() for row in mapped_services.all(): service = row._asdict() services.append(service) service_ids.add(service["probe_service_id"]) mapped_ids.add(service["id"]) if show_options: load_value = False if "value" in show_options: load_value = True show_options.remove("value") # Select all options for given services options = select(session, show_options, self.OPTIONS_COLUMNS)\ .add_column(ServiceOption.probe_service_id)\ .add_column(ServiceOption.id)\ .filter(ServiceOption.probe_service_id.in_(service_ids)) options_for_service = {} for row in options.all(): option = row._asdict() options_for_service.setdefault(option["probe_service_id"], []).append(option) values_by_mapping = {} if load_value: for row in session.query(MappedServiceOption.value, MappedServiceOption.mapped_service_id, MappedServiceOption.option_id)\ .filter(MappedServiceOption.mapped_service_id.in_(mapped_ids)).all(): value = row._asdict() values_by_mapping.setdefault( value["mapped_service_id"], {})[value["option_id"]] = value["value"] for service in services: options = [ option.copy() for option in options_for_service.get( service["probe_service_id"], []) ] if load_value: for option in options: option["value"] = values_by_mapping.get( service["id"], {}).get(option["id"]) for option in options: del option["probe_service_id"], option["id"] service["options"] = options # Clean up the result struct from internal items. for service in services: del service["probe_service_id"] if "id" not in show: del service["id"] return services finally: session.commit()
def patch(self, probe_name): """ Update mapping options. """ session = config.session() try: probe = session.query(Probe).filter(Probe.name == probe_name).one() services = session.query(MappedService, Service)\ .join(Service, MappedService.probe_service_id == Service.id)\ .filter(MappedService.id.in_([service["id"] for service in request.json]))\ .filter(Service.probe_id == probe.id)\ .all() if len(services) != len(request.json): abort(404) services_by_id = { mapping_service[0].id: mapping_service for mapping_service in services } # Modify service for service in request.json: db_mapping, db_service = services_by_id[int(service["id"])] if "name" in service: db_mapping.name = service["name"] if "description" in service: db_mapping.description = service["description"] # Modify status, but only if it is not error. if "status" in service and db_mapping.status_id != const.status[ "error"]: db_mapping.status_id = const.status[service["status"]] options_by_identifier = {} for option in db_service.options: options_by_identifier[option.identifier] = option mapped_options_by_option_id = {} for option in db_mapping.options: mapped_options_by_option_id[option.option_id] = option if "options" in service: found_options = [] required_filled = True for option_identifier, option_value in service[ "options"].items(): option = options_by_identifier[option_identifier] # Empty options are not allowed. if option_value == "": if option.required: required_filled = False continue # Test data types try: if option.data_type == "string": pass elif option.data_type == "integer": option_value = str(int(option_value)) elif option.data_type == "double": option_value = str(float(option_value)) elif option.data_type == "bool": option_value = "1" if bool( option_value) else "0" elif option.data_type == "list": option_value = "\n".join([ value for value in option_value.split("\n") if value.strip() != "" ]) else: raise ValueError() except ValueError: abort(400) found_options.append(option.id) if option.id in mapped_options_by_option_id: mapped_options_by_option_id[ option.id].value = option_value else: mapping_option = MappedServiceOption( option_id=option.id, value=option_value) db_mapping.options.append(mapping_option) for option_id, option in mapped_options_by_option_id.items( ): if option_id not in found_options: session.delete(option) # If all required options are filled and service is in error state because of this, reactivate the # service. if required_filled \ and db_mapping.status_id == const.status["error"] \ and db_mapping.error_cause_id == const.error_cause["ERROR_MISSING_REQUIRED_OPTION"]: db_mapping.status_id = const.status["active"] db_mapping.error_cause_id = None session.add(db_mapping) session.commit() return {"status": "OK"} except: session.rollback() raise finally: session.close()
def put(self, probe_name): """ Create new service mapping. :param probe_name: Name of probe. """ data = request.json # List service IDs to load. services_to_load = [mapping["service"] for mapping in data] session = config.session() try: probe = session.query(Probe).filter_by(name=probe_name).one() services = { service.name: service for service in session.query(Service).options( joinedload("options")).filter( and_(Service.name.in_(services_to_load), Service.probe_id == probe.id)).all() } services_by_id = {} for service in services.values(): services_by_id[service.id] = service if len(services) != len(services_to_load): abort(404) mappings = [] for mapping in data: service = services[mapping["service"]] db_mapping = MappedService( probe_service_id=service.id, name=mapping["name"], description=mapping.get("description", ""), status_id=const.status["active"], ) db_mapping.options = [] required_set = True for option in service.options: option_value = mapping.get("options", {}).get(option.identifier, "") if option_value == "": if option.required: required_set = False continue # Test data types try: if option.data_type == "string": pass elif option.data_type == "integer": option_value = str(int(option_value)) elif option.data_type == "double": option_value = str(float(option_value)) elif option.data_type == "bool": option_value = "1" if bool(option_value) else "0" elif option.data_type == "list": option_value = "\n".join([ value for value in option_value.split("\n") if value.strip() != "" ]) else: raise ValueError() except ValueError: abort(400) db_mapping.options.append( MappedServiceOption(option_id=option.id, value=option_value)) mappings.append(db_mapping) if not required_set: db_mapping.status_id = const.status["error"] db_mapping.error_cause_id = const.error_cause[ "ERROR_MISSING_REQUIRED_OPTION"] session.add_all(mappings) session.commit() except: session.rollback() raise finally: session.close() return {"status": "OK"}