class TestCharm(unittest.TestCase):
    def setUp(self):
        self.harness = Harness(MongoDBCharm)
        self.addCleanup(self.harness.cleanup)
        mongo_resource = {
            "registrypath": "mongodb:4.4.1",
            "username": "******",
            "password": "******"
        }
        self.harness.add_oci_resource("mongodb-image", mongo_resource)
        self.harness.begin()

    def test_replica_set_name_can_be_changed(self):
        self.harness.set_leader(True)

        # check default replica set name
        self.harness.charm.on.config_changed.emit()
        pod_spec = self.harness.get_pod_spec()
        self.assertEqual(replica_set_name(pod_spec), "rs0")

        # check replica set name can be changed
        self.harness.update_config({"replica_set_name": "new_name"})
        pod_spec = self.harness.get_pod_spec()
        self.assertEqual(replica_set_name(pod_spec), "new_name")

    @patch("mongo.Mongo.reconfigure_replica_set")
    def test_replica_set_is_reconfigured_when_peer_joins(self, mock_reconf):
        self.harness.set_leader(True)
        rel_id = self.harness.add_relation('mongodb', 'mongodb')
        self.harness.add_relation_unit(rel_id, 'mongodb/1')
        self.harness.update_relation_data(rel_id, 'mongodb/1',
                                          {'private-address': '10.0.0.1'})
        self.assertEqual(self.harness.charm.num_peers, 2)
        peers = ['mongodb-0.mongodb-endpoints', 'mongodb-1.mongodb-endpoints']
        mock_reconf.assert_called_once_with(peers)
class KafkaCharmTest(unittest.TestCase):
    def setUp(self) -> None:
        self.harness = Harness(KafkaOperator)
        self.addCleanup(self.harness.cleanup)
        self.harness.begin()
        self.harness.add_oci_resource("kafka-image")
        self.harness.update_config(BASE_CONFIG)

    def test_kafka_layer(self):

        expected = {
            "summary": "kafka layer",
            "description": "kafka layer",
            "services": {
                "kafka-setup": {
                    "override": "replace",
                    "summary":
                    "kafka setup step - initialize & format storage",
                    "command":
                    "/opt/bitnami/kafka/bin/kafka-storage.sh format -t test-id -c /opt/bitnami/kafka/config/kraft/server.properties",
                    "startup": "enabled",
                },
                "kafka": {
                    "override": "replace",
                    "summary": "kafka service",
                    "command":
                    "/opt/bitnami/kafka/bin/kafka-server-start.sh /opt/bitnami/kafka/config/kraft/server.properties",
                    "startup": "enabled",
                    "requires": ["kafka-setup"],
                },
            },
        }

        assert self.harness.charm._kafka_layer() == expected
Exemple #3
0
class TestCharm(unittest.TestCase):
    def setUp(self):
        self.harness = Harness(MongoDBCharm)
        self.addCleanup(self.harness.cleanup)
        mongo_resource = {
            "registrypath": "mongodb:4.4.1",
            "username": "******",
            "password": "******"
        }
        self.harness.add_oci_resource("mongodb-image", mongo_resource)
        self.harness.begin()

    def test_replica_set_name_can_be_changed(self):
        self.harness.set_leader(True)

        # check default replica set name
        self.harness.charm.on.config_changed.emit()
        pod_spec = self.harness.get_pod_spec()
        self.assertEqual(replica_set_name(pod_spec), "rs0")

        # check replica set name can be changed
        self.harness.update_config({"replica_set_name": "new_name"})
        pod_spec = self.harness.get_pod_spec()
        self.assertEqual(replica_set_name(pod_spec), "new_name")

    @patch("mongoserver.MongoDB.reconfigure_replica_set")
    def test_replica_set_is_reconfigured_when_peer_joins(self, mock_reconf):
        self.harness.set_leader(True)
        rel_id = self.harness.add_relation('mongodb', 'mongodb')
        self.harness.add_relation_unit(rel_id, 'mongodb/1')
        self.harness.update_relation_data(rel_id,
                                          'mongodb/1',
                                          {'private-address': '10.0.0.1'})
        peers = ['mongodb-0.mongodb-endpoints',
                 'mongodb-1.mongodb-endpoints']
        mock_reconf.assert_called_once_with(peers)

    def test_uri_data_is_generated_correctly(self):
        self.harness.set_leader(True)
        standalone_uri = self.harness.charm.mongo.standalone_uri
        replica_set_uri = self.harness.charm.mongo.replica_set_uri
        self.assertEqual(standalone_uri, 'mongodb://mongodb:27017/')
        self.assertEqual(replica_set_uri, 'mongodb://mongodb-0.mongodb-endpoints:27017/')

    def test_database_relation_data_is_set_correctly(self):
        self.harness.set_leader(True)
        rel_id = self.harness.add_relation('database', 'client')
        self.harness.add_relation_unit(rel_id, 'client/1')
        rel = self.harness.framework.model.get_relation('database', rel_id)
        unit = self.harness.framework.model.get_unit('client/1')
        self.harness.charm.on['database'].relation_changed.emit(rel, unit)
        got = self.harness.get_relation_data(rel_id, self.harness.framework.model.unit.name)
        expected = {
            'replicated': 'False',
            'replica_set_name': 'rs0',
            'standalone_uri': 'mongodb://mongodb:27017/',
            'replica_set_uri': 'mongodb://mongodb-0.mongodb-endpoints:27017/'
        }
        self.assertDictEqual(got, expected)
Exemple #4
0
 def test_add_oci_resource_no_image(self):
     harness = Harness(CharmBase,
                       meta='''
         name: test-app
         resources:
           image:
             type: file
             description: "Image to deploy."
         ''')
     with self.assertRaises(RuntimeError):
         harness.add_oci_resource("image")
     with self.assertRaises(RuntimeError):
         harness.add_oci_resource("missing-resource")
     self.assertEqual(len(harness._backend._resources_map), 0)
Exemple #5
0
 def test_add_oci_resource_custom(self):
     harness = Harness(CharmBase,
                       meta='''
         name: test-app
         resources:
           image:
             type: oci-image
             description: "Image to deploy."
         ''')
     custom = {
         "registrypath": "custompath",
         "username": "******",
         "password": "******",
     }
     harness.add_oci_resource('image', custom)
     resource = harness._resource_dir / "image" / "contents.yaml"
     with resource.open('r') as resource_file:
         contents = yaml.safe_load(resource_file.read())
     self.assertEqual(contents['registrypath'], 'custompath')
     self.assertEqual(contents['username'], 'custom_username')
     self.assertEqual(contents['password'], 'custom_password')
     self.assertEqual(len(harness._backend._resources_map), 1)
