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 NoReturn
, Optional
29 from ops
.main
import main
30 from opslib
.osm
.charm
import CharmedOsmBase
, RelationsMissing
31 from opslib
.osm
.interfaces
.kafka
import KafkaClient
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 @validator("log_level")
84 def validate_log_level(cls
, v
):
85 if v
not in {"INFO", "DEBUG"}:
86 raise ValueError("value must be INFO or DEBUG")
89 @validator("certificates")
90 def validate_certificates(cls
, v
):
91 # Raises an exception if it cannot extract the certificates
92 _extract_certificates(v
)
95 @validator("mongodb_uri")
96 def validate_mongodb_uri(cls
, v
):
97 if v
and not v
.startswith("mongodb://"):
98 raise ValueError("mongodb_uri is not properly formed")
101 @validator("mysql_port")
102 def validate_mysql_port(cls
, v
):
103 if v
and (v
<= 0 or v
>= 65535):
104 raise ValueError("Mysql port out of range")
107 @validator("image_pull_policy")
108 def validate_image_pull_policy(cls
, v
):
111 "ifnotpresent": "IfNotPresent",
115 if v
not in values
.keys():
116 raise ValueError("value must be always, ifnotpresent or never")
120 def certificates_dict(cls
):
121 return _extract_certificates(cls
.certificates
) if cls
.certificates
else {}
124 class RoCharm(CharmedOsmBase
):
125 """GrafanaCharm Charm."""
127 def __init__(self
, *args
) -> NoReturn
:
128 """Prometheus Charm constructor."""
132 debug_mode_config_key
="debug_mode",
133 debug_pubkey_config_key
="debug_pubkey",
134 vscode_workspace
=VSCODE_WORKSPACE
,
137 self
.kafka_client
= KafkaClient(self
, "kafka")
138 self
.framework
.observe(self
.on
["kafka"].relation_changed
, self
.configure_pod
)
139 self
.framework
.observe(self
.on
["kafka"].relation_broken
, self
.configure_pod
)
141 self
.mysql_client
= MysqlClient(self
, "mysql")
142 self
.framework
.observe(self
.on
["mysql"].relation_changed
, self
.configure_pod
)
143 self
.framework
.observe(self
.on
["mysql"].relation_broken
, self
.configure_pod
)
145 self
.mongodb_client
= MongoClient(self
, "mongodb")
146 self
.framework
.observe(self
.on
["mongodb"].relation_changed
, self
.configure_pod
)
147 self
.framework
.observe(self
.on
["mongodb"].relation_broken
, self
.configure_pod
)
149 self
.framework
.observe(self
.on
["ro"].relation_joined
, self
._publish
_ro
_info
)
151 def _publish_ro_info(self
, event
):
152 """Publishes RO information.
155 event (EventBase): RO relation event.
157 if self
.unit
.is_leader():
159 "host": self
.model
.app
.name
,
162 for k
, v
in rel_data
.items():
163 event
.relation
.data
[self
.app
][k
] = v
165 def _check_missing_dependencies(self
, config
: ConfigModel
):
166 missing_relations
= []
168 if config
.enable_ng_ro
:
169 if self
.kafka_client
.is_missing_data_in_unit():
170 missing_relations
.append("kafka")
171 if not config
.mongodb_uri
and self
.mongodb_client
.is_missing_data_in_unit():
172 missing_relations
.append("mongodb")
174 if not config
.mysql_host
and self
.mysql_client
.is_missing_data_in_unit():
175 missing_relations
.append("mysql")
176 if missing_relations
:
177 raise RelationsMissing(missing_relations
)
179 def _validate_mysql_config(self
, config
: ConfigModel
):
181 if not config
.mysql_user
:
182 invalid_values
.append("Mysql user is empty")
183 if not config
.mysql_password
:
184 invalid_values
.append("Mysql password is empty")
185 if not config
.mysql_root_password
:
186 invalid_values
.append("Mysql root password empty")
189 raise ValueError("Invalid values: " + ", ".join(invalid_values
))
191 def _build_cert_files(
195 cert_files_builder
= FilesV3Builder()
196 for name
, content
in config
.certificates_dict
.items():
197 cert_files_builder
.add_file(name
, decode(content
), mode
=0o600)
198 return cert_files_builder
.build()
200 def build_pod_spec(self
, image_info
):
202 config
= ConfigModel(**dict(self
.config
))
204 if config
.enable_ng_ro
:
205 if config
.mongodb_uri
and not self
.mongodb_client
.is_missing_data_in_unit():
207 "Mongodb data cannot be provided via config and relation"
210 if config
.mysql_host
and not self
.mysql_client
.is_missing_data_in_unit():
211 raise Exception("Mysql data cannot be provided via config and relation")
213 if config
.mysql_host
:
214 self
._validate
_mysql
_config
(config
)
217 self
._check
_missing
_dependencies
(config
)
219 # Create Builder for the PodSpec
220 pod_spec_builder
= PodSpecV3Builder()
223 container_builder
= ContainerV3Builder(
224 self
.app
.name
, image_info
, config
.image_pull_policy
226 certs_files
= self
._build
_cert
_files
(config
)
229 container_builder
.add_volume_config("certs", "/certs", certs_files
)
231 container_builder
.add_port(name
=self
.app
.name
, port
=PORT
)
232 container_builder
.add_http_readiness_probe(
233 "/ro/" if config
.enable_ng_ro
else "/openmano/tenants",
235 initial_delay_seconds
=10,
240 container_builder
.add_http_liveness_probe(
241 "/ro/" if config
.enable_ng_ro
else "/openmano/tenants",
243 initial_delay_seconds
=600,
248 container_builder
.add_envs(
250 "OSMRO_LOG_LEVEL": config
.log_level
,
254 if config
.enable_ng_ro
:
255 # Add secrets to the pod
256 mongodb_secret_name
= f
"{self.app.name}-mongodb-secret"
257 pod_spec_builder
.add_secret(
260 "uri": config
.mongodb_uri
or self
.mongodb_client
.connection_string
,
261 "commonkey": config
.database_commonkey
,
264 container_builder
.add_envs(
266 "OSMRO_MESSAGE_DRIVER": "kafka",
267 "OSMRO_MESSAGE_HOST": self
.kafka_client
.host
,
268 "OSMRO_MESSAGE_PORT": self
.kafka_client
.port
,
269 # MongoDB configuration
270 "OSMRO_DATABASE_DRIVER": "mongo",
273 container_builder
.add_secret_envs(
274 secret_name
=mongodb_secret_name
,
276 "OSMRO_DATABASE_URI": "uri",
277 "OSMRO_DATABASE_COMMONKEY": "commonkey",
280 restart_policy
= PodRestartPolicy()
281 restart_policy
.add_secrets(secret_names
=(mongodb_secret_name
,))
282 pod_spec_builder
.set_restart_policy(restart_policy
)
285 container_builder
.add_envs(
287 "RO_DB_HOST": config
.mysql_host
or self
.mysql_client
.host
,
288 "RO_DB_OVIM_HOST": config
.mysql_host
or self
.mysql_client
.host
,
289 "RO_DB_PORT": config
.mysql_port
or self
.mysql_client
.port
,
290 "RO_DB_OVIM_PORT": config
.mysql_port
or self
.mysql_client
.port
,
291 "RO_DB_USER": config
.mysql_user
or self
.mysql_client
.user
,
292 "RO_DB_OVIM_USER": config
.mysql_user
or self
.mysql_client
.user
,
293 "RO_DB_PASSWORD": config
.mysql_password
294 or self
.mysql_client
.password
,
295 "RO_DB_OVIM_PASSWORD": config
.mysql_password
296 or self
.mysql_client
.password
,
297 "RO_DB_ROOT_PASSWORD": config
.mysql_root_password
298 or self
.mysql_client
.root_password
,
299 "RO_DB_OVIM_ROOT_PASSWORD": config
.mysql_root_password
300 or self
.mysql_client
.root_password
,
301 "RO_DB_NAME": config
.ro_database
,
302 "RO_DB_OVIM_NAME": config
.vim_database
,
303 "OPENMANO_TENANT": config
.openmano_tenant
,
306 container
= container_builder
.build()
308 # Add container to pod spec
309 pod_spec_builder
.add_container(container
)
311 return pod_spec_builder
.build()
316 {"path": "/usr/lib/python3/dist-packages/osm_ng_ro"},
317 {"path": "/usr/lib/python3/dist-packages/osm_common"},
318 {"path": "/usr/lib/python3/dist-packages/osm_ro_plugin"},
319 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_arista_cloudvision"},
320 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_dpb"},
321 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_dynpac"},
322 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_floodlightof"},
323 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_ietfl2vpn"},
324 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_juniper_contrail"},
325 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_odlof"},
326 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_onos_vpls"},
327 {"path": "/usr/lib/python3/dist-packages/osm_rosdn_onosof"},
328 {"path": "/usr/lib/python3/dist-packages/osm_rovim_aws"},
329 {"path": "/usr/lib/python3/dist-packages/osm_rovim_azure"},
330 {"path": "/usr/lib/python3/dist-packages/osm_rovim_fos"},
331 {"path": "/usr/lib/python3/dist-packages/osm_rovim_opennebula"},
332 {"path": "/usr/lib/python3/dist-packages/osm_rovim_openstack"},
333 {"path": "/usr/lib/python3/dist-packages/osm_rovim_openvim"},
334 {"path": "/usr/lib/python3/dist-packages/osm_rovim_vmware"},
339 "module": "osm_ng_ro.ro_main",
351 if __name__
== "__main__":