X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=installers%2Fcharm%2Fpla%2Fsrc%2Fcharm.py;h=4f463bf65cecca2dc5af449063a76a1f1231f2e0;hb=49379ced23b5e344a773ce77ac9cb59c1864e19b;hp=1fc6386fa64270e98667e97fc0abf40115a917ed;hpb=a4a37f7f9f5410ff2c7833b76bdc85f752c74849;p=osm%2Fdevops.git diff --git a/installers/charm/pla/src/charm.py b/installers/charm/pla/src/charm.py index 1fc6386f..4f463bf6 100755 --- a/installers/charm/pla/src/charm.py +++ b/installers/charm/pla/src/charm.py @@ -1,147 +1,119 @@ #!/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 a copy of the License at +# 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 +# a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact: legal@canonical.com +# +# To get in touch with the maintainers, please contact: +# osm-charmers@lists.launchpad.net +## -import sys -import logging +# pylint: disable=E0213 -sys.path.append("lib") -from ops.charm import CharmBase -from ops.framework import StoredState, Object +import logging +from typing import NoReturn + from ops.main import main -from ops.model import ( - ActiveStatus, - MaintenanceStatus, - WaitingStatus, -) -from glob import glob -from pathlib import Path -from string import Template +from opslib.osm.charm import CharmedOsmBase, RelationsMissing -logger = logging.getLogger(__name__) +from opslib.osm.pod import ( + ContainerV3Builder, + PodSpecV3Builder, +) +from opslib.osm.validator import ( + ModelValidator, + validator, +) -class PLACharm(CharmBase): - state = StoredState() +from opslib.osm.interfaces.kafka import KafkaClient +from opslib.osm.interfaces.mongo import MongoClient - def __init__(self, framework, key): - super().__init__(framework, key) - self.state.set_default(spec=None) - self.state.set_default(kafka_host=None) - self.state.set_default(kafka_port=None) - self.state.set_default(mongodb_uri=None) - # Observe Charm related events - self.framework.observe(self.on.config_changed, self.on_config_changed) - self.framework.observe(self.on.start, self.on_start) - self.framework.observe(self.on.upgrade_charm, self.on_upgrade_charm) +logger = logging.getLogger(__name__) - # Relations - self.framework.observe( - self.on.kafka_relation_changed, self.on_kafka_relation_changed - ) - self.framework.observe( - self.on.mongo_relation_changed, self.on_mongo_relation_changed +PORT = 9999 + + +class ConfigModel(ModelValidator): + database_commonkey: str + log_level: str + + @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 + + +class PlaCharm(CharmedOsmBase): + def __init__(self, *args) -> NoReturn: + super().__init__(*args, oci_image="image") + + 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.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) + + 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 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", + "OSMPLA_GLOBAL_LOG_LEVEL": config.log_level, + # Kafka configuration + "OSMPLA_MESSAGE_DRIVER": "kafka", + "OSMPLA_MESSAGE_HOST": self.kafka_client.host, + "OSMPLA_MESSAGE_PORT": self.kafka_client.port, + # Database configuration + "OSMPLA_DATABASE_DRIVER": "mongo", + "OSMPLA_DATABASE_URI": self.mongodb_client.connection_string, + "OSMPLA_DATABASE_COMMONKEY": config.database_commonkey, + } ) - def _apply_spec(self): - # Only apply the spec if this unit is a leader. - unit = self.model.unit - if not unit.is_leader(): - unit.status = ActiveStatus("ready") - return - if not self.state.kafka_host or not self.state.kafka_port: - unit.status = WaitingStatus("Waiting for Kafka") - return - if not self.state.mongodb_uri: - unit.status = WaitingStatus("Waiting for MongoDB") - return - - unit.status = MaintenanceStatus("Applying new pod spec") - - new_spec = self.make_pod_spec() - if new_spec == self.state.spec: - unit.status = ActiveStatus("ready") - return - self.framework.model.pod.set_spec(new_spec) - self.state.spec = new_spec - unit.status = ActiveStatus("ready") - - def make_pod_spec(self): - config = self.framework.model.config - - ports = [ - {"name": "port", "containerPort": config["port"], "protocol": "TCP", }, - ] - - config_spec = { - "OSMPLA_MESSAGE_DRIVER": "kafka", - "OSMPLA_MESSAGE_HOST": self.state.kafka_host, - "OSMPLA_MESSAGE_PORT": self.state.kafka_port, - "OSMPLA_DATABASE_DRIVER": "mongo", - "OSMPLA_DATABASE_URI": self.state.mongodb_uri, - "OSMPLA_GLOBAL_LOG_LEVEL": config["log_level"], - "OSMPLA_DATABASE_COMMONKEY": config["database_common_key"], - } - - spec = { - "version": 2, - "containers": [ - { - "name": self.framework.model.app.name, - "image": config["image"], - "ports": ports, - "config": config_spec, - } - ], - } - - return spec - - def on_config_changed(self, event): - """Handle changes in configuration""" - self._apply_spec() - - def on_start(self, event): - """Called when the charm is being installed""" - self._apply_spec() - - def on_upgrade_charm(self, event): - """Upgrade the charm.""" - unit = self.model.unit - unit.status = MaintenanceStatus("Upgrading charm") - self.on_start(event) - - def on_kafka_relation_changed(self, event): - unit = self.model.unit - if not unit.is_leader(): - return - self.state.kafka_host = event.relation.data[event.unit].get("host") - self.state.kafka_port = event.relation.data[event.unit].get("port") - self._apply_spec() - - def on_mongo_relation_changed(self, event): - unit = self.model.unit - if not unit.is_leader(): - return - self.state.mongodb_uri = event.relation.data[event.unit].get( - "connection_string" - ) - self._apply_spec() + container = container_builder.build() + # Add container to pod spec + pod_spec_builder.add_container(container) + return pod_spec_builder.build() if __name__ == "__main__": - main(PLACharm) + main(PlaCharm)