Exemple #6
0
class TestCharm(unittest.TestCase):
    def setUp(self):
        self.harness = Harness(RedisCharm)
        self.addCleanup(self.harness.cleanup)
        redis_resource = {
            "registrypath": "ubuntu/redis"
        }
        self.harness.add_oci_resource("redis-image", redis_resource)
        self.harness.begin()

    def test_on_start_when_unit_is_not_leader(self):
        # Given
        self.harness.set_leader(False)
        # When
        self.harness.charm.on.start.emit()
        # Then
        self.assertEqual(
            self.harness.charm.unit.status,
            ActiveStatus()
        )

    @mock.patch.object(RedisClient, 'is_ready')
    def test_on_start_when_redis_is_not_ready(self, is_ready):
        # Given
        self.harness.set_leader(True)
        is_ready.return_value = False
        # When
        self.harness.charm.on.start.emit()
        # Then
        is_ready.assert_called_once_with()
        self.assertEqual(
            self.harness.charm.unit.status,
            WaitingStatus("Waiting for Redis ...")
        )

    @mock.patch.object(RedisClient, 'is_ready')
    def test_on_start_when_redis_is_ready(self, is_ready):
        # Given
        self.harness.set_leader(True)
        is_ready.return_value = True
        # When
        self.harness.charm.on.start.emit()
        # Then
        is_ready.assert_called_once_with()
        self.assertEqual(
            self.harness.charm.unit.status,
            ActiveStatus()
        )

    def test_on_stop(self):
        # When
        self.harness.charm.on.stop.emit()
        # Then
        self.assertEqual(
            self.harness.charm.unit.status,
            MaintenanceStatus('Pod is terminating.')
        )

    def test_on_config_changed_when_unit_is_not_leader(self):
        # Given
        self.harness.set_leader(False)
        # When
        self.harness.charm.on.config_changed.emit()
        # Then
        self.assertEqual(
            self.harness.charm.unit.status,
            ActiveStatus()
        )

    @mock.patch.object(RedisClient, 'is_ready')
    def test_on_config_changed_when_unit_is_leader_and_redis_is_ready(self, is_ready):
        # Given
        self.harness.set_leader(True)
        is_ready.return_value = True
        # When
        self.harness.charm.on.config_changed.emit()
        # Then
        self.assertEqual(
            self.harness.charm.unit.status,
            ActiveStatus()
        )

    @mock.patch.object(OCIImageResource, 'fetch')
    def test_on_config_changed_when_unit_is_leader_but_image_fetch_breaks(self, fetch):
        # Given
        self.harness.set_leader(True)
        fetch.side_effect = OCIImageResourceError("redis-image")
        # When
        self.harness.charm.on.config_changed.emit()
        # Then
        fetch.assert_called_once_with()
        self.assertEqual(
            self.harness.charm.unit.status,
            BlockedStatus("Error fetching image information.")
        )

    def test_on_update_status_when_unit_is_not_leader(self):
        # Given
        self.harness.set_leader(False)
        # When
        self.harness.charm.on.update_status.emit()
        # Then
        self.assertEqual(
            self.harness.charm.unit.status,
            ActiveStatus()
        )

    @mock.patch.object(RedisClient, 'is_ready')
    def test_on_update_status_when_redis_is_not_ready(self, is_ready):
        # Given
        self.harness.set_leader(True)
        is_ready.return_value = False
        # When
        self.harness.charm.on.update_status.emit()
        # Then
        is_ready.assert_called_once_with()
        self.assertEqual(
            self.harness.charm.unit.status,
            WaitingStatus("Waiting for Redis ...")
        )

    @mock.patch.object(RedisClient, 'is_ready')
    def test_on_update_status_when_redis_is_ready(self, is_ready):
        # Given
        self.harness.set_leader(True)
        is_ready.return_value = True
        # When
        self.harness.charm.on.update_status.emit()
        # Then
        is_ready.assert_called_once_with()
        self.assertEqual(
            self.harness.charm.unit.status,
            ActiveStatus()
        )

    @mock.patch.object(RedisProvides, '_bind_address')
    def test_on_relation_changed_status_when_unit_is_leader(self, bind_address):
        # Given
        self.harness.set_leader(True)
        bind_address.return_value = '10.2.1.5'

        rel_id = self.harness.add_relation('redis', 'wordpress')
        self.harness.add_relation_unit(rel_id, 'wordpress/0')
        # When
        self.harness.update_relation_data(rel_id, 'wordpress/0', {})
        rel_data = self.harness.get_relation_data(
            rel_id, self.harness.charm.unit.name
        )
        # Then
        self.assertEqual(rel_data.get('hostname'), '10.2.1.5')
        self.assertEqual(rel_data.get('port'), '6379')
