blob: 270a5479a837a791859d2f2143eb5e71c5afb030 [file] [log] [blame]
sousaeduabe73212020-11-04 15:13:35 +00001#!/usr/bin/env python3
David Garcia49379ce2021-02-24 13:48:22 +01002# Copyright 2021 Canonical Ltd.
sousaeduabe73212020-11-04 15:13:35 +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
25
sousaeduabe73212020-11-04 15:13:35 +000026import logging
David Garcia49379ce2021-02-24 13:48:22 +010027from typing import Optional, NoReturn
sousaeduabe73212020-11-04 15:13:35 +000028
sousaeduabe73212020-11-04 15:13:35 +000029from ops.main import main
sousaeduabe73212020-11-04 15:13:35 +000030
David Garcia49379ce2021-02-24 13:48:22 +010031from opslib.osm.charm import CharmedOsmBase, RelationsMissing
32
33from opslib.osm.pod import (
34 ContainerV3Builder,
35 PodSpecV3Builder,
36)
37
38from opslib.osm.validator import (
39 ModelValidator,
40 validator,
41)
42
43from opslib.osm.interfaces.kafka import KafkaClient
44from opslib.osm.interfaces.mongo import MongoClient
45from opslib.osm.interfaces.http import HttpClient
46
sousaeduabe73212020-11-04 15:13:35 +000047
48logger = logging.getLogger(__name__)
49
David Garcia49379ce2021-02-24 13:48:22 +010050PORT = 9999
sousaeduabe73212020-11-04 15:13:35 +000051
52
David Garcia49379ce2021-02-24 13:48:22 +010053class ConfigModel(ModelValidator):
54 vca_host: str
55 vca_port: int
56 vca_user: str
57 vca_password: str
58 vca_pubkey: str
59 vca_cacert: str
60 vca_cloud: str
61 vca_k8s_cloud: str
62 database_commonkey: str
63 log_level: str
64 vca_apiproxy: Optional[str]
sousaeduabe73212020-11-04 15:13:35 +000065
David Garcia49379ce2021-02-24 13:48:22 +010066 @validator("log_level")
67 def validate_log_level(cls, v):
68 if v not in {"INFO", "DEBUG"}:
69 raise ValueError("value must be INFO or DEBUG")
70 return v
sousaeduabe73212020-11-04 15:13:35 +000071
72
David Garcia49379ce2021-02-24 13:48:22 +010073class LcmCharm(CharmedOsmBase):
sousaeduabe73212020-11-04 15:13:35 +000074 def __init__(self, *args) -> NoReturn:
David Garcia49379ce2021-02-24 13:48:22 +010075 super().__init__(*args, oci_image="image")
sousaeduabe73212020-11-04 15:13:35 +000076
David Garcia49379ce2021-02-24 13:48:22 +010077 self.kafka_client = KafkaClient(self, "kafka")
78 self.framework.observe(self.on["kafka"].relation_changed, self.configure_pod)
79 self.framework.observe(self.on["kafka"].relation_broken, self.configure_pod)
sousaeduabe73212020-11-04 15:13:35 +000080
David Garcia49379ce2021-02-24 13:48:22 +010081 self.mongodb_client = MongoClient(self, "mongodb")
82 self.framework.observe(self.on["mongodb"].relation_changed, self.configure_pod)
83 self.framework.observe(self.on["mongodb"].relation_broken, self.configure_pod)
sousaeduabe73212020-11-04 15:13:35 +000084
David Garcia49379ce2021-02-24 13:48:22 +010085 self.ro_client = HttpClient(self, "ro")
86 self.framework.observe(self.on["ro"].relation_changed, self.configure_pod)
87 self.framework.observe(self.on["ro"].relation_broken, self.configure_pod)
sousaeduabe73212020-11-04 15:13:35 +000088
David Garcia49379ce2021-02-24 13:48:22 +010089 def _check_missing_dependencies(self, config: ConfigModel):
90 missing_relations = []
sousaeduabe73212020-11-04 15:13:35 +000091
David Garcia49379ce2021-02-24 13:48:22 +010092 if self.kafka_client.is_missing_data_in_unit():
93 missing_relations.append("kafka")
94 if self.mongodb_client.is_missing_data_in_unit():
95 missing_relations.append("mongodb")
96 if self.ro_client.is_missing_data_in_app():
97 missing_relations.append("ro")
sousaeduabe73212020-11-04 15:13:35 +000098
David Garcia49379ce2021-02-24 13:48:22 +010099 if missing_relations:
100 raise RelationsMissing(missing_relations)
sousaeduabe73212020-11-04 15:13:35 +0000101
David Garcia49379ce2021-02-24 13:48:22 +0100102 def build_pod_spec(self, image_info):
103 # Validate config
104 config = ConfigModel(**dict(self.config))
105 # Check relations
106 self._check_missing_dependencies(config)
107 # Create Builder for the PodSpec
108 pod_spec_builder = PodSpecV3Builder()
109 # Build Container
110 container_builder = ContainerV3Builder(self.app.name, image_info)
111 container_builder.add_port(name=self.app.name, port=PORT)
112 container_builder.add_envs(
113 {
114 # General configuration
115 "ALLOW_ANONYMOUS_LOGIN": "yes",
116 "OSMLCM_GLOBAL_LOGLEVEL": config.log_level,
117 # RO configuration
118 "OSMLCM_RO_HOST": self.ro_client.host,
119 "OSMLCM_RO_PORT": self.ro_client.port,
120 "OSMLCM_RO_TENANT": "osm",
121 # Kafka configuration
122 "OSMLCM_MESSAGE_DRIVER": "kafka",
123 "OSMLCM_MESSAGE_HOST": self.kafka_client.host,
124 "OSMLCM_MESSAGE_PORT": self.kafka_client.port,
125 # Database configuration
126 "OSMLCM_DATABASE_DRIVER": "mongo",
127 "OSMLCM_DATABASE_URI": self.mongodb_client.connection_string,
128 "OSMLCM_DATABASE_COMMONKEY": config.database_commonkey,
129 # Storage configuration
130 "OSMLCM_STORAGE_DRIVER": "mongo",
131 "OSMLCM_STORAGE_PATH": "/app/storage",
132 "OSMLCM_STORAGE_COLLECTION": "files",
133 "OSMLCM_STORAGE_URI": self.mongodb_client.connection_string,
134 # VCA configuration
135 "OSMLCM_VCA_HOST": config.vca_host,
136 "OSMLCM_VCA_PORT": config.vca_port,
137 "OSMLCM_VCA_USER": config.vca_user,
138 "OSMLCM_VCA_PUBKEY": config.vca_pubkey,
139 "OSMLCM_VCA_SECRET": config.vca_password,
140 "OSMLCM_VCA_CACERT": config.vca_cacert,
141 "OSMLCM_VCA_CLOUD": config.vca_cloud,
142 "OSMLCM_VCA_K8S_CLOUD": config.vca_k8s_cloud,
143 }
sousaeduabe73212020-11-04 15:13:35 +0000144 )
David Garcia49379ce2021-02-24 13:48:22 +0100145 if config.vca_apiproxy:
146 container_builder.add_env("OSMLCM_VCA_APIPROXY", config.vca_apiproxy)
sousaeduabe73212020-11-04 15:13:35 +0000147
David Garcia49379ce2021-02-24 13:48:22 +0100148 container = container_builder.build()
149 # Add container to pod spec
150 pod_spec_builder.add_container(container)
151 return pod_spec_builder.build()
sousaeduabe73212020-11-04 15:13:35 +0000152
153
154if __name__ == "__main__":
155 main(LcmCharm)
David Garcia49379ce2021-02-24 13:48:22 +0100156
157
158# class ConfigurePodEvent(EventBase):
159# """Configure Pod event"""
160
161# pass
162
163
164# class LcmEvents(CharmEvents):
165# """LCM Events"""
166
167# configure_pod = EventSource(ConfigurePodEvent)
168
169
170# class LcmCharm(CharmBase):
171# """LCM Charm."""
172
173# state = StoredState()
174# on = LcmEvents()
175
176# def __init__(self, *args) -> NoReturn:
177# """LCM Charm constructor."""
178# super().__init__(*args)
179
180# # Internal state initialization
181# self.state.set_default(pod_spec=None)
182
183# # Message bus data initialization
184# self.state.set_default(message_host=None)
185# self.state.set_default(message_port=None)
186
187# # Database data initialization
188# self.state.set_default(database_uri=None)
189
190# # RO data initialization
191# self.state.set_default(ro_host=None)
192# self.state.set_default(ro_port=None)
193
194# self.port = LCM_PORT
195# self.image = OCIImageResource(self, "image")
196
197# # Registering regular events
198# self.framework.observe(self.on.start, self.configure_pod)
199# self.framework.observe(self.on.config_changed, self.configure_pod)
200# self.framework.observe(self.on.upgrade_charm, self.configure_pod)
201
202# # Registering custom internal events
203# self.framework.observe(self.on.configure_pod, self.configure_pod)
204
205# # Registering required relation events
206# self.framework.observe(
207# self.on.kafka_relation_changed, self._on_kafka_relation_changed
208# )
209# self.framework.observe(
210# self.on.mongodb_relation_changed, self._on_mongodb_relation_changed
211# )
212# self.framework.observe(
213# self.on.ro_relation_changed, self._on_ro_relation_changed
214# )
215
216# # Registering required relation broken events
217# self.framework.observe(
218# self.on.kafka_relation_broken, self._on_kafka_relation_broken
219# )
220# self.framework.observe(
221# self.on.mongodb_relation_broken, self._on_mongodb_relation_broken
222# )
223# self.framework.observe(
224# self.on.ro_relation_broken, self._on_ro_relation_broken
225# )
226
227# def _on_kafka_relation_changed(self, event: EventBase) -> NoReturn:
228# """Reads information about the kafka relation.
229
230# Args:
231# event (EventBase): Kafka relation event.
232# """
233# message_host = event.relation.data[event.unit].get("host")
234# message_port = event.relation.data[event.unit].get("port")
235
236# if (
237# message_host
238# and message_port
239# and (
240# self.state.message_host != message_host
241# or self.state.message_port != message_port
242# )
243# ):
244# self.state.message_host = message_host
245# self.state.message_port = message_port
246# self.on.configure_pod.emit()
247
248# def _on_kafka_relation_broken(self, event: EventBase) -> NoReturn:
249# """Clears data from kafka relation.
250
251# Args:
252# event (EventBase): Kafka relation event.
253# """
254# self.state.message_host = None
255# self.state.message_port = None
256# self.on.configure_pod.emit()
257
258# def _on_mongodb_relation_changed(self, event: EventBase) -> NoReturn:
259# """Reads information about the DB relation.
260
261# Args:
262# event (EventBase): DB relation event.
263# """
264# database_uri = event.relation.data[event.unit].get("connection_string")
265
266# if database_uri and self.state.database_uri != database_uri:
267# self.state.database_uri = database_uri
268# self.on.configure_pod.emit()
269
270# def _on_mongodb_relation_broken(self, event: EventBase) -> NoReturn:
271# """Clears data from mongodb relation.
272
273# Args:
274# event (EventBase): DB relation event.
275# """
276# self.state.database_uri = None
277# self.on.configure_pod.emit()
278
279# def _on_ro_relation_changed(self, event: EventBase) -> NoReturn:
280# """Reads information about the RO relation.
281
282# Args:
283# event (EventBase): Keystone relation event.
284# """
285# ro_host = event.relation.data[event.unit].get("host")
286# ro_port = event.relation.data[event.unit].get("port")
287
288# if (
289# ro_host
290# and ro_port
291# and (self.state.ro_host != ro_host or self.state.ro_port != ro_port)
292# ):
293# self.state.ro_host = ro_host
294# self.state.ro_port = ro_port
295# self.on.configure_pod.emit()
296
297# def _on_ro_relation_broken(self, event: EventBase) -> NoReturn:
298# """Clears data from ro relation.
299
300# Args:
301# event (EventBase): Keystone relation event.
302# """
303# self.state.ro_host = None
304# self.state.ro_port = None
305# self.on.configure_pod.emit()
306
307# def _missing_relations(self) -> str:
308# """Checks if there missing relations.
309
310# Returns:
311# str: string with missing relations
312# """
313# data_status = {
314# "kafka": self.state.message_host,
315# "mongodb": self.state.database_uri,
316# "ro": self.state.ro_host,
317# }
318
319# missing_relations = [k for k, v in data_status.items() if not v]
320
321# return ", ".join(missing_relations)
322
323# @property
324# def relation_state(self) -> Dict[str, Any]:
325# """Collects relation state configuration for pod spec assembly.
326
327# Returns:
328# Dict[str, Any]: relation state information.
329# """
330# relation_state = {
331# "message_host": self.state.message_host,
332# "message_port": self.state.message_port,
333# "database_uri": self.state.database_uri,
334# "ro_host": self.state.ro_host,
335# "ro_port": self.state.ro_port,
336# }
337
338# return relation_state
339
340# def configure_pod(self, event: EventBase) -> NoReturn:
341# """Assemble the pod spec and apply it, if possible.
342
343# Args:
344# event (EventBase): Hook or Relation event that started the
345# function.
346# """
347# if missing := self._missing_relations():
348# self.unit.status = BlockedStatus(
349# "Waiting for {0} relation{1}".format(
350# missing, "s" if "," in missing else ""
351# )
352# )
353# return
354
355# if not self.unit.is_leader():
356# self.unit.status = ActiveStatus("ready")
357# return
358
359# self.unit.status = MaintenanceStatus("Assembling pod spec")
360
361# # Fetch image information
362# try:
363# self.unit.status = MaintenanceStatus("Fetching image information")
364# image_info = self.image.fetch()
365# except OCIImageResourceError:
366# self.unit.status = BlockedStatus("Error fetching image information")
367# return
368
369# try:
370# pod_spec = make_pod_spec(
371# image_info,
372# self.model.config,
373# self.relation_state,
374# self.model.app.name,
375# self.port,
376# )
377# except ValueError as exc:
378# logger.exception("Config/Relation data validation error")
379# self.unit.status = BlockedStatus(str(exc))
380# return
381
382# if self.state.pod_spec != pod_spec:
383# self.model.pod.set_spec(pod_spec)
384# self.state.pod_spec = pod_spec
385
386# self.unit.status = ActiveStatus("ready")
387
388
389# if __name__ == "__main__":
390# main(LcmCharm)