2 # Copyright 2021 Canonical Ltd.
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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
16 # For those usages not covered by the Apache License, Version 2.0 please
17 # contact: legal@canonical.com
19 # To get in touch with the maintainers, please contact:
20 # osm-charmers@lists.launchpad.net
23 # pylint: disable=E0213
27 from typing
import Optional
, NoReturn
29 from ops
.main
import main
31 from opslib
.osm
.charm
import CharmedOsmBase
, RelationsMissing
33 from opslib
.osm
.pod
import (
38 from opslib
.osm
.validator
import (
43 from opslib
.osm
.interfaces
.kafka
import KafkaClient
44 from opslib
.osm
.interfaces
.mongo
import MongoClient
45 from opslib
.osm
.interfaces
.http
import HttpClient
48 logger
= logging
.getLogger(__name__
)
53 class ConfigModel(ModelValidator
):
62 database_commonkey
: str
64 vca_apiproxy
: Optional
[str]
66 @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")
73 class LcmCharm(CharmedOsmBase
):
74 def __init__(self
, *args
) -> NoReturn
:
75 super().__init
__(*args
, oci_image
="image")
77 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
)
81 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
)
85 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
)
89 def _check_missing_dependencies(self
, config
: ConfigModel
):
90 missing_relations
= []
92 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")
100 raise RelationsMissing(missing_relations
)
102 def build_pod_spec(self
, image_info
):
104 config
= ConfigModel(**dict(self
.config
))
106 self
._check
_missing
_dependencies
(config
)
107 # Create Builder for the PodSpec
108 pod_spec_builder
= PodSpecV3Builder()
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(
114 # General configuration
115 "ALLOW_ANONYMOUS_LOGIN": "yes",
116 "OSMLCM_GLOBAL_LOGLEVEL": config
.log_level
,
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
,
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
,
145 if config
.vca_apiproxy
:
146 container_builder
.add_env("OSMLCM_VCA_APIPROXY", config
.vca_apiproxy
)
148 container
= container_builder
.build()
149 # Add container to pod spec
150 pod_spec_builder
.add_container(container
)
151 return pod_spec_builder
.build()
154 if __name__
== "__main__":
158 # class ConfigurePodEvent(EventBase):
159 # """Configure Pod event"""
164 # class LcmEvents(CharmEvents):
167 # configure_pod = EventSource(ConfigurePodEvent)
170 # class LcmCharm(CharmBase):
173 # state = StoredState()
176 # def __init__(self, *args) -> NoReturn:
177 # """LCM Charm constructor."""
178 # super().__init__(*args)
180 # # Internal state initialization
181 # self.state.set_default(pod_spec=None)
183 # # Message bus data initialization
184 # self.state.set_default(message_host=None)
185 # self.state.set_default(message_port=None)
187 # # Database data initialization
188 # self.state.set_default(database_uri=None)
190 # # RO data initialization
191 # self.state.set_default(ro_host=None)
192 # self.state.set_default(ro_port=None)
194 # self.port = LCM_PORT
195 # self.image = OCIImageResource(self, "image")
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)
202 # # Registering custom internal events
203 # self.framework.observe(self.on.configure_pod, self.configure_pod)
205 # # Registering required relation events
206 # self.framework.observe(
207 # self.on.kafka_relation_changed, self._on_kafka_relation_changed
209 # self.framework.observe(
210 # self.on.mongodb_relation_changed, self._on_mongodb_relation_changed
212 # self.framework.observe(
213 # self.on.ro_relation_changed, self._on_ro_relation_changed
216 # # Registering required relation broken events
217 # self.framework.observe(
218 # self.on.kafka_relation_broken, self._on_kafka_relation_broken
220 # self.framework.observe(
221 # self.on.mongodb_relation_broken, self._on_mongodb_relation_broken
223 # self.framework.observe(
224 # self.on.ro_relation_broken, self._on_ro_relation_broken
227 # def _on_kafka_relation_changed(self, event: EventBase) -> NoReturn:
228 # """Reads information about the kafka relation.
231 # event (EventBase): Kafka relation event.
233 # message_host = event.relation.data[event.unit].get("host")
234 # message_port = event.relation.data[event.unit].get("port")
240 # self.state.message_host != message_host
241 # or self.state.message_port != message_port
244 # self.state.message_host = message_host
245 # self.state.message_port = message_port
246 # self.on.configure_pod.emit()
248 # def _on_kafka_relation_broken(self, event: EventBase) -> NoReturn:
249 # """Clears data from kafka relation.
252 # event (EventBase): Kafka relation event.
254 # self.state.message_host = None
255 # self.state.message_port = None
256 # self.on.configure_pod.emit()
258 # def _on_mongodb_relation_changed(self, event: EventBase) -> NoReturn:
259 # """Reads information about the DB relation.
262 # event (EventBase): DB relation event.
264 # database_uri = event.relation.data[event.unit].get("connection_string")
266 # if database_uri and self.state.database_uri != database_uri:
267 # self.state.database_uri = database_uri
268 # self.on.configure_pod.emit()
270 # def _on_mongodb_relation_broken(self, event: EventBase) -> NoReturn:
271 # """Clears data from mongodb relation.
274 # event (EventBase): DB relation event.
276 # self.state.database_uri = None
277 # self.on.configure_pod.emit()
279 # def _on_ro_relation_changed(self, event: EventBase) -> NoReturn:
280 # """Reads information about the RO relation.
283 # event (EventBase): Keystone relation event.
285 # ro_host = event.relation.data[event.unit].get("host")
286 # ro_port = event.relation.data[event.unit].get("port")
291 # and (self.state.ro_host != ro_host or self.state.ro_port != ro_port)
293 # self.state.ro_host = ro_host
294 # self.state.ro_port = ro_port
295 # self.on.configure_pod.emit()
297 # def _on_ro_relation_broken(self, event: EventBase) -> NoReturn:
298 # """Clears data from ro relation.
301 # event (EventBase): Keystone relation event.
303 # self.state.ro_host = None
304 # self.state.ro_port = None
305 # self.on.configure_pod.emit()
307 # def _missing_relations(self) -> str:
308 # """Checks if there missing relations.
311 # str: string with missing relations
314 # "kafka": self.state.message_host,
315 # "mongodb": self.state.database_uri,
316 # "ro": self.state.ro_host,
319 # missing_relations = [k for k, v in data_status.items() if not v]
321 # return ", ".join(missing_relations)
324 # def relation_state(self) -> Dict[str, Any]:
325 # """Collects relation state configuration for pod spec assembly.
328 # Dict[str, Any]: relation state information.
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,
338 # return relation_state
340 # def configure_pod(self, event: EventBase) -> NoReturn:
341 # """Assemble the pod spec and apply it, if possible.
344 # event (EventBase): Hook or Relation event that started the
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 ""
355 # if not self.unit.is_leader():
356 # self.unit.status = ActiveStatus("ready")
359 # self.unit.status = MaintenanceStatus("Assembling pod spec")
361 # # Fetch image information
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")
370 # pod_spec = make_pod_spec(
373 # self.relation_state,
374 # self.model.app.name,
377 # except ValueError as exc:
378 # logger.exception("Config/Relation data validation error")
379 # self.unit.status = BlockedStatus(str(exc))
382 # if self.state.pod_spec != pod_spec:
383 # self.model.pod.set_spec(pod_spec)
384 # self.state.pod_spec = pod_spec
386 # self.unit.status = ActiveStatus("ready")
389 # if __name__ == "__main__":