Fix juju status in OSM Charms
[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");
5 # you may not use this file except in compliance with the License.
6 # You may obtain 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,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 import sys
17 import logging
18 import base64
19
20 sys.path.append("lib")
21
22 from ops.charm import CharmBase
23 from ops.framework import StoredState, Object
24 from ops.main import main
25 from ops.model import (
26 ActiveStatus,
27 MaintenanceStatus,
28 BlockedStatus,
29 ModelError,
30 WaitingStatus,
31 )
32
33 from glob import glob
34 from pathlib import Path
35 from string import Template
36
37 logger = logging.getLogger(__name__)
38
39
40 class NGUICharm(CharmBase):
41 state = StoredState()
42
43 def __init__(self, framework, key):
44 super().__init__(framework, key)
45 self.state.set_default(spec=None)
46 self.state.set_default(nbi_host=None)
47 self.state.set_default(nbi_port=None)
48
49 # Observe Charm related events
50 self.framework.observe(self.on.config_changed, self.on_config_changed)
51 self.framework.observe(self.on.start, self.on_start)
52 self.framework.observe(self.on.upgrade_charm, self.on_upgrade_charm)
53 self.framework.observe(
54 self.on.nbi_relation_changed, self.on_nbi_relation_changed
55 )
56
57 # SSL Certificate path
58 self.ssl_folder = "/certs"
59 self.ssl_crt_name = "ssl_certificate.crt"
60 self.ssl_key_name = "ssl_certificate.key"
61
62 def _apply_spec(self):
63 # Only apply the spec if this unit is a leader.
64 unit = self.model.unit
65 if not unit.is_leader():
66 unit.status = ActiveStatus("ready")
67 return
68 if not self.state.nbi_host or not self.state.nbi_port:
69 unit.status = WaitingStatus("Waiting for NBI")
70 return
71 unit.status = MaintenanceStatus("Applying new pod spec")
72
73 new_spec = self.make_pod_spec()
74 if new_spec == self.state.spec:
75 unit.status = ActiveStatus("ready")
76 return
77 self.framework.model.pod.set_spec(new_spec)
78 self.state.spec = new_spec
79 unit.status = ActiveStatus("ready")
80
81 def make_pod_spec(self):
82 config = self.framework.model.config
83
84 config_spec = {
85 "http_port": config["port"],
86 "https_port": config["https_port"],
87 "server_name": config["server_name"],
88 "client_max_body_size": config["client_max_body_size"],
89 "nbi_host": self.state.nbi_host or config["nbi_host"],
90 "nbi_port": self.state.nbi_port or config["nbi_port"],
91 "ssl_crt": "",
92 "ssl_crt_key": "",
93 }
94
95 ssl_certificate = None
96 ssl_certificate_key = None
97 ssl_enabled = False
98
99 if "ssl_certificate" in config and "ssl_certificate_key" in config:
100 # Get bytes of cert and key
101 cert_b = base64.b64decode(config["ssl_certificate"])
102 key_b = base64.b64decode(config["ssl_certificate_key"])
103 # Decode key and cert
104 ssl_certificate = cert_b.decode("utf-8")
105 ssl_certificate_key = key_b.decode("utf-8")
106 # Get paths
107 cert_path = "{}/{}".format(self.ssl_folder, self.ssl_crt_name)
108 key_path = "{}/{}".format(self.ssl_folder, self.ssl_key_name)
109
110 config_spec["port"] = "{} ssl".format(config["https_port"])
111 config_spec["ssl_crt"] = "ssl_certificate {};".format(cert_path)
112 config_spec["ssl_crt_key"] = "ssl_certificate_key {};".format(key_path)
113 ssl_enabled = True
114 else:
115 config_spec["ssl_crt"] = ""
116 config_spec["ssl_crt_key"] = ""
117
118 files = [
119 {
120 "name": "configuration",
121 "mountPath": "/etc/nginx/sites-available/",
122 "files": {
123 Path(filename)
124 .name: Template(Path(filename).read_text())
125 .substitute(config_spec)
126 for filename in glob("files/*")
127 },
128 }
129 ]
130 port = config["https_port"] if ssl_enabled else config["port"]
131 ports = [
132 {"name": "port", "containerPort": port, "protocol": "TCP", },
133 ]
134
135 kubernetes = {
136 "readinessProbe": {
137 "tcpSocket": {"port": port},
138 "timeoutSeconds": 5,
139 "periodSeconds": 5,
140 "initialDelaySeconds": 10,
141 },
142 "livenessProbe": {
143 "tcpSocket": {"port": port},
144 "timeoutSeconds": 5,
145 "initialDelaySeconds": 45,
146 },
147 }
148
149 if ssl_certificate and ssl_certificate_key:
150 files.append(
151 {
152 "name": "ssl",
153 "mountPath": self.ssl_folder,
154 "files": {
155 self.ssl_crt_name: ssl_certificate,
156 self.ssl_key_name: ssl_certificate_key,
157 },
158 }
159 )
160
161 spec = {
162 "version": 2,
163 "containers": [
164 {
165 "name": self.framework.model.app.name,
166 "image": "{}".format(config["image"]),
167 "ports": ports,
168 "kubernetes": kubernetes,
169 "files": files,
170 }
171 ],
172 }
173
174 return spec
175
176 def on_config_changed(self, event):
177 """Handle changes in configuration"""
178 self._apply_spec()
179
180 def on_start(self, event):
181 """Called when the charm is being installed"""
182 self._apply_spec()
183
184 def on_upgrade_charm(self, event):
185 """Upgrade the charm."""
186 unit = self.model.unit
187 unit.status = MaintenanceStatus("Upgrading charm")
188 self.on_start(event)
189
190 def on_nbi_relation_changed(self, event):
191 nbi_host = event.relation.data[event.unit].get("host")
192 nbi_port = event.relation.data[event.unit].get("port")
193 if nbi_host and self.state.nbi_host != nbi_host:
194 self.state.nbi_host = nbi_host
195 if nbi_port and self.state.nbi_port != nbi_port:
196 self.state.nbi_port = nbi_port
197 self._apply_spec()
198
199
200 if __name__ == "__main__":
201 main(NGUICharm)