X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=installers%2Fcharm%2Fpla%2Fsrc%2Fcharm.py;h=4f463bf65cecca2dc5af449063a76a1f1231f2e0;hb=49379ced23b5e344a773ce77ac9cb59c1864e19b;hp=b94d62bdbeb441a8545ed7c90cb75c9580629909;hpb=18c7a8b4367c8190a19327a421bdfa0ea9b2cb51;p=osm%2Fdevops.git diff --git a/installers/charm/pla/src/charm.py b/installers/charm/pla/src/charm.py index b94d62bd..4f463bf6 100755 --- a/installers/charm/pla/src/charm.py +++ b/installers/charm/pla/src/charm.py @@ -1,124 +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, + +from opslib.osm.charm import CharmedOsmBase, RelationsMissing + +from opslib.osm.pod import ( + ContainerV3Builder, + PodSpecV3Builder, ) -from glob import glob -from pathlib import Path -from string import Template +from opslib.osm.validator import ( + ModelValidator, + validator, +) -logger = logging.getLogger(__name__) +from opslib.osm.interfaces.kafka import KafkaClient +from opslib.osm.interfaces.mongo import MongoClient -class PLACharm(CharmBase): - state = StoredState() - - def __init__(self, framework, key): - super().__init__(framework, key) - self.state.set_default(spec=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) - - def _apply_spec(self): - # Only apply the spec if this unit is a leader. - if not self.framework.model.unit.is_leader(): - return - new_spec = self.make_pod_spec() - if new_spec == self.state.spec: - return - self.framework.model.pod.set_spec(new_spec) - self.state.spec = new_spec - - def make_pod_spec(self): - config = self.framework.model.config - - ports = [ - {"name": "port", "containerPort": config["port"], "protocol": "TCP",}, - ] - - kubernetes = { - "readinessProbe": { - "tcpSocket": {"port": config["port"]}, - "timeoutSeconds": 5, - "periodSeconds": 5, - "initialDelaySeconds": 10, - }, - "livenessProbe": { - "tcpSocket": {"port": config["port"]}, - "timeoutSeconds": 5, - "initialDelaySeconds": 45, - }, - } - config_spec = { - "OSMPLA_MESSAGE_DRIVER": "kafka", - "OSMPLA_MESSAGE_HOST": config["kafka_host"], - "OSMPLA_MESSAGE_PORT": config["kafka_port"], - "OSMPLA_DATABASE_DRIVER": "mongo", - "OSMPLA_DATABASE_URI": config["mongo_uri"], - "OSMPLA_GLOBAL_LOG_LEVEL": config["log_level"], - } - - spec = { - "version": 2, - "containers": [ - { - "name": self.framework.model.app.name, - "image": "{}".format(config["image"]), - "ports": ports, - "kubernetes": kubernetes, - "config": config_spec, - } - ], - } - - return spec - - def on_config_changed(self, event): - """Handle changes in configuration""" - unit = self.model.unit - unit.status = MaintenanceStatus("Applying new pod spec") - self._apply_spec() - unit.status = ActiveStatus("Ready") - - def on_start(self, event): - """Called when the charm is being installed""" - unit = self.model.unit - unit.status = MaintenanceStatus("Applying pod spec") - self._apply_spec() - unit.status = ActiveStatus("Ready") - - def on_upgrade_charm(self, event): - """Upgrade the charm.""" - unit = self.model.unit - unit.status = MaintenanceStatus("Upgrading charm") - self.on_start(event) +logger = logging.getLogger(__name__) + +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, + } + ) + + 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)