From: sousaedu Date: Sun, 2 May 2021 22:22:43 +0000 (+0200) Subject: Adding manual external DB URI config X-Git-Tag: release-v10.0-start~16 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2Fdevops.git;a=commitdiff_plain;h=996a5604c7d31f3758503b08a426f1f40619b17b Adding manual external DB URI config Change-Id: I24782d5b9594aebae2ec97b1196acfa2bafb6dbd Signed-off-by: sousaedu --- diff --git a/installers/charm/keystone/config.yaml b/installers/charm/keystone/config.yaml index 2758d1c4..402096ca 100644 --- a/installers/charm/keystone/config.yaml +++ b/installers/charm/keystone/config.yaml @@ -45,6 +45,15 @@ options: type: string description: Keystone DB Password default: admin + mysql_host: + type: string + description: MySQL Host (external database) + mysql_port: + type: int + description: MySQL Port (external database) + mysql_root_password: + type: string + description: MySQL Root Password (external database) admin_username: type: string description: Admin username to be created when starting the service diff --git a/installers/charm/keystone/src/charm.py b/installers/charm/keystone/src/charm.py index 5d8d3174..b5ce0cc6 100755 --- a/installers/charm/keystone/src/charm.py +++ b/installers/charm/keystone/src/charm.py @@ -80,6 +80,9 @@ class ConfigModel(ModelValidator): site_url: Optional[str] ingress_whitelist_source_range: Optional[str] tls_secret_name: Optional[str] + mysql_host: Optional[str] + mysql_port: Optional[int] + mysql_root_password: Optional[str] @validator("max_file_size") def validate_max_file_size(cls, v): @@ -101,6 +104,12 @@ class ConfigModel(ModelValidator): ip_network(v) return v + @validator("mysql_port") + def validate_mysql_port(cls, v): + if v and (v <= 0 or v >= 65535): + raise ValueError("Mysql port out of range") + return v + class ConfigLdapModel(ModelValidator): ldap_enabled: bool @@ -170,11 +179,19 @@ class KeystoneCharm(CharmedOsmBase): def _check_missing_dependencies(self, config: ConfigModel): missing_relations = [] - if self.mysql_client.is_missing_data_in_unit(): + if not config.mysql_host and self.mysql_client.is_missing_data_in_unit(): missing_relations.append("mysql") if missing_relations: raise RelationsMissing(missing_relations) + def _validate_mysql_config(self, config: ConfigModel): + invalid_values = [] + if not config.mysql_root_password: + invalid_values.append("Mysql root password must be provided") + + if invalid_values: + raise ValueError("Invalid values: " + ", ".join(invalid_values)) + def _generate_keys(self) -> Tuple[List[str], List[str]]: """Generating new fernet tokens. @@ -229,13 +246,23 @@ class KeystoneCharm(CharmedOsmBase): # Validate config config = ConfigModel(**dict(self.config)) config_ldap = ConfigLdapModel(**dict(self.config)) + + if config.mysql_host and not self.mysql_client.is_missing_data_in_unit(): + raise Exception("Mysql data cannot be provided via config and relation") + + if config.mysql_host: + self._validate_mysql_config(config) + # Check relations self._check_missing_dependencies(config) + # Create Builder for the PodSpec pod_spec_builder = PodSpecV3Builder() + # Build Container container_builder = ContainerV3Builder(self.app.name, image_info) container_builder.add_port(name=self.app.name, port=PORT) + # Build files credential_files, fernet_files = self._build_files(config) container_builder.add_volume_config( @@ -246,10 +273,11 @@ class KeystoneCharm(CharmedOsmBase): ) container_builder.add_envs( { - "DB_HOST": self.mysql_client.host, - "DB_PORT": self.mysql_client.port, + "DB_HOST": config.mysql_host or self.mysql_client.host, + "DB_PORT": config.mysql_port or self.mysql_client.port, "ROOT_DB_USER": "root", - "ROOT_DB_PASSWORD": self.mysql_client.root_password, + "ROOT_DB_PASSWORD": config.mysql_root_password + or self.mysql_client.root_password, "KEYSTONE_DB_PASSWORD": config.keystone_db_password, "REGION_ID": config.region_id, "KEYSTONE_HOST": self.app.name, @@ -263,7 +291,6 @@ class KeystoneCharm(CharmedOsmBase): ) if config_ldap.ldap_enabled: - container_builder.add_envs( { "LDAP_AUTHENTICATION_DOMAIN_NAME": config_ldap.ldap_authentication_domain_name, @@ -325,8 +352,10 @@ class KeystoneCharm(CharmedOsmBase): } ) container = container_builder.build() + # Add container to pod spec pod_spec_builder.add_container(container) + # Add ingress resources to pod spec if site url exists if config.site_url: parsed = urlparse(config.site_url) diff --git a/installers/charm/keystone/tests/test_charm.py b/installers/charm/keystone/tests/test_charm.py index d16e75d7..e281b201 100644 --- a/installers/charm/keystone/tests/test_charm.py +++ b/installers/charm/keystone/tests/test_charm.py @@ -42,6 +42,9 @@ class TestCharm(unittest.TestCase): self.config = { "region_id": "str", "keystone_db_password": "str", + "mysql_host": "", + "mysql_port": 3306, + "mysql_root_password": "manopw", "admin_username": "str", "admin_password": "str", "admin_project": "str", @@ -83,6 +86,12 @@ class TestCharm(unittest.TestCase): # Assertions self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus) + def test_with_config(self) -> NoReturn: + "Test with mysql config" + self.initialize_mysql_config() + # Verifying status + self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_with_relations( self, ) -> NoReturn: @@ -91,6 +100,24 @@ class TestCharm(unittest.TestCase): # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_exception_mysql_relation_and_config( + self, + ) -> NoReturn: + "Test with relations and config. Must throw exception" + self.initialize_mysql_config() + self.initialize_mysql_relation() + # Verifying status + self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) + + def initialize_mysql_config(self): + self.harness.update_config( + { + "mysql_host": "mysql", + "mysql_port": 3306, + "mysql_root_password": "manopw", + } + ) + def initialize_mysql_relation(self): relation_id = self.harness.add_relation("db", "mysql") self.harness.add_relation_unit(relation_id, "mysql/0") diff --git a/installers/charm/keystone/tox.ini b/installers/charm/keystone/tox.ini index d0d25709..cb37bc87 100644 --- a/installers/charm/keystone/tox.ini +++ b/installers/charm/keystone/tox.ini @@ -108,6 +108,7 @@ commands = ignore = W291, W293, + W503, E123, E125, E226, diff --git a/installers/charm/lcm/config.yaml b/installers/charm/lcm/config.yaml index 6301622e..0f0cebbe 100644 --- a/installers/charm/lcm/config.yaml +++ b/installers/charm/lcm/config.yaml @@ -59,6 +59,9 @@ options: description: Database common key type: string default: osm + mongodb_uri: + type: string + description: MongoDB URI (external database) log_level: description: "Log Level" type: string diff --git a/installers/charm/lcm/src/charm.py b/installers/charm/lcm/src/charm.py index e9552fd1..52bc5cf4 100755 --- a/installers/charm/lcm/src/charm.py +++ b/installers/charm/lcm/src/charm.py @@ -51,6 +51,7 @@ class ConfigModel(ModelValidator): vca_cloud: str vca_k8s_cloud: str database_commonkey: str + mongodb_uri: Optional[str] log_level: str vca_apiproxy: Optional[str] # Model-config options @@ -114,6 +115,12 @@ class ConfigModel(ModelValidator): raise ValueError("value must be INFO or DEBUG") return v + @validator("mongodb_uri") + def validate_mongodb_uri(cls, v): + if v and not v.startswith("mongodb://"): + raise ValueError("mongodb_uri is not properly formed") + return v + class LcmCharm(CharmedOsmBase): def __init__(self, *args) -> NoReturn: @@ -136,7 +143,7 @@ class LcmCharm(CharmedOsmBase): if self.kafka_client.is_missing_data_in_unit(): missing_relations.append("kafka") - if self.mongodb_client.is_missing_data_in_unit(): + if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit(): missing_relations.append("mongodb") if self.ro_client.is_missing_data_in_app(): missing_relations.append("ro") @@ -147,10 +154,16 @@ class LcmCharm(CharmedOsmBase): def build_pod_spec(self, image_info): # Validate config config = ConfigModel(**dict(self.config)) + + if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit(): + raise Exception("Mongodb data cannot be provided via config and relation") + # Check relations self._check_missing_dependencies(config) + # Create Builder for the PodSpec pod_spec_builder = PodSpecV3Builder() + # Build Container container_builder = ContainerV3Builder(self.app.name, image_info) container_builder.add_port(name=self.app.name, port=PORT) @@ -169,7 +182,8 @@ class LcmCharm(CharmedOsmBase): "OSMLCM_MESSAGE_PORT": self.kafka_client.port, # Database configuration "OSMLCM_DATABASE_DRIVER": "mongo", - "OSMLCM_DATABASE_URI": self.mongodb_client.connection_string, + "OSMLCM_DATABASE_URI": config.mongodb_uri + or self.mongodb_client.connection_string, "OSMLCM_DATABASE_COMMONKEY": config.database_commonkey, # Storage configuration "OSMLCM_STORAGE_DRIVER": "mongo", @@ -195,11 +209,15 @@ class LcmCharm(CharmedOsmBase): for k, v in self.config.items() if k.startswith("vca_model_config") } + if model_config_envs: container_builder.add_envs(model_config_envs) + container = container_builder.build() + # Add container to pod spec pod_spec_builder.add_container(container) + return pod_spec_builder.build() diff --git a/installers/charm/lcm/tests/test_charm.py b/installers/charm/lcm/tests/test_charm.py index 831e1762..3e6b2a4f 100644 --- a/installers/charm/lcm/tests/test_charm.py +++ b/installers/charm/lcm/tests/test_charm.py @@ -49,6 +49,7 @@ class TestCharm(unittest.TestCase): "vca_cloud": "cloud", "vca_k8s_cloud": "k8scloud", "database_commonkey": "commonkey", + "mongodb_uri": "", "log_level": "INFO", } self.harness.update_config(self.config) @@ -79,6 +80,16 @@ class TestCharm(unittest.TestCase): # Assertions self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus) + def test_with_relations_and_mongodb_config( + self, + ) -> NoReturn: + "Test with relations and mongodb config" + self.initialize_kafka_relation() + self.initialize_mongo_config() + self.initialize_ro_relation() + # Verifying status + self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_with_relations( self, ) -> NoReturn: @@ -89,6 +100,15 @@ class TestCharm(unittest.TestCase): # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_exception_mongodb_relation_and_config( + self, + ) -> NoReturn: + "Test with all relations and config for mongodb. Must fail" + self.initialize_mongo_relation() + self.initialize_mongo_config() + # Verifying status + self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_build_pod_spec( self, ) -> NoReturn: @@ -200,6 +220,9 @@ class TestCharm(unittest.TestCase): kafka_relation_id, "kafka/0", {"host": "kafka", "port": 9092} ) + def initialize_mongo_config(self): + self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"}) + def initialize_mongo_relation(self): mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb") self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0") diff --git a/installers/charm/lcm/tox.ini b/installers/charm/lcm/tox.ini index 30e0ea8c..f207ac34 100644 --- a/installers/charm/lcm/tox.ini +++ b/installers/charm/lcm/tox.ini @@ -108,6 +108,7 @@ commands = ignore = W291, W293, + W503, E123, E125, E226, diff --git a/installers/charm/mon/config.yaml b/installers/charm/mon/config.yaml index f6d1ae19..d06b68df 100644 --- a/installers/charm/mon/config.yaml +++ b/installers/charm/mon/config.yaml @@ -36,6 +36,9 @@ options: description: Database common key type: string default: osm + mongodb_uri: + type: string + description: MongoDB URI (external database) collector_interval: description: Collector interval type: int diff --git a/installers/charm/mon/src/charm.py b/installers/charm/mon/src/charm.py index ab6dd2df..8c0a6bc7 100755 --- a/installers/charm/mon/src/charm.py +++ b/installers/charm/mon/src/charm.py @@ -70,6 +70,7 @@ class ConfigModel(ModelValidator): vca_secret: str vca_cacert: str database_commonkey: str + mongodb_uri: Optional[str] log_level: str openstack_default_granularity: int global_request_timeout: int @@ -92,6 +93,12 @@ class ConfigModel(ModelValidator): _extract_certificates(v) return v + @validator("mongodb_uri") + def validate_mongodb_uri(cls, v): + if v and not v.startswith("mongodb://"): + raise ValueError("mongodb_uri is not properly formed") + return v + @property def certificates_dict(cls): return _extract_certificates(cls.certificates) if cls.certificates else {} @@ -126,7 +133,7 @@ class MonCharm(CharmedOsmBase): if self.kafka_client.is_missing_data_in_unit(): missing_relations.append("kafka") - if self.mongodb_client.is_missing_data_in_unit(): + if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit(): missing_relations.append("mongodb") if self.prometheus_client.is_missing_data_in_app(): missing_relations.append("prometheus") @@ -149,15 +156,23 @@ class MonCharm(CharmedOsmBase): def build_pod_spec(self, image_info): # Validate config config = ConfigModel(**dict(self.config)) + + if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit(): + raise Exception("Mongodb data cannot be provided via config and relation") + # Check relations self._check_missing_dependencies(config) + # Create Builder for the PodSpec pod_spec_builder = PodSpecV3Builder() + # Build Container container_builder = ContainerV3Builder(self.app.name, image_info) certs_files = self._build_cert_files(config) + if certs_files: container_builder.add_volume_config("certs", "/certs", certs_files) + container_builder.add_port(name=self.app.name, port=PORT) container_builder.add_envs( { @@ -174,7 +189,8 @@ class MonCharm(CharmedOsmBase): "OSMMON_MESSAGE_PORT": self.kafka_client.port, # Database configuration "OSMMON_DATABASE_DRIVER": "mongo", - "OSMMON_DATABASE_URI": self.mongodb_client.connection_string, + "OSMMON_DATABASE_URI": config.mongodb_uri + or self.mongodb_client.connection_string, "OSMMON_DATABASE_COMMONKEY": config.database_commonkey, # Prometheus configuration "OSMMON_PROMETHEUS_URL": f"http://{self.prometheus_client.hostname}:{self.prometheus_client.port}", @@ -200,10 +216,11 @@ class MonCharm(CharmedOsmBase): "OSMMON_KEYSTONE_SERVICE_PROJECT": self.keystone_client.service, } ) - container = container_builder.build() + # Add container to pod spec pod_spec_builder.add_container(container) + return pod_spec_builder.build() diff --git a/installers/charm/mon/tests/test_charm.py b/installers/charm/mon/tests/test_charm.py index dcf74ed5..a7cef207 100644 --- a/installers/charm/mon/tests/test_charm.py +++ b/installers/charm/mon/tests/test_charm.py @@ -76,6 +76,7 @@ class TestCharm(unittest.TestCase): "vca_secret": "admin", "vca_cacert": "cacert", "database_commonkey": "commonkey", + "mongodb_uri": "", "log_level": "INFO", "openstack_default_granularity": 10, "global_request_timeout": 10, @@ -112,6 +113,17 @@ class TestCharm(unittest.TestCase): # Assertions self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus) + def test_with_relations_and_mongodb_config( + self, + ) -> NoReturn: + "Test with relations (internal)" + self.initialize_kafka_relation() + self.initialize_mongo_config() + self.initialize_prometheus_relation() + self.initialize_keystone_relation() + # Verifying status + self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_with_relations( self, ) -> NoReturn: @@ -123,6 +135,15 @@ class TestCharm(unittest.TestCase): # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_exception_mongodb_relation_and_config( + self, + ) -> NoReturn: + "Test with relations and config for mongodb. Must fail" + self.initialize_mongo_relation() + self.initialize_mongo_config() + # Verifying status + self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) + def initialize_kafka_relation(self): kafka_relation_id = self.harness.add_relation("kafka", "kafka") self.harness.add_relation_unit(kafka_relation_id, "kafka/0") @@ -130,6 +151,9 @@ class TestCharm(unittest.TestCase): kafka_relation_id, "kafka/0", {"host": "kafka", "port": 9092} ) + def initialize_mongo_config(self): + self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"}) + def initialize_mongo_relation(self): mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb") self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0") diff --git a/installers/charm/mon/tox.ini b/installers/charm/mon/tox.ini index 30e0ea8c..f207ac34 100644 --- a/installers/charm/mon/tox.ini +++ b/installers/charm/mon/tox.ini @@ -108,6 +108,7 @@ commands = ignore = W291, W293, + W503, E123, E125, E226, diff --git a/installers/charm/nbi/config.yaml b/installers/charm/nbi/config.yaml index ef0792bc..a3d94bc2 100644 --- a/installers/charm/nbi/config.yaml +++ b/installers/charm/nbi/config.yaml @@ -64,3 +64,6 @@ options: type: boolean description: Enable test endpoints of NBI. default: false + mongodb_uri: + type: string + description: MongoDB URI (external database) diff --git a/installers/charm/nbi/src/charm.py b/installers/charm/nbi/src/charm.py index 1f5812af..550c88b0 100755 --- a/installers/charm/nbi/src/charm.py +++ b/installers/charm/nbi/src/charm.py @@ -59,6 +59,7 @@ class ConfigModel(ModelValidator): cluster_issuer: Optional[str] ingress_whitelist_source_range: Optional[str] tls_secret_name: Optional[str] + mongodb_uri: Optional[str] @validator("auth_backend") def validate_auth_backend(cls, v): @@ -92,6 +93,12 @@ class ConfigModel(ModelValidator): ip_network(v) return v + @validator("mongodb_uri") + def validate_mongodb_uri(cls, v): + if v and not v.startswith("mongodb://"): + raise ValueError("mongodb_uri is not properly formed") + return v + class NbiCharm(CharmedOsmBase): def __init__(self, *args) -> NoReturn: @@ -134,7 +141,7 @@ class NbiCharm(CharmedOsmBase): if self.kafka_client.is_missing_data_in_unit(): missing_relations.append("kafka") - if self.mongodb_client.is_missing_data_in_unit(): + if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit(): missing_relations.append("mongodb") if self.prometheus_client.is_missing_data_in_app(): missing_relations.append("prometheus") @@ -148,10 +155,16 @@ class NbiCharm(CharmedOsmBase): def build_pod_spec(self, image_info): # Validate config config = ConfigModel(**dict(self.config)) + + if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit(): + raise Exception("Mongodb data cannot be provided via config and relation") + # Check relations self._check_missing_dependencies(config) + # Create Builder for the PodSpec pod_spec_builder = PodSpecV3Builder() + # Build Init Container pod_spec_builder.add_init_container( { @@ -164,6 +177,7 @@ class NbiCharm(CharmedOsmBase): ], } ) + # Build Container container_builder = ContainerV3Builder(self.app.name, image_info) container_builder.add_port(name=self.app.name, port=PORT) @@ -189,7 +203,8 @@ class NbiCharm(CharmedOsmBase): "OSMNBI_MESSAGE_PORT": self.kafka_client.port, # Database configuration "OSMNBI_DATABASE_DRIVER": "mongo", - "OSMNBI_DATABASE_URI": self.mongodb_client.connection_string, + "OSMNBI_DATABASE_URI": config.mongodb_uri + or self.mongodb_client.connection_string, "OSMNBI_DATABASE_COMMONKEY": config.database_commonkey, # Storage configuration "OSMNBI_STORAGE_DRIVER": "mongo", @@ -219,8 +234,10 @@ class NbiCharm(CharmedOsmBase): } ) container = container_builder.build() + # Add container to pod spec pod_spec_builder.add_container(container) + # Add ingress resources to pod spec if site url exists if config.site_url: parsed = urlparse(config.site_url) @@ -254,7 +271,9 @@ class NbiCharm(CharmedOsmBase): ingress_resource_builder.add_rule(parsed.hostname, self.app.name, PORT) ingress_resource = ingress_resource_builder.build() pod_spec_builder.add_ingress_resource(ingress_resource) + logger.debug(pod_spec_builder.build()) + return pod_spec_builder.build() diff --git a/installers/charm/nbi/tests/test_charm.py b/installers/charm/nbi/tests/test_charm.py index 65433351..116c06bc 100644 --- a/installers/charm/nbi/tests/test_charm.py +++ b/installers/charm/nbi/tests/test_charm.py @@ -43,6 +43,7 @@ class TestCharm(unittest.TestCase): "enable_test": False, "auth_backend": "internal", "database_commonkey": "key", + "mongodb_uri": "", "log_level": "INFO", "max_file_size": 0, "ingress_whitelist_source_range": "", @@ -78,6 +79,16 @@ class TestCharm(unittest.TestCase): # Assertions self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus) + def test_with_relations_internal_and_mongodb_config( + self, + ) -> NoReturn: + "Test with relations and mongodb config (internal)" + self.initialize_kafka_relation() + self.initialize_mongo_config() + self.initialize_prometheus_relation() + # Verifying status + self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_with_relations_internal( self, ) -> NoReturn: @@ -88,6 +99,18 @@ class TestCharm(unittest.TestCase): # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_with_relations_and_mongodb_config_with_keystone_missing( + self, + ) -> NoReturn: + "Test with relations and mongodb config (keystone)" + self.harness.update_config({"auth_backend": "keystone"}) + self.initialize_kafka_relation() + self.initialize_mongo_config() + self.initialize_prometheus_relation() + # Verifying status + self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) + self.assertTrue("keystone" in self.harness.charm.unit.status.message) + def test_with_relations_keystone_missing( self, ) -> NoReturn: @@ -100,6 +123,18 @@ class TestCharm(unittest.TestCase): self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) self.assertTrue("keystone" in self.harness.charm.unit.status.message) + def test_with_relations_and_mongodb_config_with_keystone( + self, + ) -> NoReturn: + "Test with relations (keystone)" + self.harness.update_config({"auth_backend": "keystone"}) + self.initialize_kafka_relation() + self.initialize_mongo_config() + self.initialize_prometheus_relation() + self.initialize_keystone_relation() + # Verifying status + self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_with_relations_keystone( self, ) -> NoReturn: @@ -112,6 +147,14 @@ class TestCharm(unittest.TestCase): # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_mongodb_exception_relation_and_config( + self, + ) -> NoReturn: + self.initialize_mongo_config() + self.initialize_mongo_relation() + # Verifying status + self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) + def initialize_kafka_relation(self): kafka_relation_id = self.harness.add_relation("kafka", "kafka") self.harness.add_relation_unit(kafka_relation_id, "kafka/0") @@ -119,6 +162,9 @@ class TestCharm(unittest.TestCase): kafka_relation_id, "kafka/0", {"host": "kafka", "port": 9092} ) + def initialize_mongo_config(self): + self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"}) + def initialize_mongo_relation(self): mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb") self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0") diff --git a/installers/charm/nbi/tox.ini b/installers/charm/nbi/tox.ini index 30e0ea8c..f207ac34 100644 --- a/installers/charm/nbi/tox.ini +++ b/installers/charm/nbi/tox.ini @@ -108,6 +108,7 @@ commands = ignore = W291, W293, + W503, E123, E125, E226, diff --git a/installers/charm/pla/config.yaml b/installers/charm/pla/config.yaml index ae903042..1820188a 100644 --- a/installers/charm/pla/config.yaml +++ b/installers/charm/pla/config.yaml @@ -24,3 +24,6 @@ options: description: Common Key for Mongo database type: string default: osm + mongodb_uri: + type: string + description: MongoDB URI (external database) diff --git a/installers/charm/pla/src/charm.py b/installers/charm/pla/src/charm.py index 15ae0958..d9dfaa45 100755 --- a/installers/charm/pla/src/charm.py +++ b/installers/charm/pla/src/charm.py @@ -24,7 +24,7 @@ import logging -from typing import NoReturn +from typing import NoReturn, Optional from ops.main import main from opslib.osm.charm import CharmedOsmBase, RelationsMissing @@ -44,6 +44,7 @@ PORT = 9999 class ConfigModel(ModelValidator): database_commonkey: str + mongodb_uri: Optional[str] log_level: str @validator("log_level") @@ -52,6 +53,12 @@ class ConfigModel(ModelValidator): raise ValueError("value must be INFO or DEBUG") return v + @validator("mongodb_uri") + def validate_mongodb_uri(cls, v): + if v and not v.startswith("mongodb://"): + raise ValueError("mongodb_uri is not properly formed") + return v + class PlaCharm(CharmedOsmBase): def __init__(self, *args) -> NoReturn: @@ -70,7 +77,7 @@ class PlaCharm(CharmedOsmBase): if self.kafka_client.is_missing_data_in_unit(): missing_relations.append("kafka") - if self.mongodb_client.is_missing_data_in_unit(): + if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit(): missing_relations.append("mongodb") if missing_relations: @@ -79,10 +86,16 @@ class PlaCharm(CharmedOsmBase): def build_pod_spec(self, image_info): # Validate config config = ConfigModel(**dict(self.config)) + + if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit(): + raise Exception("Mongodb data cannot be provided via config and relation") + # Check relations self._check_missing_dependencies(config) + # Create Builder for the PodSpec pod_spec_builder = PodSpecV3Builder() + # Build Container container_builder = ContainerV3Builder(self.app.name, image_info) container_builder.add_port(name=self.app.name, port=PORT) @@ -97,14 +110,17 @@ class PlaCharm(CharmedOsmBase): "OSMPLA_MESSAGE_PORT": self.kafka_client.port, # Database configuration "OSMPLA_DATABASE_DRIVER": "mongo", - "OSMPLA_DATABASE_URI": self.mongodb_client.connection_string, + "OSMPLA_DATABASE_URI": config.mongodb_uri + or self.mongodb_client.connection_string, "OSMPLA_DATABASE_COMMONKEY": config.database_commonkey, } ) container = container_builder.build() + # Add container to pod spec pod_spec_builder.add_container(container) + return pod_spec_builder.build() diff --git a/installers/charm/pla/tests/test_charm.py b/installers/charm/pla/tests/test_charm.py index 80cb3158..debc378b 100644 --- a/installers/charm/pla/tests/test_charm.py +++ b/installers/charm/pla/tests/test_charm.py @@ -41,6 +41,7 @@ class TestCharm(unittest.TestCase): self.harness.begin() self.config = { "log_level": "INFO", + "mongodb_uri": "", } self.harness.update_config(self.config) @@ -70,6 +71,15 @@ class TestCharm(unittest.TestCase): # Assertions self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus) + def test_with_relations_and_mongodb_config( + self, + ) -> NoReturn: + "Test with relations and mongodb config (internal)" + self.initialize_kafka_relation() + self.initialize_mongo_config() + # Verifying status + self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_with_relations( self, ) -> NoReturn: @@ -79,6 +89,15 @@ class TestCharm(unittest.TestCase): # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_exception_mongodb_relation_and_config( + self, + ) -> NoReturn: + "Test with relation and config for Mongodb. Test must fail" + self.initialize_mongo_relation() + self.initialize_mongo_config() + # Verifying status + self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) + def initialize_kafka_relation(self): kafka_relation_id = self.harness.add_relation("kafka", "kafka") self.harness.add_relation_unit(kafka_relation_id, "kafka/0") @@ -86,6 +105,9 @@ class TestCharm(unittest.TestCase): kafka_relation_id, "kafka/0", {"host": "kafka", "port": 9092} ) + def initialize_mongo_config(self): + self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"}) + def initialize_mongo_relation(self): mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb") self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0") diff --git a/installers/charm/pla/tox.ini b/installers/charm/pla/tox.ini index 30e0ea8c..f207ac34 100644 --- a/installers/charm/pla/tox.ini +++ b/installers/charm/pla/tox.ini @@ -108,6 +108,7 @@ commands = ignore = W291, W293, + W503, E123, E125, E226, diff --git a/installers/charm/pol/config.yaml b/installers/charm/pol/config.yaml index 1ae26a4e..59f695be 100644 --- a/installers/charm/pol/config.yaml +++ b/installers/charm/pol/config.yaml @@ -24,3 +24,6 @@ options: description: "Log Level" type: string default: "INFO" + mongodb_uri: + type: string + description: MongoDB URI (external database) diff --git a/installers/charm/pol/src/charm.py b/installers/charm/pol/src/charm.py index d339e1ce..b2e58836 100755 --- a/installers/charm/pol/src/charm.py +++ b/installers/charm/pol/src/charm.py @@ -24,7 +24,7 @@ import logging -from typing import NoReturn +from typing import NoReturn, Optional from ops.main import main from opslib.osm.charm import CharmedOsmBase, RelationsMissing @@ -44,6 +44,7 @@ PORT = 9999 class ConfigModel(ModelValidator): log_level: str + mongodb_uri: Optional[str] @validator("log_level") def validate_log_level(cls, v): @@ -51,6 +52,12 @@ class ConfigModel(ModelValidator): raise ValueError("value must be INFO or DEBUG") return v + @validator("mongoddb_uri") + def validate_mongodb_uri(cls, v): + if v and not v.startswith("mongodb://"): + raise ValueError("mongodb_uri is not properly formed") + return v + class PolCharm(CharmedOsmBase): def __init__(self, *args) -> NoReturn: @@ -69,7 +76,7 @@ class PolCharm(CharmedOsmBase): if self.kafka_client.is_missing_data_in_unit(): missing_relations.append("kafka") - if self.mongodb_client.is_missing_data_in_unit(): + if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit(): missing_relations.append("mongodb") if missing_relations: @@ -78,10 +85,16 @@ class PolCharm(CharmedOsmBase): def build_pod_spec(self, image_info): # Validate config config = ConfigModel(**dict(self.config)) + + if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit(): + raise Exception("Mongodb data cannot be provided via config and relation") + # Check relations self._check_missing_dependencies(config) + # Create Builder for the PodSpec pod_spec_builder = PodSpecV3Builder() + # Build Container container_builder = ContainerV3Builder(self.app.name, image_info) container_builder.add_port(name=self.app.name, port=PORT) @@ -96,13 +109,15 @@ class PolCharm(CharmedOsmBase): "OSMPOL_MESSAGE_PORT": self.kafka_client.port, # Database configuration "OSMPOL_DATABASE_DRIVER": "mongo", - "OSMPOL_DATABASE_URI": self.mongodb_client.connection_string, + "OSMPOL_DATABASE_URI": config.mongodb_uri + or self.mongodb_client.connection_string, } ) - container = container_builder.build() + # Add container to pod spec pod_spec_builder.add_container(container) + return pod_spec_builder.build() diff --git a/installers/charm/pol/tests/test_charm.py b/installers/charm/pol/tests/test_charm.py index 32b1acd6..089b6e52 100644 --- a/installers/charm/pol/tests/test_charm.py +++ b/installers/charm/pol/tests/test_charm.py @@ -40,6 +40,7 @@ class TestCharm(unittest.TestCase): self.harness.begin() self.config = { "log_level": "INFO", + "mongodb_uri": "", } self.harness.update_config(self.config) @@ -69,6 +70,15 @@ class TestCharm(unittest.TestCase): # Assertions self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus) + def test_with_relations_and_mongodb_config( + self, + ) -> NoReturn: + "Test with relations and mongodb config (internal)" + self.initialize_kafka_relation() + self.initialize_mongo_config() + # Verifying status + self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_with_relations( self, ) -> NoReturn: @@ -78,6 +88,15 @@ class TestCharm(unittest.TestCase): # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_exception_mongodb_relation_and_config( + self, + ) -> NoReturn: + "Test with relation and config for Mongodb. Must fail" + self.initialize_mongo_relation() + self.initialize_mongo_config() + # Verifying status + self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) + def initialize_kafka_relation(self): kafka_relation_id = self.harness.add_relation("kafka", "kafka") self.harness.add_relation_unit(kafka_relation_id, "kafka/0") @@ -85,6 +104,9 @@ class TestCharm(unittest.TestCase): kafka_relation_id, "kafka/0", {"host": "kafka", "port": 9092} ) + def initialize_mongo_config(self): + self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"}) + def initialize_mongo_relation(self): mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb") self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0") diff --git a/installers/charm/pol/tox.ini b/installers/charm/pol/tox.ini index 30e0ea8c..f207ac34 100644 --- a/installers/charm/pol/tox.ini +++ b/installers/charm/pol/tox.ini @@ -108,6 +108,7 @@ commands = ignore = W291, W293, + W503, E123, E125, E226, diff --git a/installers/charm/ro/config.yaml b/installers/charm/ro/config.yaml index bb667d73..0d3c9a0d 100644 --- a/installers/charm/ro/config.yaml +++ b/installers/charm/ro/config.yaml @@ -28,10 +28,28 @@ options: description: Database COMMON KEY type: string default: osm + mongodb_uri: + type: string + description: MongoDB URI (external database) log_level: description: "Log Level" type: string default: "INFO" + mysql_host: + type: string + description: MySQL Host (external database) + mysql_port: + type: int + description: MySQL Port (external database) + mysql_user: + type: string + description: MySQL User (external database) + mysql_password: + type: string + description: MySQL Password (external database) + mysql_root_password: + type: string + description: MySQL Root Password (external database) vim_database: type: string description: "The database name." diff --git a/installers/charm/ro/src/charm.py b/installers/charm/ro/src/charm.py index 5b40c160..951f281b 100755 --- a/installers/charm/ro/src/charm.py +++ b/installers/charm/ro/src/charm.py @@ -62,7 +62,13 @@ def decode(content: str): class ConfigModel(ModelValidator): enable_ng_ro: bool database_commonkey: str + mongodb_uri: Optional[str] log_level: str + mysql_host: Optional[str] + mysql_port: Optional[int] + mysql_user: Optional[str] + mysql_password: Optional[str] + mysql_root_password: Optional[str] vim_database: str ro_database: str openmano_tenant: str @@ -80,6 +86,18 @@ class ConfigModel(ModelValidator): _extract_certificates(v) return v + @validator("mongodb_uri") + def validate_mongodb_uri(cls, v): + if v and not v.startswith("mongodb://"): + raise ValueError("mongodb_uri is not properly formed") + return v + + @validator("mysql_port") + def validate_mysql_port(cls, v): + if v and (v <= 0 or v >= 65535): + raise ValueError("Mysql port out of range") + return v + @property def certificates_dict(cls): return _extract_certificates(cls.certificates) if cls.certificates else {} @@ -126,14 +144,26 @@ class RoCharm(CharmedOsmBase): if config.enable_ng_ro: if self.kafka_client.is_missing_data_in_unit(): missing_relations.append("kafka") - if self.mongodb_client.is_missing_data_in_unit(): + if not config.mongodb_uri and self.mongodb_client.is_missing_data_in_unit(): missing_relations.append("mongodb") else: - if self.mysql_client.is_missing_data_in_unit(): + if not config.mysql_host and self.mysql_client.is_missing_data_in_unit(): missing_relations.append("mysql") if missing_relations: raise RelationsMissing(missing_relations) + def _validate_mysql_config(self, config: ConfigModel): + invalid_values = [] + if not config.mysql_user: + invalid_values.append("Mysql user is empty") + if not config.mysql_password: + invalid_values.append("Mysql password is empty") + if not config.mysql_root_password: + invalid_values.append("Mysql root password empty") + + if invalid_values: + raise ValueError("Invalid values: " + ", ".join(invalid_values)) + def _build_cert_files( self, config: ConfigModel, @@ -146,15 +176,32 @@ class RoCharm(CharmedOsmBase): def build_pod_spec(self, image_info): # Validate config config = ConfigModel(**dict(self.config)) + + if config.enable_ng_ro: + if config.mongodb_uri and not self.mongodb_client.is_missing_data_in_unit(): + raise Exception( + "Mongodb data cannot be provided via config and relation" + ) + else: + if config.mysql_host and not self.mysql_client.is_missing_data_in_unit(): + raise Exception("Mysql data cannot be provided via config and relation") + + if config.mysql_host: + self._validate_mysql_config(config) + # Check relations self._check_missing_dependencies(config) + # Create Builder for the PodSpec pod_spec_builder = PodSpecV3Builder() + # Build Container container_builder = ContainerV3Builder(self.app.name, image_info) certs_files = self._build_cert_files(config) + if certs_files: container_builder.add_volume_config("certs", "/certs", certs_files) + container_builder.add_port(name=self.app.name, port=PORT) container_builder.add_http_readiness_probe( "/ro/" if config.enable_ng_ro else "/openmano/tenants", @@ -177,6 +224,7 @@ class RoCharm(CharmedOsmBase): "OSMRO_LOG_LEVEL": config.log_level, } ) + if config.enable_ng_ro: container_builder.add_envs( { @@ -185,7 +233,8 @@ class RoCharm(CharmedOsmBase): "OSMRO_MESSAGE_PORT": self.kafka_client.port, # MongoDB configuration "OSMRO_DATABASE_DRIVER": "mongo", - "OSMRO_DATABASE_URI": self.mongodb_client.connection_string, + "OSMRO_DATABASE_URI": config.mongodb_uri + or self.mongodb_client.connection_string, "OSMRO_DATABASE_COMMONKEY": config.database_commonkey, } ) @@ -193,24 +242,30 @@ class RoCharm(CharmedOsmBase): else: container_builder.add_envs( { - "RO_DB_HOST": self.mysql_client.host, - "RO_DB_OVIM_HOST": self.mysql_client.host, - "RO_DB_PORT": self.mysql_client.port, - "RO_DB_OVIM_PORT": self.mysql_client.port, - "RO_DB_USER": self.mysql_client.user, - "RO_DB_OVIM_USER": self.mysql_client.user, - "RO_DB_PASSWORD": self.mysql_client.password, - "RO_DB_OVIM_PASSWORD": self.mysql_client.password, - "RO_DB_ROOT_PASSWORD": self.mysql_client.root_password, - "RO_DB_OVIM_ROOT_PASSWORD": self.mysql_client.root_password, + "RO_DB_HOST": config.mysql_host or self.mysql_client.host, + "RO_DB_OVIM_HOST": config.mysql_host or self.mysql_client.host, + "RO_DB_PORT": config.mysql_port or self.mysql_client.port, + "RO_DB_OVIM_PORT": config.mysql_port or self.mysql_client.port, + "RO_DB_USER": config.mysql_user or self.mysql_client.user, + "RO_DB_OVIM_USER": config.mysql_user or self.mysql_client.user, + "RO_DB_PASSWORD": config.mysql_password + or self.mysql_client.password, + "RO_DB_OVIM_PASSWORD": config.mysql_password + or self.mysql_client.password, + "RO_DB_ROOT_PASSWORD": config.mysql_root_password + or self.mysql_client.root_password, + "RO_DB_OVIM_ROOT_PASSWORD": config.mysql_root_password + or self.mysql_client.root_password, "RO_DB_NAME": config.ro_database, "RO_DB_OVIM_NAME": config.vim_database, "OPENMANO_TENANT": config.openmano_tenant, } ) container = container_builder.build() + # Add container to pod spec pod_spec_builder.add_container(container) + return pod_spec_builder.build() diff --git a/installers/charm/ro/tests/test_charm.py b/installers/charm/ro/tests/test_charm.py index 9cced455..2d317bd3 100644 --- a/installers/charm/ro/tests/test_charm.py +++ b/installers/charm/ro/tests/test_charm.py @@ -71,6 +71,7 @@ class TestCharm(unittest.TestCase): self.config = { "enable_ng_ro": True, "database_commonkey": "commonkey", + "mongodb_uri": "", "log_level": "INFO", "vim_database": "db_name", "ro_database": "ro_db_name", @@ -115,6 +116,24 @@ class TestCharm(unittest.TestCase): # Assertions self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus) + def test_with_relations_and_mongodb_config_ng( + self, + ) -> NoReturn: + "Test with relations (ng-ro)" + + # Initializing the kafka relation + kafka_relation_id = self.harness.add_relation("kafka", "kafka") + self.harness.add_relation_unit(kafka_relation_id, "kafka/0") + self.harness.update_relation_data( + kafka_relation_id, "kafka/0", {"host": "kafka", "port": 9092} + ) + + # Initializing the mongodb config + self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"}) + + # Verifying status + self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_with_relations_ng( self, ) -> NoReturn: @@ -139,6 +158,25 @@ class TestCharm(unittest.TestCase): # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) + def test_ng_exception_mongodb_relation_and_config( + self, + ) -> NoReturn: + "Test NG-RO mongodb relation and config. Must fail" + # Initializing the mongo relation + mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb") + self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0") + self.harness.update_relation_data( + mongodb_relation_id, + "mongodb/0", + {"connection_string": "mongodb://mongo:27017"}, + ) + + # Initializing the mongodb config + self.harness.update_config({"mongodb_uri": "mongodb://mongo:27017"}) + + # Verifying status + self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) + if __name__ == "__main__": unittest.main() diff --git a/installers/charm/ro/tox.ini b/installers/charm/ro/tox.ini index 30e0ea8c..f207ac34 100644 --- a/installers/charm/ro/tox.ini +++ b/installers/charm/ro/tox.ini @@ -108,6 +108,7 @@ commands = ignore = W291, W293, + W503, E123, E125, E226,