コード例 #1
0
class TestConsumer(unittest.TestCase):
    def setUp(self):
        self.harness = Harness(DummyCharmForTestingConsumer)
        self.addCleanup(self.harness.cleanup)
        self.harness.set_leader(True)
        self.harness.begin()

    def test_relation_changed(self):
        # container name has to be `alertmanager` because that's what the harness expects based on
        # metadata.yaml
        container = self.harness.model.unit.get_container("alertmanager")

        # Emit the PebbleReadyEvent carrying the alertmanager container

        self.harness.charm.on.alertmanager_pebble_ready.emit(container)

        rel_id = self.harness.add_relation(relation_name="alerting",
                                           remote_app="alertmanager-k8s")
        # self.harness.add_relation_unit(rel_id, "alertmanager-k8s/0")
        self.harness.add_relation_unit(rel_id, "prometheus-k8s/0")
        rel = self.harness.charm.framework.model.get_relation(
            "alerting", rel_id)

        self.assertEqual(0, self.harness.charm._stored.on_available_emitted)
        self.harness.update_relation_data(rel_id, "prometheus-k8s/0",
                                          {"public_address": "1.1.1.1"})
        self.harness.charm.on["alerting"].relation_changed.emit(rel)
        self.assertEqual(1, self.harness.charm._stored.on_available_emitted)
コード例 #2
0
def test_provide_one_relation():
    harness = Harness(
        ProvideCharm,
        meta="""
        name: test-app
        requires:
            app-provides:
                interface: serialized-data
        """,
    )
    harness.set_leader(True)
    rel_id = harness.add_relation("app-provides", "foo")
    harness.add_relation_unit(rel_id, "foo/0")
    harness.update_relation_data(
        rel_id,
        "foo",
        {"_supported_versions": yaml.dump(["v1"])},
    )
    harness.begin()
    assert harness.charm.interface.get_data() == {}

    data = {
        "service": "my-service",
        "port": 4242,
        "access-key": "my-access-key",
        "secret-key": "my-secret-key",
    }
    harness.charm.interface.send_data(data)
    rel_data = harness.charm.interface.get_data()
    assert rel_data == {(rel, app): data
                        for rel in harness.model.relations["app-provides"]
                        for app, bag in rel.data.items()
                        if isinstance(app, Application) and "data" in bag}
コード例 #3
0
class TestNonStandardProviders(unittest.TestCase):
    def setup(self, **kwargs):
        bad_provider_charm = customize_endpoint_provider(
            alert_rules_path=kwargs["alert_rules_path"])
        self.harness = Harness(bad_provider_charm, meta=PROVIDER_META)
        self.addCleanup(self.harness.cleanup)
        self.harness.set_leader(True)
        self.harness.begin()

    @patch("ops.testing._TestingModelBackend.network_get")
    def test_a_bad_alert_expression_logs_an_error(self, _):
        self.setup(alert_rules_path="./tests/unit/bad_alert_expressions")

        with self.assertLogs(level="ERROR") as logger:
            rel_id = self.harness.add_relation(RELATION_NAME, "provider")
            self.harness.add_relation_unit(rel_id, "provider/0")
            messages = sorted(logger.output)
            self.assertEqual(len(messages), 1)
            self.assertIn("Invalid rules file: missing_expr.rule", messages[0])

    @patch("ops.testing._TestingModelBackend.network_get")
    def test_a_bad_alert_rules_logs_an_error(self, _):
        self.setup(alert_rules_path="./tests/unit/bad_alert_rules")

        with self.assertLogs(level="ERROR") as logger:
            rel_id = self.harness.add_relation(RELATION_NAME, "provider")
            self.harness.add_relation_unit(rel_id, "provider/0")
            messages = sorted(logger.output)
            self.assertEqual(len(messages), 1)
            self.assertIn("Failed to read alert rules from bad_yaml.rule",
                          messages[0])
コード例 #4
0
    def test_update_relation_no_local_app_change_event(self):
        # language=YAML
        harness = Harness(CharmBase,
                          meta='''
            name: my-charm
            requires:
              db:
                interface: pgsql
            ''')
        harness.begin()
        harness.set_leader(False)
        helper = DBRelationChangedHelper(harness.charm, "helper")
        rel_id = harness.add_relation('db', 'postgresql')
        # TODO: remove this as soon as https://github.com/canonical/operator/issues/175 is fixed.
        harness.add_relation_unit(rel_id, 'postgresql/0')
        self.assertEqual(helper.changes, [])

        harness.update_relation_data(rel_id, 'my-charm', {'new': 'value'})
        rel = harness.charm.model.get_relation('db')
        self.assertEqual(rel.data[harness.charm.app]['new'], 'value')

        # Our app data bag got updated.
        self.assertEqual(rel.data[harness.charm.model.app]['new'], 'value')
        # But there were no changed events registered by our unit.
        self.assertEqual(helper.changes, [])
コード例 #5
0
def test_dual_interface_charm():
    harness = Harness(
        RequiresCharm,
        meta="""
        name: test-app
        requires:
            app-requires:
                interface: serialized-data
        """,
    )
    harness.set_leader(True)
    rel_id = harness.add_relation("app-requires", "foo")
    harness.add_relation_unit(rel_id, "foo/0")
    harness.update_relation_data(
        rel_id,
        "foo",
        {"_supported_versions": yaml.dump(["v2"])},
    )
    harness.update_relation_data(
        rel_id,
        "foo",
        {"data": "foo: bar"},
    )
    harness.begin()
    harness.charm.interface.send_data({"bar": None})

    rel_data = harness.charm.interface.get_data()
    assert rel_data == {(rel, app): {
        "bar": None
    } if app._is_our_app else {
        "foo": "bar"
    }
                        for rel in harness.model.relations["app-requires"]
                        for app, bag in rel.data.items()
                        if isinstance(app, Application) and "data" in bag}
コード例 #6
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("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)
コード例 #7
0
class TestMySQLProvider(unittest.TestCase):
    def setup_harness(self, config: dict, meta: dict) -> None:
        config_yaml = CONFIG_YAML.format(**config)
        meta_yaml = PROVIDER_META.format(**meta)
        self.harness = Harness(MySQLCharm, meta=meta_yaml, config=config_yaml)
        self.addCleanup(self.harness.cleanup)
        self.harness.set_leader(True)
        self.harness.begin()

    def test_databases_are_created_when_requested(self):
        config = CONFIG.copy()
        meta = METADATA.copy()
        self.setup_harness(config, meta)

        requested_database = ["mysql_database"]
        json_request = json.dumps(requested_database)
        consumer_data = {"databases": json_request}

        rel_id = self.harness.add_relation("database", "consumer")
        data = self.harness.get_relation_data(rel_id,
                                              self.harness.model.app.name)
        self.assertDictEqual(data, {})
        self.harness.add_relation_unit(rel_id, "consumer/0")
        self.harness.update_relation_data(rel_id, "consumer", consumer_data)
        data = self.harness.get_relation_data(rel_id,
                                              self.harness.model.app.name)
        databases = json.loads(data["databases"])
        self.assertListEqual(databases, requested_database)
コード例 #8
0
 def test_get_backend_calls(self):
     harness = Harness(CharmBase,
                       meta='''
         name: test-charm
         requires:
             db:
                 interface: pgsql
         ''')
     harness.begin()
     # No calls to the backend yet
     self.assertEqual(harness._get_backend_calls(), [])
     rel_id = harness.add_relation('db', 'postgresql')
     # update_relation_data ensures the cached data for the relation is wiped
     harness.update_relation_data(rel_id, 'test-charm/0', {'foo': 'bar'})
     self.assertEqual(harness._get_backend_calls(reset=True), [
         ('relation_ids', 'db'),
         ('relation_list', rel_id),
     ])
     # add_relation_unit resets the relation_list, but doesn't trigger backend calls
     harness.add_relation_unit(rel_id, 'postgresql/0')
     self.assertEqual([], harness._get_backend_calls(reset=False))
     # however, update_relation_data does, because we are preparing relation-changed
     harness.update_relation_data(rel_id, 'postgresql/0', {'foo': 'bar'})
     self.assertEqual(harness._get_backend_calls(reset=False), [
         ('relation_ids', 'db'),
         ('relation_list', rel_id),
     ])
     # If we check again, they are still there, but now we reset it
     self.assertEqual(harness._get_backend_calls(reset=True), [
         ('relation_ids', 'db'),
         ('relation_list', rel_id),
     ])
     # And the calls are gone
     self.assertEqual(harness._get_backend_calls(), [])
コード例 #9
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)
コード例 #10
0
ファイル: test_charm.py プロジェクト: charmed-osm/5g-core
class TestCharm(unittest.TestCase):
    """Test script for checking relations"""

    def setUp(self) -> NoReturn:
        """Test setup."""
        self.harness = Harness(MongodbCharm)
        self.harness.set_leader(is_leader=True)
        self.harness.begin()

    def test_on_configure_pod(self) -> NoReturn:
        """Test installation without any relation."""
        self.harness.charm.on.config_changed.emit()
        expected_result = {
            "version": 3,
            "containers": [
                {
                    "name": "mongodb",
                    "imageDetails": self.harness.charm.image.fetch(),
                    "imagePullPolicy": "Always",
                    "ports": [
                        {
                            "name": "mongodb",
                            "containerPort": 27017,
                            "protocol": "TCP",
                        }
                    ],
                    "command": [
                        "mongod",
                        "--bind_ip",
                        "mongodb-endpoints",
                        "--port",
                        "27017",
                    ],
                }
            ],
        }

        # Verifying status
        self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus)

        # Verifying status message
        self.assertGreater(len(self.harness.charm.unit.status.message), 0)

        pod_spec, _ = self.harness.get_pod_spec()
        self.assertDictEqual(expected_result, pod_spec)

    def test_publish_mongodb_info(self) -> NoReturn:
        """Test to see if mongodb relation is updated."""
        expected_result = {
            "hostname": "mongodb",
            "mongodb_uri": "mongodb://mongodb:27017",
        }
        relation_id = self.harness.add_relation("mongodb", "nrf")
        self.harness.add_relation_unit(relation_id, "nrf/0")
        relation_data = self.harness.get_relation_data(relation_id, "mongodb")
        print("relation_data", relation_data)
        self.assertDictEqual(expected_result, relation_data)
