2 # Copyright 2020 Canonical Ltd.
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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
16 # For those usages not covered by the Apache License, Version 2.0 please
17 # contact: legal@canonical.com
19 # To get in touch with the maintainers, please contact:
20 # osm-charmers@lists.launchpad.net
23 from ipaddress
import ip_network
24 from typing
import Any
, Callable
, Dict
, List
, NoReturn
25 from urllib
.parse
import urlparse
28 def _validate_max_file_size(max_file_size
: int, site_url
: str) -> bool:
29 """Validate max_file_size.
32 max_file_size (int): maximum file size allowed.
33 site_url (str): endpoint url.
36 bool: True if valid, false otherwise.
41 parsed
= urlparse(site_url
)
43 if not parsed
.scheme
.startswith("http"):
46 if max_file_size
is None:
49 return max_file_size
>= 0
52 def _validate_ip_network(network
: str) -> bool:
53 """Validate IP network.
56 network (str): IP network range.
59 bool: True if valid, false otherwise.
72 def _validate_keystone_config(keystone
: bool, value
: Any
, validator
: Callable
) -> bool:
73 """Validate keystone configurations.
76 keystone (bool): is keystone enabled, true if so, false otherwise.
77 value (Any): value to be validated.
78 validator (Callable): function to validate configuration.
81 bool: true if valid, false otherwise.
86 return validator(value
)
90 config_data
: Dict
[str, Any
], relation_data
: Dict
[str, Any
], keystone
: bool
92 """Validate input data.
95 config_data (Dict[str, Any]): configuration data.
96 relation_data (Dict[str, Any]): relation data.
97 keystone (bool): is keystone to be used.
100 "enable_test": lambda value
, _
: isinstance(value
, bool),
101 "database_commonkey": lambda value
, _
: isinstance(value
, str)
103 "log_level": lambda value
, _
: isinstance(value
, str)
104 and (value
== "INFO" or value
== "DEBUG"),
105 "auth_backend": lambda value
, _
: isinstance(value
, str)
106 and (value
== "internal" or value
== "keystone"),
107 "site_url": lambda value
, _
: isinstance(value
, str)
110 "max_file_size": lambda value
, values
: _validate_max_file_size(
111 value
, values
.get("site_url")
113 "ingress_whitelist_source_range": lambda value
, _
: _validate_ip_network(value
),
114 "tls_secret_name": lambda value
, _
: isinstance(value
, str)
118 relation_validators
= {
119 "message_host": lambda value
, _
: isinstance(value
, str),
120 "message_port": lambda value
, _
: isinstance(value
, int) and value
> 0,
121 "database_uri": lambda value
, _
: isinstance(value
, str)
122 and value
.startswith("mongodb://"),
123 "prometheus_host": lambda value
, _
: isinstance(value
, str),
124 "prometheus_port": lambda value
, _
: isinstance(value
, int) and value
> 0,
125 "keystone_host": lambda value
, _
: _validate_keystone_config(
126 keystone
, value
, lambda x
: isinstance(x
, str) and len(x
) > 0
128 "keystone_port": lambda value
, _
: _validate_keystone_config(
129 keystone
, value
, lambda x
: isinstance(x
, int) and x
> 0
131 "keystone_user_domain_name": lambda value
, _
: _validate_keystone_config(
132 keystone
, value
, lambda x
: isinstance(x
, str) and len(x
) > 0
134 "keystone_project_domain_name": lambda value
, _
: _validate_keystone_config(
135 keystone
, value
, lambda x
: isinstance(x
, str) and len(x
) > 0
137 "keystone_username": lambda value
, _
: _validate_keystone_config(
138 keystone
, value
, lambda x
: isinstance(x
, str) and len(x
) > 0
140 "keystone_password": lambda value
, _
: _validate_keystone_config(
141 keystone
, value
, lambda x
: isinstance(x
, str) and len(x
) > 0
143 "keystone_service": lambda value
, _
: _validate_keystone_config(
144 keystone
, value
, lambda x
: isinstance(x
, str) and len(x
) > 0
149 for key
, validator
in config_validators
.items():
150 valid
= validator(config_data
.get(key
), config_data
)
155 for key
, validator
in relation_validators
.items():
156 valid
= validator(relation_data
.get(key
), relation_data
)
161 if len(problems
) > 0:
162 raise ValueError("Errors found in: {}".format(", ".join(problems
)))
165 def _make_pod_ports(port
: int) -> List
[Dict
[str, Any
]]:
166 """Generate pod ports details.
169 port (int): port to expose.
172 List[Dict[str, Any]]: pod port details.
174 return [{"name": "nbi", "containerPort": port
, "protocol": "TCP"}]
177 def _make_pod_envconfig(
178 config
: Dict
[str, Any
], relation_state
: Dict
[str, Any
]
180 """Generate pod environment configuration.
183 config (Dict[str, Any]): configuration information.
184 relation_state (Dict[str, Any]): relation state information.
187 Dict[str, Any]: pod environment configuration.
190 # General configuration
191 "ALLOW_ANONYMOUS_LOGIN": "yes",
192 "OSMNBI_SERVER_ENABLE_TEST": config
["enable_test"],
193 "OSMNBI_STATIC_DIR": "/app/osm_nbi/html_public",
194 # Kafka configuration
195 "OSMNBI_MESSAGE_HOST": relation_state
["message_host"],
196 "OSMNBI_MESSAGE_DRIVER": "kafka",
197 "OSMNBI_MESSAGE_PORT": relation_state
["message_port"],
198 # Database configuration
199 "OSMNBI_DATABASE_DRIVER": "mongo",
200 "OSMNBI_DATABASE_URI": relation_state
["database_uri"],
201 "OSMNBI_DATABASE_COMMONKEY": config
["database_commonkey"],
202 # Storage configuration
203 "OSMNBI_STORAGE_DRIVER": "mongo",
204 "OSMNBI_STORAGE_PATH": "/app/storage",
205 "OSMNBI_STORAGE_COLLECTION": "files",
206 "OSMNBI_STORAGE_URI": relation_state
["database_uri"],
207 # Prometheus configuration
208 "OSMNBI_PROMETHEUS_HOST": relation_state
["prometheus_host"],
209 "OSMNBI_PROMETHEUS_PORT": relation_state
["prometheus_port"],
211 "OSMNBI_LOG_LEVEL": config
["log_level"],
214 if config
["auth_backend"] == "internal":
215 envconfig
["OSMNBI_AUTHENTICATION_BACKEND"] = "internal"
216 elif config
["auth_backend"] == "keystone":
219 "OSMNBI_AUTHENTICATION_BACKEND": "keystone",
220 "OSMNBI_AUTHENTICATION_AUTH_URL": relation_state
["keystone_host"],
221 "OSMNBI_AUTHENTICATION_AUTH_PORT": relation_state
["keystone_port"],
222 "OSMNBI_AUTHENTICATION_USER_DOMAIN_NAME": relation_state
[
223 "keystone_user_domain_name"
225 "OSMNBI_AUTHENTICATION_PROJECT_DOMAIN_NAME": relation_state
[
226 "keystone_project_domain_name"
228 "OSMNBI_AUTHENTICATION_SERVICE_USERNAME": relation_state
[
231 "OSMNBI_AUTHENTICATION_SERVICE_PASSWORD": relation_state
[
234 "OSMNBI_AUTHENTICATION_SERVICE_PROJECT": relation_state
[
240 raise ValueError("auth_backend needs to be either internal or keystone")
245 def _make_pod_ingress_resources(
246 config
: Dict
[str, Any
], app_name
: str, port
: int
247 ) -> List
[Dict
[str, Any
]]:
248 """Generate pod ingress resources.
251 config (Dict[str, Any]): configuration information.
252 app_name (str): application name.
253 port (int): port to expose.
256 List[Dict[str, Any]]: pod ingress resources.
258 site_url
= config
.get("site_url")
263 parsed
= urlparse(site_url
)
265 if not parsed
.scheme
.startswith("http"):
268 max_file_size
= config
["max_file_size"]
269 ingress_whitelist_source_range
= config
["ingress_whitelist_source_range"]
272 "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
273 str(max_file_size
) + "m" if max_file_size
> 0 else max_file_size
275 "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
278 if ingress_whitelist_source_range
:
280 "nginx.ingress.kubernetes.io/whitelist-source-range"
281 ] = ingress_whitelist_source_range
283 ingress_spec_tls
= None
285 if parsed
.scheme
== "https":
286 ingress_spec_tls
= [{"hosts": [parsed
.hostname
]}]
287 tls_secret_name
= config
["tls_secret_name"]
289 ingress_spec_tls
[0]["secretName"] = tls_secret_name
291 annotations
["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
294 "name": "{}-ingress".format(app_name
),
295 "annotations": annotations
,
299 "host": parsed
.hostname
,
305 "serviceName": app_name
,
316 ingress
["spec"]["tls"] = ingress_spec_tls
321 def _make_startup_probe() -> Dict
[str, Any
]:
322 """Generate startup probe.
325 Dict[str, Any]: startup probe.
328 "exec": {"command": ["/usr/bin/pgrep python3"]},
329 "initialDelaySeconds": 60,
334 def _make_readiness_probe(port
: int) -> Dict
[str, Any
]:
335 """Generate readiness probe.
338 port (int): [description]
341 Dict[str, Any]: readiness probe.
348 "initialDelaySeconds": 45,
353 def _make_liveness_probe(port
: int) -> Dict
[str, Any
]:
354 """Generate liveness probe.
357 port (int): [description]
360 Dict[str, Any]: liveness probe.
367 "initialDelaySeconds": 45,
373 image_info
: Dict
[str, str],
374 config
: Dict
[str, Any
],
375 relation_state
: Dict
[str, Any
],
376 app_name
: str = "nbi",
379 """Generate the pod spec information.
382 image_info (Dict[str, str]): Object provided by
383 OCIImageResource("image").fetch().
384 config (Dict[str, Any]): Configuration information.
385 relation_state (Dict[str, Any]): Relation state information.
386 app_name (str, optional): Application name. Defaults to "nbi".
387 port (int, optional): Port for the container. Defaults to 9999.
390 Dict[str, Any]: Pod spec dictionary for the charm.
395 _validate_data(config
, relation_state
, config
.get("auth_backend") == "keystone")
397 ports
= _make_pod_ports(port
)
398 env_config
= _make_pod_envconfig(config
, relation_state
)
399 ingress_resources
= _make_pod_ingress_resources(config
, app_name
, port
)
406 "imageDetails": image_info
,
407 "imagePullPolicy": "Always",
409 "envConfig": env_config
,
412 "kubernetesResources": {
413 "ingressResources": ingress_resources
or [],