Exemple #7
0
class TestKuberneteseInfluxdbCharm(unittest.TestCase):
    def setUp(self) -> None:
        """Setup the harness object."""
        self.harness = Harness(KubernetesInfluxdbCharm)
        self.harness.begin()
        self.harness.add_oci_resource("influxdb2-image")

    def tearDown(self):
        """Cleanup the harness."""
        self.harness.cleanup()

    #
    # Hooks
    #
    def test__on_config_changed(self) -> None:
        self.harness.update_config({"port": "9999"})
        self.assertEqual(self.harness.charm.unit.status,
                         ActiveStatus("Pod is ready"))

    def test__on_config_changed_pebble_api_connection_error_1(self) -> None:
        self.harness.charm.unit.get_container = mock.MagicMock()
        self.harness.charm.unit.get_container.return_value.get_plan = mock.MagicMock(
            side_effect=ConnectionError("connection timeout"))
        with self.assertLogs(level="DEBUG") as logger:
            self.harness.update_config({"port": "9999"})
            self.assertIn(
                "DEBUG:charm:The Pebble API is not ready yet. Error message: connection timeout",
                logger.output,
            )
            self.assertNotIn(
                "DEBUG:charm:Pebble plan has already been loaded. No need to update the config.",
                logger.output,
            )

    def test__on_config_changed_pebble_api_connection_error_2(self) -> None:
        self.harness.charm.unit.get_container = mock.MagicMock()
        self.harness.charm.unit.get_container.return_value.get_plan.return_value.to_dict = (
            mock.MagicMock(return_value={}))
        self.harness.charm.unit.get_container.return_value.add_layer = mock.MagicMock(
            side_effect=ConnectionError("connection timeout"))
        with self.assertLogs(level="DEBUG") as logger:
            self.harness.update_config({"port": "9999"})
            self.assertIn(
                "DEBUG:charm:The Pebble API is not ready yet. Error message: connection timeout",
                logger.output,
            )
            self.assertNotIn(
                "DEBUG:charm:Pebble plan has already been loaded. No need to update the config.",
                logger.output,
            )

    def test__on_config_changed_same_plan(self) -> None:
        self.harness.charm.unit.get_container = mock.MagicMock()
        self.harness.charm.unit.get_container.return_value.get_plan.return_value.to_dict = (
            mock.MagicMock(return_value=self.harness.charm._influxdb2_layer()))
        with self.assertLogs(level="DEBUG") as logger:
            self.harness.update_config({"port": "9999"})
            self.assertIn(
                "DEBUG:charm:Pebble plan has already been loaded. No need to update the config.",
                logger.output,
            )
            self.assertNotIn(
                "DEBUG:charm:The Pebble API is not ready yet. Error message: connection timeout",
                logger.output,
            )

    #
    # Test Relations
    #
    def test__grafana_source_data(self,
                                  expected_reldata: Optional[Dict] = None
                                  ) -> None:
        # Initialize values
        interface: str = "grafana-source"
        rel_app: str = "grafana"
        rel_unit: str = "grafana/0"
        rel_data: Dict[str, str] = {}
        expected: Dict[str, str] = {}

        if expected_reldata is None:
            # relation not initialized
            expected_reldata = {
                key: ""
                for key in ["private-address", "port", "source-type"]
            }

        # Initialize unit state (related to grafana)
        rel_id = self.harness.add_relation(interface, rel_app)
        self.harness.add_relation_unit(rel_id, rel_unit)

        # Trigger the -relation-changed hook, which will call the observed event
        self.harness.update_relation_data(rel_id, rel_app, rel_data)

        self.assertIsInstance(rel_id, int)

        # Verify the relation data set by the influxdb2 charm
        relation = self.harness.model.get_relation(interface)

        for key, expected_val in expected.items():
            self.assertEqual(
                relation.data[self.harness.charm.unit].get(key, ""),
                expected_val)

    @mock.patch("subprocess.check_output")
    def test__grafana_source_data_leader(
            self, mock_check_output: mock.MagicMock) -> None:
        mock_check_output.return_value = b"10.0.0.1"
        expected_reldata: Dict[str, str] = {
            "private-address": "10.0.0.1",
            "port": "8086",
            "source-type": "influxdb",
        }
        self.harness.set_leader(True)
        self.test__grafana_source_data(expected_reldata=expected_reldata)

    #
    # Test Helpers
    #
    def test__influxdb2_layer(self) -> None:
        expected = {
            "summary": "influxdb2 layer",
            "description": "pebble config layer for influxdb2",
            "services": {
                "influxdb2": {
                    "override": "replace",
                    "summary": "influxdb2 service",
                    "command": "/entrypoint.sh influxd",
                    "startup": "enabled",
                    "environment": {
                        "DOCKER_INFLUXDB_INIT_MODE": "setup",
                        "DOCKER_INFLUXDB_INIT_USERNAME": "******",
                        "DOCKER_INFLUXDB_INIT_PASSWORD": "******",
                        "DOCKER_INFLUXDB_INIT_ORG": "influxdata",
                        "DOCKER_INFLUXDB_INIT_BUCKET": "default",
                        "DOCKER_INFLUXDB_INIT_RETENTION": "0s",
                        "DOCKER_INFLUXDB_INIT_ADMIN_TOKEN": "asdfasdfasdf",
                        "INFLUXD_BOLT_PATH":
                        "/var/lib/influxdbv2/influxd.bolt",
                        "INFLUXD_ENGINE_PATH": "/var/lib/influxdbv2",
                        "INFLUXD_HTTP_BIND_ADDRESS": ":8086",
                    },
                }
            },
        }

        self.assertEqual(set(self.harness.charm._influxdb2_layer()),
                         set(expected))

    def test__is_running(self) -> None:
        container = self.harness.charm.unit.get_container(WORKLOAD_CONTAINER)
        service_not_running = self.harness.charm._is_running(
            container, "influxd")
        self.assertFalse(service_not_running)
class TestCharm(unittest.TestCase):
    def setUp(self):
        self.harness = Harness(MongoDBCharm)
        self.addCleanup(self.harness.cleanup)
        mongo_resource = {
            "registrypath": "mongodb:4.4.1",
            "username": "******",
            "password": "******"
        }
        self.harness.add_oci_resource("mongodb-image", mongo_resource)
        self.harness.begin()
        self.peer_rel_id = self.harness.add_relation('mongodb', 'mongodb')

    @patch('ops.testing._TestingPebbleClient.pull')
    def test_replica_set_name_can_be_changed(self, _):
        self.harness.set_leader(True)
        self.harness.container_pebble_ready("mongodb")

        # check default replica set name
        plan = self.harness.get_container_pebble_plan("mongodb")
        self.assertEqual(replica_set_name(plan), "rs0")

        # check replica set name can be changed
        self.harness.update_config({"replica_set_name": "new_name"})
        plan = self.harness.get_container_pebble_plan("mongodb")
        self.assertEqual(replica_set_name(plan), "new_name")

    @patch("mongoserver.MongoDB.reconfigure_replica_set")
    def test_replica_set_is_reconfigured_when_peer_joins(self, mock_reconf):
        self.harness.set_leader(True)
        self.harness.add_relation_unit(self.peer_rel_id, 'mongodb/1')
        self.harness.update_relation_data(self.peer_rel_id, 'mongodb/1',
                                          {'private-address': '10.0.0.1'})
        peers = [
            'mongodb-k8s-0.mongodb-k8s-endpoints',
            'mongodb-k8s-1.mongodb-k8s-endpoints'
        ]
        mock_reconf.assert_called_once_with(peers)

    def test_replica_set_uri_data_is_generated_correctly(self):
        self.harness.set_leader(True)
        replica_set_uri = self.harness.charm.mongo.replica_set_uri()
        data = self.harness.get_relation_data(self.peer_rel_id,
                                              self.harness.model.app.name)
        cred = "root:{}".format(data['root_password'])
        self.assertEqual(
            replica_set_uri,
            'mongodb://{}@mongodb-k8s-0.mongodb-k8s-endpoints:27017/admin'.
            format(cred))

    def test_leader_sets_key_and_root_credentials(self):
        self.harness.set_leader(False)
        self.harness.set_leader(True)
        data = self.harness.get_relation_data(self.peer_rel_id,
                                              self.harness.model.app.name)
        self.assertIsNotNone(data['root_password'])
        self.assertIsNotNone(data['security_key'])

    @patch('mongoserver.MongoDB.version')
    def test_charm_provides_version(self, mock_version):
        self.harness.set_leader(True)
        mock_version.return_value = "4.4.1"
        version = self.harness.charm.mongo.version()
        self.assertEqual(version, "4.4.1")

    @patch('mongoserver.MongoDB.is_ready')
    def test_start_is_deferred_if_monog_is_not_ready(self, is_ready):
        is_ready.return_value = False
        self.harness.set_leader(True)
        with self.assertLogs(level="DEBUG") as logger:
            self.harness.charm.on.start.emit()
            is_ready.assert_called()
            for message in sorted(logger.output):
                if "DEBUG:ops.framework:Deferring" in message:
                    self.assertIn("StartEvent", message)

    @patch('mongoserver.MongoDB.initialize_replica_set')
    @patch('mongoserver.MongoDB.is_ready')
    def test_start_is_deffered_if_monog_is_not_initialized(
            self, is_ready, initialize):
        is_ready.return_value = True
        initialize.side_effect = RuntimeError("Not Initialized")
        self.harness.set_leader(True)
        with self.assertLogs(level="DEBUG") as logger:
            self.harness.charm.on.start.emit()
            is_ready.assert_called()
            self.assertIn(
                "INFO:charm:Deferring on_start since : error=Not Initialized",
                sorted(logger.output))