コード例 #11
0
def test_not_leader():
    received_data = {
        "service": "my-service",
        "port": 4242,
        "access-key": "my-access-key",
        "secret-key": "my-secret-key",
    }
    sent_data = {"response": "ok"}

    harness = Harness(
        RequireCharm,
        meta="""
        name: test-app
        requires:
            app-requires:
                interface: serialized-data
        """,
    )
    harness.set_leader(False)
    rel_id = harness.add_relation("app-requires", "foo")
    harness.add_relation_unit(rel_id, "foo/0")
    harness.update_relation_data(
        rel_id,
        "foo",
        {
            "_supported_versions": yaml.dump(["v1"]),
            "data": yaml.dump(received_data),
        },
    )
    harness.begin()

    # confirm that reading remote data doesn't require leadership
    rel = harness.charm.model.get_relation("app-requires", rel_id)
    assert harness.charm.interface.get_data() == {
        (rel, rel.app): received_data,
    }

    # confirm that sending data still requires leadership
    with pytest.raises(sdi.errors.RelationPermissionError):
        harness.charm.interface.send_data(sent_data)
    harness.set_leader(True)
    harness.charm.interface.send_data(sent_data)

    # confirm that leader can see sent data
    assert harness.charm.interface.get_data() == {
        (rel, rel.app): received_data,
        (rel, harness.charm.app): sent_data,
    }

    # confirm that non-leader cannot see sent data
    harness.set_leader(False)
    assert harness.charm.interface.get_data() == {
        (rel, rel.app): received_data,
    }
コード例 #12
0
 def test_publish_relation_joined(self, mock_open_call, os_path_isdir,
                                  os_makedirs, os_symlink):
     harness = Harness(SimpleStreamsCharm)
     harness.begin()
     default_config = self.default_config()
     self.assertEqual(harness.charm._stored.config, {})
     harness.update_config(default_config)
     relation_id = harness.add_relation('publish', 'webserver')
     harness.add_relation_unit(relation_id, 'webserver/0')
     assert harness.get_relation_data(relation_id, harness._unit_name)\
            == {'path': '{}/publish'.format(default_config['image-dir'])}
def test_send_data_multiple_versions():
    harness = Harness(
        ProvideCharm,
        meta="""
        name: test-app
        provides:
            app-provides:
                interface: serialized-data
        """,
    )
    harness.set_leader(True)

    rel_id1 = harness.add_relation("app-provides", "appv1")
    harness.add_relation_unit(rel_id1, "appv1/0")
    harness.update_relation_data(
        rel_id1,
        "appv1",
        {"_supported_versions": "- v1"},
    )

    rel_id2 = harness.add_relation("app-provides", "appv2")
    harness.add_relation_unit(rel_id2, "appv2/0")
    harness.update_relation_data(
        rel_id2,
        "appv2",
        {"_supported_versions": "- v2"},
    )

    harness.begin()
    data = {
        "service": "my-service",
        "port": 4242,
        "access-key": "my-access-key",
        "secret-key": "my-secret-key",
    }
    harness.update_relation_data(rel_id1, "appv1", {"data": yaml.dump(data)})
    harness.update_relation_data(rel_id2, "appv2",
                                 {"data": yaml.dump({"bar": None})})

    harness.charm.interface.send_data(data, "appv1")
    harness.charm.interface.send_data(
        {"foo": "sillema sillema nika su"},
        app_name="appv2",
    )

    # Can't send for an invalid app
    with pytest.raises(sdi.errors.InvalidAppNameError):
        harness.charm.interface.send_data({}, "invalid-app")

    # Can't send invalid data
    with pytest.raises(sdi.errors.RelationDataError):
        harness.charm.interface.send_data({"bad": "guy"}, "appv2")
コード例 #14
0
 def test_update_relation_remove_data(self):
     # language=YAML
     harness = Harness(CharmBase, meta='''
         name: my-charm
         requires:
           db:
             interface: pgsql
         ''')
     harness.begin()
     viewer = RelationChangedViewer(harness.charm, 'db')
     rel_id = harness.add_relation('db', 'postgresql')
     harness.add_relation_unit(rel_id, 'postgresql/0', remote_unit_data={'initial': 'data'})
     harness.update_relation_data(rel_id, 'postgresql/0', {'initial': ''})
     self.assertEqual(viewer.changes, [{'initial': 'data'}, {}])
コード例 #15
0
 def test_relation_events(self):
     harness = Harness(RelationEventCharm,
                       meta='''
         name: test-app
         requires:
             db:
                 interface: pgsql
     ''')
     self.addCleanup(harness.cleanup)
     harness.begin()
     harness.charm.observe_relation_events('db')
     self.assertEqual(harness.charm.get_changes(), [])
     rel_id = harness.add_relation('db', 'postgresql')
     self.assertEqual(harness.charm.get_changes(), [{
         'name': 'relation-created',
         'data': {
             'app': 'postgresql',
             'unit': None,
             'relation_id': rel_id,
         }
     }])
     harness.add_relation_unit(rel_id, 'postgresql/0')
     self.assertEqual(harness.charm.get_changes(), [{
         'name': 'relation-joined',
         'data': {
             'app': 'postgresql',
             'unit': 'postgresql/0',
             'relation_id': rel_id,
         }
     }])
     harness.update_relation_data(rel_id, 'postgresql', {'foo': 'bar'})
     self.assertEqual(harness.charm.get_changes(), [{
         'name': 'relation-changed',
         'data': {
             'app': 'postgresql',
             'unit': None,
             'relation_id': rel_id,
         }
     }])
     harness.update_relation_data(rel_id, 'postgresql/0', {'baz': 'bing'})
     self.assertEqual(harness.charm.get_changes(), [{
         'name': 'relation-changed',
         'data': {
             'app': 'postgresql',
             'unit': 'postgresql/0',
             'relation_id': rel_id,
         }
     }])
コード例 #16
0
 def test_relation_set_deletes(self):
     harness = Harness(CharmBase,
                       meta='''
         name: test-charm
         requires:
             db:
                 interface: pgsql
         ''')
     harness.begin()
     harness.set_leader(False)
     rel_id = harness.add_relation('db', 'postgresql')
     harness.update_relation_data(rel_id, 'test-charm/0', {'foo': 'bar'})
     harness.add_relation_unit(rel_id, 'postgresql/0')
     rel = harness.charm.model.get_relation('db', rel_id)
     del rel.data[harness.charm.model.unit]['foo']
     self.assertEqual({}, harness.get_relation_data(rel_id, 'test-charm/0'))
コード例 #17
0
 def test_add_relation_and_unit(self):
     # language=YAML
     harness = Harness(CharmBase, meta='''
         name: test-app
         requires:
             db:
                 interface: pgsql
         ''')
     remote_unit = 'postgresql/0'
     rel_id = harness.add_relation('db', 'postgresql', remote_app_data={'app': 'data'})
     self.assertIsInstance(rel_id, int)
     harness.add_relation_unit(rel_id, remote_unit, remote_unit_data={'foo': 'bar'})
     backend = harness._backend
     self.assertEqual([rel_id], backend.relation_ids('db'))
     self.assertEqual([remote_unit], backend.relation_list(rel_id))
     self.assertEqual({'foo': 'bar'}, backend.relation_get(rel_id, remote_unit, is_app=False))
     self.assertEqual({'app': 'data'}, backend.relation_get(rel_id, remote_unit, is_app=True))
