Migrate to new NBI charm, and change in NGUI charm
[osm/devops.git] / installers / charm / ng-ui / src / charm.py
1 #!/usr/bin/env python3
2 # Copyright 2020 Canonical Ltd.
3 #
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
23 import logging
24 from typing import Any, Dict, NoReturn
25 from pydantic import ValidationError
26
27 from ops.charm import CharmBase, CharmEvents
28 from ops.framework import EventBase, EventSource, StoredState
29 from ops.main import main
30 from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus
31 from oci_image import OCIImageResource, OCIImageResourceError
32
33 from pod_spec import make_pod_spec
34
35 logger = logging.getLogger(__name__)
36
37 NGUI_PORT = 80
38
39
40 class ConfigurePodEvent(EventBase):
41 """Configure Pod event"""
42
43 pass
44
45
46 class NgUiEvents(CharmEvents):
47 """NGUI Events"""
48
49 configure_pod = EventSource(ConfigurePodEvent)
50
51
52 class NgUiCharm(CharmBase):
53 """NGUI Charm."""
54
55 state = StoredState()
56 on = NgUiEvents()
57
58 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
66 self.state.set_default(nbi_host=None)
67 self.state.set_default(nbi_port=None)
68
69 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
81 self.framework.observe(
82 self.on.nbi_relation_changed, self._on_nbi_relation_changed
83 )
84
85 # Registering required relation departed events
86 self.framework.observe(
87 self.on.nbi_relation_departed, self._on_nbi_relation_departed
88 )
89
90 def _on_nbi_relation_changed(self, event: EventBase) -> NoReturn:
91 """Reads information about the nbi relation.
92
93 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")
100
101 if (
102 nbi_host
103 and nbi_port
104 and (self.state.nbi_host != nbi_host or self.state.nbi_port != nbi_port)
105 ):
106 self.state.nbi_host = nbi_host
107 self.state.nbi_port = nbi_port
108 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")
191
192
193 if __name__ == "__main__":
194 main(NgUiCharm)