Exemple #9
0
class TestCharm(unittest.TestCase):
    def setUp(self):
        self.harness = Harness(RedisCharm)
        self.addCleanup(self.harness.cleanup)
        redis_resource = {
            "registrypath": "redis:6.0",
            # "username" and "password" are useless, but oci-resource
            # library fetch() fails if we do not provide them ...
            "username": "",
            "password": ""
        }
        self.harness.add_oci_resource("redis-image", redis_resource)
        self.harness.begin()

    def test_on_start_when_unit_is_not_leader(self):
        # Given
        self.harness.set_leader(False)
        # When
        self.harness.charm.on.start.emit()
        # Then
        self.assertEqual(self.harness.charm.unit.status, ActiveStatus())

    @mock.patch.object(RedisClient, 'is_ready')
    def test_on_start_when_redis_is_not_ready(self, is_ready):
        # Given
        self.harness.set_leader(True)
        is_ready.return_value = False
        # When
        self.harness.charm.on.start.emit()
        # Then
        is_ready.assert_called_once_with()
        self.assertEqual(self.harness.charm.unit.status,
                         WaitingStatus("Waiting for Redis ..."))

    @mock.patch.object(RedisClient, 'is_ready')
    def test_on_start_when_redis_is_ready(self, is_ready):
        # Given
        self.harness.set_leader(True)
        is_ready.return_value = True
        # When
        self.harness.charm.on.start.emit()
        # Then
        is_ready.assert_called_once_with()
        self.assertEqual(self.harness.charm.unit.status, ActiveStatus())

    def test_on_stop(self):
        # When
        self.harness.charm.on.stop.emit()
        # Then
        self.assertEqual(self.harness.charm.unit.status,
                         MaintenanceStatus('Pod is terminating.'))

    def test_on_config_changed_when_unit_is_not_leader(self):
        # Given
        self.harness.set_leader(False)
        # When
        self.harness.charm.on.config_changed.emit()
        # Then
        self.assertEqual(self.harness.charm.unit.status, ActiveStatus())

    @mock.patch.object(RedisClient, 'is_ready')
    def test_on_config_changed_when_unit_is_leader_and_redis_is_ready(
            self, is_ready):
        # Given
        self.harness.set_leader(True)
        is_ready.return_value = True
        # When
        self.harness.charm.on.config_changed.emit()
        # Then
        self.assertIsNotNone(self.harness.charm.state.pod_spec)
        self.assertEqual(self.harness.charm.unit.status, ActiveStatus())

    @mock.patch.object(OCIImageResource, 'fetch')
    def test_on_config_changed_when_unit_is_leader_but_image_fetch_breaks(
            self, fetch):
        # Given
        self.harness.set_leader(True)
        fetch.side_effect = OCIImageResourceError("redis-image")
        # When
        self.harness.charm.on.config_changed.emit()
        # Then
        fetch.assert_called_once_with()
        self.assertEqual(self.harness.charm.unit.status,
                         BlockedStatus("Error fetching image information."))

    def test_on_update_status_when_unit_is_not_leader(self):
        # Given
        self.harness.set_leader(False)
        # When
        self.harness.charm.on.update_status.emit()
        # Then
        self.assertEqual(self.harness.charm.unit.status, ActiveStatus())

    @mock.patch.object(RedisClient, 'is_ready')
    def test_on_update_status_when_redis_is_not_ready(self, is_ready):
        # Given
        self.harness.set_leader(True)
        is_ready.return_value = False
        # When
        self.harness.charm.on.update_status.emit()
        # Then
        is_ready.assert_called_once_with()
        self.assertEqual(self.harness.charm.unit.status,
                         WaitingStatus("Waiting for Redis ..."))

    @mock.patch.object(RedisClient, 'is_ready')
    def test_on_update_status_when_redis_is_ready(self, is_ready):
        # Given
        self.harness.set_leader(True)
        is_ready.return_value = True
        # When
        self.harness.charm.on.update_status.emit()
        # Then
        is_ready.assert_called_once_with()
        self.assertEqual(self.harness.charm.unit.status, ActiveStatus())
class TestCharm(unittest.TestCase):
    def setUp(self) -> None:
        # charm setup
        self.harness = Harness(GraylogCharm)
        self.addCleanup(self.harness.cleanup)
        self.harness.begin()
        self.harness.add_oci_resource('graylog-image')

        # patches
        self.mock_bind_address = \
            mock.patch('charm.GraylogCharm.bind_address', new_callable=mock.PropertyMock)
        self.mock_external_uri = \
            mock.patch('charm.GraylogCharm.external_uri', new_callable=mock.PropertyMock)

        self.mock_bind_address.start()
        self.mock_external_uri.start()

        # cleanup
        self.addCleanup(self.mock_bind_address.stop)
        self.addCleanup(self.mock_external_uri.stop)

    def test_pod_spec_port(self):
        self.harness.set_leader(True)
        # pretend to have mongo and elasticsearch
        self.harness.charm._stored.mongodb_uri = 'mongo://test_uri/'
        self.harness.charm._stored.elasticsearch_uri = 'http://test_es_uri'

        self.harness.update_config(BASE_CONFIG)
        self.harness.charm.on.config_changed.emit()

        spec, _ = self.harness.get_pod_spec()
        expected_port = 9000
        actual_port = spec['containers'][0]['ports'][0]['containerPort']
        self.assertEqual(expected_port, actual_port)

    def test_elasticsearch_and_mongodb_conn_strings(self):
        self.harness.set_leader(True)
        self.harness.update_config(BASE_CONFIG)

        # add the elasticsearch relation
        es_rel_id = self.harness.add_relation('elasticsearch', 'elasticsearch')
        mongo_rel_id = self.harness.add_relation('mongodb', 'mongodb')
        self.harness.add_relation_unit(es_rel_id, 'elasticsearch/0')
        self.harness.add_relation_unit(mongo_rel_id, 'mongodb/0')

        # add elasticsearch relation data
        es_rel_data = {
            'ingress-address': '10.183.1.2',
            'port': 9200,
        }
        self.harness.update_relation_data(es_rel_id, 'elasticsearch/0',
                                          es_rel_data)
        self.assertTrue(self.harness.charm.has_elasticsearch)

        # add mongodb relation data
        mongo_rel_data = {
            'replica_set_uri': 'mongo://10.0.0.2:14001,10.0.0.3:14002',
            'replicated': 'True',
            'replica_set_name': 'rs0',
        }
        self.harness.update_relation_data(mongo_rel_id, 'mongodb/0',
                                          mongo_rel_data)
        self.assertTrue(self.harness.charm.has_mongodb)

        # test that elasticsearch-uri properly made it to the _stored variable
        expected_uri = 'http://10.183.1.2:9200'
        self.assertEqual(expected_uri,
                         self.harness.charm._stored.elasticsearch_uri)

        # now emit the relation broken events and make sure the _stored variables are cleared
        es_rel = self.harness.model.get_relation('elasticsearch')
        mongo_rel = self.harness.model.get_relation('mongodb')
        self.harness.charm.on.elasticsearch_relation_broken.emit(es_rel)
        self.harness.charm.on.mongodb_relation_broken.emit(mongo_rel)
        self.assertEqual(str(), self.harness.charm._stored.elasticsearch_uri)
        self.assertEqual(str(), self.harness.charm._stored.mongodb_uri)

    def test_blocking_without_mongodb_and_elasticsearch(self):
        self.harness.set_leader(True)
        with self.assertLogs(level='WARNING') as logger:
            self.harness.update_config(BASE_CONFIG)
            msg = 'WARNING:charm:Need both mongodb and Elasticsearch ' \
                  'relation for Graylog to function properly. Blocking.'
            self.assertEqual(sorted(logger.output), [msg])

    def test_check_config_with_missing_option(self):
        self.harness.set_leader(True)
        missing_password_config = {'port': 9000, 'admin-password': ''}
        with self.assertLogs(level='WARNING') as logger:
            self.harness.update_config(missing_password_config)
            msg = 'ERROR:charm:Need admin-password config option before setting pod spec.'
            self.assertEqual(sorted(logger.output), [msg])
            self.assertEqual(
                self.harness.model.unit.status,
                BlockedStatus("Need 'admin-password' config option."))