コード例 #18
0
class TestCharm(unittest.TestCase):
    def setUp(self):
        self.harness = Harness(CassandraOperatorCharm)
        self.addCleanup(self.harness.cleanup)
        self.harness.begin()
        self.harness.set_leader(True)

    def test_relation_is_set(self):
        rel_id = self.harness.add_relation("cql", "otherapp")
        self.assertIsInstance(rel_id, int)
        self.harness.add_relation_unit(rel_id, "otherapp/0")
        self.harness.update_relation_data(rel_id, "otherapp", {})
        self.assertEqual(
            self.harness.get_relation_data(
                rel_id, self.harness.model.app.name)["port"],
            "9042",
        )
コード例 #19
0
class TestNonStandardProviders(unittest.TestCase):
    def setup(self, **kwargs):
        bad_provider_charm = customize_endpoint_provider(
            alert_rules_path=kwargs["alert_rules_path"])
        self.harness = Harness(bad_provider_charm, meta=PROVIDER_META)
        self.addCleanup(self.harness.cleanup)
        self.harness.set_leader(True)
        self.harness.begin()

    @patch("ops.testing._TestingModelBackend.network_get")
    def test_a_bad_alert_expression_logs_an_error(self, _):
        self.setup(alert_rules_path="./tests/unit/bad_alert_expressions")

        with self.assertLogs(level="ERROR") as logger:
            rel_id = self.harness.add_relation(RELATION_NAME, "provider")
            self.harness.add_relation_unit(rel_id, "provider/0")
            messages = sorted(logger.output)
            self.assertEqual(len(messages), 1)
            self.assertIn(
                "Invalid alert rule missing_expr.rule: missing an 'expr' property",
                messages[0])

    @patch("ops.testing._TestingModelBackend.network_get")
    def test_a_bad_alert_rules_logs_an_error(self, _):
        self.setup(alert_rules_path="./tests/unit/bad_alert_rules")

        with self.assertLogs(level="ERROR") as logger:
            rel_id = self.harness.add_relation(RELATION_NAME, "provider")
            self.harness.add_relation_unit(rel_id, "provider/0")
            messages = sorted(logger.output)
            self.assertEqual(len(messages), 1)
            self.assertIn("Failed to read alert rules from bad_yaml.rule",
                          messages[0])

    def test_provider_default_scrape_relations_not_in_meta(self):
        self.setup(
            alert_rules_path="./tests/unit/non_standard_prometheus_alert_rules"
        )

        alert_groups = self.harness.charm.provider._labeled_alert_groups
        self.assertTrue(len(alert_groups), 1)
        alert_group = alert_groups[0]
        rules = alert_group["rules"]
        self.assertTrue(len(rules), 1)
        rule = rules[0]
        self.assertEqual(rule["alert"], "OddRule")
コード例 #20
0
 def test_add_relation_and_unit(self):
     harness = Harness(CharmBase,
                       meta='''
         name: test-app
         requires:
             db:
                 interface: pgsql
         ''')
     rel_id = harness.add_relation('db', 'postgresql')
     self.assertIsInstance(rel_id, int)
     harness.add_relation_unit(rel_id, 'postgresql/0')
     harness.update_relation_data(rel_id, 'postgresql/0', {'foo': 'bar'})
     backend = harness._backend
     self.assertEqual(backend.relation_ids('db'), [rel_id])
     self.assertEqual(backend.relation_list(rel_id), ['postgresql/0'])
     self.assertEqual(
         backend.relation_get(rel_id, 'postgresql/0', is_app=False),
         {'foo': 'bar'})
コード例 #21
0
def test_version_mismatch():
    harness = Harness(
        RequireCharm,
        meta="""
        name: test-app
        requires:
            app-requires:
                interface: serialized-data
        """,
    )
    harness.set_leader(True)
    rel_id = harness.add_relation("app-requires", "foo")
    harness.add_relation_unit(rel_id, "foo/0")
    harness.update_relation_data(
        rel_id,
        "foo",
        {"_supported_versions": yaml.dump(["v2"])},
    )
    with pytest.raises(sdi.NoCompatibleVersions):
        harness.begin()
コード例 #22
0
 def test_relation_set_app_not_leader(self):
     # language=YAML
     harness = Harness(RecordingCharm, meta='''
         name: test-charm
         requires:
             db:
                 interface: pgsql
         ''')
     harness.set_leader(False)
     rel_id = harness.add_relation('db', 'postgresql')
     harness.add_relation_unit(rel_id, 'postgresql/0')
     harness.begin()
     rel = harness.charm.model.get_relation('db')
     with self.assertRaises(ModelError):
         rel.data[harness.charm.app]['foo'] = 'bar'
     # The data has not actually been changed
     self.assertEqual(harness.get_relation_data(rel_id, 'test-charm'), {})
     harness.set_leader(True)
     rel.data[harness.charm.app]['foo'] = 'bar'
     self.assertEqual(harness.get_relation_data(rel_id, 'test-charm'), {'foo': 'bar'})
コード例 #23
0
class TestRemoteWriteProvider(unittest.TestCase):
    @patch_network_get(private_address="1.1.1.1")
    def setUp(self, *unused):
        self.harness = Harness(PrometheusCharm)
        self.harness.set_model_info("lma", "123456")
        self.addCleanup(self.harness.cleanup)

    @patch.object(KubernetesServicePatch, "_service_object", new=lambda *args: None)
    @patch.object(Prometheus, "reload_configuration", new=lambda _: True)
    @patch_network_get(private_address="1.1.1.1")
    def test_port_is_set(self, *unused):
        self.harness.begin_with_initial_hooks()

        rel_id = self.harness.add_relation(RELATION_NAME, "consumer")
        self.harness.add_relation_unit(rel_id, "consumer/0")
        self.assertEqual(
            self.harness.get_relation_data(rel_id, self.harness.charm.unit.name),
            {"remote_write": json.dumps({"url": "http://1.1.1.1:9090/api/v1/write"})},
        )
        self.assertIsInstance(self.harness.charm.unit.status, ActiveStatus)

    @patch.object(KubernetesServicePatch, "_service_object", new=lambda *args: None)
    @patch.object(Prometheus, "reload_configuration", new=lambda _: True)
    @patch_network_get(private_address="1.1.1.1")
    def test_alert_rules(self, *unused):
        self.harness.begin_with_initial_hooks()

        rel_id = self.harness.add_relation(RELATION_NAME, "consumer")
        self.harness.update_relation_data(
            rel_id,
            "consumer",
            {"alert_rules": json.dumps(ALERT_RULES)},
        )

        self.harness.add_relation_unit(rel_id, "consumer/0")

        alerts = self.harness.charm.remote_write_provider.alerts()
        alerts = list(alerts.values())[0]  # drop the topology identifier
        self.assertEqual(len(alerts), 1)
        self.assertDictEqual(alerts, ALERT_RULES)
コード例 #24
0
 def test_update_relation_exposes_new_data(self):
     harness = Harness(CharmBase,
                       meta='''
         name: my-charm
         requires:
           db:
             interface: pgsql
         ''')
     harness.begin()
     viewer = RelationChangedViewer(harness.charm, 'db')
     rel_id = harness.add_relation('db', 'postgresql')
     harness.add_relation_unit(rel_id, 'postgresql/0')
     harness.update_relation_data(rel_id, 'postgresql/0',
                                  {'initial': 'data'})
     self.assertEqual(viewer.changes, [{'initial': 'data'}])
     harness.update_relation_data(rel_id, 'postgresql/0', {'new': 'value'})
     self.assertEqual(viewer.changes, [{
         'initial': 'data'
     }, {
         'initial': 'data',
         'new': 'value'
     }])
コード例 #25
0
class TestCharm(unittest.TestCase):
    def setUp(self):
        self.harness = Harness(AlertmanagerCharm)
        self.addCleanup(self.harness.cleanup)
        self.harness.begin()
        self.harness.set_leader(True)
        self.harness.update_config({"pagerduty_key": "123"})

    def test_config_changed(self):
        self.harness.update_config({"pagerduty_key": "abc"})
        config = self.get_config()
        self.assertEqual(
            config["receivers"][0]["pagerduty_configs"][0]["service_key"],
            "abc")

    def test_port_change(self):
        rel_id = self.harness.add_relation("alerting", "prometheus")
        self.assertIsInstance(rel_id, int)
        self.harness.add_relation_unit(rel_id, "prometheus/0")
        self.harness.update_config({"port": "9096"})
        self.assertEqual(
            self.harness.get_relation_data(
                rel_id, self.harness.model.app.name)["port"],
            "9096",
        )

    def test_bad_config(self):
        self.harness.update_config({"pagerduty_key": ""})
        self.assertEqual(type(self.harness.model.unit.status),
                         ops.model.BlockedStatus)

    # TODO figure out how to test scaling up the application

    def get_config(self):
        pod_spec = self.harness.get_pod_spec()
        config_yaml = pod_spec[0]["containers"][0]["volumeConfig"][0]["files"][
            0]["content"]
        return yaml.safe_load(config_yaml)
