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)
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}
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])
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, [])
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}
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 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)
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(), [])
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)
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)
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, }
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")
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'}, {}])
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, } }])
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'))
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))
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", )
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")
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'})
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()
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'})
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)
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' }])
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)
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) == []
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() )
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)
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')
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'])