2 # Copyright 2020 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
24 from typing
import Any
, Dict
, NoReturn
26 from ops
.charm
import CharmBase
, CharmEvents
27 from ops
.framework
import EventBase
, EventSource
, StoredState
28 from ops
.main
import main
29 from ops
.model
import ActiveStatus
, BlockedStatus
, MaintenanceStatus
30 from oci_image
import OCIImageResource
, OCIImageResourceError
32 from pod_spec
import make_pod_spec
34 LOGGER
= logging
.getLogger(__name__
)
39 class ConfigurePodEvent(EventBase
):
40 """Configure Pod event"""
45 class MonEvents(CharmEvents
):
48 configure_pod
= EventSource(ConfigurePodEvent
)
51 class MonCharm(CharmBase
):
57 def __init__(self
, *args
) -> NoReturn
:
58 """MON Charm constructor."""
59 super().__init
__(*args
)
61 # Internal state initialization
62 self
.state
.set_default(pod_spec
=None)
64 # Message bus data initialization
65 self
.state
.set_default(message_host
=None)
66 self
.state
.set_default(message_port
=None)
68 # Database data initialization
69 self
.state
.set_default(database_uri
=None)
71 # Prometheus data initialization
72 self
.state
.set_default(prometheus_host
=None)
73 self
.state
.set_default(prometheus_port
=None)
76 self
.image
= OCIImageResource(self
, "image")
78 # Registering regular events
79 self
.framework
.observe(self
.on
.start
, self
.configure_pod
)
80 self
.framework
.observe(self
.on
.config_changed
, self
.configure_pod
)
81 self
.framework
.observe(self
.on
.upgrade_charm
, self
.configure_pod
)
83 # Registering custom internal events
84 self
.framework
.observe(self
.on
.configure_pod
, self
.configure_pod
)
86 # Registering required relation events
87 self
.framework
.observe(
88 self
.on
.kafka_relation_changed
, self
._on
_kafka
_relation
_changed
90 self
.framework
.observe(
91 self
.on
.mongodb_relation_changed
, self
._on
_mongodb
_relation
_changed
93 self
.framework
.observe(
94 self
.on
.prometheus_relation_changed
, self
._on
_prometheus
_relation
_changed
97 # Registering required relation departed events
98 self
.framework
.observe(
99 self
.on
.kafka_relation_departed
, self
._on
_kafka
_relation
_departed
101 self
.framework
.observe(
102 self
.on
.mongodb_relation_departed
, self
._on
_mongodb
_relation
_departed
104 self
.framework
.observe(
105 self
.on
.prometheus_relation_departed
, self
._on
_prometheus
_relation
_departed
108 def _on_kafka_relation_changed(self
, event
: EventBase
) -> NoReturn
:
109 """Reads information about the kafka relation.
112 event (EventBase): Kafka relation event.
114 message_host
= event
.relation
.data
[event
.unit
].get("host")
115 message_port
= event
.relation
.data
[event
.unit
].get("port")
121 self
.state
.message_host
!= message_host
122 or self
.state
.message_port
!= message_port
125 self
.state
.message_host
= message_host
126 self
.state
.message_port
= message_port
127 self
.on
.configure_pod
.emit()
129 def _on_kafka_relation_departed(self
, event
: EventBase
) -> NoReturn
:
130 """Clear kafka relation data.
133 event (EventBase): Kafka relation event.
135 self
.state
.message_host
= None
136 self
.state
.message_port
= None
137 self
.on
.configure_pod
.emit()
139 def _on_mongodb_relation_changed(self
, event
: EventBase
) -> NoReturn
:
140 """Reads information about the DB relation.
143 event (EventBase): DB relation event.
145 database_uri
= event
.relation
.data
[event
.unit
].get("connection_string")
147 if database_uri
and self
.state
.database_uri
!= database_uri
:
148 self
.state
.database_uri
= database_uri
149 self
.on
.configure_pod
.emit()
151 def _on_mongodb_relation_departed(self
, event
: EventBase
) -> NoReturn
:
152 """Clear mongodb relation data.
155 event (EventBase): DB relation event.
157 self
.state
.database_uri
= None
158 self
.on
.configure_pod
.emit()
160 def _on_prometheus_relation_changed(self
, event
: EventBase
) -> NoReturn
:
161 """Reads information about the prometheus relation.
164 event (EventBase): Prometheus relation event.
166 prometheus_host
= event
.relation
.data
[event
.unit
].get("hostname")
167 prometheus_port
= event
.relation
.data
[event
.unit
].get("port")
173 self
.state
.prometheus_host
!= prometheus_host
174 or self
.state
.prometheus_port
!= prometheus_port
177 self
.state
.prometheus_host
= prometheus_host
178 self
.state
.prometheus_port
= prometheus_port
179 self
.on
.configure_pod
.emit()
181 def _on_prometheus_relation_departed(self
, event
: EventBase
) -> NoReturn
:
182 """Clear prometheus relation data.
185 event (EventBase): Prometheus relation event.
187 self
.state
.prometheus_host
= None
188 self
.state
.prometheus_port
= None
189 self
.on
.configure_pod
.emit()
191 def _missing_relations(self
) -> str:
192 """Checks if there missing relations.
195 str: string with missing relations
198 "kafka": self
.state
.message_host
,
199 "mongodb": self
.state
.database_uri
,
200 "prometheus": self
.state
.prometheus_host
,
203 missing_relations
= [k
for k
, v
in data_status
.items() if not v
]
205 return ", ".join(missing_relations
)
208 def relation_state(self
) -> Dict
[str, Any
]:
209 """Collects relation state configuration for pod spec assembly.
212 Dict[str, Any]: relation state information.
215 "message_host": self
.state
.message_host
,
216 "message_port": self
.state
.message_port
,
217 "database_uri": self
.state
.database_uri
,
218 "prometheus_host": self
.state
.prometheus_host
,
219 "prometheus_port": self
.state
.prometheus_port
,
222 return relation_state
224 def configure_pod(self
, event
: EventBase
) -> NoReturn
:
225 """Assemble the pod spec and apply it, if possible.
228 event (EventBase): Hook or Relation event that started the
231 if missing
:= self
._missing
_relations
():
232 self
.unit
.status
= BlockedStatus(
233 "Waiting for {0} relation{1}".format(
234 missing
, "s" if "," in missing
else ""
239 if not self
.unit
.is_leader():
240 self
.unit
.status
= ActiveStatus("ready")
243 self
.unit
.status
= MaintenanceStatus("Assembling pod spec")
245 # Fetch image information
247 self
.unit
.status
= MaintenanceStatus("Fetching image information")
248 image_info
= self
.image
.fetch()
249 except OCIImageResourceError
:
250 self
.unit
.status
= BlockedStatus("Error fetching image information")
254 pod_spec
= make_pod_spec(
261 except ValueError as exc
:
262 LOGGER
.exception("Config/Relation data validation error")
263 self
.unit
.status
= BlockedStatus(str(exc
))
266 if self
.state
.pod_spec
!= pod_spec
:
267 self
.model
.pod
.set_spec(pod_spec
)
268 self
.state
.pod_spec
= pod_spec
270 self
.unit
.status
= ActiveStatus("ready")
273 if __name__
== "__main__":