コード例 #26
0
class TestRemoteWriteConsumer(unittest.TestCase):
    def setUp(self):
        self.harness = Harness(RemoteWriteConsumerCharm, meta=METADATA)
        self.harness.add_resource(
            "promql-transform-amd64",
            open("./promql-transform", "rb").read(),
        )
        self.addCleanup(self.harness.cleanup)
        self.harness.set_leader(True)
        self.harness.begin_with_initial_hooks()

    def test_address_is_set(self):
        rel_id = self.harness.add_relation(RELATION_NAME, "provider")
        self.harness.add_relation_unit(rel_id, "provider/0")
        self.harness.update_relation_data(
            rel_id,
            "provider/0",
            {"remote_write": json.dumps({"url": "http://1.1.1.1:9090/api/v1/write"})},
        )
        assert list(self.harness.charm.remote_write_consumer.endpoints) == [
            {"url": "http://1.1.1.1:9090/api/v1/write"}
        ]

    @patch.object(RemoteWriteConsumerCharm, "_handle_endpoints_changed")
    def test_config_is_set(self, mock_handle_endpoints_changed):
        rel_id = self.harness.add_relation(RELATION_NAME, "provider")

        self.harness.add_relation_unit(rel_id, "provider/0")
        self.harness.update_relation_data(
            rel_id,
            "provider/0",
            {"remote_write": json.dumps({"url": "http://1.1.1.1:9090/api/v1/write"})},
        )

        mock_handle_endpoints_changed.assert_called()
        event = mock_handle_endpoints_changed.call_args.args[0]
        self.assertEqual(rel_id, event.relation_id)

        assert list(self.harness.charm.remote_write_consumer.endpoints) == [
            {"url": "http://1.1.1.1:9090/api/v1/write"}
        ]

    def test_no_remote_write_endpoint_provided(self):
        rel_id = self.harness.add_relation(RELATION_NAME, "provider")
        self.harness.add_relation_unit(rel_id, "provider/0")
        self.harness.update_relation_data(rel_id, "provider/0", {})
        assert list(self.harness.charm.remote_write_consumer.endpoints) == []
コード例 #27
0
class TestCharm(unittest.TestCase):
    def setUp(self):
        self.harness = Harness(ElasticsearchOperatorCharm)
        self.addCleanup(self.harness.cleanup)
        self.harness.begin()

        # patch definitions
        self.mock_es_client = mock.patch('charm.ElasticsearchOperatorCharm._get_es_client')
        self.mock_es = mock.patch('elasticsearch.Elasticsearch')
        self.mock_current_mmn = \
            mock.patch('charm.ElasticsearchOperatorCharm.current_minimum_master_nodes',
                       new_callable=mock.PropertyMock)

        # start patches
        self.mock_es_client.start()
        self.mock_es.start()
        self.mock_current_mmn.start()

        # cleanup patches
        self.addCleanup(self.mock_es_client.stop)
        self.addCleanup(self.mock_es.stop)
        self.addCleanup(self.mock_current_mmn.stop)

    def test_cluster_name_can_be_changed(self):
        self.harness.set_leader(True)
        name_config = MINIMAL_CONFIG.copy()
        name_config['cluster-name'] = 'new name'
        self.harness.update_config(name_config)
        pod_spec, _ = self.harness.get_pod_spec()
        config = elastic_config(pod_spec)
        self.assertEqual(config['cluster']['name'],
                         name_config['cluster-name'])

    def test_seed_nodes_are_added_when_fewer_than_minimum(self):
        self.harness.set_leader(True)
        seed_config = MINIMAL_CONFIG.copy()
        self.harness.update_config(seed_config)

        # create a peer relation and add a peer unit
        rel_id = self.harness.add_relation('elasticsearch', 'elasticsearch')
        self.assertIsInstance(rel_id, int)
        self.harness.add_relation_unit(rel_id, 'elasticsearch-operator-0')

        # check number of seed hosts is the default value
        pod_spec, _ = self.harness.get_pod_spec()
        seed_hosts_file = config_file(pod_spec, 'unicast_hosts.txt')
        self.assertEqual(charm.SEED_SIZE, len(seed_hosts_file['content'].split("\n")))

        # increase number of seed hosts and add a unit to trigger the change
        charm.SEED_SIZE = 4
        self.harness.add_relation_unit(rel_id, 'elasticsearch-operator-1')
        self.harness.update_config(seed_config)

        # check the number of seed hosts has now increased
        pod_spec, _ = self.harness.get_pod_spec()
        seed_hosts_file = config_file(pod_spec, 'unicast_hosts.txt')
        self.assertEqual(charm.SEED_SIZE, 4)
        self.assertEqual(charm.SEED_SIZE, len(seed_hosts_file['content'].split("\n")))

    def test_num_hosts_is_equal_to_num_units(self):
        self.harness.set_leader(True)
        seed_config = MINIMAL_CONFIG.copy()
        self.harness.update_config(seed_config)

        # add a random number of peer units
        rel_id = self.harness.add_relation('elasticsearch', 'elasticsearch')
        self.assertIsInstance(rel_id, int)
        num_units = random.randint(2, 10)

        # elasticsearch-operator/0 already exists as the starting unit
        for i in range(1, num_units):
            self.harness.add_relation_unit(rel_id, 'elasticsearch-operator/{}'.format(i))
        self.assertEqual(self.harness.charm.num_hosts, num_units)

    def test_minimum_master_nodes_matches_formula(self):
        # Test whether _minimum_master_nodes function
        # matches formula N / 2 + 1 for N > 2, 1 otherwise
        self.harness.set_leader(True)
        seed_config = MINIMAL_CONFIG.copy()
        self.harness.update_config(seed_config)
        rel_id = self.harness.add_relation('elasticsearch', 'elasticsearch')

        # create inputs of three main case categories with two examples each
        # case categories: num_nodes <= 2, num_nodes is even, num_nodes is odd
        total_mmn_cases = [(1, 1), (2, 1), (4, 3), (5, 3), (6, 4), (7, 4)]
        for (num_nodes, expected_mmn) in total_mmn_cases:
            with self.subTest():
                for i in range(1, num_nodes):
                    self.harness.add_relation_unit(rel_id, 'elasticsearch-operator/{}'.format(i))
                actual_mmn = self.harness.charm.ideal_minimum_master_nodes
                self.assertEqual(expected_mmn, actual_mmn)

    def test_dynamic_settings_payload_has_correct_minimum_master_nodes(self):
        self.harness.set_leader(True)
        seed_config = MINIMAL_CONFIG.copy()
        self.harness.update_config(seed_config)

        # create the peer relation
        rel_id = self.harness.add_relation('elasticsearch', 'elasticsearch')

        # when number of nodes is 6, min master nodes should be 6 / 2 + 1 = 4
        num_nodes = 6
        expected_mmn = 4
        for i in range(1, num_nodes):
            self.harness.add_relation_unit(rel_id, 'elasticsearch-operator/{}'.format(i))
        payload = self.harness.charm._build_dynamic_settings_payload()
        actual_mmn = payload['persistent']['discovery.zen.minimum_master_nodes']
        self.assertEqual(expected_mmn, actual_mmn)

    def test_peer_changed_handler_with_single_node_via_update_status_event(self):
        self.harness.set_leader(True)
        seed_config = MINIMAL_CONFIG.copy()
        self.harness.update_config(seed_config)

        # check that the number of nodes and the status is correct
        # after emitting the update_status event
        self.assertEqual(self.harness.charm.num_hosts, 1)
        self.harness.charm.on.update_status.emit()
        self.assertEqual(
            self.harness.charm.unit.status,
            ActiveStatus()
        )

    @mock.patch('charm.ElasticsearchOperatorCharm.num_es_nodes', new_callable=mock.PropertyMock)
    def test_relation_changed_with_node_and_unit_mismatch(self, mock_es_nodes):
        self.harness.set_leader(True)
        seed_config = MINIMAL_CONFIG.copy()
        self.harness.update_config(seed_config)

        expected_num_es_nodes = 2
        mock_es_nodes.return_value = expected_num_es_nodes
        expected_num_units = 3

        # add a different number of units than number of es_nodes
        rel_id = self.harness.add_relation('elasticsearch', 'elasticsearch')
        rel = self.harness.model.get_relation('elasticsearch')
        for i in range(1, expected_num_units):
            self.harness.add_relation_unit(rel_id, 'elasticsearch-operator/{}'.format(i))

        # check that there is a mismatch
        self.assertEqual(expected_num_es_nodes, self.harness.charm.num_es_nodes)
        self.assertEqual(expected_num_units, self.harness.charm.num_hosts)

        # check that the proper status has been set in _elasticsearch_relation_changed
        self.harness.charm.on.elasticsearch_relation_changed.emit(rel)
        self.assertEqual(
            self.harness.charm.unit.status,
            MaintenanceStatus('Waiting for nodes to join ES cluster')
        )

    @mock.patch('charm.ElasticsearchOperatorCharm.num_es_nodes', new_callable=mock.PropertyMock)
    def test_relation_changed_with_node_and_unit_mismatch_via_update_status(self, mock_es_nodes):
        self.harness.set_leader(True)
        seed_config = MINIMAL_CONFIG.copy()
        self.harness.update_config(seed_config)

        expected_num_es_nodes = 2
        mock_es_nodes.return_value = expected_num_es_nodes
        expected_num_units = 3

        # add a different number of units than number of es_nodes
        rel_id = self.harness.add_relation('elasticsearch', 'elasticsearch')
        for i in range(1, expected_num_units):
            self.harness.add_relation_unit(rel_id, 'elasticsearch-operator/{}'.format(i))

        # check that there is a mismatch
        self.assertEqual(expected_num_es_nodes, self.harness.charm.num_es_nodes)
        self.assertEqual(expected_num_units, self.harness.charm.num_hosts)

        # check that the proper status has been set in _elasticsearch_relation_changed
        self.harness.charm.on.update_status.emit()
        self.assertEqual(
            self.harness.charm.unit.status,
            MaintenanceStatus('Waiting for nodes to join ES cluster')
        )

    @mock.patch('charm.ElasticsearchOperatorCharm.num_es_nodes', new_callable=mock.PropertyMock)
    def test_relation_changed_with_node_and_unit_match(self, mock_es_nodes):
        self.harness.set_leader(True)
        seed_config = MINIMAL_CONFIG.copy()
        self.harness.update_config(seed_config)

        expected_num_es_nodes = 3
        mock_es_nodes.return_value = expected_num_es_nodes
        expected_num_units = 3

        # add same number of units as number of es_nodes
        rel_id = self.harness.add_relation('elasticsearch', 'elasticsearch')
        rel = self.harness.model.get_relation('elasticsearch')
        for i in range(1, expected_num_units):
            self.harness.add_relation_unit(rel_id, 'elasticsearch-operator/{}'.format(i))

        # check that there is a match
        self.assertEqual(expected_num_es_nodes, self.harness.charm.num_es_nodes)
        self.assertEqual(expected_num_units, self.harness.charm.num_hosts)

        # check that the proper status has been set and that the logs are correct
        with self.assertLogs(level='INFO') as logger:
            self.harness.charm.on.elasticsearch_relation_changed.emit(rel)
            # check the logs
            expected_logs = ['INFO:charm:Attempting to configure dynamic settings.']
            self.assertEqual(sorted(logger.output), expected_logs)
            # check the status
            self.assertEqual(
                self.harness.charm.unit.status,
                ActiveStatus()
            )

    @mock.patch('charm.ElasticsearchOperatorCharm.num_es_nodes', new_callable=mock.PropertyMock)
    def test_relation_changed_with_node_and_unit_match_via_update_status(self, mock_es_nodes):
        self.harness.set_leader(True)
        seed_config = MINIMAL_CONFIG.copy()
        self.harness.update_config(seed_config)

        expected_num_es_nodes = 3
        mock_es_nodes.return_value = expected_num_es_nodes
        expected_num_units = 3

        # add same number of units as number of es_nodes
        rel_id = self.harness.add_relation('elasticsearch', 'elasticsearch')
        for i in range(1, expected_num_units):
            self.harness.add_relation_unit(rel_id, 'elasticsearch-operator/{}'.format(i))

        # check that there is a match
        self.assertEqual(expected_num_es_nodes, self.harness.charm.num_es_nodes)
        self.assertEqual(expected_num_units, self.harness.charm.num_hosts)

        # check that the proper status has been set and that the logs are correct
        with self.assertLogs(level='INFO') as logger:
            self.harness.charm.on.update_status.emit()
            # check the logs (there will be two calls to _configure_dynamic_settings
            expected_logs = ['INFO:charm:Attempting to configure dynamic settings.']
            self.assertEqual(sorted(logger.output), expected_logs)
            # check the status
            self.assertEqual(
                self.harness.charm.unit.status,
                ActiveStatus()
            )
