Major improvement in OSM charms
[osm/devops.git] / installers / charm / ro / src / pod_spec.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, List, NoReturn
25
26 logger = logging.getLogger(__name__)
27
28
29 def _validate_data(
30 config_data: Dict[str, Any], relation_data: Dict[str, Any]
31 ) -> NoReturn:
32 """Validates passed information.
33
34 Args:
35 config_data (Dict[str, Any]): configuration information.
36 relation_data (Dict[str, Any]): relation information
37
38 Raises:
39 ValueError: when config and/or relation data is not valid.
40 """
41 config_validators = {
42 "enable_ng_ro": lambda value, _: isinstance(value, bool),
43 "database_commonkey": lambda value, values: (
44 isinstance(value, str) and len(value) > 0
45 )
46 if values.get("enable_ng_ro", True)
47 else True,
48 "log_level": lambda value, _: (
49 isinstance(value, str) and value in ("INFO", "DEBUG")
50 ),
51 "vim_database": lambda value, values: (
52 isinstance(value, str) and len(value) > 0
53 )
54 if not values.get("enable_ng_ro", True)
55 else True,
56 "ro_database": lambda value, values: (isinstance(value, str) and len(value) > 0)
57 if not values.get("enable_ng_ro", True)
58 else True,
59 "openmano_tenant": lambda value, values: (
60 isinstance(value, str) and len(value) > 0
61 )
62 if not values.get("enable_ng_ro", True)
63 else True,
64 }
65 relation_validators = {
66 "kafka_host": lambda value, _: (isinstance(value, str) and len(value) > 0)
67 if config_data.get("enable_ng_ro", True)
68 else True,
69 "kafka_port": lambda value, _: (isinstance(value, str) and len(value) > 0)
70 if config_data.get("enable_ng_ro", True)
71 else True,
72 "mongodb_connection_string": lambda value, _: (
73 isinstance(value, str) and value.startswith("mongodb://")
74 )
75 if config_data.get("enable_ng_ro", True)
76 else True,
77 "mysql_host": lambda value, _: (isinstance(value, str) and len(value) > 0)
78 if not config_data.get("enable_ng_ro", True)
79 else True,
80 "mysql_port": lambda value, _: (isinstance(value, int) and value > 0)
81 if not config_data.get("enable_ng_ro", True)
82 else True,
83 "mysql_user": lambda value, _: (isinstance(value, str) and len(value) > 0)
84 if not config_data.get("enable_ng_ro", True)
85 else True,
86 "mysql_password": lambda value, _: (isinstance(value, str) and len(value) > 0)
87 if not config_data.get("enable_ng_ro", True)
88 else True,
89 "mysql_root_password": lambda value, _: (
90 isinstance(value, str) and len(value) > 0
91 )
92 if not config_data.get("enable_ng_ro", True)
93 else True,
94 }
95 problems = []
96
97 for key, validator in config_validators.items():
98 valid = validator(config_data.get(key), config_data)
99
100 if not valid:
101 problems.append(key)
102
103 for key, validator in relation_validators.items():
104 valid = validator(relation_data.get(key), relation_data)
105
106 if not valid:
107 problems.append(key)
108
109 if len(problems) > 0:
110 raise ValueError("Errors found in: {}".format(", ".join(problems)))
111
112
113 def _make_pod_ports(port: int) -> List[Dict[str, Any]]:
114 """Generate pod ports details.
115
116 Args:
117 port (int): port to expose.
118
119 Returns:
120 List[Dict[str, Any]]: pod port details.
121 """
122 return [{"name": "ro", "containerPort": port, "protocol": "TCP"}]
123
124
125 def _make_pod_envconfig(
126 config: Dict[str, Any], relation_state: Dict[str, Any]
127 ) -> Dict[str, Any]:
128 """Generate pod environment configuration.
129
130 Args:
131 config (Dict[str, Any]): configuration information.
132 relation_state (Dict[str, Any]): relation state information.
133
134 Returns:
135 Dict[str, Any]: pod environment configuration.
136 """
137 envconfig = {
138 # General configuration
139 "OSMRO_LOG_LEVEL": config["log_level"],
140 }
141
142 if config.get("enable_ng_ro", True):
143 # Kafka configuration
144 envconfig["OSMRO_MESSAGE_DRIVER"] = "kafka"
145 envconfig["OSMRO_MESSAGE_HOST"] = relation_state["kafka_host"]
146 envconfig["OSMRO_MESSAGE_PORT"] = relation_state["kafka_port"]
147
148 # MongoDB configuration
149 envconfig["OSMRO_DATABASE_DRIVER"] = "mongo"
150 envconfig["OSMRO_DATABASE_URI"] = relation_state["mongodb_connection_string"]
151 envconfig["OSMRO_DATABASE_COMMONKEY"] = config["database_commonkey"]
152 else:
153 envconfig["RO_DB_HOST"] = relation_state["mysql_host"]
154 envconfig["RO_DB_OVIM_HOST"] = relation_state["mysql_host"]
155 envconfig["RO_DB_PORT"] = relation_state["mysql_port"]
156 envconfig["RO_DB_OVIM_PORT"] = relation_state["mysql_port"]
157 envconfig["RO_DB_USER"] = relation_state["mysql_user"]
158 envconfig["RO_DB_OVIM_USER"] = relation_state["mysql_user"]
159 envconfig["RO_DB_PASSWORD"] = relation_state["mysql_password"]
160 envconfig["RO_DB_OVIM_PASSWORD"] = relation_state["mysql_password"]
161 envconfig["RO_DB_ROOT_PASSWORD"] = relation_state["mysql_root_password"]
162 envconfig["RO_DB_OVIM_ROOT_PASSWORD"] = relation_state["mysql_root_password"]
163 envconfig["RO_DB_NAME"] = config["ro_database"]
164 envconfig["RO_DB_OVIM_NAME"] = config["vim_database"]
165 envconfig["OPENMANO_TENANT"] = config["openmano_tenant"]
166
167 return envconfig
168
169
170 def _make_startup_probe() -> Dict[str, Any]:
171 """Generate startup probe.
172
173 Returns:
174 Dict[str, Any]: startup probe.
175 """
176 return {
177 "exec": {"command": ["/usr/bin/pgrep", "python3"]},
178 "initialDelaySeconds": 60,
179 "timeoutSeconds": 5,
180 }
181
182
183 def _make_readiness_probe(port: int) -> Dict[str, Any]:
184 """Generate readiness probe.
185
186 Args:
187 port (int): service port.
188
189 Returns:
190 Dict[str, Any]: readiness probe.
191 """
192 return {
193 "httpGet": {
194 "path": "/openmano/tenants",
195 "port": port,
196 },
197 "periodSeconds": 10,
198 "timeoutSeconds": 5,
199 "successThreshold": 1,
200 "failureThreshold": 3,
201 }
202
203
204 def _make_liveness_probe(port: int) -> Dict[str, Any]:
205 """Generate liveness probe.
206
207 Args:
208 port (int): service port.
209
210 Returns:
211 Dict[str, Any]: liveness probe.
212 """
213 return {
214 "httpGet": {
215 "path": "/openmano/tenants",
216 "port": port,
217 },
218 "initialDelaySeconds": 600,
219 "periodSeconds": 10,
220 "timeoutSeconds": 5,
221 "successThreshold": 1,
222 "failureThreshold": 3,
223 }
224
225
226 def make_pod_spec(
227 image_info: Dict[str, str],
228 config: Dict[str, Any],
229 relation_state: Dict[str, Any],
230 app_name: str = "ro",
231 port: int = 9090,
232 ) -> Dict[str, Any]:
233 """Generate the pod spec information.
234
235 Args:
236 image_info (Dict[str, str]): Object provided by
237 OCIImageResource("image").fetch().
238 config (Dict[str, Any]): Configuration information.
239 relation_state (Dict[str, Any]): Relation state information.
240 app_name (str, optional): Application name. Defaults to "ro".
241 port (int, optional): Port for the container. Defaults to 9090.
242
243 Returns:
244 Dict[str, Any]: Pod spec dictionary for the charm.
245 """
246 if not image_info:
247 return None
248
249 _validate_data(config, relation_state)
250
251 ports = _make_pod_ports(port)
252 env_config = _make_pod_envconfig(config, relation_state)
253 startup_probe = _make_startup_probe()
254 readiness_probe = _make_readiness_probe(port)
255 liveness_probe = _make_liveness_probe(port)
256
257 return {
258 "version": 3,
259 "containers": [
260 {
261 "name": app_name,
262 "imageDetails": image_info,
263 "imagePullPolicy": "Always",
264 "ports": ports,
265 "envConfig": env_config,
266 "kubernetes": {
267 "startupProbe": startup_probe,
268 "readinessProbe": readiness_probe,
269 "livenessProbe": liveness_probe,
270 },
271 }
272 ],
273 "kubernetesResources": {
274 "ingressResources": [],
275 },
276 }