blob: 5b40c1606f2e6197002cb6554f84a890591563d4 [file] [log] [blame]
sousaeduccfacbb2020-11-04 21:44:01 +00001#!/usr/bin/env python3
David Garcia49379ce2021-02-24 13:48:22 +01002# Copyright 2021 Canonical Ltd.
sousaeduccfacbb2020-11-04 21:44:01 +00003#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15#
16# For those usages not covered by the Apache License, Version 2.0 please
17# contact: legal@canonical.com
18#
19# To get in touch with the maintainers, please contact:
20# osm-charmers@lists.launchpad.net
21##
22
David Garcia49379ce2021-02-24 13:48:22 +010023# pylint: disable=E0213
24
David Garcia5d1ec6e2021-03-25 15:04:52 +010025import base64
sousaeduccfacbb2020-11-04 21:44:01 +000026import logging
David Garcia5d1ec6e2021-03-25 15:04:52 +010027from typing import NoReturn, Optional
sousaeduccfacbb2020-11-04 21:44:01 +000028
sousaeduccfacbb2020-11-04 21:44:01 +000029from ops.main import main
David Garcia49379ce2021-02-24 13:48:22 +010030from opslib.osm.charm import CharmedOsmBase, RelationsMissing
David Garciac753dc52021-03-17 15:28:47 +010031from opslib.osm.interfaces.kafka import KafkaClient
32from opslib.osm.interfaces.mongo import MongoClient
33from opslib.osm.interfaces.mysql import MysqlClient
David Garcia5d1ec6e2021-03-25 15:04:52 +010034from opslib.osm.pod import ContainerV3Builder, FilesV3Builder, PodSpecV3Builder
David Garciac753dc52021-03-17 15:28:47 +010035from opslib.osm.validator import ModelValidator, validator
David Garcia49379ce2021-02-24 13:48:22 +010036
sousaeduccfacbb2020-11-04 21:44:01 +000037logger = logging.getLogger(__name__)
38
David Garcia49379ce2021-02-24 13:48:22 +010039PORT = 9090
sousaeduccfacbb2020-11-04 21:44:01 +000040
41
David Garcia5d1ec6e2021-03-25 15:04:52 +010042def _check_certificate_data(name: str, content: str):
43 if not name or not content:
44 raise ValueError("certificate name and content must be a non-empty string")
45
46
47def _extract_certificates(certs_config: str):
48 certificates = {}
49 if certs_config:
50 cert_list = certs_config.split(",")
51 for cert in cert_list:
52 name, content = cert.split(":")
53 _check_certificate_data(name, content)
54 certificates[name] = content
55 return certificates
56
57
58def decode(content: str):
59 return base64.b64decode(content.encode("utf-8")).decode("utf-8")
60
61
David Garcia49379ce2021-02-24 13:48:22 +010062class ConfigModel(ModelValidator):
63 enable_ng_ro: bool
64 database_commonkey: str
65 log_level: str
66 vim_database: str
67 ro_database: str
68 openmano_tenant: str
David Garcia5d1ec6e2021-03-25 15:04:52 +010069 certificates: Optional[str]
David Garcia49379ce2021-02-24 13:48:22 +010070
71 @validator("log_level")
72 def validate_log_level(cls, v):
73 if v not in {"INFO", "DEBUG"}:
74 raise ValueError("value must be INFO or DEBUG")
75 return v
sousaeduccfacbb2020-11-04 21:44:01 +000076
David Garcia5d1ec6e2021-03-25 15:04:52 +010077 @validator("certificates")
78 def validate_certificates(cls, v):
79 # Raises an exception if it cannot extract the certificates
80 _extract_certificates(v)
81 return v
82
83 @property
84 def certificates_dict(cls):
85 return _extract_certificates(cls.certificates) if cls.certificates else {}
86
sousaeduccfacbb2020-11-04 21:44:01 +000087
David Garcia49379ce2021-02-24 13:48:22 +010088class RoCharm(CharmedOsmBase):
89 """GrafanaCharm Charm."""
sousaeduccfacbb2020-11-04 21:44:01 +000090
91 def __init__(self, *args) -> NoReturn:
David Garcia49379ce2021-02-24 13:48:22 +010092 """Prometheus Charm constructor."""
93 super().__init__(*args, oci_image="image")
sousaeduccfacbb2020-11-04 21:44:01 +000094
David Garcia49379ce2021-02-24 13:48:22 +010095 self.kafka_client = KafkaClient(self, "kafka")
96 self.framework.observe(self.on["kafka"].relation_changed, self.configure_pod)
97 self.framework.observe(self.on["kafka"].relation_broken, self.configure_pod)
sousaeduccfacbb2020-11-04 21:44:01 +000098
David Garcia49379ce2021-02-24 13:48:22 +010099 self.mysql_client = MysqlClient(self, "mysql")
100 self.framework.observe(self.on["mysql"].relation_changed, self.configure_pod)
101 self.framework.observe(self.on["mysql"].relation_broken, self.configure_pod)
sousaeduccfacbb2020-11-04 21:44:01 +0000102
David Garcia49379ce2021-02-24 13:48:22 +0100103 self.mongodb_client = MongoClient(self, "mongodb")
104 self.framework.observe(self.on["mongodb"].relation_changed, self.configure_pod)
105 self.framework.observe(self.on["mongodb"].relation_broken, self.configure_pod)
sousaeduccfacbb2020-11-04 21:44:01 +0000106
David Garcia49379ce2021-02-24 13:48:22 +0100107 self.framework.observe(self.on["ro"].relation_joined, self._publish_ro_info)
sousaeduccfacbb2020-11-04 21:44:01 +0000108
David Garcia49379ce2021-02-24 13:48:22 +0100109 def _publish_ro_info(self, event):
sousaeduccfacbb2020-11-04 21:44:01 +0000110 """Publishes RO information.
111
112 Args:
113 event (EventBase): RO relation event.
114 """
115 if self.unit.is_leader():
116 rel_data = {
117 "host": self.model.app.name,
David Garcia49379ce2021-02-24 13:48:22 +0100118 "port": str(PORT),
sousaeduccfacbb2020-11-04 21:44:01 +0000119 }
120 for k, v in rel_data.items():
121 event.relation.data[self.app][k] = v
122
David Garcia49379ce2021-02-24 13:48:22 +0100123 def _check_missing_dependencies(self, config: ConfigModel):
124 missing_relations = []
125
126 if config.enable_ng_ro:
127 if self.kafka_client.is_missing_data_in_unit():
128 missing_relations.append("kafka")
129 if self.mongodb_client.is_missing_data_in_unit():
130 missing_relations.append("mongodb")
sousaeduccfacbb2020-11-04 21:44:01 +0000131 else:
David Garcia49379ce2021-02-24 13:48:22 +0100132 if self.mysql_client.is_missing_data_in_unit():
133 missing_relations.append("mysql")
134 if missing_relations:
135 raise RelationsMissing(missing_relations)
sousaeduccfacbb2020-11-04 21:44:01 +0000136
David Garcia5d1ec6e2021-03-25 15:04:52 +0100137 def _build_cert_files(
138 self,
139 config: ConfigModel,
140 ):
141 cert_files_builder = FilesV3Builder()
142 for name, content in config.certificates_dict.items():
143 cert_files_builder.add_file(name, decode(content), mode=0o600)
144 return cert_files_builder.build()
145
David Garcia49379ce2021-02-24 13:48:22 +0100146 def build_pod_spec(self, image_info):
147 # Validate config
148 config = ConfigModel(**dict(self.config))
149 # Check relations
150 self._check_missing_dependencies(config)
151 # Create Builder for the PodSpec
152 pod_spec_builder = PodSpecV3Builder()
153 # Build Container
154 container_builder = ContainerV3Builder(self.app.name, image_info)
David Garcia5d1ec6e2021-03-25 15:04:52 +0100155 certs_files = self._build_cert_files(config)
156 if certs_files:
157 container_builder.add_volume_config("certs", "/certs", certs_files)
David Garcia49379ce2021-02-24 13:48:22 +0100158 container_builder.add_port(name=self.app.name, port=PORT)
159 container_builder.add_http_readiness_probe(
160 "/ro/" if config.enable_ng_ro else "/openmano/tenants",
161 PORT,
162 initial_delay_seconds=10,
163 period_seconds=10,
164 timeout_seconds=5,
165 failure_threshold=3,
166 )
167 container_builder.add_http_liveness_probe(
168 "/ro/" if config.enable_ng_ro else "/openmano/tenants",
169 PORT,
170 initial_delay_seconds=600,
171 period_seconds=10,
172 timeout_seconds=5,
173 failure_threshold=3,
174 )
175 container_builder.add_envs(
176 {
177 "OSMRO_LOG_LEVEL": config.log_level,
178 }
179 )
180 if config.enable_ng_ro:
181 container_builder.add_envs(
182 {
183 "OSMRO_MESSAGE_DRIVER": "kafka",
184 "OSMRO_MESSAGE_HOST": self.kafka_client.host,
185 "OSMRO_MESSAGE_PORT": self.kafka_client.port,
186 # MongoDB configuration
187 "OSMRO_DATABASE_DRIVER": "mongo",
188 "OSMRO_DATABASE_URI": self.mongodb_client.connection_string,
189 "OSMRO_DATABASE_COMMONKEY": config.database_commonkey,
190 }
sousaeduccfacbb2020-11-04 21:44:01 +0000191 )
sousaeduccfacbb2020-11-04 21:44:01 +0000192
David Garcia49379ce2021-02-24 13:48:22 +0100193 else:
194 container_builder.add_envs(
195 {
196 "RO_DB_HOST": self.mysql_client.host,
197 "RO_DB_OVIM_HOST": self.mysql_client.host,
198 "RO_DB_PORT": self.mysql_client.port,
199 "RO_DB_OVIM_PORT": self.mysql_client.port,
200 "RO_DB_USER": self.mysql_client.user,
201 "RO_DB_OVIM_USER": self.mysql_client.user,
202 "RO_DB_PASSWORD": self.mysql_client.password,
203 "RO_DB_OVIM_PASSWORD": self.mysql_client.password,
204 "RO_DB_ROOT_PASSWORD": self.mysql_client.root_password,
205 "RO_DB_OVIM_ROOT_PASSWORD": self.mysql_client.root_password,
206 "RO_DB_NAME": config.ro_database,
207 "RO_DB_OVIM_NAME": config.vim_database,
208 "OPENMANO_TENANT": config.openmano_tenant,
209 }
210 )
211 container = container_builder.build()
212 # Add container to pod spec
213 pod_spec_builder.add_container(container)
214 return pod_spec_builder.build()
sousaeduccfacbb2020-11-04 21:44:01 +0000215
216
217if __name__ == "__main__":
218 main(RoCharm)