コード例 #28
0
class TestDashboardProvider(unittest.TestCase):
    def setUp(self):
        patcher = patch(
            "charms.grafana_k8s.v0.grafana_dashboard._resolve_dir_against_charm_path"
        )
        self.mock_resolve_dir = patcher.start()
        self.addCleanup(patcher.stop)

        self.mock_resolve_dir.return_value = "./tests/unit/dashboard_templates"
        self.harness = Harness(ProviderCharm, meta=CONSUMER_META)
        self.harness._backend.model_name = "testing"
        self.harness._backend.model_uuid = "abcdefgh-1234"
        self.addCleanup(self.harness.cleanup)
        self.harness.begin()
        self.harness.set_leader(True)

    def test_provider_sets_dashboard_data(self):
        rel_id = self.harness.add_relation("grafana-dashboard", "other_app")
        self.harness.add_relation_unit(rel_id, "other_app/0")
        data = json.loads(
            self.harness.get_relation_data(
                rel_id, self.harness.model.app.name)["dashboards"])

        self.assertDictEqual(
            {
                "templates": RELATION_TEMPLATES_DATA,
                "uuid": "12345678",
            },
            data,
        )

    def test_provider_can_remove_programmatically_added_dashboards(self):
        self.harness.charm.provider.add_dashboard("third")

        rel_id = self.harness.add_relation("grafana-dashboard", "other_app")
        self.harness.add_relation_unit(rel_id, "other_app/0")
        actual_data = json.loads(
            self.harness.get_relation_data(
                rel_id, self.harness.model.app.name)["dashboards"])

        expected_data_builtin_dashboards = {
            "templates": copy.deepcopy(RELATION_TEMPLATES_DATA),
            "uuid": "12345678",
        }

        expected_data = copy.deepcopy(expected_data_builtin_dashboards)
        expected_templates = expected_data["templates"]
        expected_templates["prog:uC2Arx+2"] = {  # type: ignore
            "charm": "provider-tester",
            "content": "/Td6WFoAAATm1rRGAgAhARYAAAB0L+WjAQAEdGhpcmQAAAAAtr5hbOrisy0AAR0FuC2Arx+2830BAAAAAARZWg==",
            "juju_topology": {
                "model": "testing",
                "model_uuid": "abcdefgh-1234",
                "application": "provider-tester",
                "unit": "provider-tester/0",
            },
        }

        self.assertDictEqual(expected_data, actual_data)
        self.harness.charm.provider.remove_non_builtin_dashboards()
        self.assertEqual(
            expected_data_builtin_dashboards,
            json.loads(
                self.harness.get_relation_data(
                    rel_id, self.harness.model.app.name)["dashboards"]),
        )

    def test_provider_cannot_remove_builtin_dashboards(self):
        rel_id = self.harness.add_relation("grafana-dashboard", "other_app")
        self.harness.add_relation_unit(rel_id, "other_app/0")
        actual_data = json.loads(
            self.harness.get_relation_data(
                rel_id, self.harness.model.app.name)["dashboards"])

        expected_data = {
            "templates": RELATION_TEMPLATES_DATA,
            "uuid": "12345678",
        }

        self.assertDictEqual(expected_data, actual_data)

        self.harness.charm.provider.remove_non_builtin_dashboards()
        self.assertEqual(
            expected_data,
            json.loads(
                self.harness.get_relation_data(
                    rel_id, self.harness.model.app.name)["dashboards"]),
        )

    def test_provider_destroys_old_data_on_rescan(self):
        rel_id = self.harness.add_relation("grafana-dashboard", "other_app")
        self.harness.add_relation_unit(rel_id, "other_app/0")
        actual_data = json.loads(
            self.harness.get_relation_data(
                rel_id, self.harness.model.app.name)["dashboards"])
        expected_data = {
            "templates": RELATION_TEMPLATES_DATA,
            "uuid": "12345678",
        }
        self.assertDictEqual(expected_data, actual_data)

        self.harness.charm.provider._dashboards_path = "./tests/unit/manual_dashboards"
        self.harness.charm.provider._reinitialize_dashboard_data()
        actual_data = json.loads(
            self.harness.get_relation_data(
                rel_id, self.harness.model.app.name)["dashboards"])
        expected_data = {
            "templates": MANUAL_TEMPLATE_DATA,
            "uuid": "12345678",
        }
        self.assertDictEqual(expected_data, actual_data)

    def test_provider_empties_data_on_exception(self):
        rel_id = self.harness.add_relation("grafana-dashboard", "other_app")
        self.harness.add_relation_unit(rel_id, "other_app/0")
        actual_data = json.loads(
            self.harness.get_relation_data(
                rel_id, self.harness.model.app.name)["dashboards"])
        expected_data = {
            "templates": RELATION_TEMPLATES_DATA,
            "uuid": "12345678",
        }
        self.assertDictEqual(expected_data, actual_data)

        self.mock_resolve_dir.side_effect = InvalidDirectoryPathError(
            "foo", "bar")
        self.harness.charm.provider._reinitialize_dashboard_data()
        actual_data = json.loads(
            self.harness.get_relation_data(
                rel_id, self.harness.model.app.name)["dashboards"])
        empty_data = {
            "templates": {},
            "uuid": "12345678",
        }
        self.assertDictEqual(empty_data, actual_data)

    def test_provider_clears_data_on_empty_dir(self):
        rel_id = self.harness.add_relation("grafana-dashboard", "other_app")
        self.harness.add_relation_unit(rel_id, "other_app/0")
        actual_data = json.loads(
            self.harness.get_relation_data(
                rel_id, self.harness.model.app.name)["dashboards"])
        expected_data = {
            "templates": RELATION_TEMPLATES_DATA,
            "uuid": "12345678",
        }
        self.assertDictEqual(expected_data, actual_data)

        self.harness.charm.provider._dashboards_path = "./tests/unit/empty_dashboards"
        self.harness.charm.provider._reinitialize_dashboard_data()
        actual_data = json.loads(
            self.harness.get_relation_data(
                rel_id, self.harness.model.app.name)["dashboards"])
        empty_data = {
            "templates": {},
            "uuid": "12345678",
        }
        self.assertDictEqual(empty_data, actual_data)
