blob: 7510a6cbb7c87230a74eb4d2173dcbd6820b670d [file] [log] [blame]
beierlma4a37f72020-06-26 12:55:01 -04001#!/usr/bin/env python3
David Garciaef349d92020-12-10 21:16:12 +01002# Copyright 2020 Canonical Ltd.
beierlma4a37f72020-06-26 12:55:01 -04003#
David Garciaef349d92020-12-10 21:16:12 +01004# 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
beierlma4a37f72020-06-26 12:55:01 -04007#
David Garciaef349d92020-12-10 21:16:12 +01008# http://www.apache.org/licenses/LICENSE-2.0
beierlma4a37f72020-06-26 12:55:01 -04009#
David Garciaef349d92020-12-10 21:16:12 +010010# 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##
beierlma4a37f72020-06-26 12:55:01 -040022
beierlmb1a1c462020-10-23 14:54:56 -040023import logging
David Garciaef349d92020-12-10 21:16:12 +010024from typing import Any, Dict, NoReturn
25from pydantic import ValidationError
beierlma4a37f72020-06-26 12:55:01 -040026
David Garciaef349d92020-12-10 21:16:12 +010027from ops.charm import CharmBase, CharmEvents
28from ops.framework import EventBase, EventSource, StoredState
beierlma4a37f72020-06-26 12:55:01 -040029from ops.main import main
David Garciaef349d92020-12-10 21:16:12 +010030from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus
31from oci_image import OCIImageResource, OCIImageResourceError
beierlma4a37f72020-06-26 12:55:01 -040032
David Garciaef349d92020-12-10 21:16:12 +010033from pod_spec import make_pod_spec
beierlmb1a1c462020-10-23 14:54:56 -040034
beierlma4a37f72020-06-26 12:55:01 -040035logger = logging.getLogger(__name__)
36
David Garciaef349d92020-12-10 21:16:12 +010037NGUI_PORT = 80
beierlma4a37f72020-06-26 12:55:01 -040038
David Garciaef349d92020-12-10 21:16:12 +010039
40class ConfigurePodEvent(EventBase):
41 """Configure Pod event"""
42
43 pass
44
45
46class NgUiEvents(CharmEvents):
47 """NGUI Events"""
48
49 configure_pod = EventSource(ConfigurePodEvent)
50
51
52class NgUiCharm(CharmBase):
53 """NGUI Charm."""
54
beierlma4a37f72020-06-26 12:55:01 -040055 state = StoredState()
David Garciaef349d92020-12-10 21:16:12 +010056 on = NgUiEvents()
beierlma4a37f72020-06-26 12:55:01 -040057
David Garciaef349d92020-12-10 21:16:12 +010058 def __init__(self, *args) -> NoReturn:
59 """NGUI Charm constructor."""
60 super().__init__(*args)
61
62 # Internal state initialization
63 self.state.set_default(pod_spec=None)
64
65 # North bound interface initialization
beierlma4a37f72020-06-26 12:55:01 -040066 self.state.set_default(nbi_host=None)
67 self.state.set_default(nbi_port=None)
68
David Garciaef349d92020-12-10 21:16:12 +010069 self.http_port = NGUI_PORT
70 self.image = OCIImageResource(self, "image")
71
72 # Registering regular events
73 self.framework.observe(self.on.start, self.configure_pod)
74 self.framework.observe(self.on.config_changed, self.configure_pod)
75 # self.framework.observe(self.on.upgrade_charm, self.configure_pod)
76
77 # Registering custom internal events
78 self.framework.observe(self.on.configure_pod, self.configure_pod)
79
80 # Registering required relation changed events
beierlma4a37f72020-06-26 12:55:01 -040081 self.framework.observe(
David Garciaef349d92020-12-10 21:16:12 +010082 self.on.nbi_relation_changed, self._on_nbi_relation_changed
beierlma4a37f72020-06-26 12:55:01 -040083 )
84
David Garciaef349d92020-12-10 21:16:12 +010085 # Registering required relation departed events
86 self.framework.observe(
87 self.on.nbi_relation_departed, self._on_nbi_relation_departed
88 )
beierlma4a37f72020-06-26 12:55:01 -040089
David Garciaef349d92020-12-10 21:16:12 +010090 def _on_nbi_relation_changed(self, event: EventBase) -> NoReturn:
91 """Reads information about the nbi relation.
beierlma4a37f72020-06-26 12:55:01 -040092
David Garciaef349d92020-12-10 21:16:12 +010093 Args:
94 event (EventBase): NBI relation event.
95 """
96 data_loc = event.unit if event.unit else event.app
97 logger.error(dict(event.relation.data))
98 nbi_host = event.relation.data[data_loc].get("host")
99 nbi_port = event.relation.data[data_loc].get("port")
beierlma4a37f72020-06-26 12:55:01 -0400100
David Garciaef349d92020-12-10 21:16:12 +0100101 if (
102 nbi_host
103 and nbi_port
104 and (self.state.nbi_host != nbi_host or self.state.nbi_port != nbi_port)
105 ):
David Garcia68faf8d2020-09-01 10:12:16 +0200106 self.state.nbi_host = nbi_host
David Garcia68faf8d2020-09-01 10:12:16 +0200107 self.state.nbi_port = nbi_port
David Garciaef349d92020-12-10 21:16:12 +0100108 self.on.configure_pod.emit()
109
110 def _on_nbi_relation_departed(self, event: EventBase) -> NoReturn:
111 """Clears data from nbi relation.
112
113 Args:
114 event (EventBase): NBI relation event.
115 """
116 self.state.nbi_host = None
117 self.state.nbi_port = None
118 self.on.configure_pod.emit()
119
120 def _missing_relations(self) -> str:
121 """Checks if there missing relations.
122
123 Returns:
124 str: string with missing relations
125 """
126 data_status = {
127 "nbi": self.state.nbi_host,
128 }
129
130 missing_relations = [k for k, v in data_status.items() if not v]
131
132 return ", ".join(missing_relations)
133
134 @property
135 def relation_state(self) -> Dict[str, Any]:
136 """Collects relation state configuration for pod spec assembly.
137
138 Returns:
139 Dict[str, Any]: relation state information.
140 """
141 relation_state = {
142 "nbi_host": self.state.nbi_host,
143 "nbi_port": self.state.nbi_port,
144 }
145 return relation_state
146
147 def configure_pod(self, event: EventBase) -> NoReturn:
148 """Assemble the pod spec and apply it, if possible.
149
150 Args:
151 event (EventBase): Hook or Relation event that started the
152 function.
153 """
154 if missing := self._missing_relations():
155 self.unit.status = BlockedStatus(
156 f"Waiting for {missing} relation{'s' if ',' in missing else ''}"
157 )
158 return
159
160 if not self.unit.is_leader():
161 self.unit.status = ActiveStatus("ready")
162 return
163
164 self.unit.status = MaintenanceStatus("Assembling pod spec")
165
166 # Fetch image information
167 try:
168 self.unit.status = MaintenanceStatus("Fetching image information")
169 image_info = self.image.fetch()
170 except OCIImageResourceError:
171 self.unit.status = BlockedStatus("Error fetching image information")
172 return
173
174 try:
175 pod_spec = make_pod_spec(
176 image_info,
177 self.config,
178 self.relation_state,
179 self.model.app.name,
180 )
181 except ValidationError as exc:
182 logger.exception("Config/Relation data validation error")
183 self.unit.status = BlockedStatus(str(exc))
184 return
185
186 if self.state.pod_spec != pod_spec:
187 self.model.pod.set_spec(pod_spec)
188 self.state.pod_spec = pod_spec
189
190 self.unit.status = ActiveStatus("ready")
beierlma4a37f72020-06-26 12:55:01 -0400191
192
193if __name__ == "__main__":
David Garciaef349d92020-12-10 21:16:12 +0100194 main(NgUiCharm)