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
24 from pydantic
import (
32 from typing
import Any
, Dict
, List
, Optional
33 from urllib
.parse
import urlparse
35 logger
= logging
.getLogger(__name__
)
38 class ConfigData(BaseModel
):
39 """Configuration data model."""
42 database_commonkey
: constr(min_length
=1)
43 log_level
: constr(regex
=r
"^(INFO|DEBUG)$")
44 auth_backend
: constr(regex
=r
"^(internal|keystone)$")
45 site_url
: Optional
[str]
46 max_file_size
: Optional
[conint(ge
=0)]
47 ingress_whitelist_source_range
: Optional
[IPvAnyNetwork
]
48 tls_secret_name
: Optional
[str]
50 @validator("max_file_size", pre
=True, always
=True)
51 def validate_max_file_size(cls
, value
, values
, **kwargs
):
52 site_url
= values
.get("site_url")
57 parsed
= urlparse(site_url
)
59 if not parsed
.scheme
.startswith("http"):
63 raise ValueError("max_file_size needs to be defined if site_url is defined")
67 @validator("ingress_whitelist_source_range", pre
=True, always
=True)
68 def validate_ingress_whitelist_source_range(cls
, value
, values
, **kwargs
):
75 class RelationData(BaseModel
):
76 """Relation data model."""
79 message_port
: PositiveInt
80 database_uri
: constr(regex
=r
"^(mongodb://)")
82 prometheus_port
: PositiveInt
84 keystone_host
: Optional
[constr(min_length
=1)]
85 keystone_port
: Optional
[PositiveInt
]
86 keystone_user_domain_name
: Optional
[constr(min_length
=1)]
87 keystone_project_domain_name
: Optional
[constr(min_length
=1)]
88 keystone_username
: Optional
[constr(min_length
=1)]
89 keystone_password
: Optional
[constr(min_length
=1)]
90 keystone_service
: Optional
[constr(min_length
=1)]
92 @validator("keystone_host", pre
=True, always
=True)
93 def validate_keystone_host(cls
, value
, values
, **kwargs
):
94 keystone
= values
.get("keystone")
101 "keystone_host needs to be defined if keystone is configured"
106 @validator("keystone_port", pre
=True, always
=True)
107 def validate_keystone_port(cls
, value
, values
, **kwargs
):
108 keystone
= values
.get("keystone")
115 "keystone_port needs to be defined if keystone is configured"
120 @validator("keystone_user_domain_name", pre
=True, always
=True)
121 def validate_keystone_user_domain_name(cls
, value
, values
, **kwargs
):
122 keystone
= values
.get("keystone")
129 "keystone_user_domain_name needs to be defined if keystone is configured"
134 @validator("keystone_project_domain_name", pre
=True, always
=True)
135 def validate_keystone_project_domain_name(cls
, value
, values
, **kwargs
):
136 keystone
= values
.get("keystone")
143 "keystone_project_domain_name needs to be defined if keystone is configured"
148 @validator("keystone_username", pre
=True, always
=True)
149 def validate_keystone_username(cls
, value
, values
, **kwargs
):
150 keystone
= values
.get("keystone")
157 "keystone_username needs to be defined if keystone is configured"
162 @validator("keystone_password", pre
=True, always
=True)
163 def validate_keystone_password(cls
, value
, values
, **kwargs
):
164 keystone
= values
.get("keystone")
171 "keystone_password needs to be defined if keystone is configured"
176 @validator("keystone_service", pre
=True, always
=True)
177 def validate_keystone_service(cls
, value
, values
, **kwargs
):
178 keystone
= values
.get("keystone")
185 "keystone_service needs to be defined if keystone is configured"
191 def _make_pod_ports(port
: int) -> List
[Dict
[str, Any
]]:
192 """Generate pod ports details.
195 port (int): port to expose.
198 List[Dict[str, Any]]: pod port details.
200 return [{"name": "nbi", "containerPort": port
, "protocol": "TCP"}]
203 def _make_pod_envconfig(
204 config
: Dict
[str, Any
], relation_state
: Dict
[str, Any
]
206 """Generate pod environment configuration.
209 config (Dict[str, Any]): configuration information.
210 relation_state (Dict[str, Any]): relation state information.
213 Dict[str, Any]: pod environment configuration.
216 # General configuration
217 "ALLOW_ANONYMOUS_LOGIN": "yes",
218 "OSMNBI_SERVER_ENABLE_TEST": config
["enable_test"],
219 "OSMNBI_STATIC_DIR": "/app/osm_nbi/html_public",
220 # Kafka configuration
221 "OSMNBI_MESSAGE_HOST": relation_state
["message_host"],
222 "OSMNBI_MESSAGE_DRIVER": "kafka",
223 "OSMNBI_MESSAGE_PORT": relation_state
["message_port"],
224 # Database configuration
225 "OSMNBI_DATABASE_DRIVER": "mongo",
226 "OSMNBI_DATABASE_URI": relation_state
["database_uri"],
227 "OSMNBI_DATABASE_COMMONKEY": config
["database_commonkey"],
228 # Storage configuration
229 "OSMNBI_STORAGE_DRIVER": "mongo",
230 "OSMNBI_STORAGE_PATH": "/app/storage",
231 "OSMNBI_STORAGE_COLLECTION": "files",
232 "OSMNBI_STORAGE_URI": relation_state
["database_uri"],
233 # Prometheus configuration
234 "OSMNBI_PROMETHEUS_HOST": relation_state
["prometheus_host"],
235 "OSMNBI_PROMETHEUS_PORT": relation_state
["prometheus_port"],
237 "OSMNBI_LOG_LEVEL": config
["log_level"],
240 if config
["auth_backend"] == "internal":
241 envconfig
["OSMNBI_AUTHENTICATION_BACKEND"] = "internal"
242 elif config
["auth_backend"] == "keystone":
245 "OSMNBI_AUTHENTICATION_BACKEND": "keystone",
246 "OSMNBI_AUTHENTICATION_AUTH_URL": relation_state
["keystone_host"],
247 "OSMNBI_AUTHENTICATION_AUTH_PORT": relation_state
["keystone_port"],
248 "OSMNBI_AUTHENTICATION_USER_DOMAIN_NAME": relation_state
[
249 "keystone_user_domain_name"
251 "OSMNBI_AUTHENTICATION_PROJECT_DOMAIN_NAME": relation_state
[
252 "keystone_project_domain_name"
254 "OSMNBI_AUTHENTICATION_SERVICE_USERNAME": relation_state
[
257 "OSMNBI_AUTHENTICATION_SERVICE_PASSWORD": relation_state
[
260 "OSMNBI_AUTHENTICATION_SERVICE_PROJECT": relation_state
[
266 raise ValueError("auth_backend needs to be either internal or keystone")
271 def _make_pod_ingress_resources(
272 config
: Dict
[str, Any
], app_name
: str, port
: int
273 ) -> List
[Dict
[str, Any
]]:
274 """Generate pod ingress resources.
277 config (Dict[str, Any]): configuration information.
278 app_name (str): application name.
279 port (int): port to expose.
282 List[Dict[str, Any]]: pod ingress resources.
284 site_url
= config
.get("site_url")
289 parsed
= urlparse(site_url
)
291 if not parsed
.scheme
.startswith("http"):
294 max_file_size
= config
["max_file_size"]
295 ingress_whitelist_source_range
= config
["ingress_whitelist_source_range"]
298 "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
299 max_file_size
+ "m" if max_file_size
> 0 else max_file_size
301 "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
304 if ingress_whitelist_source_range
:
306 "nginx.ingress.kubernetes.io/whitelist-source-range"
307 ] = ingress_whitelist_source_range
309 ingress_spec_tls
= None
311 if parsed
.scheme
== "https":
312 ingress_spec_tls
= [{"hosts": [parsed
.hostname
]}]
313 tls_secret_name
= config
["tls_secret_name"]
315 ingress_spec_tls
[0]["secretName"] = tls_secret_name
317 annotations
["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
320 "name": "{}-ingress".format(app_name
),
321 "annotations": annotations
,
325 "host": parsed
.hostname
,
331 "serviceName": app_name
,
342 ingress
["spec"]["tls"] = ingress_spec_tls
347 def _make_startup_probe() -> Dict
[str, Any
]:
348 """Generate startup probe.
351 Dict[str, Any]: startup probe.
354 "exec": {"command": ["/usr/bin/pgrep python3"]},
355 "initialDelaySeconds": 60,
360 def _make_readiness_probe(port
: int) -> Dict
[str, Any
]:
361 """Generate readiness probe.
364 port (int): [description]
367 Dict[str, Any]: readiness probe.
374 "initialDelaySeconds": 45,
379 def _make_liveness_probe(port
: int) -> Dict
[str, Any
]:
380 """Generate liveness probe.
383 port (int): [description]
386 Dict[str, Any]: liveness probe.
393 "initialDelaySeconds": 45,
399 image_info
: Dict
[str, str],
400 config
: Dict
[str, Any
],
401 relation_state
: Dict
[str, Any
],
402 app_name
: str = "nbi",
405 """Generate the pod spec information.
408 image_info (Dict[str, str]): Object provided by
409 OCIImageResource("image").fetch().
410 config (Dict[str, Any]): Configuration information.
411 relation_state (Dict[str, Any]): Relation state information.
412 app_name (str, optional): Application name. Defaults to "nbi".
413 port (int, optional): Port for the container. Defaults to 9999.
416 Dict[str, Any]: Pod spec dictionary for the charm.
421 ConfigData(**(config
))
424 keystone
=True if config
.get("auth_backend") == "keystone" else False,
427 ports
= _make_pod_ports(port
)
428 env_config
= _make_pod_envconfig(config
, relation_state
)
429 ingress_resources
= _make_pod_ingress_resources(config
, app_name
, port
)
436 "imageDetails": image_info
,
437 "imagePullPolicy": "Always",
439 "envConfig": env_config
,
442 "kubernetesResources": {
443 "ingressResources": ingress_resources
or [],