blob: 21102e4c651e4168686560cb2c4183cedd893319 [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 """
David Garciaab11f842020-12-16 17:25:15 +010096 if not event.unit in event.relation.data:
97 return
98 relation_data = event.relation.data[event.unit]
99 nbi_host = relation_data.get("host")
100 nbi_port = relation_data.get("port")
beierlma4a37f72020-06-26 12:55:01 -0400101
David Garciaef349d92020-12-10 21:16:12 +0100102 if (
103 nbi_host
104 and nbi_port
105 and (self.state.nbi_host != nbi_host or self.state.nbi_port != nbi_port)
106 ):
David Garcia68faf8d2020-09-01 10:12:16 +0200107 self.state.nbi_host = nbi_host
David Garcia68faf8d2020-09-01 10:12:16 +0200108 self.state.nbi_port = nbi_port
David Garciaef349d92020-12-10 21:16:12 +0100109 self.on.configure_pod.emit()
110
111 def _on_nbi_relation_departed(self, event: EventBase) -> NoReturn:
112 """Clears data from nbi relation.
113
114 Args:
115 event (EventBase): NBI relation event.
116 """
117 self.state.nbi_host = None
118 self.state.nbi_port = None
119 self.on.configure_pod.emit()
120
121 def _missing_relations(self) -> str:
122 """Checks if there missing relations.
123
124 Returns:
125 str: string with missing relations
126 """
127 data_status = {
128 "nbi": self.state.nbi_host,
129 }
130
131 missing_relations = [k for k, v in data_status.items() if not v]
132
133 return ", ".join(missing_relations)
134
135 @property
136 def relation_state(self) -> Dict[str, Any]:
137 """Collects relation state configuration for pod spec assembly.
138
139 Returns:
140 Dict[str, Any]: relation state information.
141 """
142 relation_state = {
143 "nbi_host": self.state.nbi_host,
144 "nbi_port": self.state.nbi_port,
145 }
146 return relation_state
147
148 def configure_pod(self, event: EventBase) -> NoReturn:
149 """Assemble the pod spec and apply it, if possible.
150
151 Args:
152 event (EventBase): Hook or Relation event that started the
153 function.
154 """
155 if missing := self._missing_relations():
156 self.unit.status = BlockedStatus(
157 f"Waiting for {missing} relation{'s' if ',' in missing else ''}"
158 )
159 return
160
161 if not self.unit.is_leader():
162 self.unit.status = ActiveStatus("ready")
163 return
164
165 self.unit.status = MaintenanceStatus("Assembling pod spec")
166
167 # Fetch image information
168 try:
169 self.unit.status = MaintenanceStatus("Fetching image information")
170 image_info = self.image.fetch()
171 except OCIImageResourceError:
172 self.unit.status = BlockedStatus("Error fetching image information")
173 return
174
175 try:
176 pod_spec = make_pod_spec(
177 image_info,
178 self.config,
179 self.relation_state,
180 self.model.app.name,
181 )
182 except ValidationError as exc:
183 logger.exception("Config/Relation data validation error")
184 self.unit.status = BlockedStatus(str(exc))
185 return
186
187 if self.state.pod_spec != pod_spec:
188 self.model.pod.set_spec(pod_spec)
189 self.state.pod_spec = pod_spec
190
191 self.unit.status = ActiveStatus("ready")
beierlma4a37f72020-06-26 12:55:01 -0400192
193
194if __name__ == "__main__":
David Garciaef349d92020-12-10 21:16:12 +0100195 main(NgUiCharm)