Exemple #11
0
class TestCharm(unittest.TestCase):
    def setUp(self) -> None:
        self.harness = Harness(MySQLCharm)
        self.addCleanup(self.harness.cleanup)
        self.harness.begin()
        self.harness.add_oci_resource("mysql-image")

    def test_env_config(self):
        config_1 = {
            "MYSQL_ROOT_PASSWORD": "******",
            "MYSQL_USER": "******",
            "MYSQL_PASSWORD": "******",
            "MYSQL_DATABASE": "db_10",
        }
        self.harness.update_config(config_1)
        self.assertEqual(self.harness.charm.env_config["MYSQL_ROOT_PASSWORD"],
                         "D10S")
        self.assertEqual(self.harness.charm.env_config["MYSQL_USER"],
                         "DiegoArmando")
        self.assertEqual(self.harness.charm.env_config["MYSQL_PASSWORD"],
                         "SegurolaYHabana")
        self.assertEqual(self.harness.charm.env_config["MYSQL_DATABASE"],
                         "db_10")

    def test_generate_random_root_password(self):
        self.harness.set_leader(True)
        config_2 = {
            "MYSQL_ROOT_PASSWORD": "",
        }
        self.harness.update_config(config_2)
        self.assertEqual(
            len(self.harness.charm.env_config["MYSQL_ROOT_PASSWORD"]), 20)

    def test_pod_spec(self):
        self.harness.set_leader(True)
        config_1 = {
            "MYSQL_ROOT_PASSWORD": "******",
        }
        self.harness.update_config(config_1)
        pod_spec = self.harness.charm._build_pod_spec()
        self.assertEqual(pod_spec["containers"][0]["name"], "mysql")
        self.assertEqual(
            pod_spec["containers"][0]["ports"][0]["containerPort"], 3306)
        self.assertEqual(pod_spec["containers"][0]["ports"][0]["protocol"],
                         "TCP")
        self.assertEqual(
            pod_spec["containers"][0]["envConfig"]["MYSQL_ROOT_PASSWORD"],
            "D10S",
        )
        self.assertEqual(pod_spec["version"], 3)

    def test_status(self):
        config_1 = {
            "MYSQL_ROOT_PASSWORD": "******",
        }
        self.harness.set_leader(True)
        self.harness.update_config(config_1)
        self.assertEqual(self.harness.charm.unit.status, ActiveStatus())

    def test_default_configs(self):
        config = self.harness.model.config
        self.assertEqual(config["port"], 3306)
        self.assertTrue("MYSQL_ROOT_PASSWORD" in config)
        self.assertEqual(config["MYSQL_ROOT_PASSWORD"], "")

    def test_ony_leader_can_configure_root_password(self):
        self.harness.set_leader(False)
        config = {
            "MYSQL_ROOT_PASSWORD": "******",
        }
        self.harness.update_config(config)
        self.assertNotIn("MYSQL_ROOT_PASSWORD",
                         self.harness.charm._stored.mysql_setup)

    def test_new_unit_has_password(self):
        config = {
            "MYSQL_ROOT_PASSWORD": "******",
        }

        self.harness.update_config(config)
        relation_id = self.harness.add_relation("mysql", "mysql")
        self.harness.add_relation_unit(relation_id, "mysql/1")
        self.harness.update_relation_data(relation_id, "mysql", config)
        self.assertEqual(
            config["MYSQL_ROOT_PASSWORD"],
            self.harness.charm._stored.mysql_setup["MYSQL_ROOT_PASSWORD"],
        )
