2 # Copyright 2021 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 # pylint: disable=E0213
27 from typing
import Dict
, NoReturn
, Optional
29 from charms
.kafka_k8s
.v0
.kafka
import KafkaEvents
, KafkaRequires
30 from ops
.main
import main
31 from opslib
.osm
.charm
import CharmedOsmBase
, RelationsMissing
32 from opslib
.osm
.interfaces
.mongo
import MongoClient
33 from opslib
.osm
.interfaces
.mysql
import MysqlClient
34 from opslib
.osm
.pod
import (
40 from opslib
.osm
.validator
import ModelValidator
, validator
42 logger
= logging
.getLogger(__name__
)
47 def _check_certificate_data(name
: str, content
: str):
48 if not name
or not content
:
49 raise ValueError("certificate name and content must be a non-empty string")
52 def _extract_certificates(certs_config
: str):
55 cert_list
= certs_config
.split(",")
56 for cert
in cert_list
:
57 name
, content
= cert
.split(":")
58 _check_certificate_data(name
, content
)
59 certificates
[name
] = content
63 def decode(content
: str):
64 return base64
.b64decode(content
.encode("utf-8")).decode("utf-8")
67 class ConfigModel(ModelValidator
):
69 database_commonkey
: str
70 mongodb_uri
: Optional
[str]
72 mysql_host
: Optional
[str]
73 mysql_port
: Optional
[int]
74 mysql_user
: Optional
[str]
75 mysql_password
: Optional
[str]
76 mysql_root_password
: Optional
[str]
80 certificates
: Optional
[str]
81 image_pull_policy
: str
83 security_context
: bool
84 period_refresh_active
: Optional
[int]
86 @validator("log_level")
87 def validate_log_level(cls
, v
):
88 if v
not in {"INFO", "DEBUG"}:
89 raise ValueError("value must be INFO or DEBUG")
92 @validator("certificates")
93 def validate_certificates(cls
, v
):
94 # Raises an exception if it cannot extract the certificates
95 _extract_certificates(v
)
98 @validator("mongodb_uri")
99 def validate_mongodb_uri(cls
, v
):
100 if v
and not v
.startswith("mongodb://"):
101 raise ValueError("mongodb_uri is not properly formed")
104 @validator("mysql_port")
105 def validate_mysql_port(cls
, v
):
106 if v
and (v
<= 0 or v
>= 65535):
107 raise ValueError("Mysql port out of range")
110 @validator("image_pull_policy")
111 def validate_image_pull_policy(cls
, v
):
114 "ifnotpresent": "IfNotPresent",
118 if v
not in values
.keys():
119 raise ValueError("value must be always, ifnotpresent or never")
123 def certificates_dict(cls
):
124 return _extract_certificates(cls
.certificates
) if cls
.certificates
else {}
126 @validator("period_refresh_active")
127 def validate_vim_refresh_period(cls
, v
):
128 if v
and v
< 60 and v
!= -1:
130 "Refresh Period is too tight, insert >= 60 seconds or disable using -1"
135 class RoCharm(CharmedOsmBase
):
136 """GrafanaCharm Charm."""
140 def __init__(self
, *args
) -> NoReturn
:
141 """Prometheus Charm constructor."""
145 vscode_workspace
=VSCODE_WORKSPACE
,
147 if self
.config
.get("debug_mode"):
148 self
.enable_debug_mode(
149 pubkey
=self
.config
.get("debug_pubkey"),
152 "hostpath": self
.config
.get("debug_common_local_path"),
153 "container-path": "/usr/lib/python3/dist-packages/osm_common",
155 **_get_ro_host_paths(self
.config
.get("debug_ro_local_path")),
158 self
.kafka
= KafkaRequires(self
)
159 self
.framework
.observe(self
.on
.kafka_available
, self
.configure_pod
)
160 self
.framework
.observe(self
.on
.kafka_broken
, self
.configure_pod
)
162 self
.mysql_client
= MysqlClient(self
, "mysql")
163 self
.framework
.observe(self
.on
["mysql"].relation_changed
, self
.configure_pod
)
164 self
.framework
.observe(self
.on
["mysql"].relation_broken
, self
.configure_pod
)
166 self
.mongodb_client
= MongoClient(self
, "mongodb")
167 self
.framework
.observe(self
.on
["mongodb"].relation_changed
, self
.configure_pod
)
168 self
.framework
.observe(self
.on
["mongodb"].relation_broken
, self
.configure_pod
)
170 self
.framework
.observe(self
.on
["ro"].relation_joined
, self
._publish
_ro
_info
)
172 def _publish_ro_info(self
, event
):
173 """Publishes RO information.
176 event (EventBase): RO relation event.
178 if self
.unit
.is_leader():
180 "host": self
.model
.app
.name
,
183 for k
, v
in rel_data
.items():
184 event
.relation
.data
[self
.app
][k
] = v
186 def _check_missing_dependencies(self
, config
: ConfigModel
):
187 missing_relations
= []
189 if config
.enable_ng_ro
:
190 if not self
.kafka
.host
or not self
.kafka
.port
:
191 missing_relations
.append("kafka")
192 if not config
.mongodb_uri
and self
.mongodb_client
.is_missing_data_in_unit():
193 missing_relations
.append("mongodb")
195 if not config
.mysql_host
and self
.mysql_client
.is_missing_data_in_unit():
196 missing_relations
.append("mysql")
197 if missing_relations
:
198 raise RelationsMissing(missing_relations
)
200 def _validate_mysql_config(self
, config
: ConfigModel
):
202 if not config
.mysql_user
:
203 invalid_values
.append("Mysql user is empty")
204 if not config
.mysql_password
:
205 invalid_values
.append("Mysql password is empty")
206 if not config
.mysql_root_password
:
207 invalid_values
.append("Mysql root password empty")
210 raise ValueError("Invalid values: " + ", ".join(invalid_values
))
212 def _build_cert_files(
216 cert_files_builder
= FilesV3Builder()
217 for name
, content
in config
.certificates_dict
.items():
218 cert_files_builder
.add_file(name
, decode(content
), mode
=0o600)
219 return cert_files_builder
.build()
221 def build_pod_spec(self
, image_info
):
223 config
= ConfigModel(**dict(self
.config
))
225 if config
.enable_ng_ro
:
226 if config
.mongodb_uri
and not self
.mongodb_client
.is_missing_data_in_unit():
228 "Mongodb data cannot be provided via config and relation"
231 if config
.mysql_host
and not self
.mysql_client
.is_missing_data_in_unit():
232 raise Exception("Mysql data cannot be provided via config and relation")
234 if config
.mysql_host
:
235 self
._validate
_mysql
_config
(config
)
238 self
._check
_missing
_dependencies
(config
)
240 security_context_enabled
= (
241 config
.security_context
if not config
.debug_mode
else False
244 # Create Builder for the PodSpec
245 pod_spec_builder
= PodSpecV3Builder(
246 enable_security_context
=security_context_enabled
250 container_builder
= ContainerV3Builder(
253 config
.image_pull_policy
,
254 run_as_non_root
=security_context_enabled
,
256 certs_files
= self
._build
_cert
_files
(config
)
259 container_builder
.add_volume_config("certs", "/certs", certs_files
)
261 container_builder
.add_port(name
=self
.app
.name
, port
=PORT
)
262 container_builder
.add_http_readiness_probe(
263 "/ro/" if config
.enable_ng_ro
else "/openmano/tenants",
265 initial_delay_seconds
=10,
270 container_builder
.add_http_liveness_probe(
271 "/ro/" if config
.enable_ng_ro
else "/openmano/tenants",
273 initial_delay_seconds
=600,
278 container_builder
.add_envs(
280 "OSMRO_LOG_LEVEL": config
.log_level
,
283 if config
.period_refresh_active
:
284 container_builder
.add_envs(
286 "OSMRO_PERIOD_REFRESH_ACTIVE": config
.period_refresh_active
,
289 if config
.enable_ng_ro
:
290 # Add secrets to the pod
291 mongodb_secret_name
= f
"{self.app.name}-mongodb-secret"
292 pod_spec_builder
.add_secret(
295 "uri": config
.mongodb_uri
or self
.mongodb_client
.connection_string
,
296 "commonkey": config
.database_commonkey
,
299 container_builder
.add_envs(
301 "OSMRO_MESSAGE_DRIVER": "kafka",
302 "OSMRO_MESSAGE_HOST": self
.kafka
.host
,
303 "OSMRO_MESSAGE_PORT": self
.kafka
.port
,
304 # MongoDB configuration
305 "OSMRO_DATABASE_DRIVER": "mongo",
308 container_builder
.add_secret_envs(
309 secret_name
=mongodb_secret_name
,
311 "OSMRO_DATABASE_URI": "uri",
312 "OSMRO_DATABASE_COMMONKEY": "commonkey",
315 restart_policy
= PodRestartPolicy()
316 restart_policy
.add_secrets(secret_names
=(mongodb_secret_name
,))
317 pod_spec_builder
.set_restart_policy(restart_policy
)
320 container_builder
.add_envs(
322 "RO_DB_HOST": config
.mysql_host
or self
.mysql_client
.host
,
323 "RO_DB_OVIM_HOST": config
.mysql_host
or self
.mysql_client
.host
,
324 "RO_DB_PORT": config
.mysql_port
or self
.mysql_client
.port
,
325 "RO_DB_OVIM_PORT": config
.mysql_port
or self
.mysql_client
.port
,
326 "RO_DB_USER": config
.mysql_user
or self
.mysql_client
.user
,
327 "RO_DB_OVIM_USER": config
.mysql_user
or self
.mysql_client
.user
,
328 "RO_DB_PASSWORD": config
.mysql_password
329 or self
.mysql_client
.password
,
330 "RO_DB_OVIM_PASSWORD": config
.mysql_password
331 or self
.mysql_client
.password
,
332 "RO_DB_ROOT_PASSWORD": config
.mysql_root_password
333 or self
.mysql_client
.root_password
,
334 "RO_DB_OVIM_ROOT_PASSWORD": config
.mysql_root_password
335 or self
.mysql_client
.root_password
,
336 "RO_DB_NAME": config
.ro_database
,
337 "RO_DB_OVIM_NAME": config
.vim_database
,
338 "OPENMANO_TENANT": config
.openmano_tenant
,
341 container
= container_builder
.build()
343 # Add container to pod spec
344 pod_spec_builder
.add_container(container
)
346 return pod_spec_builder
.build()
351 {"path": "/usr/lib/python3/dist-packages/osm_ng_ro"},
352 {"path": "/usr/lib/python3/dist-packages/osm_common"},
353 {"path": "/usr/lib/python3/dist-packages/osm_ro_plugin"},
354 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_arista_cloudvision"},
355 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_dpb"},
356 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_dynpac"},
357 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_floodlightof"},
358 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_ietfl2vpn"},
359 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_juniper_contrail"},
360 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_odlof"},
361 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_onos_vpls"},
362 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_onosof"},
363 {"path": "/usr/lib/python3/dist-packages/osm_rovim_aws"},
364 {"path": "/usr/lib/python3/dist-packages/osm_rovim_azure"},
365 {"path": "/usr/lib/python3/dist-packages/osm_rovim_gcp"},
366 {"path": "/usr/lib/python3/dist-packages/osm_rovim_openstack"},
367 {"path": "/usr/lib/python3/dist-packages/osm_rovim_openvim"},
368 {"path": "/usr/lib/python3/dist-packages/osm_rovim_vmware"},
373 "module": "osm_ng_ro.ro_main",
386 def _get_ro_host_paths(ro_host_path
: str) -> Dict
:
387 """Get RO host paths"""
391 "hostpath": f
"{ro_host_path}/NG-RO",
392 "container-path": "/usr/lib/python3/dist-packages/osm_ng_ro",
395 "hostpath": f
"{ro_host_path}/RO-plugin",
396 "container-path": "/usr/lib/python3/dist-packages/osm_ro_plugin",
398 "RO-SDN-arista_cloudvision": {
399 "hostpath": f
"{ro_host_path}/RO-SDN-arista_cloudvision",
400 "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_arista_cloudvision",
403 "hostpath": f
"{ro_host_path}/RO-SDN-dpb",
404 "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_dpb",
407 "hostpath": f
"{ro_host_path}/RO-SDN-dynpac",
408 "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_dynpac",
410 "RO-SDN-floodlight_openflow": {
411 "hostpath": f
"{ro_host_path}/RO-SDN-floodlight_openflow",
412 "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_floodlightof",
414 "RO-SDN-ietfl2vpn": {
415 "hostpath": f
"{ro_host_path}/RO-SDN-ietfl2vpn",
416 "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_ietfl2vpn",
418 "RO-SDN-juniper_contrail": {
419 "hostpath": f
"{ro_host_path}/RO-SDN-juniper_contrail",
420 "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_juniper_contrail",
422 "RO-SDN-odl_openflow": {
423 "hostpath": f
"{ro_host_path}/RO-SDN-odl_openflow",
424 "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_odlof",
426 "RO-SDN-onos_openflow": {
427 "hostpath": f
"{ro_host_path}/RO-SDN-onos_openflow",
428 "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_onosof",
430 "RO-SDN-onos_vpls": {
431 "hostpath": f
"{ro_host_path}/RO-SDN-onos_vpls",
432 "container-path": "/usr/lib/python3/dist-packages/osm_rosdn_onos_vpls",
435 "hostpath": f
"{ro_host_path}/RO-VIM-aws",
436 "container-path": "/usr/lib/python3/dist-packages/osm_rovim_aws",
439 "hostpath": f
"{ro_host_path}/RO-VIM-azure",
440 "container-path": "/usr/lib/python3/dist-packages/osm_rovim_azure",
443 "hostpath": f
"{ro_host_path}/RO-VIM-gcp",
444 "container-path": "/usr/lib/python3/dist-packages/osm_rovim_gcp",
446 "RO-VIM-openstack": {
447 "hostpath": f
"{ro_host_path}/RO-VIM-openstack",
448 "container-path": "/usr/lib/python3/dist-packages/osm_rovim_openstack",
451 "hostpath": f
"{ro_host_path}/RO-VIM-openvim",
452 "container-path": "/usr/lib/python3/dist-packages/osm_rovim_openvim",
455 "hostpath": f
"{ro_host_path}/RO-VIM-vmware",
456 "container-path": "/usr/lib/python3/dist-packages/osm_rovim_vmware",
464 if __name__
== "__main__":