コード例 #29
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')
コード例 #30
0
class TestInterfaceMssqlCluster(unittest.TestCase):

    TEST_BIND_ADDRESS = '10.0.0.10'
    TEST_NODE_NAME = 'test-mssql-node'
    TEST_MASTER_CERT = {
        'master_key_password': '******',
        'master_cert': b64encode('test_master_cert'.encode()).decode(),
        'master_cert_key': b64encode('test_master_cert_key'.encode()).decode(),
        'master_cert_key_password': '******',
    }
    TEST_PRIMARY_REPLICA_NAME = 'test-primary-name'
    TEST_PRIMARY_LOGINS = {
        'test-login-1': {
            'sid': 'sid1',
            'password_hash': 'test-password-hash1',
            'roles': ['test-role1']
        },
        'test-login-2': {
            'sid': 'sid2',
            'password_hash': 'test-password-hash2',
            'roles': ['test-role2']
        },
        'test-login-3': {
            'sid': 'sid3',
            'password_hash': 'test-password-hash3',
            'roles': ['test-role3']
        },
        'test-login-4': {
            'sid': 'sid4',
            'password_hash': 'test-password-hash4',
            'roles': ['test-role4']
        }
    }
    TEST_SECONDARY_LOGINS = {
        'test-login-1': {
            'sid': 'sid1',
            'password_hash': 'test-password-hash1',
            'roles': ['test-role1']
        },
        'test-login-2': {
            'sid': 'sid2',
            'password_hash': 'test-password-hash2',
            'roles': ['test-role2']
        }
    }

    def setUp(self):
        self.harness = Harness(CharmBase,
                               meta='''
            name: mssql
            peers:
              cluster:
                interface: mssql-cluster
        ''')
        self.addCleanup(self.harness.cleanup)

        mocked_node_name = mock.patch.object(
            interface_mssql_cluster.MssqlCluster,
            'node_name',
            new_callable=mock.PropertyMock).start()
        mocked_node_name.return_value = self.TEST_NODE_NAME
        mocked_bind_address = mock.patch.object(
            interface_mssql_cluster.MssqlCluster,
            'bind_address',
            new_callable=mock.PropertyMock).start()
        mocked_bind_address.return_value = self.TEST_BIND_ADDRESS
        self.addCleanup(mock.patch.stopall)

    def test_on_joined(self):
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.state.initialized_nodes = {
            self.TEST_NODE_NAME: {
                'address': self.TEST_BIND_ADDRESS,
                'ready_to_cluster': 'true',
            }
        }
        rel_id = self.harness.add_relation('cluster', 'mssql')
        self.harness.add_relation_unit(rel_id, 'mssql/1')

        rel_data = self.harness.get_relation_data(rel_id, 'mssql/0')
        self.assertEqual(rel_data.get('node_name'), self.TEST_NODE_NAME)
        self.assertEqual(rel_data.get('node_address'), self.TEST_BIND_ADDRESS)
        self.assertEqual(rel_data.get('ready_to_cluster'), 'true')
        self.assertIsNone(rel_data.get('clustered'))

    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'configure_cluster_node')
    @mock.patch.object(interface_mssql_cluster, 'append_hosts_entry')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'set_sa_password')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'master_cert',
                       new_callable=mock.PropertyMock)
    def test_on_changed(self, _master_cert, _set_sa_password,
                        _append_hosts_entry, _configure_cluster_node):
        _master_cert.return_value = self.TEST_MASTER_CERT
        self.harness.set_leader()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        rel_id = self.harness.add_relation('cluster', 'mssql')
        self.harness.add_relation_unit(rel_id, 'mssql/1')
        self.harness.update_relation_data(
            rel_id, 'mssql/1', {
                'node_name': self.TEST_NODE_NAME,
                'node_address': self.TEST_BIND_ADDRESS,
                'ready_to_cluster': 'true',
            })

        node_state = cluster.state.initialized_nodes.get(self.TEST_NODE_NAME)
        self.assertIsNotNone(node_state)
        self.assertEqual(node_state.get('address'), self.TEST_BIND_ADDRESS)
        self.assertTrue(node_state.get('ready_to_cluster'))
        self.assertIsNone(node_state.get('clustered'))
        _set_sa_password.assert_called_once_with()
        _append_hosts_entry.assert_called_once_with(self.TEST_BIND_ADDRESS,
                                                    [self.TEST_NODE_NAME])
        _configure_cluster_node.assert_called_once_with()

    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'configure_cluster_node')
    @mock.patch.object(interface_mssql_cluster, 'append_hosts_entry')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'set_master_cert')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'master_cert',
                       new_callable=mock.PropertyMock)
    def test_on_initialized_unit(self, _master_cert, _set_master_cert,
                                 _append_hosts_entry, _configure_cluster_node):
        _master_cert.return_value = None
        self.harness.set_leader()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        rel_id = self.harness.add_relation('cluster', 'mssql')
        self.harness.add_relation_unit(rel_id, 'mssql/1')
        cluster.on.initialized_unit.emit()

        node_state = cluster.state.initialized_nodes.get(self.TEST_NODE_NAME)
        self.assertIsNotNone(node_state)
        self.assertEqual(node_state.get('address'), self.TEST_BIND_ADDRESS)
        self.assertIsNone(node_state.get('ready_to_cluster'))
        self.assertIsNone(node_state.get('clustered'))

        rel_data = self.harness.get_relation_data(rel_id, 'mssql/0')
        self.assertEqual(rel_data.get('node_name'), self.TEST_NODE_NAME)
        self.assertEqual(rel_data.get('node_address'), self.TEST_BIND_ADDRESS)
        self.assertIsNone(rel_data.get('ready_to_cluster'))
        self.assertIsNone(rel_data.get('clustered'))

        _append_hosts_entry.assert_called_once_with(self.TEST_BIND_ADDRESS,
                                                    [self.TEST_NODE_NAME])
        _set_master_cert.assert_called_once_with()
        _configure_cluster_node.assert_not_called()

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'master_cert',
                       new_callable=mock.PropertyMock)
    def test_configure_master_cert(self, _master_cert, _mssql_db_client):
        _master_cert.return_value = self.TEST_MASTER_CERT
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.state.initialized_nodes = {
            self.TEST_NODE_NAME: {
                'address': self.TEST_BIND_ADDRESS,
                'ready_to_cluster': 'true',
            }
        }
        cluster.configure_master_cert()

        self.assertTrue(cluster.state.master_cert_configured)
        _mssql_db_client.assert_called_once_with()
        mock_ret_value = _mssql_db_client.return_value
        mock_ret_value.create_master_encryption_key.assert_called_once_with(
            'test_key_password')
        mock_ret_value.setup_master_cert.assert_called_once_with(
            'test_master_cert'.encode(), 'test_master_cert_key'.encode(),
            'test_cert_key_password')

    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'configure_secondary_replica')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'configure_primary_replica')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'is_primary_replica',
                       new_callable=mock.PropertyMock)
    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'configure_master_cert')
    def test_configure_cluster_node_primary_replica(
            self, _configure_master_cert, _mssql_db_client,
            _is_primary_replica, _configure_primary_replica,
            _configure_secondary_replica):

        _is_primary_replica.return_value = True
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.state.initialized_nodes[self.TEST_NODE_NAME] = {
            'address': self.TEST_BIND_ADDRESS
        }
        rel_id = self.harness.add_relation('cluster', 'mssql')
        self.harness.add_relation_unit(rel_id, 'mssql/1')
        cluster.configure_cluster_node()

        _configure_master_cert.assert_called_once_with()
        _mssql_db_client.assert_called_once_with()
        mock_ret_value = _mssql_db_client.return_value
        mock_ret_value.setup_db_mirroring_endpoint.assert_called_once_with()
        self.assertTrue(cluster.state.initialized_nodes[self.TEST_NODE_NAME]
                        ['ready_to_cluster'])
        rel_data = self.harness.get_relation_data(rel_id, 'mssql/0')
        self.assertEqual(rel_data.get('ready_to_cluster'), 'true')
        _configure_primary_replica.assert_called_once_with()
        _configure_secondary_replica.assert_not_called()

    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'configure_secondary_replica')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'configure_primary_replica')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'is_primary_replica',
                       new_callable=mock.PropertyMock)
    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'configure_master_cert')
    def test_configure_cluster_node_secondary_replica(
            self, _configure_master_cert, _mssql_db_client,
            _is_primary_replica, _configure_primary_replica,
            _configure_secondary_replica):

        _is_primary_replica.return_value = False
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.state.initialized_nodes[self.TEST_NODE_NAME] = {
            'address': self.TEST_BIND_ADDRESS
        }
        rel_id = self.harness.add_relation('cluster', 'mssql')
        self.harness.add_relation_unit(rel_id, 'mssql/1')
        cluster.configure_cluster_node()

        _configure_master_cert.assert_called_once_with()
        _mssql_db_client.assert_called_once_with()
        mock_ret_value = _mssql_db_client.return_value
        mock_ret_value.setup_db_mirroring_endpoint.assert_called_once_with()
        self.assertTrue(cluster.state.initialized_nodes[self.TEST_NODE_NAME]
                        ['ready_to_cluster'])
        rel_data = self.harness.get_relation_data(rel_id, 'mssql/0')
        self.assertEqual(rel_data.get('ready_to_cluster'), 'true')
        _configure_primary_replica.assert_not_called()
        _configure_secondary_replica.assert_called_once_with()

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'create_ag')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'is_ag_ready',
                       new_callable=mock.PropertyMock)
    def test_configure_primary_replica_ag_not_ready(self, _is_ag_ready,
                                                    _create_ag):
        _is_ag_ready.return_value = False
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.configure_primary_replica()
        _create_ag.assert_called_once_with()

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'create_ag')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'ag_replicas',
                       new_callable=mock.PropertyMock)
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'ready_nodes',
                       new_callable=mock.PropertyMock)
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'is_ag_ready',
                       new_callable=mock.PropertyMock)
    def test_configure_primary_replica_ag_ready(self, _is_ag_ready,
                                                _ready_nodes, _ag_replicas,
                                                _create_ag, _mssql_db_client):

        _is_ag_ready.return_value = True
        _ready_nodes.return_value = {
            'test-node-1': {
                'address': '10.0.0.11'
            },
            'test-node-2': {
                'address': '10.0.0.12'
            },
            'test-node-3': {
                'address': '10.0.0.13'
            }
        }
        _ag_replicas.return_value = ['test-node-1', 'test-node-2']
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        rel_id = self.harness.add_relation('cluster', 'mssql')
        self.harness.add_relation_unit(rel_id, 'mssql/1')
        cluster.configure_primary_replica()

        _create_ag.assert_not_called()
        _mssql_db_client.assert_called_once_with()
        mock_ret_value = _mssql_db_client.return_value
        mock_ret_value.add_replicas.assert_called_once_with(
            cluster.AG_NAME, {'test-node-3': {
                'address': '10.0.0.13'
            }})
        rel_data = self.harness.get_relation_data(rel_id, 'mssql/0')
        self.assertIsNotNone(rel_data.get('nonce'))

    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'sync_logins_from_primary_replica')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'join_existing_ag')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'is_ag_ready',
                       new_callable=mock.PropertyMock)
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'ag_primary_replica',
                       new_callable=mock.PropertyMock)
    def test_configure_secondary_replica(self, _ag_primary_replica,
                                         _is_ag_ready, _mssql_db_client,
                                         _join_existing_ag,
                                         _sync_logins_from_primary_replica):

        _is_ag_ready.return_value = True
        _ag_primary_replica.return_value = self.TEST_PRIMARY_REPLICA_NAME
        _mssql_db_client.return_value.get_ag_replicas.return_value = \
            [self.TEST_PRIMARY_REPLICA_NAME, self.TEST_NODE_NAME]
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.configure_secondary_replica()

        _mssql_db_client.assert_called_once_with(
            self.TEST_PRIMARY_REPLICA_NAME)
        mock_ret_value = _mssql_db_client.return_value
        mock_ret_value.get_ag_replicas.assert_called_once_with(cluster.AG_NAME)
        _join_existing_ag.assert_called_once_with()
        _sync_logins_from_primary_replica.assert_called_once_with()

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'ag_primary_replica',
                       new_callable=mock.PropertyMock)
    def test_sync_logins_from_primary_replica(self, _ag_primary_replica,
                                              _mssql_db_client):
        _ag_primary_replica.return_value = self.TEST_PRIMARY_REPLICA_NAME
        mocked_primary_db_client = mock.MagicMock()
        mocked_primary_db_client.get_sql_logins.return_value = \
            self.TEST_PRIMARY_LOGINS
        mocked_this_db_client = mock.MagicMock()
        mocked_this_db_client.get_sql_logins.return_value = \
            self.TEST_SECONDARY_LOGINS
        _mssql_db_client.side_effect = [
            mocked_primary_db_client, mocked_this_db_client
        ]
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.sync_logins_from_primary_replica()

        _mssql_db_client.assert_has_calls(
            [mock.call(self.TEST_PRIMARY_REPLICA_NAME),
             mock.call()])
        mocked_this_db_client.assert_has_calls([mock.call.get_sql_logins()])
        mocked_this_db_client.assert_has_calls([
            mock.call.get_sql_logins(),
            mock.call.create_login(name='test-login-3',
                                   sid='sid3',
                                   password='******',
                                   is_hashed_password=True,
                                   server_roles=['test-role3']),
            mock.call.create_login(name='test-login-4',
                                   sid='sid4',
                                   password='******',
                                   is_hashed_password=True,
                                   server_roles=['test-role4'])
        ])

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'ready_nodes',
                       new_callable=mock.PropertyMock)
    def test_create_ag(self, _ready_nodes, _mssql_db_client):
        _ready_nodes.return_value = ['node1', 'node2', 'node3']
        self.harness.set_leader()
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        rel_id = self.harness.add_relation('cluster', 'mssql')
        self.harness.add_relation_unit(rel_id, 'mssql/1')
        cluster.create_ag()

        self.assertTrue(cluster.state.ag_configured)
        self.assertEqual(self.harness.charm.unit.status,
                         cluster.UNIT_ACTIVE_STATUS)
        _mssql_db_client.assert_called_once_with()
        _mssql_db_client.return_value.create_ag.assert_called_once_with(
            cluster.AG_NAME, ['node1', 'node2', 'node3'])
        unit_rel_data = self.harness.get_relation_data(rel_id, 'mssql/0')
        self.assertEqual(unit_rel_data.get('clustered'), 'true')
        self.assertIsNotNone(unit_rel_data.get('nonce'))
        app_rel_data = self.harness.get_relation_data(rel_id, 'mssql')
        self.assertEqual(app_rel_data.get('ag_ready'), 'true')

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    def test_join_existing_ag(self, _mssql_db_client):
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        rel_id = self.harness.add_relation('cluster', 'mssql')
        self.harness.add_relation_unit(rel_id, 'mssql/1')
        cluster.join_existing_ag()

        self.assertTrue(cluster.state.ag_configured)
        self.assertEqual(self.harness.charm.unit.status,
                         cluster.UNIT_ACTIVE_STATUS)
        _mssql_db_client.assert_called_once_with()
        _mssql_db_client.return_value.join_ag.assert_called_once_with(
            cluster.AG_NAME)
        unit_rel_data = self.harness.get_relation_data(rel_id, 'mssql/0')
        self.assertEqual(unit_rel_data.get('clustered'), 'true')

    @mock.patch.object(interface_mssql_cluster, 'append_hosts_entry')
    def test_add_to_initialized_nodes(self, _append_hosts_entry):
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.add_to_initialized_nodes(self.TEST_NODE_NAME,
                                         self.TEST_BIND_ADDRESS,
                                         ready_to_cluster=True,
                                         clustered=True)

        node_state = cluster.state.initialized_nodes.get(self.TEST_NODE_NAME)
        self.assertIsNotNone(node_state)
        self.assertEqual(node_state.get('address'), self.TEST_BIND_ADDRESS)
        self.assertTrue(node_state.get('ready_to_cluster'))
        self.assertTrue(node_state.get('clustered'))

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch('charmhelpers.core.host.pwgen')
    def test_set_master_cert(self, _pwgen, _mssql_db_client):
        _pwgen.side_effect = [
            'test-master-key-password', 'test-master-cert-key-password'
        ]
        _mssql_db_client.return_value.create_master_cert.return_value = \
            ('test-cert'.encode(), 'test-cert-key'.encode())
        self.harness.set_leader()
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        rel_id = self.harness.add_relation('cluster', 'mssql')
        self.harness.add_relation_unit(rel_id, 'mssql/1')
        cluster.set_master_cert()

        self.assertTrue(cluster.state.master_cert_configured)
        _pwgen.assert_has_calls([mock.call(32), mock.call(32)])
        _mssql_db_client.assert_called_once_with()
        mock_ret_value = _mssql_db_client.return_value
        mock_ret_value.create_master_encryption_key.assert_called_once_with(
            'test-master-key-password')
        mock_ret_value.create_master_cert.assert_called_once_with(
            'test-master-cert-key-password')
        app_rel_data = self.harness.get_relation_data(rel_id, 'mssql')
        self.assertEqual(app_rel_data.get('master_key_password'),
                         'test-master-key-password')
        self.assertEqual(app_rel_data.get('master_cert'),
                         b64encode('test-cert'.encode()).decode())
        self.assertEqual(app_rel_data.get('master_cert_key'),
                         b64encode('test-cert-key'.encode()).decode())
        self.assertEqual(app_rel_data.get('master_cert_key_password'),
                         'test-master-cert-key-password')

    @mock.patch.object(interface_mssql_cluster.secrets, 'choice')
    def test_set_sa_password(self, _choice):
        _choice.return_value = 'p'
        self.harness.set_leader()
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        rel_id = self.harness.add_relation('cluster', 'mssql')
        self.harness.add_relation_unit(rel_id, 'mssql/1')
        cluster.set_sa_password()

        _choice_calls = []
        _choice_calls += [mock.call(string.ascii_lowercase)] * 8
        _choice_calls += [mock.call(string.ascii_uppercase)] * 8
        _choice_calls += [mock.call(string.digits)] * 8
        _choice_calls += [mock.call(string.punctuation)] * 8
        _choice.assert_has_calls(_choice_calls)
        app_rel_data = self.harness.get_relation_data(rel_id, 'mssql')
        self.assertEqual(app_rel_data.get('sa_password'), 'p' * 32)

    def test_clustered_nodes(self):
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.state.initialized_nodes['node-1'] = {
            'address': '10.0.0.11',
            'clustered': True
        }
        cluster.state.initialized_nodes['node-2'] = {
            'address': '10.0.0.12',
            'clustered': True
        }
        cluster.state.initialized_nodes['node-3'] = {'address': '10.0.0.13'}
        clustered_nodes = cluster.clustered_nodes

        self.assertEqual(
            clustered_nodes, {
                'node-1': {
                    'address': '10.0.0.11',
                    'clustered': True
                },
                'node-2': {
                    'address': '10.0.0.12',
                    'clustered': True
                }
            })

    def test_ready_nodes(self):
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.state.initialized_nodes['node-1'] = {
            'address': '10.0.0.11',
            'ready_to_cluster': True
        }
        cluster.state.initialized_nodes['node-2'] = {
            'address': '10.0.0.12',
            'ready_to_cluster': True,
            'clustered': True
        }
        cluster.state.initialized_nodes['node-3'] = {'address': '10.0.0.13'}
        ready_nodes = cluster.ready_nodes

        self.assertEqual(
            ready_nodes, {
                'node-1': {
                    'address': '10.0.0.11',
                    'ready_to_cluster': True
                },
                'node-2': {
                    'address': '10.0.0.12',
                    'ready_to_cluster': True,
                    'clustered': True
                }
            })

    def test_master_cert(self):
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        rel_id = self.harness.add_relation('cluster', 'mssql')
        self.harness.add_relation_unit(rel_id, 'mssql/1')
        self.harness.update_relation_data(
            rel_id, 'mssql', {
                'master_key_password': '******',
                'master_cert': 'test-cert',
                'master_cert_key': 'test-cert-key',
                'master_cert_key_password': '******',
            })
        master_cert = cluster.master_cert

        self.assertEqual(
            master_cert, {
                'master_key_password': '******',
                'master_cert': 'test-cert',
                'master_cert_key': 'test-cert-key',
                'master_cert_key_password': '******',
            })

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'is_ag_ready',
                       new_callable=mock.PropertyMock)
    def test_ag_primary_replica_ag_configured(self, _is_ag_ready,
                                              _mssql_db_client):

        _is_ag_ready.return_value = True
        _mssql_db_client.return_value.get_ag_primary_replica.return_value = \
            self.TEST_PRIMARY_REPLICA_NAME
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.state.ag_configured = True
        primary_replica = cluster.ag_primary_replica

        _mssql_db_client.assert_called_once_with()
        mock_ret_value = _mssql_db_client.return_value
        mock_ret_value.get_ag_primary_replica.assert_called_once_with(
            cluster.AG_NAME)
        self.assertEqual(primary_replica, self.TEST_PRIMARY_REPLICA_NAME)

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'is_ag_ready',
                       new_callable=mock.PropertyMock)
    def test_ag_primary_replica_no_clustered_nodes(self, _is_ag_ready,
                                                   _mssql_db_client):
        _is_ag_ready.return_value = True
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.state.ag_configured = False
        primary_replica = cluster.ag_primary_replica

        _mssql_db_client.assert_not_called()
        self.assertIsNone(primary_replica)

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'clustered_nodes',
                       new_callable=mock.PropertyMock)
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'is_ag_ready',
                       new_callable=mock.PropertyMock)
    def test_ag_primary_replica_other_clustered_node(self, _is_ag_ready,
                                                     _clustered_nodes,
                                                     _mssql_db_client):

        _is_ag_ready.return_value = True
        _mssql_db_client.return_value.get_ag_primary_replica.return_value = \
            self.TEST_PRIMARY_REPLICA_NAME
        _clustered_nodes.return_value = {
            'node-1': {
                'address': '10.0.0.11',
                'clustered': True
            },
            'node-2': {
                'address': '10.0.0.12',
                'clustered': True
            }
        }
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.state.ag_configured = False
        primary_replica = cluster.ag_primary_replica

        _mssql_db_client.assert_called_once_with('node-2')
        mock_ret_value = _mssql_db_client.return_value
        mock_ret_value.get_ag_primary_replica.assert_called_once_with(
            cluster.AG_NAME)
        self.assertEqual(primary_replica, self.TEST_PRIMARY_REPLICA_NAME)

    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'ag_primary_replica',
                       new_callable=mock.PropertyMock)
    def test_is_primary_replica_current_node(self, _ag_primary_replica):
        _ag_primary_replica.return_value = self.TEST_NODE_NAME
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')

        self.assertTrue(cluster.is_primary_replica)

    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'ag_primary_replica',
                       new_callable=mock.PropertyMock)
    def test_is_primary_replica_other_node(self, _ag_primary_replica):
        _ag_primary_replica.return_value = 'mssql-primary-replica'
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')

        self.assertFalse(cluster.is_primary_replica)

    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'ag_primary_replica',
                       new_callable=mock.PropertyMock)
    def test_is_primary_replica_leader_node(self, _ag_primary_replica):
        _ag_primary_replica.return_value = None
        self.harness.set_leader()
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')

        self.assertTrue(cluster.is_primary_replica)

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'is_ag_ready',
                       new_callable=mock.PropertyMock)
    def test_ag_replicas_ag_configured(self, _is_ag_ready, _mssql_db_client):
        _is_ag_ready.return_value = True
        _mssql_db_client.return_value.get_ag_replicas.return_value = \
            ['node-1', 'node-2', 'node-3']
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.state.ag_configured = True
        ag_replicas = cluster.ag_replicas

        _mssql_db_client.assert_called_once_with()
        mock_ret_value = _mssql_db_client.return_value
        mock_ret_value.get_ag_replicas.assert_called_once_with(cluster.AG_NAME)
        self.assertListEqual(ag_replicas, ['node-1', 'node-2', 'node-3'])

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'is_ag_ready',
                       new_callable=mock.PropertyMock)
    def test_ag_replicas_no_clustered_nodes(self, _is_ag_ready,
                                            _mssql_db_client):
        _is_ag_ready.return_value = True
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        ag_replicas = cluster.ag_replicas

        _mssql_db_client.assert_not_called()
        self.assertListEqual(ag_replicas, [])

    @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client')
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'clustered_nodes',
                       new_callable=mock.PropertyMock)
    @mock.patch.object(interface_mssql_cluster.MssqlCluster,
                       'is_ag_ready',
                       new_callable=mock.PropertyMock)
    def test_ag_replicas_other_clustered_node(self, _is_ag_ready,
                                              _clustered_nodes,
                                              _mssql_db_client):

        _is_ag_ready.return_value = True
        _mssql_db_client.return_value.get_ag_replicas.return_value = \
            ['node-1', 'node-2']
        _clustered_nodes.return_value = {
            'node-1': {
                'address': '10.0.0.11',
                'clustered': True
            },
            'node-2': {
                'address': '10.0.0.12',
                'clustered': True
            }
        }
        self.harness.disable_hooks()
        self.harness.begin()
        cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm,
                                                       'cluster')
        cluster.state.ag_configured = False
        ag_replicas = cluster.ag_replicas

        _mssql_db_client.assert_called_once_with('node-2')
        mock_ret_value = _mssql_db_client.return_value
        mock_ret_value.get_ag_replicas.assert_called_once_with(cluster.AG_NAME)
        self.assertListEqual(ag_replicas, ['node-1', 'node-2'])