class GrafanaCharmTest(unittest.TestCase):
    def setUp(self) -> None:
        self.harness = Harness(GrafanaK8s)
        self.addCleanup(self.harness.cleanup)
        self.harness.begin()
        self.harness.add_oci_resource('grafana-image')

    def test__grafana_source_data(self):

        self.harness.set_leader(True)
        self.harness.update_config(BASE_CONFIG)
        self.assertEqual(self.harness.charm.datastore.sources, {})

        rel_id = self.harness.add_relation('grafana-source', 'prometheus')
        self.harness.add_relation_unit(rel_id, 'prometheus/0')
        self.assertIsInstance(rel_id, int)

        # test that the unit data propagates the correct way
        # which is through the triggering of on_relation_changed
        self.harness.update_relation_data(
            rel_id, 'prometheus/0', {
                'private-address': '192.0.2.1',
                'port': 1234,
                'source-type': 'prometheus',
                'source-name': 'prometheus-app',
            })

        expected_first_source_data = {
            'private-address': '192.0.2.1',
            'port': 1234,
            'source-name': 'prometheus-app',
            'source-type': 'prometheus',
            'isDefault': 'true',
            'unit_name': 'prometheus/0'
        }
        self.assertEqual(expected_first_source_data,
                         dict(self.harness.charm.datastore.sources[rel_id]))

        # test that clearing the relation data leads to
        # the datastore for this data source being cleared
        self.harness.update_relation_data(rel_id, 'prometheus/0', {
            'private-address': None,
            'port': None,
        })
        self.assertEqual(None,
                         self.harness.charm.datastore.sources.get(rel_id))

    def test__ha_database_and_status_check(self):
        """If there is a peer connection and no database (needed for HA),
        the charm should put the application in a blocked state."""

        # start charm with one peer and no database relation
        self.harness.set_leader(True)
        self.harness.update_config(BASE_CONFIG)
        self.assertEqual(self.harness.charm.unit.status, ActiveStatus())

        # ensure _check_high_availability() ends up with the correct status
        status = self.harness.charm._check_high_availability()
        self.assertEqual(status,
                         MaintenanceStatus('Grafana ready on single node.'))

        # make sure that triggering 'update-status' hook does not
        # overwrite the current active status
        self.harness.charm.on.update_status.emit()
        self.assertEqual(self.harness.charm.unit.status, ActiveStatus())

        peer_rel_id = self.harness.add_relation('grafana', 'grafana')

        # add main unit and its data
        # self.harness.add_relation_unit(peer_rel_id, 'grafana/0')
        # will trigger the grafana-changed hook
        self.harness.update_relation_data(peer_rel_id, 'grafana/0',
                                          {'private-address': '10.1.2.3'})

        # add peer unit and its data
        self.harness.add_relation_unit(peer_rel_id, 'grafana/1')
        self.harness.update_relation_data(peer_rel_id, 'grafana/1',
                                          {'private-address': '10.0.0.1'})

        self.assertTrue(self.harness.charm.has_peer)
        self.assertFalse(self.harness.charm.has_db)
        self.assertEqual(self.harness.charm.unit.status,
                         BlockedStatus('Need database relation for HA.'))

        # ensure update-status hook doesn't overwrite this
        self.harness.charm.on.update_status.emit()
        self.assertEqual(self.harness.charm.unit.status,
                         BlockedStatus('Need database relation for HA.'))

        # now add the database connection and the model should
        # not have a blocked status
        db_rel_id = self.harness.add_relation('database', 'mysql')
        self.harness.add_relation_unit(db_rel_id, 'mysql/0')
        self.harness.update_relation_data(
            db_rel_id, 'mysql/0', {
                'type': 'mysql',
                'host': '10.10.10.10:3306',
                'name': 'test_mysql_db',
                'user': '******',
                'password': '******',
            })
        self.assertTrue(self.harness.charm.has_db)
        self.assertEqual(self.harness.charm.unit.status, ActiveStatus())

        # ensure _check_high_availability() ends up with the correct status
        status = self.harness.charm._check_high_availability()
        self.assertEqual(status, MaintenanceStatus('Grafana ready for HA.'))

    def test__database_relation_data(self):
        self.harness.set_leader(True)
        self.harness.update_config(BASE_CONFIG)
        self.assertEqual(self.harness.charm.datastore.database, {})

        # add relation and update relation data
        rel_id = self.harness.add_relation('database', 'mysql')
        rel = self.harness.model.get_relation('database')
        self.harness.add_relation_unit(rel_id, 'mysql/0')
        test_relation_data = {
            'type': 'mysql',
            'host': '0.1.2.3:3306',
            'name': 'my-test-db',
            'user': '******',
            'password': '******',
        }
        self.harness.update_relation_data(rel_id, 'mysql/0',
                                          test_relation_data)
        # check that charm datastore was properly set
        self.assertEqual(dict(self.harness.charm.datastore.database),
                         test_relation_data)

        # now depart this relation and ensure the datastore is emptied
        self.harness.charm.on.database_relation_broken.emit(rel)
        self.assertEqual({}, dict(self.harness.charm.datastore.database))

    def test__multiple_database_relation_handling(self):
        self.harness.set_leader(True)
        self.harness.update_config(BASE_CONFIG)
        self.assertEqual(self.harness.charm.datastore.database, {})

        # add first database relation
        self.harness.add_relation('database', 'mysql')

        # add second database relation -- should fail here
        with self.assertRaises(TooManyRelatedAppsError):
            self.harness.add_relation('database', 'mysql')
            self.harness.charm.model.get_relation('database')

    def test__multiple_source_relations(self):
        """This will test data-source config text with multiple sources.

        Specifically, it will test multiple grafana-source relations."""
        self.harness.set_leader(True)
        self.harness.update_config(BASE_CONFIG)
        self.assertEqual(self.harness.charm.datastore.sources, {})

        # add first relation
        rel_id0 = self.harness.add_relation('grafana-source', 'prometheus')
        self.harness.add_relation_unit(rel_id0, 'prometheus/0')

        # add test data to grafana-source relation
        # and test that _make_data_source_config_text() works as expected
        prom_source_data = {
            'private-address': '192.0.2.1',
            'port': 4321,
            'source-type': 'prometheus'
        }
        self.harness.update_relation_data(rel_id0, 'prometheus/0',
                                          prom_source_data)
        header_text = textwrap.dedent("""
                apiVersion: 1

                datasources:""")
        correct_config_text0 = header_text + textwrap.dedent("""
            - name: prometheus_0
              type: prometheus
              access: proxy
              url: http://192.0.2.1:4321
              isDefault: true
              editable: true
              orgId: 1""")

        generated_text = self.harness.charm._make_data_source_config_text()
        self.assertEqual(correct_config_text0 + '\n', generated_text)

        # add another source relation and check the resulting config text
        jaeger_source_data = {
            'private-address': '255.255.255.0',
            'port': 7890,
            'source-type': 'jaeger',
            'source-name': 'jaeger-application'
        }
        rel_id1 = self.harness.add_relation('grafana-source', 'jaeger')
        self.harness.add_relation_unit(rel_id1, 'jaeger/0')
        self.harness.update_relation_data(rel_id1, 'jaeger/0',
                                          jaeger_source_data)

        correct_config_text1 = correct_config_text0 + textwrap.dedent("""
            - name: jaeger-application
              type: jaeger
              access: proxy
              url: http://255.255.255.0:7890
              isDefault: false
              editable: true
              orgId: 1""")

        generated_text = self.harness.charm._make_data_source_config_text()
        self.assertEqual(correct_config_text1 + '\n', generated_text)

        # test removal of second source results in config_text
        # that is the same as the original
        self.harness.update_relation_data(rel_id1, 'jaeger/0', {
            'private-address': None,
            'port': None,
        })
        generated_text = self.harness.charm._make_data_source_config_text()
        correct_text_after_removal = textwrap.dedent("""
            apiVersion: 1

            deleteDatasources:
            - name: jaeger-application
              orgId: 1

            datasources:
            - name: prometheus_0
              type: prometheus
              access: proxy
              url: http://192.0.2.1:4321
              isDefault: true
              editable: true
              orgId: 1""")

        self.assertEqual(correct_text_after_removal + '\n', generated_text)

        # now test that the 'deleteDatasources' is gone
        generated_text = self.harness.charm._make_data_source_config_text()
        self.assertEqual(correct_config_text0 + '\n', generated_text)

    def test__pod_spec_container_datasources(self):
        self.harness.set_leader(True)
        self.harness.update_config(BASE_CONFIG)
        self.assertEqual(self.harness.charm.datastore.sources, {})

        # add first relation
        rel_id = self.harness.add_relation('grafana-source', 'prometheus')
        self.harness.add_relation_unit(rel_id, 'prometheus/0')

        # add test data to grafana-source relation
        # and test that _make_data_source_config_text() works as expected
        prom_source_data = {
            'private-address': '192.0.2.1',
            'port': 4321,
            'source-type': 'prometheus'
        }
        self.harness.update_relation_data(rel_id, 'prometheus/0',
                                          prom_source_data)

        data_source_file_text = textwrap.dedent("""
            apiVersion: 1

            datasources:
            - name: prometheus_0
              type: prometheus
              access: proxy
              url: http://192.0.2.1:4321
              isDefault: true
              editable: true
              orgId: 1
              """)

        config_ini_file_text = textwrap.dedent("""
        [paths]
        provisioning = /etc/grafana/provisioning

        [log]
        mode = console
        level = {0}
        """).format(self.harness.model.config['grafana_log_level'], )

        expected_container_files_spec = [{
            'name':
            'grafana-datasources',
            'mountPath':
            '/etc/grafana/provisioning/datasources',
            'files': [{
                'path': 'datasources.yaml',
                'content': data_source_file_text,
            }],
        }, {
            'name':
            'grafana-config-ini',
            'mountPath':
            '/etc/grafana',
            'files': [{
                'path': 'grafana.ini',
                'content': config_ini_file_text,
            }]
        }]
        pod_spec, _ = self.harness.get_pod_spec()
        container = get_container(pod_spec, 'grafana')
        actual_container_files_spec = container['volumeConfig']
        self.assertEqual(expected_container_files_spec,
                         actual_container_files_spec)

    def test__access_sqlite_storage_location(self):
        expected_path = '/var/lib/grafana'
        actual_path = self.harness.charm.meta.storages['sqlitedb'].location
        self.assertEqual(expected_path, actual_path)

    def test__config_ini_without_database(self):
        self.harness.update_config(BASE_CONFIG)
        expected_config_text = textwrap.dedent("""
        [paths]
        provisioning = /etc/grafana/provisioning

        [log]
        mode = console
        level = {0}
        """).format(self.harness.model.config['grafana_log_level'], )

        actual_config_text = self.harness.charm._make_config_ini_text()
        self.assertEqual(expected_config_text, actual_config_text)

    def test__config_ini_with_database(self):
        self.harness.set_leader(True)
        self.harness.update_config(BASE_CONFIG)

        # add database relation and update relation data
        rel_id = self.harness.add_relation('database', 'mysql')
        self.harness.add_relation_unit(rel_id, 'mysql/0')
        test_relation_data = {
            'type': 'mysql',
            'host': '0.1.2.3:3306',
            'name': 'my-test-db',
            'user': '******',
            'password': '******',
        }
        self.harness.update_relation_data(rel_id, 'mysql/0',
                                          test_relation_data)

        # test the results of _make_config_ini_text()
        expected_config_text = textwrap.dedent(
            """
        [paths]
        provisioning = /etc/grafana/provisioning

        [log]
        mode = console
        level = {0}

        [database]
        type = mysql
        host = 0.1.2.3:3306
        name = my-test-db
        user = test-user
        password = super!secret!password
        url = mysql://test-user:[email protected]:3306/my-test-db"""
        ).format(self.harness.model.config['grafana_log_level'], )

        actual_config_text = self.harness.charm._make_config_ini_text()
        self.assertEqual(expected_config_text, actual_config_text)

    def test__duplicate_source_names(self):
        self.harness.set_leader(True)
        self.harness.update_config(BASE_CONFIG)
        self.assertEqual(self.harness.charm.datastore.sources, {})

        # add first relation
        p_rel_id = self.harness.add_relation('grafana-source', 'prometheus')
        p_rel = self.harness.model.get_relation('grafana-source', p_rel_id)
        self.harness.add_relation_unit(p_rel_id, 'prometheus/0')

        # add test data to grafana-source relation
        prom_source_data0 = {
            'private-address': '192.0.2.1',
            'port': 4321,
            'source-type': 'prometheus',
            'source-name': 'duplicate-source-name'
        }
        self.harness.update_relation_data(p_rel_id, 'prometheus/0',
                                          prom_source_data0)
        expected_prom_source_data = {
            'private-address': '192.0.2.1',
            'port': 4321,
            'source-name': 'duplicate-source-name',
            'source-type': 'prometheus',
            'isDefault': 'true',
            'unit_name': 'prometheus/0'
        }
        self.assertEqual(dict(self.harness.charm.datastore.sources[p_rel_id]),
                         expected_prom_source_data)

        # add second source with the same name as the first source
        g_rel_id = self.harness.add_relation('grafana-source', 'graphite')
        g_rel = self.harness.model.get_relation('grafana-source', g_rel_id)
        self.harness.add_relation_unit(g_rel_id, 'graphite/0')

        graphite_source_data0 = {
            'private-address': '192.12.23.34',
            'port': 4321,
            'source-type': 'graphite',
            'source-name': 'duplicate-source-name'
        }
        expected_graphite_source_data = {
            'isDefault': 'false',
            'port': 4321,
            'private-address': '192.12.23.34',
            'source-name': 'graphite_1',
            'source-type': 'graphite',
            'unit_name': 'graphite/0'
        }
        self.harness.update_relation_data(g_rel_id, 'graphite/0',
                                          graphite_source_data0)
        self.assertEqual(
            expected_graphite_source_data,
            dict(self.harness.charm.datastore.sources.get(g_rel_id)))
        self.assertEqual(2, len(self.harness.charm.datastore.sources))

        # now remove the relation and ensure datastore source-name is removed
        self.harness.charm.on.grafana_source_relation_broken.emit(p_rel)
        self.assertEqual(None,
                         self.harness.charm.datastore.sources.get(p_rel_id))
        self.assertEqual(1, len(self.harness.charm.datastore.sources))

        # remove graphite relation
        self.harness.charm.on.grafana_source_relation_broken.emit(g_rel)
        self.assertEqual(None,
                         self.harness.charm.datastore.sources.get(g_rel_id))
        self.assertEqual(0, len(self.harness.charm.datastore.sources))

    def test__idempotent_datasource_file_hash(self):
        self.harness.set_leader(True)
        self.harness.update_config(BASE_CONFIG)

        rel_id = self.harness.add_relation('grafana-source', 'prometheus')
        self.harness.add_relation_unit(rel_id, 'prometheus/0')
        self.assertIsInstance(rel_id, int)

        # test that the unit data propagates the correct way
        # which is through the triggering of on_relation_changed
        self.harness.update_relation_data(
            rel_id, 'prometheus/0', {
                'private-address': '192.0.2.1',
                'port': 1234,
                'source-type': 'prometheus',
                'source-name': 'prometheus-app',
            })

        # get a hash of the created file and check that it matches the pod spec
        pod_spec, _ = self.harness.get_pod_spec()
        container = get_container(pod_spec, 'grafana')
        hash_text = hashlib.md5(container['volumeConfig'][0]['files'][0]
                                ['content'].encode()).hexdigest()
        self.assertEqual(container['envConfig']['DATASOURCES_YAML'], hash_text)

        # test the idempotence of the call by re-configuring the pod spec
        self.harness.charm.configure_pod()
        self.assertEqual(container['envConfig']['DATASOURCES_YAML'], hash_text)
