X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=installers%2Fcharm%2Fmon%2Fsrc%2Fcharm.py;h=98c3297be76dc239af55b6928c7860424a671b7d;hb=49379ced23b5e344a773ce77ac9cb59c1864e19b;hp=f51213daea3fcee9e937f6ce88c80cbb72a68119;hpb=aeb1e5ee2d7642f4fcd7f81d808530bde779359e;p=osm%2Fdevops.git diff --git a/installers/charm/mon/src/charm.py b/installers/charm/mon/src/charm.py index f51213da..98c3297b 100755 --- a/installers/charm/mon/src/charm.py +++ b/installers/charm/mon/src/charm.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright 2020 Canonical Ltd. +# Copyright 2021 Canonical Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain @@ -20,254 +20,135 @@ # osm-charmers@lists.launchpad.net ## +# pylint: disable=E0213 + + import logging -from typing import Any, Dict, NoReturn +from typing import NoReturn -from ops.charm import CharmBase, CharmEvents -from ops.framework import EventBase, EventSource, StoredState from ops.main import main -from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus -from oci_image import OCIImageResource, OCIImageResourceError - -from pod_spec import make_pod_spec -LOGGER = logging.getLogger(__name__) +from opslib.osm.charm import CharmedOsmBase, RelationsMissing -MON_PORT = 8000 +from opslib.osm.pod import ( + ContainerV3Builder, + PodSpecV3Builder, +) +from opslib.osm.validator import ( + ModelValidator, + validator, +) -class ConfigurePodEvent(EventBase): - """Configure Pod event""" +from opslib.osm.interfaces.kafka import KafkaClient +from opslib.osm.interfaces.mongo import MongoClient +from opslib.osm.interfaces.prometheus import PrometheusClient - pass +logger = logging.getLogger(__name__) -class MonEvents(CharmEvents): - """MON Events""" +PORT = 8000 - configure_pod = EventSource(ConfigurePodEvent) +class ConfigModel(ModelValidator): + vca_host: str + vca_user: str + vca_password: str + vca_cacert: str + database_commonkey: str + log_level: str + openstack_default_granularity: int + global_request_timeout: int + collector_interval: int + evaluator_interval: int + grafana_url: str + grafana_user: str + grafana_password: str -class MonCharm(CharmBase): - """MON Charm.""" + @validator("log_level") + def validate_log_level(cls, v): + if v not in {"INFO", "DEBUG"}: + raise ValueError("value must be INFO or DEBUG") + return v - state = StoredState() - on = MonEvents() +class MonCharm(CharmedOsmBase): def __init__(self, *args) -> NoReturn: - """MON Charm constructor.""" - super().__init__(*args) - - # Internal state initialization - self.state.set_default(pod_spec=None) - - # Message bus data initialization - self.state.set_default(message_host=None) - self.state.set_default(message_port=None) - - # Database data initialization - self.state.set_default(database_uri=None) + super().__init__(*args, oci_image="image") - # Prometheus data initialization - self.state.set_default(prometheus_host=None) - self.state.set_default(prometheus_port=None) + self.kafka_client = KafkaClient(self, "kafka") + self.framework.observe(self.on["kafka"].relation_changed, self.configure_pod) + self.framework.observe(self.on["kafka"].relation_broken, self.configure_pod) - self.port = MON_PORT - self.image = OCIImageResource(self, "image") + self.mongodb_client = MongoClient(self, "mongodb") + self.framework.observe(self.on["mongodb"].relation_changed, self.configure_pod) + self.framework.observe(self.on["mongodb"].relation_broken, self.configure_pod) - # Registering regular events - self.framework.observe(self.on.start, self.configure_pod) - self.framework.observe(self.on.config_changed, self.configure_pod) - self.framework.observe(self.on.upgrade_charm, self.configure_pod) - - # Registering custom internal events - self.framework.observe(self.on.configure_pod, self.configure_pod) - - # Registering required relation events + self.prometheus_client = PrometheusClient(self, "prometheus") self.framework.observe( - self.on.kafka_relation_changed, self._on_kafka_relation_changed + self.on["prometheus"].relation_changed, self.configure_pod ) self.framework.observe( - self.on.mongodb_relation_changed, self._on_mongodb_relation_changed - ) - self.framework.observe( - self.on.prometheus_relation_changed, self._on_prometheus_relation_changed + self.on["prometheus"].relation_broken, self.configure_pod ) - # Registering required relation departed events - self.framework.observe( - self.on.kafka_relation_departed, self._on_kafka_relation_departed + def _check_missing_dependencies(self, config: ConfigModel): + missing_relations = [] + + if self.kafka_client.is_missing_data_in_unit(): + missing_relations.append("kafka") + if self.mongodb_client.is_missing_data_in_unit(): + missing_relations.append("mongodb") + if self.prometheus_client.is_missing_data_in_app(): + missing_relations.append("prometheus") + + if missing_relations: + raise RelationsMissing(missing_relations) + + def build_pod_spec(self, image_info): + # Validate config + config = ConfigModel(**dict(self.config)) + # Check relations + self._check_missing_dependencies(config) + # Create Builder for the PodSpec + pod_spec_builder = PodSpecV3Builder() + # Build Container + container_builder = ContainerV3Builder(self.app.name, image_info) + container_builder.add_port(name=self.app.name, port=PORT) + container_builder.add_envs( + { + # General configuration + "ALLOW_ANONYMOUS_LOGIN": "yes", + "OSMMON_OPENSTACK_DEFAULT_GRANULARITY": config.openstack_default_granularity, + "OSMMON_GLOBAL_REQUEST_TIMEOUT": config.global_request_timeout, + "OSMMON_GLOBAL_LOGLEVEL": config.log_level, + "OSMMON_COLLECTOR_INTERVAL": config.collector_interval, + "OSMMON_EVALUATOR_INTERVAL": config.evaluator_interval, + # Kafka configuration + "OSMMON_MESSAGE_DRIVER": "kafka", + "OSMMON_MESSAGE_HOST": self.kafka_client.host, + "OSMMON_MESSAGE_PORT": self.kafka_client.port, + # Database configuration + "OSMMON_DATABASE_DRIVER": "mongo", + "OSMMON_DATABASE_URI": self.mongodb_client.connection_string, + "OSMMON_DATABASE_COMMONKEY": config.database_commonkey, + # Prometheus configuration + "OSMMON_PROMETHEUS_URL": f"http://{self.prometheus_client.hostname}:{self.prometheus_client.port}", + # VCA configuration + "OSMMON_VCA_HOST": config.vca_host, + "OSMMON_VCA_USER": config.vca_user, + "OSMMON_VCA_SECRET": config.vca_password, + "OSMMON_VCA_CACERT": config.vca_cacert, + "OSMMON_GRAFANA_URL": config.grafana_url, + "OSMMON_GRAFANA_USER": config.grafana_user, + "OSMMON_GRAFANA_PASSWORD": config.grafana_password, + } ) - self.framework.observe( - self.on.mongodb_relation_departed, self._on_mongodb_relation_departed - ) - self.framework.observe( - self.on.prometheus_relation_departed, self._on_prometheus_relation_departed - ) - - def _on_kafka_relation_changed(self, event: EventBase) -> NoReturn: - """Reads information about the kafka relation. - - Args: - event (EventBase): Kafka relation event. - """ - message_host = event.relation.data[event.unit].get("host") - message_port = event.relation.data[event.unit].get("port") - - if ( - message_host - and message_port - and ( - self.state.message_host != message_host - or self.state.message_port != message_port - ) - ): - self.state.message_host = message_host - self.state.message_port = message_port - self.on.configure_pod.emit() - - def _on_kafka_relation_departed(self, event: EventBase) -> NoReturn: - """Clear kafka relation data. - - Args: - event (EventBase): Kafka relation event. - """ - self.state.message_host = None - self.state.message_port = None - self.on.configure_pod.emit() - - def _on_mongodb_relation_changed(self, event: EventBase) -> NoReturn: - """Reads information about the DB relation. - - Args: - event (EventBase): DB relation event. - """ - database_uri = event.relation.data[event.unit].get("connection_string") - - if database_uri and self.state.database_uri != database_uri: - self.state.database_uri = database_uri - self.on.configure_pod.emit() - - def _on_mongodb_relation_departed(self, event: EventBase) -> NoReturn: - """Clear mongodb relation data. - - Args: - event (EventBase): DB relation event. - """ - self.state.database_uri = None - self.on.configure_pod.emit() - - def _on_prometheus_relation_changed(self, event: EventBase) -> NoReturn: - """Reads information about the prometheus relation. - - Args: - event (EventBase): Prometheus relation event. - """ - prometheus_host = event.relation.data[event.unit].get("hostname") - prometheus_port = event.relation.data[event.unit].get("port") - - if ( - prometheus_host - and prometheus_port - and ( - self.state.prometheus_host != prometheus_host - or self.state.prometheus_port != prometheus_port - ) - ): - self.state.prometheus_host = prometheus_host - self.state.prometheus_port = prometheus_port - self.on.configure_pod.emit() - - def _on_prometheus_relation_departed(self, event: EventBase) -> NoReturn: - """Clear prometheus relation data. - - Args: - event (EventBase): Prometheus relation event. - """ - self.state.prometheus_host = None - self.state.prometheus_port = None - self.on.configure_pod.emit() - - def _missing_relations(self) -> str: - """Checks if there missing relations. - - Returns: - str: string with missing relations - """ - data_status = { - "kafka": self.state.message_host, - "mongodb": self.state.database_uri, - "prometheus": self.state.prometheus_host, - } - - missing_relations = [k for k, v in data_status.items() if not v] - - return ", ".join(missing_relations) - - @property - def relation_state(self) -> Dict[str, Any]: - """Collects relation state configuration for pod spec assembly. - - Returns: - Dict[str, Any]: relation state information. - """ - relation_state = { - "message_host": self.state.message_host, - "message_port": self.state.message_port, - "database_uri": self.state.database_uri, - "prometheus_host": self.state.prometheus_host, - "prometheus_port": self.state.prometheus_port, - } - - return relation_state - - def configure_pod(self, event: EventBase) -> NoReturn: - """Assemble the pod spec and apply it, if possible. - - Args: - event (EventBase): Hook or Relation event that started the - function. - """ - if missing := self._missing_relations(): - self.unit.status = BlockedStatus( - "Waiting for {0} relation{1}".format( - missing, "s" if "," in missing else "" - ) - ) - return - - if not self.unit.is_leader(): - self.unit.status = ActiveStatus("ready") - return - - self.unit.status = MaintenanceStatus("Assembling pod spec") - - # Fetch image information - try: - self.unit.status = MaintenanceStatus("Fetching image information") - image_info = self.image.fetch() - except OCIImageResourceError: - self.unit.status = BlockedStatus("Error fetching image information") - return - - try: - pod_spec = make_pod_spec( - image_info, - self.model.config, - self.relation_state, - self.model.app.name, - self.port, - ) - except ValueError as exc: - LOGGER.exception("Config/Relation data validation error") - self.unit.status = BlockedStatus(str(exc)) - return - - if self.state.pod_spec != pod_spec: - self.model.pod.set_spec(pod_spec) - self.state.pod_spec = pod_spec - self.unit.status = ActiveStatus("ready") + container = container_builder.build() + # Add container to pod spec + pod_spec_builder.add_container(container) + return pod_spec_builder.build() if __name__ == "__main__":