class TestCharm(unittest.TestCase):
    def setUp(self) -> None:
        self.harness = Harness(MySQLCharm)
        self.addCleanup(self.harness.cleanup)
        self.harness.begin()
        self.harness.add_oci_resource("mysql-image")

    def test_pebble_layer_is_dict(self):
        self.harness.set_leader(True)
        config = {}
        relation_id = self.harness.add_relation("mysql", "mysql")
        self.harness.add_relation_unit(relation_id, "mysql/1")
        self.harness.update_config(config)
        layer = self.harness.charm._build_pebble_layer()
        self.assertIsInstance(layer["services"]["mysql"], dict)

    def test_pebble_layer_has_random_root_password(self):
        self.harness.set_leader(True)
        config = {}
        relation_id = self.harness.add_relation("mysql", "mysql")
        self.harness.add_relation_unit(relation_id, "mysql/1")
        self.harness.update_config(config)
        env = self.harness.charm._build_pebble_layer(
        )["services"]["mysql"]["environment"]
        self.assertEqual(len(env["MYSQL_ROOT_PASSWORD"]), 20)

    def test_pebble_layer_with_custom_config(self):
        self.harness.set_leader(True)
        config = {
            "mysql_root_password": "******",
            "mysql_user": "******",
            "mysql_password": "******",
            "mysql_database": "db_10",
        }
        relation_id = self.harness.add_relation("mysql", "mysql")
        self.harness.add_relation_unit(relation_id, "mysql/1")
        self.harness.update_config(config)
        env = self.harness.charm._build_pebble_layer(
        )["services"]["mysql"]["environment"]
        self.assertEqual(env["MYSQL_ROOT_PASSWORD"],
                         config["mysql_root_password"])
        self.assertEqual(env["MYSQL_USER"], config["mysql_user"])
        self.assertEqual(env["MYSQL_PASSWORD"], config["mysql_password"])
        self.assertEqual(env["MYSQL_DATABASE"], config["mysql_database"])

    def test_default_configs(self):
        config = self.harness.model.config
        self.assertTrue("mysql_root_password" in config)
        self.assertEqual(config["mysql_root_password"], "")

    def test__on_config_changed_is_leader(self):
        self.harness.set_leader(True)
        config = {
            "mysql_root_password": "******",
        }
        relation_id = self.harness.add_relation("mysql", "mysql")
        self.harness.add_relation_unit(relation_id, "mysql/1")
        self.harness.update_config(config)
        peers_data = self.harness.charm.model.get_relation("mysql").data[
            self.harness.charm.app]
        self.assertIn("mysql_root_password", peers_data)

    def test__on_config_changed_is_not_leader(self):
        self.harness.set_leader(False)
        config = {
            "mysql_root_password": "******",
        }
        relation_id = self.harness.add_relation("mysql", "mysql")
        self.harness.add_relation_unit(relation_id, "mysql/1")
        self.harness.update_config(config)
        peers_data = self.harness.charm.model.get_relation("mysql").data[
            self.harness.charm.app]
        self.assertNotIn("mysql_root_password", peers_data)

    @patch("charm.MySQLCharm.unit_ip")
    def test__update_status_unit_is_leader_mysql_is_not_ready(
            self, mock_unit_ip):
        mock_unit_ip.return_value = "10.0.0.1"

        with patch("mysqlserver.MySQL.is_ready") as mock_is_ready:
            mock_is_ready.return_value = False
            self.harness.set_leader(True)
            config = {
                "mysql_root_password": "******",
            }
            relation_id = self.harness.add_relation("mysql", "mysql")
            self.harness.add_relation_unit(relation_id, "mysql/1")
            self.harness.update_config(config)
            self.assertEqual(self.harness.charm.on.update_status.emit(), None)
            self.assertEqual(type(self.harness.charm.unit.status),
                             WaitingStatus)
            self.assertEqual(self.harness.charm.unit.status.message,
                             "MySQL not ready yet")

    @patch("charm.MySQLCharm._is_mysql_initialized")
    @patch("mysqlserver.MySQL.is_ready")
    @patch("charm.MySQLCharm.unit_ip")
    def test__update_status_unit_is_leader_mysql_not_initialized(
            self, mock_unit_ip, mock_is_ready, mock_is_mysql_initialized):
        mock_unit_ip.return_value = "10.0.0.1"
        mock_is_ready.return_value = True
        mock_is_mysql_initialized.return_value = False

        self.harness.set_leader(True)
        config = {
            "mysql_root_password": "******",
        }
        relation_id = self.harness.add_relation("mysql", "mysql")
        self.harness.add_relation_unit(relation_id, "mysql/1")
        self.harness.update_config(config)
        self.harness.charm.on.update_status.emit()
        self.assertEqual(type(self.harness.charm.unit.status), WaitingStatus)
        self.assertEqual(self.harness.charm.unit.status.message,
                         "MySQL not initialized")

    @patch("charm.MySQLCharm._is_mysql_initialized")
    @patch("mysqlserver.MySQL.is_ready")
    @patch("charm.MySQLCharm.unit_ip")
    def test__update_status_unit_is_leader_mysql_initialized(
            self, mock_unit_ip, mock_is_ready, mock_is_mysql_initialized):
        mock_unit_ip.return_value = "10.0.0.1"
        mock_is_ready.return_value = True
        mock_is_mysql_initialized.return_value = True

        self.harness.set_leader(True)
        config = {
            "mysql_root_password": "******",
        }
        relation_id = self.harness.add_relation("mysql", "mysql")
        self.harness.add_relation_unit(relation_id, "mysql/1")
        self.harness.update_config(config)
        self.harness.charm.on.update_status.emit()
        self.assertEqual(type(self.harness.charm.unit.status), ActiveStatus)

    def test__restart_service_model_error(self):
        self.harness.set_leader(True)

        with self.assertLogs(level="DEBUG") as logger:
            self.harness.charm._restart_service()
            self.assertEqual(
                sorted(logger.output),
                ["DEBUG:charm:MySQL service is not yet ready"],
            )