class HAServiceRequires(unittest.TestCase): class MyCharm(CharmBase): def __init__(self, *args): super().__init__(*args) self.seen_events = [] self.ha = ops_ha_interface.HAServiceRequires(self, 'ha') self.framework.observe( self.ha.on.ha_ready, self._log_event) def _log_event(self, event): self.seen_events.append(type(event).__name__) def setUp(self): super().setUp() self.harness = Harness( self.MyCharm, meta=''' name: my-charm requires: ha: interface: hacluster scope: container ''' ) def test_local_vars(self): self.harness.begin() self.harness.charm.ha.set_local('a', 'b') self.assertEqual( self.harness.charm.ha.get_local('a'), 'b') self.harness.charm.ha.set_local(**{'c': 'd', 'e': 'f'}) self.assertEqual( self.harness.charm.ha.get_local('c'), 'd') self.assertEqual( self.harness.charm.ha.get_local('e'), 'f') self.harness.charm.ha.set_local(data={'g': 'h', 'i': 'j'}) self.assertEqual( self.harness.charm.ha.get_local('g'), 'h') self.assertEqual( self.harness.charm.ha.get_local('i'), 'j') def test_remote_vars(self): self.harness.begin() rel_id = self.harness.add_relation( 'ha', 'hacluster') self.harness.add_relation_unit( rel_id, 'hacluster/0') self.harness.charm.ha.set_remote('a', 'b') rel_data = self.harness.get_relation_data( rel_id, 'my-charm/0') self.assertEqual(rel_data, {'a': 'b'}) def test_get_remote_all(self): self.harness.begin() rel_id1 = self.harness.add_relation( 'ha', 'hacluster-a') self.harness.add_relation_unit( rel_id1, 'hacluster-a/0') self.harness.update_relation_data( rel_id1, 'hacluster-a/0', {'fruit': 'banana'}) self.harness.add_relation_unit( rel_id1, 'hacluster-a/1') self.harness.update_relation_data( rel_id1, 'hacluster-a/1', {'fruit': 'orange'}) rel_id2 = self.harness.add_relation( 'ha', 'hacluster-b') self.harness.add_relation_unit( rel_id2, 'hacluster-b/0') self.harness.update_relation_data( rel_id2, 'hacluster-b/0', {'fruit': 'grape'}) self.harness.add_relation_unit( rel_id2, 'hacluster-b/1') self.harness.update_relation_data( rel_id2, 'hacluster-b/1', {'veg': 'carrot'}) self.assertEqual( self.harness.charm.ha.get_remote_all('fruit'), ['orange', 'grape', 'banana']) def test_ha_ready(self): self.harness.begin() self.assertEqual( self.harness.charm.seen_events, []) rel_id = self.harness.add_relation( 'ha', 'hacluster') self.harness.add_relation_unit( rel_id, 'hacluster/0') self.harness.update_relation_data( rel_id, 'hacluster/0', {'clustered': 'yes'}) self.assertEqual( self.harness.charm.seen_events, ['HAServiceReadyEvent']) def test_data_changed(self): self.harness.begin() self.assertTrue( self.harness.charm.ha.data_changed( 'relation-data', {'a': 'b'})) self.assertFalse( self.harness.charm.ha.data_changed( 'relation-data', {'a': 'b'})) self.assertTrue( self.harness.charm.ha.data_changed( 'relation-data', {'a': 'c'}))
class TestCharm(unittest.TestCase): def setUp(self): self.harness = Harness(HelloKubeconCharm) self.addCleanup(self.harness.cleanup) self.harness.begin() def test_gosherve_layer(self): # Test with empty config. self.assertEqual(self.harness.charm.config["redirect-map"], "https://jnsgr.uk/demo-routes") expected = { "summary": "gosherve layer", "description": "pebble config layer for gosherve", "services": { "gosherve": { "override": "replace", "summary": "gosherve", "command": "/gosherve", "startup": "enabled", "environment": { "REDIRECT_MAP_URL": "https://jnsgr.uk/demo-routes", "WEBROOT": "/srv", }, } }, } self.assertEqual(self.harness.charm._gosherve_layer(), expected) # And now test with a different value in the redirect-map config option. # Disable hook firing first. self.harness.disable_hooks() self.harness.update_config({"redirect-map": "test value"}) expected["services"]["gosherve"]["environment"]["REDIRECT_MAP_URL"] = "test value" self.assertEqual(self.harness.charm._gosherve_layer(), expected) def test_on_config_changed(self): plan = self.harness.get_container_pebble_plan("gosherve") self.assertEqual(plan.to_dict(), {}) # Trigger a config-changed hook. Since there was no plan initially, the # "gosherve" service in the container won't be running so we'll be # testing the `is_running() == False` codepath. self.harness.update_config({"redirect-map": "test value"}) plan = self.harness.get_container_pebble_plan("gosherve") # Get the expected layer from the gosherve_layer method (tested above) expected = self.harness.charm._gosherve_layer() expected.pop("summary", "") expected.pop("description", "") # Check the plan is as expected self.assertEqual(plan.to_dict(), expected) self.assertEqual(self.harness.model.unit.status, ActiveStatus()) container = self.harness.model.unit.get_container("gosherve") self.assertEqual(container.get_service("gosherve").is_running(), True) # Now test again with different config, knowing that the "gosherve" # service is running (because we've just tested it above), so we'll # be testing the `is_running() == True` codepath. self.harness.update_config({"redirect-map": "test2 value"}) plan = self.harness.get_container_pebble_plan("gosherve") # Adjust the expected plan expected["services"]["gosherve"]["environment"]["REDIRECT_MAP_URL"] = "test2 value" self.assertEqual(plan.to_dict(), expected) self.assertEqual(container.get_service("gosherve").is_running(), True) self.assertEqual(self.harness.model.unit.status, ActiveStatus()) # And finally test again with the same config to ensure we exercise # the case where the plan we've created matches the active one. We're # going to mock the container.stop and container.start calls to confirm # they were not called. with patch('ops.model.Container.start') as _start, patch('ops.model.Container.stop') as _stop: self.harness.charm.on.config_changed.emit() _start.assert_not_called() _stop.assert_not_called() @patch("charm.HelloKubeconCharm._fetch_site") def test_on_install(self, _fetch_site): self.harness.charm._on_install("mock_event") _fetch_site.assert_called_once @patch("charm.HelloKubeconCharm._fetch_site") def test_pull_site_action(self, _fetch_site): mock_event = Mock() self.harness.charm._pull_site_action(mock_event) _fetch_site.assert_called_once mock_event.called_once_with({"result": "site pulled"})
def test__init__works_without_a_hitch(self): # Setup harness = Harness(charm.Charm) # Exercise harness.begin()
class GrafanaCharmTest(unittest.TestCase): def setUp(self) -> None: self.harness = Harness(GrafanaK8s) self.addCleanup(self.harness.cleanup) self.harness.begin() def test__grafana_source_data(self): self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) self.assertEqual(self.harness.charm.datastore.sources, {}) rel_id = self.harness.add_relation('grafana-source', 'prometheus') self.harness.add_relation_unit(rel_id, 'prometheus/0') self.assertIsInstance(rel_id, int) # test that the unit data propagates the correct way # which is through the triggering of on_relation_changed self.harness.update_relation_data( rel_id, 'prometheus/0', { 'private-address': '192.0.2.1', 'port': 1234, 'source-type': 'prometheus', 'source-name': 'prometheus-app', }) expected_first_source_data = { 'private-address': '192.0.2.1', 'port': 1234, 'source-name': 'prometheus-app', 'source-type': 'prometheus', 'isDefault': 'true', 'unit_name': 'prometheus/0' } self.assertEqual(expected_first_source_data, dict(self.harness.charm.datastore.sources[rel_id])) # test that clearing the relation data leads to # the datastore for this data source being cleared self.harness.update_relation_data(rel_id, 'prometheus/0', { 'private-address': None, 'port': None, }) self.assertEqual(None, self.harness.charm.datastore.sources.get(rel_id)) def test__ha_database_and_status_check(self): """If there is a peer connection and no database (needed for HA), the charm should put the application in a blocked state.""" # start charm with one peer and no database relation self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) self.assertEqual(self.harness.charm.unit.status, APPLICATION_ACTIVE_STATUS) # ensure _check_high_availability() ends up with the correct status status = self.harness.charm._check_high_availability() self.assertEqual(status, SINGLE_NODE_STATUS) # make sure that triggering 'update-status' hook does not # overwrite the current active status self.harness.charm.on.update_status.emit() self.assertEqual(self.harness.charm.unit.status, APPLICATION_ACTIVE_STATUS) peer_rel_id = self.harness.add_relation('grafana', 'grafana') # add main unit and its data # self.harness.add_relation_unit(peer_rel_id, 'grafana/0') # will trigger the grafana-changed hook self.harness.update_relation_data(peer_rel_id, 'grafana/0', {'private-address': '10.1.2.3'}) # add peer unit and its data self.harness.add_relation_unit(peer_rel_id, 'grafana/1') self.harness.update_relation_data(peer_rel_id, 'grafana/1', {'private-address': '10.0.0.1'}) self.assertTrue(self.harness.charm.has_peer) self.assertFalse(self.harness.charm.has_db) self.assertEqual(self.harness.charm.unit.status, HA_NOT_READY_STATUS) # ensure update-status hook doesn't overwrite this self.harness.charm.on.update_status.emit() self.assertEqual(self.harness.charm.unit.status, HA_NOT_READY_STATUS) # now add the database connection and the model should # not have a blocked status db_rel_id = self.harness.add_relation('database', 'mysql') self.harness.add_relation_unit(db_rel_id, 'mysql/0') self.harness.update_relation_data( db_rel_id, 'mysql/0', { 'type': 'mysql', 'host': '10.10.10.10:3306', 'name': 'test_mysql_db', 'user': '******', 'password': '******', }) self.assertTrue(self.harness.charm.has_db) self.assertEqual(self.harness.charm.unit.status, APPLICATION_ACTIVE_STATUS) # ensure _check_high_availability() ends up with the correct status status = self.harness.charm._check_high_availability() self.assertEqual(status, HA_READY_STATUS) def test__database_relation_data(self): self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) self.assertEqual(self.harness.charm.datastore.database, {}) # add relation and update relation data rel_id = self.harness.add_relation('database', 'mysql') rel = self.harness.model.get_relation('database') self.harness.add_relation_unit(rel_id, 'mysql/0') test_relation_data = { 'type': 'mysql', 'host': '0.1.2.3:3306', 'name': 'my-test-db', 'user': '******', 'password': '******', } self.harness.update_relation_data(rel_id, 'mysql/0', test_relation_data) # check that charm datastore was properly set self.assertEqual(dict(self.harness.charm.datastore.database), test_relation_data) # now depart this relation and ensure the datastore is emptied self.harness.charm.on.database_relation_departed.emit(rel) self.assertEqual({}, dict(self.harness.charm.datastore.database)) def test__multiple_database_relation_handling(self): self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) self.assertEqual(self.harness.charm.datastore.database, {}) # add first database relation self.harness.add_relation('database', 'mysql') # add second database relation -- should fail here with self.assertRaises(TooManyRelatedAppsError): self.harness.add_relation('database', 'mysql') self.harness.charm.model.get_relation('database') def test__multiple_source_relations(self): """This will test data-source config text with multiple sources. Specifically, it will test multiple grafana-source relations.""" self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) self.assertEqual(self.harness.charm.datastore.sources, {}) # add first relation rel_id0 = self.harness.add_relation('grafana-source', 'prometheus') self.harness.add_relation_unit(rel_id0, 'prometheus/0') # add test data to grafana-source relation # and test that _make_data_source_config_text() works as expected prom_source_data = { 'private-address': '192.0.2.1', 'port': 4321, 'source-type': 'prometheus' } self.harness.update_relation_data(rel_id0, 'prometheus/0', prom_source_data) header_text = textwrap.dedent(""" apiVersion: 1 datasources:""") correct_config_text0 = header_text + textwrap.dedent(""" - name: prometheus/0 type: prometheus access: proxy url: http://192.0.2.1:4321 isDefault: true editable: true orgId: 1 basicAuthUser: {0} secureJsonData: basicAuthPassword: {1}""").format( self.harness.model.config['basic_auth_password'], self.harness.model.config['basic_auth_username']) generated_text = self.harness.charm._make_data_source_config_text() self.assertEqual(correct_config_text0 + '\n', generated_text) # add another source relation and check the resulting config text jaeger_source_data = { 'private-address': '255.255.255.0', 'port': 7890, 'source-type': 'jaeger', 'source-name': 'jaeger-application' } rel_id1 = self.harness.add_relation('grafana-source', 'jaeger') self.harness.add_relation_unit(rel_id1, 'jaeger/0') self.harness.update_relation_data(rel_id1, 'jaeger/0', jaeger_source_data) correct_config_text1 = correct_config_text0 + textwrap.dedent(""" - name: jaeger-application type: jaeger access: proxy url: http://255.255.255.0:7890 isDefault: false editable: true orgId: 1 basicAuthUser: {0} secureJsonData: basicAuthPassword: {1}""").format( self.harness.model.config['basic_auth_password'], self.harness.model.config['basic_auth_username']) generated_text = self.harness.charm._make_data_source_config_text() self.assertEqual(correct_config_text1 + '\n', generated_text) # test removal of second source results in config_text # that is the same as the original # self.harness.charm.on.grafana_source_relation_departed.emit(rel) self.harness.update_relation_data(rel_id1, 'jaeger/0', { 'private-address': None, 'port': None, }) generated_text = self.harness.charm._make_data_source_config_text() correct_text_after_removal = textwrap.dedent(""" apiVersion: 1 deleteDatasources: - name: jaeger-application orgId: 1 datasources: - name: prometheus/0 type: prometheus access: proxy url: http://192.0.2.1:4321 isDefault: true editable: true orgId: 1 basicAuthUser: {0} secureJsonData: basicAuthPassword: {1}""").format( self.harness.model.config['basic_auth_password'], self.harness.model.config['basic_auth_username']) self.assertEqual(correct_text_after_removal + '\n', generated_text) # now test that the 'deleteDatasources' is gone generated_text = self.harness.charm._make_data_source_config_text() self.assertEqual(correct_config_text0 + '\n', generated_text) def test__check_config_missing_image_path(self): self.harness.update_config(MISSING_IMAGE_PASSWORD_CONFIG) # test the return value of _check_config missing = self.harness.charm._check_config() expected = ['grafana_image_password'] self.assertEqual(missing, expected) def test__check_config_missing_password(self): self.harness.update_config(MISSING_IMAGE_CONFIG) # test the return value of _check_config missing = self.harness.charm._check_config() expected = ['grafana_image_path'] self.assertEqual(missing, expected) def test__pod_spec_container_datasources(self): self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) self.assertEqual(self.harness.charm.datastore.sources, {}) # add first relation rel_id = self.harness.add_relation('grafana-source', 'prometheus') self.harness.add_relation_unit(rel_id, 'prometheus/0') # add test data to grafana-source relation # and test that _make_data_source_config_text() works as expected prom_source_data = { 'private-address': '192.0.2.1', 'port': 4321, 'source-type': 'prometheus' } self.harness.update_relation_data(rel_id, 'prometheus/0', prom_source_data) data_source_file_text = textwrap.dedent(""" apiVersion: 1 datasources: - name: prometheus/0 type: prometheus access: proxy url: http://192.0.2.1:4321 isDefault: true editable: true orgId: 1 basicAuthUser: {0} secureJsonData: basicAuthPassword: {1} """).format(self.harness.model.config['basic_auth_password'], self.harness.model.config['basic_auth_username']) config_ini_file_text = textwrap.dedent(""" [paths] provisioning = {0} [security] admin_user = {1} admin_password = {2} [log] mode = {3} level = {4} """).format( self.harness.model.config['provisioning_path'], self.harness.model.config['basic_auth_username'], self.harness.model.config['basic_auth_password'], self.harness.model.config['grafana_log_mode'], self.harness.model.config['grafana_log_level'], ) expected_container_files_spec = [{ 'name': 'grafana-datasources', 'mountPath': self.harness.model.config['datasource_mount_path'], 'files': { 'datasources.yaml': data_source_file_text, }, }, { 'name': 'grafana-config-ini', 'mountPath': self.harness.model.config['config_ini_mount_path'], 'files': { 'grafana.ini': config_ini_file_text } }] pod_spec = self.harness.get_pod_spec()[0] container = get_container(pod_spec, 'grafana') actual_container_files_spec = container['files'] self.assertEqual(expected_container_files_spec, actual_container_files_spec) def test__access_sqlite_storage_location(self): expected_path = '/var/lib/grafana' actual_path = self.harness.charm.meta.storages['sqlitedb'].location self.assertEqual(expected_path, actual_path) def test__config_ini_without_database(self): self.harness.update_config(BASE_CONFIG) expected_config_text = textwrap.dedent(""" [paths] provisioning = {0} [security] admin_user = {1} admin_password = {2} [log] mode = {3} level = {4} """).format( self.harness.model.config['provisioning_path'], self.harness.model.config['basic_auth_username'], self.harness.model.config['basic_auth_password'], self.harness.model.config['grafana_log_mode'], self.harness.model.config['grafana_log_level'], ) actual_config_text = self.harness.charm._make_config_ini_text() self.assertEqual(expected_config_text, actual_config_text) def test__config_ini_with_database(self): self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) # add database relation and update relation data rel_id = self.harness.add_relation('database', 'mysql') # rel = self.harness.charm.model.get_relation('database') self.harness.add_relation_unit(rel_id, 'mysql/0') test_relation_data = { 'type': 'mysql', 'host': '0.1.2.3:3306', 'name': 'my-test-db', 'user': '******', 'password': '******', } self.harness.update_relation_data(rel_id, 'mysql/0', test_relation_data) # test the results of _make_config_ini_text() expected_config_text = textwrap.dedent( """ [paths] provisioning = {0} [security] admin_user = {1} admin_password = {2} [log] mode = {3} level = {4} [database] type = mysql host = 0.1.2.3:3306 name = my-test-db user = test-user password = super!secret!password url = mysql://test-user:[email protected]:3306/my-test-db""" ).format( self.harness.model.config['provisioning_path'], self.harness.model.config['basic_auth_username'], self.harness.model.config['basic_auth_password'], self.harness.model.config['grafana_log_mode'], self.harness.model.config['grafana_log_level'], ) actual_config_text = self.harness.charm._make_config_ini_text() self.assertEqual(expected_config_text, actual_config_text) def test__duplicate_source_names(self): self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) self.assertEqual(self.harness.charm.datastore.sources, {}) # add first relation p_rel_id = self.harness.add_relation('grafana-source', 'prometheus') p_rel = self.harness.model.get_relation('grafana-source', p_rel_id) self.harness.add_relation_unit(p_rel_id, 'prometheus/0') # add test data to grafana-source relation prom_source_data0 = { 'private-address': '192.0.2.1', 'port': 4321, 'source-type': 'prometheus', 'source-name': 'duplicate-source-name' } self.harness.update_relation_data(p_rel_id, 'prometheus/0', prom_source_data0) expected_source_data = { 'private-address': '192.0.2.1', 'port': 4321, 'source-name': 'duplicate-source-name', 'source-type': 'prometheus', 'isDefault': 'true', 'unit_name': 'prometheus/0' } self.assertEqual(dict(self.harness.charm.datastore.sources[p_rel_id]), expected_source_data) # add second source with the same name as the first source g_rel_id = self.harness.add_relation('grafana-source', 'graphite') self.harness.add_relation_unit(g_rel_id, 'graphite/0') graphite_source_data0 = { 'private-address': '192.12.23.34', 'port': 4321, 'source-type': 'graphite', 'source-name': 'duplicate-source-name' } self.harness.update_relation_data(g_rel_id, 'graphite/0', graphite_source_data0) self.assertEqual(None, self.harness.charm.datastore.sources.get(g_rel_id)) self.assertEqual(1, len(self.harness.charm.datastore.sources)) # now remove the relation and ensure datastore source-name is removed self.harness.charm.on.grafana_source_relation_departed.emit(p_rel) self.assertEqual(None, self.harness.charm.datastore.sources.get(p_rel_id)) self.assertEqual(0, len(self.harness.charm.datastore.sources)) def test__idempotent_datasource_file_hash(self): self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) rel_id = self.harness.add_relation('grafana-source', 'prometheus') self.harness.add_relation_unit(rel_id, 'prometheus/0') self.assertIsInstance(rel_id, int) # test that the unit data propagates the correct way # which is through the triggering of on_relation_changed self.harness.update_relation_data( rel_id, 'prometheus/0', { 'private-address': '192.0.2.1', 'port': 1234, 'source-type': 'prometheus', 'source-name': 'prometheus-app', }) # get a hash of the created file and check that it matches the pod spec container = get_container(self.harness.get_pod_spec()[0], 'grafana') hash_text = hashlib.md5(container['files'][0]['files'] ['datasources.yaml'].encode()).hexdigest() self.assertEqual(container['config']['DATASOURCES_YAML'], hash_text) # test the idempotence of the call by re-configuring the pod spec self.harness.charm.configure_pod() self.assertEqual(container['config']['DATASOURCES_YAML'], hash_text)
class TestTransform(unittest.TestCase): """Test that the promql-transform implementation works.""" def setUp(self): self.harness = Harness(TransformProviderCharm, meta=META) self.harness.set_model_name("transform") self.addCleanup(self.harness.cleanup) self.harness.add_resource("promql-transform-amd64", "dummy resource") self.harness.begin() # pylint: disable=protected-access @unittest.mock.patch("platform.processor", lambda: "teakettle") def test_disable_on_invalid_arch(self): transform = self.harness.charm.transformer self.assertIsNone(transform.path) self.assertTrue(transform._disabled) # pylint: disable=protected-access @unittest.mock.patch("platform.processor", lambda: "x86_64") def test_gives_path_on_valid_arch(self): """When given a valid arch, it should return the resource path.""" transformer = self.harness.charm.transformer self.assertIsInstance(transformer.path, PosixPath) @unittest.mock.patch("platform.processor", lambda: "x86_64") def test_setup_transformer(self): """When setup it should know the path to the binary.""" transform = self.harness.charm.transformer self.assertIsInstance(transform.path, PosixPath) p = str(transform.path) self.assertTrue( p.startswith("/") and p.endswith("promql-transform-amd64")) @unittest.mock.patch("platform.processor", lambda: "x86_64") @unittest.mock.patch("subprocess.run") def test_returns_original_expression_when_subprocess_call_errors( self, mocked_run): mocked_run.side_effect = subprocess.CalledProcessError( returncode=10, cmd="promql-transform", stderr="") transform = self.harness.charm.transformer output = transform.apply_label_matchers({ "groups": [{ "alert": "CPUOverUse", "expr": "process_cpu_seconds_total > 0.12", "for": "0m", "labels": { "severity": "Low", "juju_model": "None", "juju_model_uuid": "f2c1b2a6-e006-11eb-ba80-0242ac130004", "juju_application": "consumer-tester", }, "annotations": { "summary": "Instance {{ $labels.instance }} CPU over use", "description": "{{ $labels.instance }} of job " "{{ $labels.job }} has used too much CPU.", }, }] }) self.assertEqual(output["groups"][0]["expr"], "process_cpu_seconds_total > 0.12") @unittest.mock.patch("platform.processor", lambda: "invalid") def test_uses_original_expression_when_resource_missing(self): transform = self.harness.charm.transformer output = transform.apply_label_matchers({ "groups": [{ "alert": "CPUOverUse", "expr": "process_cpu_seconds_total > 0.12", "for": "0m", "labels": { "severity": "Low", "juju_model": "None", "juju_model_uuid": "f2c1b2a6-e006-11eb-ba80-0242ac130004", "juju_application": "consumer-tester", }, "annotations": { "summary": "Instance {{ $labels.instance }} CPU over use", "description": "{{ $labels.instance }} of job " "{{ $labels.job }} has used too much CPU.", }, }] }) self.assertEqual(output["groups"][0]["expr"], "process_cpu_seconds_total > 0.12") @unittest.mock.patch("platform.processor", lambda: "x86_64") def test_fetches_the_correct_expression(self): self.harness.add_resource( "promql-transform-amd64", open("./promql-transform", "rb").read(), ) transform = self.harness.charm.transformer output = transform._apply_label_matcher( "up", {"juju_model": "some_juju_model"}) assert output == 'up{juju_model="some_juju_model"}' @unittest.mock.patch("platform.processor", lambda: "x86_64") def test_handles_comparisons(self): self.harness.add_resource( "promql-transform-amd64", open("./promql-transform", "rb").read(), ) transform = self.harness.charm.transformer output = transform._apply_label_matcher( "up > 1", {"juju_model": "some_juju_model"}) assert output == 'up{juju_model="some_juju_model"} > 1' @unittest.mock.patch("platform.processor", lambda: "x86_64") def test_handles_multiple_labels(self): self.harness.add_resource( "promql-transform-amd64", open("./promql-transform", "rb").read(), ) transform = self.harness.charm.transformer output = transform._apply_label_matcher( "up > 1", { "juju_model": "some_juju_model", "juju_model_uuid": "123ABC", "juju_application": "some_application", "juju_unit": "some_application/1", }, ) assert ( output == 'up{juju_application="some_application",juju_model="some_juju_model"' ',juju_model_uuid="123ABC",juju_unit="some_application/1"} > 1')
class TestCharm(unittest.TestCase): def setUp(self): self.harness = Harness(PrometheusCharm) self.addCleanup(self.harness.cleanup) self.harness.begin() def test_image_path_is_required(self): missing_image_config = { 'prometheus-image-path': '', 'prometheus-image-username': '', 'prometheus-image-password': '' } with self.assertLogs(level='ERROR') as logger: self.harness.update_config(missing_image_config) expected_logs = [ "ERROR:charm:Incomplete Configuration : ['prometheus-image-path']. " "Application will be blocked." ] self.assertEqual(sorted(logger.output), expected_logs) missing = self.harness.charm._check_config() expected = ['prometheus-image-path'] self.assertEqual(missing, expected) def test_password_is_required_when_username_is_set(self): missing_password_config = { 'prometheus-image-path': 'prom/prometheus:latest', 'prometheus-image-username': '******', 'prometheus-image-password': '', } with self.assertLogs(level='ERROR') as logger: self.harness.update_config(missing_password_config) expected_logs = [ "ERROR:charm:Incomplete Configuration : ['prometheus-image-password']. " "Application will be blocked." ] self.assertEqual(sorted(logger.output), expected_logs) missing = self.harness.charm._check_config() expected = ['prometheus-image-password'] self.assertEqual(missing, expected) def test_alerting_config_is_updated_by_alertmanager_relation(self): self.harness.set_leader(True) # check alerting config is empty without alertmanager relation self.harness.update_config(MINIMAL_CONFIG) self.assertEqual(self.harness.charm.stored.alertmanagers, {}) rel_id = self.harness.add_relation('alertmanager', 'smtp') self.assertIsInstance(rel_id, int) self.harness.add_relation_unit(rel_id, 'smtp/0') pod_spec = self.harness.get_pod_spec() self.assertEqual(alerting_config(pod_spec), str()) # check alerting config is updated when a alertmanager joins self.harness.update_relation_data( rel_id, 'smtp/0', {'alerting_config': yaml.dump(SMTP_ALERTING_CONFIG)}) pod_spec = self.harness.get_pod_spec() self.assertEqual(alerting_config(pod_spec), SMTP_ALERTING_CONFIG) def test_alerting_config_is_removed_when_alertmanager_departs(self): self.harness.set_leader(True) # ensure there is a non-empty alerting config self.harness.update_config(MINIMAL_CONFIG) rel_id = self.harness.add_relation('alertmanager', 'smtp') rel = self.harness.model.get_relation('alertmanager') self.assertIsInstance(rel_id, int) self.harness.add_relation_unit(rel_id, 'smtp/0') self.harness.update_relation_data( rel_id, 'smtp/0', {'alerting_config': yaml.dump(SMTP_ALERTING_CONFIG)}) pod_spec = self.harness.get_pod_spec() self.assertEqual(alerting_config(pod_spec), SMTP_ALERTING_CONFIG) # check alerting config is removed when relation departs self.harness.charm.on.alertmanager_relation_departed.emit(rel) pod_spec = self.harness.get_pod_spec() self.assertEqual(alerting_config(pod_spec), str()) def test_grafana_is_provided_port_and_source(self): self.harness.set_leader(True) self.harness.update_config(MINIMAL_CONFIG) rel_id = self.harness.add_relation('grafana-source', 'grafana') self.harness.add_relation_unit(rel_id, 'grafana/0') self.harness.update_relation_data(rel_id, 'grafana/0', {}) data = self.harness.get_relation_data(rel_id, self.harness.model.unit.name) self.assertEqual(int(data['port']), MINIMAL_CONFIG['advertised-port']) self.assertEqual(data['source-type'], 'prometheus') def test_default_cli_log_level_is_info(self): self.harness.set_leader(True) self.harness.update_config(MINIMAL_CONFIG) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--log.level'), 'info') def test_invalid_log_level_defaults_to_debug(self): self.harness.set_leader(True) bad_log_config = MINIMAL_CONFIG.copy() bad_log_config['log-level'] = 'bad-level' with self.assertLogs(level='ERROR') as logger: self.harness.update_config(bad_log_config) expected_logs = [ "ERROR:root:Invalid loglevel: bad-level given, " "debug/info/warn/error/fatal allowed. " "defaulting to DEBUG loglevel." ] self.assertEqual(sorted(logger.output), expected_logs) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--log.level'), 'debug') def test_valid_log_level_is_accepted(self): self.harness.set_leader(True) valid_log_config = MINIMAL_CONFIG.copy() valid_log_config['log-level'] = 'warn' self.harness.update_config(valid_log_config) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--log.level'), 'warn') def test_web_admin_api_can_be_enabled(self): self.harness.set_leader(True) # without web admin enabled self.harness.update_config(MINIMAL_CONFIG) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--web.enable-admin-api'), None) # with web admin enabled admin_api_config = MINIMAL_CONFIG.copy() admin_api_config['web-enable-admin-api'] = True self.harness.update_config(admin_api_config) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--web.enable-admin-api'), '--web.enable-admin-api') def test_web_page_title_can_be_set(self): self.harness.set_leader(True) web_config = MINIMAL_CONFIG.copy() web_config['web-page-title'] = 'Prometheus Test Page' self.harness.update_config(web_config) pod_spec = self.harness.get_pod_spec() self.assertEqual( cli_arg(pod_spec, '--web.page-title')[1:-1], # striping quotes web_config['web-page-title']) def test_tsdb_compression_is_not_enabled_by_default(self): self.harness.set_leader(True) compress_config = MINIMAL_CONFIG.copy() self.harness.update_config(compress_config) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--storage.tsdb.wal-compression'), None) def test_tsdb_compression_can_be_enabled(self): self.harness.set_leader(True) compress_config = MINIMAL_CONFIG.copy() compress_config['tsdb-wal-compression'] = True self.harness.update_config(compress_config) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--storage.tsdb.wal-compression'), '--storage.tsdb.wal-compression') def test_valid_tsdb_retention_times_can_be_set(self): self.harness.set_leader(True) retention_time_config = MINIMAL_CONFIG.copy() acceptable_units = ['y', 'w', 'd', 'h', 'm', 's'] for unit in acceptable_units: retention_time = '{}{}'.format(1, unit) retention_time_config['tsdb-retention-time'] = retention_time self.harness.update_config(retention_time_config) pod_spec = self.harness.get_pod_spec() self.assertEqual( cli_arg(pod_spec, '--storage.tsdb.retention.time'), retention_time) def test_invalid_tsdb_retention_times_can_not_be_set(self): self.harness.set_leader(True) retention_time_config = MINIMAL_CONFIG.copy() # invalid unit retention_time = '{}{}'.format(1, 'x') retention_time_config['tsdb-retention-time'] = retention_time with self.assertLogs(level='ERROR') as logger: self.harness.update_config(retention_time_config) expected_logs = ["ERROR:charm:Invalid unit x in time spec"] self.assertEqual(sorted(logger.output), expected_logs) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--storage.tsdb.retention.time'), None) # invalid time value retention_time = '{}{}'.format(0, 'd') retention_time_config['tsdb-retention-time'] = retention_time with self.assertLogs(level='ERROR') as logger: self.harness.update_config(retention_time_config) expected_logs = [ "ERROR:charm:Expected positive time spec but got 0" ] self.assertEqual(sorted(logger.output), expected_logs) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--storage.tsdb.retention.time'), None) def test_max_web_connections_can_be_set(self): self.harness.set_leader(True) maxcon_config = MINIMAL_CONFIG.copy() maxcon_config['web-max-connections'] = 512 self.harness.update_config(maxcon_config) pod_spec = self.harness.get_pod_spec() self.assertEqual(int(cli_arg(pod_spec, '--web.max-connections')), maxcon_config['web-max-connections']) def test_alertmanager_queue_capacity_can_be_set(self): self.harness.set_leader(True) queue_config = MINIMAL_CONFIG.copy() queue_config['alertmanager-notification-queue-capacity'] = 512 self.harness.update_config(queue_config) pod_spec = self.harness.get_pod_spec() self.assertEqual( int(cli_arg(pod_spec, '--alertmanager.notification-queue-capacity')), queue_config['alertmanager-notification-queue-capacity']) def test_alertmanager_timeout_can_be_set(self): self.harness.set_leader(True) timeout_config = MINIMAL_CONFIG.copy() acceptable_units = ['y', 'w', 'd', 'h', 'm', 's'] for unit in acceptable_units: timeout_config['alertmanager-timeout'] = '{}{}'.format(1, unit) self.harness.update_config(timeout_config) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--alertmanager.timeout'), timeout_config['alertmanager-timeout']) def test_global_scrape_interval_can_be_set(self): self.harness.set_leader(True) scrapeint_config = MINIMAL_CONFIG.copy() acceptable_units = ['y', 'w', 'd', 'h', 'm', 's'] for unit in acceptable_units: scrapeint_config['scrape-interval'] = '{}{}'.format(1, unit) self.harness.update_config(scrapeint_config) pod_spec = self.harness.get_pod_spec() gconfig = global_config(pod_spec) self.assertEqual(gconfig['scrape_interval'], scrapeint_config['scrape-interval']) def test_global_scrape_timeout_can_be_set(self): self.harness.set_leader(True) scrapetime_config = MINIMAL_CONFIG.copy() acceptable_units = ['y', 'w', 'd', 'h', 'm', 's'] for unit in acceptable_units: scrapetime_config['scrape-timeout'] = '{}{}'.format(1, unit) self.harness.update_config(scrapetime_config) pod_spec = self.harness.get_pod_spec() gconfig = global_config(pod_spec) self.assertEqual(gconfig['scrape_timeout'], scrapetime_config['scrape-timeout']) def test_global_evaluation_interval_can_be_set(self): self.harness.set_leader(True) evalint_config = MINIMAL_CONFIG.copy() acceptable_units = ['y', 'w', 'd', 'h', 'm', 's'] for unit in acceptable_units: evalint_config['evaluation-interval'] = '{}{}'.format(1, unit) self.harness.update_config(evalint_config) pod_spec = self.harness.get_pod_spec() gconfig = global_config(pod_spec) self.assertEqual(gconfig['evaluation_interval'], evalint_config['evaluation-interval']) def test_valid_external_labels_can_be_set(self): self.harness.set_leader(True) label_config = MINIMAL_CONFIG.copy() labels = {'name1': 'value1', 'name2': 'value2'} label_config['external-labels'] = json.dumps(labels) self.harness.update_config(label_config) pod_spec = self.harness.get_pod_spec() gconfig = global_config(pod_spec) self.assertIsNotNone(gconfig['external_labels']) self.assertEqual(labels, gconfig['external_labels']) def test_invalid_external_labels_can_not_be_set(self): self.harness.set_leader(True) label_config = MINIMAL_CONFIG.copy() # label value must be string labels = {'name': 1} label_config['external-labels'] = json.dumps(labels) with self.assertLogs(level='ERROR') as logger: self.harness.update_config(label_config) expected_logs = [ "ERROR:charm:External label keys/values must be strings" ] self.assertEqual(sorted(logger.output), expected_logs) pod_spec = self.harness.get_pod_spec() gconfig = global_config(pod_spec) self.assertIsNone(gconfig.get('external_labels')) def test_default_scrape_config_is_always_set(self): self.harness.set_leader(True) self.harness.update_config(MINIMAL_CONFIG) pod_spec = self.harness.get_pod_spec() prometheus_scrape_config = scrape_config(pod_spec, 'prometheus') self.assertIsNotNone(prometheus_scrape_config, 'No default config found') def test_k8s_scrape_config_can_be_set(self): self.harness.set_leader(True) k8s_config = MINIMAL_CONFIG.copy() k8s_config['monitor-k8s'] = True self.harness.update_config(k8s_config) pod_spec = self.harness.get_pod_spec() k8s_api_scrape_config = scrape_config(pod_spec, 'kubernetes-apiservers') self.assertIsNotNone(k8s_api_scrape_config, 'No k8s API server scrape config found') k8s_node_scrape_config = scrape_config(pod_spec, 'kubernetes-nodes') self.assertIsNotNone(k8s_node_scrape_config, 'No k8s nodes scrape config found') k8s_ca_scrape_config = scrape_config(pod_spec, 'kubernetes-cadvisor') self.assertIsNotNone(k8s_ca_scrape_config, 'No k8s cAdvisor scrape config found') k8s_ep_scrape_config = scrape_config(pod_spec, 'kubernetes-service-endpoints') self.assertIsNotNone(k8s_ep_scrape_config, 'No k8s service endpoints scrape config found') k8s_svc_scrape_config = scrape_config(pod_spec, 'kubernetes-services') self.assertIsNotNone(k8s_svc_scrape_config, 'No k8s services scrape config found') k8s_in_scrape_config = scrape_config(pod_spec, 'kubernetes-ingresses') self.assertIsNotNone(k8s_in_scrape_config, 'No k8s ingress scrape config found') k8s_pod_scrape_config = scrape_config(pod_spec, 'kubernetes-pods') self.assertIsNotNone(k8s_pod_scrape_config, 'No k8s pods scrape config found')
class TestCharm(unittest.TestCase): """Test script for checking relations""" def setUp(self) -> NoReturn: """Test setup.""" self.harness = Harness(HssCharm) self.harness.set_leader(is_leader=True) self.harness.begin() def test_on_start_without_relations(self) -> NoReturn: """Test installation without any relation.""" self.harness.charm.on.config_changed.emit() # Verifying status self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) # Verifying status message self.assertGreater(len(self.harness.charm.unit.status.message), 0) self.assertTrue( self.harness.charm.unit.status.message.startswith("Waiting for ")) def test_on_start_with_relations(self) -> NoReturn: """Test installation with relation.""" self.harness.charm.on.start.emit() expected_result = { "version": 3, "containers": [{ "name": "hss", "image": "localhost:32000/ims_hss:1.0", "imagePullPolicy": "Always", "ports": [ { "name": "diahss", "containerPort": 3868, "protocol": "TCP" }, { "name": "hss", "containerPort": 8080, "protocol": "TCP" }, ], "envConfig": { "MYSQL_HOST": "mysql-endpoints", "MYSQL_USER": "******", "MYSQL_ROOT_PASSWORD": "******", }, "command": ["./init_hss.sh", "&"], }], } # Check if mysql is initialized self.assertIsNone(self.harness.charm.state.mysql) # Initializing mysql relation mysql_relation_id = self.harness.add_relation("mysql", "mysql") self.harness.add_relation_unit(mysql_relation_id, "mysql/0") self.harness.update_relation_data( mysql_relation_id, "mysql", { "hostname": "mysql", "mysql_user": "******", "mysql_pwd": "root" }, ) # Checking if nrf data is stored self.assertEqual(self.harness.charm.state.mysql, "mysql") # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) pod_spec, _ = self.harness.get_pod_spec() self.assertDictEqual(expected_result, pod_spec) def test_on_mysql_app_relation_changed(self) -> NoReturn: """Test to see if mysql app relation is updated.""" self.harness.charm.on.start.emit() self.assertIsNone(self.harness.charm.state.mysql) relation_id = self.harness.add_relation("mysql", "mysql") self.harness.add_relation_unit(relation_id, "mysql/0") self.harness.update_relation_data( relation_id, "mysql", { "hostname": "mysql", "mysql_user": "******", "mysql_pwd": "root" }, ) # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) # Verifying status message self.assertGreater(len(self.harness.charm.unit.status.message), 0) self.assertFalse( self.harness.charm.unit.status.message.startswith("Waiting for ")) def test_publish_hss_info(self) -> NoReturn: """Test to see if hss relation is updated.""" expected_result = {"private-address": "127.1.1.1", "hostname": "hss"} self.harness.charm.on.start.emit() relation_id = self.harness.add_relation("dns-source", "dns_source") relation_data = {"private-address": "127.1.1.1", "hostname": "hss"} self.harness.update_relation_data(relation_id, "dns_source", relation_data) relation_data = self.harness.get_relation_data(relation_id, "dns_source") self.assertDictEqual(expected_result, relation_data)
class TestEndpointProvider(unittest.TestCase): def setUp(self): self.harness = Harness(EndpointProviderCharm, meta=PROVIDER_META) self.addCleanup(self.harness.cleanup) self.harness.set_leader(True) self.harness.begin() def test_provider_default_scrape_relations_not_in_meta(self): """Tests that the Provider raises exception when no promethes_scrape in meta.""" harness = Harness( EndpointProviderCharm, # No provider relation with `prometheus_scrape` as interface meta=""" name: provider-tester containers: prometheus: resource: prometheus-image prometheus-tester: {} provides: non-standard-name: interface: prometheus_scrape """, ) self.assertRaises(RelationNotFoundError, harness.begin) def test_provider_default_scrape_relation_wrong_interface(self): """Tests that Provider raises exception if the default relation has the wrong interface.""" harness = Harness( EndpointProviderCharm, # No provider relation with `prometheus_scrape` as interface meta=""" name: provider-tester containers: prometheus: resource: prometheus-image prometheus-tester: {} provides: metrics-endpoint: interface: not_prometheus_scrape """, ) self.assertRaises(RelationInterfaceMismatchError, harness.begin) def test_provider_default_scrape_relation_wrong_role(self): """Tests that Provider raises exception if the default relation has the wrong role.""" harness = Harness( EndpointProviderCharm, # No provider relation with `prometheus_scrape` as interface meta=""" name: provider-tester containers: prometheus: resource: prometheus-image prometheus-tester: {} requires: metrics-endpoint: interface: prometheus_scrape """, ) self.assertRaises(RelationRoleMismatchError, harness.begin) @patch("ops.testing._TestingModelBackend.network_get") def test_provider_sets_scrape_metadata(self, _): rel_id = self.harness.add_relation(RELATION_NAME, "provider") self.harness.add_relation_unit(rel_id, "provider/0") data = self.harness.get_relation_data(rel_id, self.harness.model.app.name) self.assertIn("scrape_metadata", data) scrape_metadata = data["scrape_metadata"] self.assertIn("model", scrape_metadata) self.assertIn("model_uuid", scrape_metadata) self.assertIn("application", scrape_metadata) @patch_network_get(private_address="192.0.8.2") def test_provider_unit_sets_bind_address_on_pebble_ready(self, *unused): rel_id = self.harness.add_relation(RELATION_NAME, "provider") self.harness.container_pebble_ready("prometheus-tester") data = self.harness.get_relation_data(rel_id, self.harness.charm.unit.name) self.assertIn("prometheus_scrape_unit_address", data) self.assertEqual(data["prometheus_scrape_unit_address"], "192.0.8.2") @patch_network_get(private_address="192.0.8.2") def test_provider_unit_sets_bind_address_on_relation_joined(self, *unused): rel_id = self.harness.add_relation(RELATION_NAME, "provider") self.harness.add_relation_unit(rel_id, "provider/0") data = self.harness.get_relation_data(rel_id, self.harness.charm.unit.name) self.assertIn("prometheus_scrape_unit_address", data) self.assertEqual(data["prometheus_scrape_unit_address"], "192.0.8.2") self.assertIn("prometheus_scrape_unit_name", data) @patch("ops.testing._TestingModelBackend.network_get") def test_provider_supports_multiple_jobs(self, _): rel_id = self.harness.add_relation(RELATION_NAME, "provider") self.harness.add_relation_unit(rel_id, "provider/0") data = self.harness.get_relation_data(rel_id, self.harness.model.app.name) self.assertIn("scrape_jobs", data) jobs = json.loads(data["scrape_jobs"]) self.assertEqual(len(jobs), len(JOBS)) names = [job["job_name"] for job in jobs] job_names = [job["job_name"] for job in JOBS] self.assertListEqual(names, job_names) @patch("ops.testing._TestingModelBackend.network_get") def test_provider_sanitizes_jobs(self, _): rel_id = self.harness.add_relation(RELATION_NAME, "provider") self.harness.add_relation_unit(rel_id, "provider/0") data = self.harness.get_relation_data(rel_id, self.harness.model.app.name) self.assertIn("scrape_jobs", data) jobs = json.loads(data["scrape_jobs"]) for job in jobs: keys = set(job.keys()) self.assertTrue(keys.issubset(ALLOWED_KEYS)) @patch("ops.testing._TestingModelBackend.network_get") def test_each_alert_rule_is_topology_labeled(self, _): rel_id = self.harness.add_relation(RELATION_NAME, "provider") self.harness.add_relation_unit(rel_id, "provider/0") data = self.harness.get_relation_data(rel_id, self.harness.model.app.name) self.assertIn("alert_rules", data) alerts = json.loads(data["alert_rules"]) self.assertIn("groups", alerts) self.assertEqual(len(alerts["groups"]), 5) group = alerts["groups"][0] for rule in group["rules"]: self.assertIn("labels", rule) labels = rule["labels"] self.assertIn("juju_model", labels) self.assertIn("juju_application", labels) self.assertIn("juju_model_uuid", labels) self.assertIn("juju_charm", labels) @patch("ops.testing._TestingModelBackend.network_get") def test_each_alert_expression_is_topology_labeled(self, _): rel_id = self.harness.add_relation(RELATION_NAME, "provider") self.harness.add_relation_unit(rel_id, "provider/0") data = self.harness.get_relation_data(rel_id, self.harness.model.app.name) self.assertIn("alert_rules", data) alerts = json.loads(data["alert_rules"]) self.assertIn("groups", alerts) self.assertEqual(len(alerts["groups"]), 5) group = alerts["groups"][0] for rule in group["rules"]: self.assertIn("expr", rule) for labels in expression_labels(rule["expr"]): self.assertIn("juju_model", labels) self.assertIn("juju_model_uuid", labels) self.assertIn("juju_application", labels) self.assertIn("juju_charm", labels)
class TestManilaNetappCharm(TestCase): REQUIRED_CHARM_CONFIG_BY_DEFAULT = { 'management-address': '10.0.0.1', 'admin-password': '******', 'vserver-name': 'svm0', } def setUp(self): self.harness = Harness(charm.ManilaNetappCharm) self.addCleanup(self.harness.cleanup) def test_custom_status_check_default_config(self): self.harness.disable_hooks() self.harness.begin() self.assertFalse(self.harness.charm.custom_status_check()) expected_status = BlockedStatus('Missing configs: {}'.format( list(self.REQUIRED_CHARM_CONFIG_BY_DEFAULT.keys()))) self.assertEqual(self.harness.charm.unit.status, expected_status) def test_custom_status_check_valid_config(self): self.harness.update_config(self.REQUIRED_CHARM_CONFIG_BY_DEFAULT) self.harness.disable_hooks() self.harness.begin() self.assertTrue(self.harness.charm.custom_status_check()) @mock.patch.object(charm.ops_openstack.core.OSBaseCharm, 'install_pkgs') @mock.patch.object(charm.interface_manila_plugin.ManilaPluginProvides, 'send_backend_config') @mock.patch('charmhelpers.contrib.openstack.templating.get_loader') @mock.patch('charmhelpers.core.templating.render') def test_send_config_dhss_disabled(self, _render, _get_loader, _send_backend_config, _install_pkgs): _render.return_value = 'test-rendered-manila-backend-config' _get_loader.return_value = 'test-loader' self.harness.update_config(self.REQUIRED_CHARM_CONFIG_BY_DEFAULT) rel_id = self.harness.add_relation('manila-plugin', 'manila') self.harness.add_relation_unit(rel_id, 'manila/0') self.harness.begin_with_initial_hooks() self.assertTrue(self.harness.charm.state.is_started) _render.assert_called_once_with(source='manila.conf', template_loader='test-loader', target=None, context=self.harness.charm.adapters) _get_loader.assert_called_once_with('templates/', 'default') _send_backend_config.assert_called_once_with( 'netapp-ontap', 'test-rendered-manila-backend-config') _install_pkgs.assert_called_once_with() self.assertEqual(self.harness.charm.unit.status, ActiveStatus('Unit is ready')) @mock.patch.object(charm.ops_openstack.core.OSBaseCharm, 'install_pkgs') @mock.patch.object(charm.interface_manila_plugin.ManilaPluginProvides, 'send_backend_config') @mock.patch('charmhelpers.contrib.openstack.templating.get_loader') @mock.patch('charmhelpers.core.templating.render') def test_send_config_dhss_enabled(self, _render, _get_loader, _send_backend_config, _install_pkgs): _render.return_value = 'test-rendered-manila-backend-config' _get_loader.return_value = 'test-loader' config = copy.deepcopy(self.REQUIRED_CHARM_CONFIG_BY_DEFAULT) config['driver-handles-share-servers'] = True config['root-volume-aggregate-name'] = 'test_cluster_01_VM_DISK_1' self.harness.update_config(config) self.harness.begin_with_initial_hooks() # Validate workflow with incomplete relation data self.assertFalse(self.harness.charm.state.is_started) _render.assert_not_called() _get_loader.assert_not_called() _send_backend_config.assert_not_called() _install_pkgs.assert_called_once_with() self.assertEqual(self.harness.charm.unit.status, UnknownStatus()) # Validate workflow with complete relation data rel_id = self.harness.add_relation('manila-plugin', 'manila') self.harness.add_relation_unit(rel_id, 'manila/0') self.harness.update_relation_data(rel_id, 'manila/0', { '_authentication_data': json.dumps({'data': 'test-manila-auth-data'}) }) self.assertTrue(self.harness.charm.state.is_started) _render.assert_called_once_with(source='manila.conf', template_loader='test-loader', target=None, context=self.harness.charm.adapters) _get_loader.assert_called_once_with('templates/', 'default') _send_backend_config.assert_called_once_with( 'netapp-ontap', 'test-rendered-manila-backend-config') self.assertEqual(self.harness.charm.unit.status, ActiveStatus('Unit is ready'))
class TestCharm(unittest.TestCase): """Test script for checking relations""" def setUp(self) -> NoReturn: """Test setup""" self.harness = Harness(Upf1Charm) self.harness.set_leader(is_leader=True) self.harness.begin() def test_on_start_without_relations(self) -> NoReturn: """Test installation without any relation.""" self.harness.charm.on.config_changed.emit() # Verifying status self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) # Verifying status message self.assertGreater(len(self.harness.charm.unit.status.message), 0) self.assertTrue( self.harness.charm.unit.status.message.startswith("Waiting for ")) def test_on_start_with_relations(self) -> NoReturn: """Test installation with any relation.""" annot = { "annotations": { "k8s.v1.cni.cncf.io/networks": '[\n{\n"name" : "n6-network",' '\n"interface": "eth1",\n"ips": []\n}\n]' }, "securityContext": { "runAsUser": 0000, "runAsGroup": 0000 }, } service = [{ "name": "upf-e", "labels": { "juju-app": "upf1" }, "spec": { "selector": { "juju-app": "upf1" }, "ports": [{ "protocol": "TCP", "port": 80, "targetPort": 80 }], "type": "ClusterIP", }, }] expected_result = { "version": 3, "containers": [{ "name": "upf1", "imageDetails": self.harness.charm.image.fetch(), "imagePullPolicy": "Always", "ports": [{ "name": "upf1", "containerPort": 2152, "protocol": "UDP", }], "envConfig": { "UE_RANGE": "60.60.0.0/24", "STATIC_IP": "192.168.70.15", }, "command": ["./upf_start.sh", "&"], "kubernetes": { "securityContext": { "privileged": True } }, }], "kubernetesResources": { "services": service, "pod": annot, }, } # Check if natapp is initialized self.assertIsNone(self.harness.charm.state.natapp_ip) self.assertIsNone(self.harness.charm.state.natapp_host) # Initializing the natapp relation natapp_relation_id = self.harness.add_relation("natapp", "natapp") self.harness.add_relation_unit(natapp_relation_id, "natapp/0") self.harness.update_relation_data( natapp_relation_id, "natapp", { "hostname": "natapp", "static_ip": "192.168.70.15" }, ) # Checking if natapp data is stored self.assertEqual(self.harness.charm.state.natapp_ip, "192.168.70.15") # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) pod_spec, _ = self.harness.get_pod_spec() self.assertDictEqual(expected_result, pod_spec) def test_on_config_change(self) -> NoReturn: """Test installation without any relation.""" expected_result = { "version": 3, "containers": [{ "name": "upf1", "imageDetails": self.harness.charm.image.fetch(), "imagePullPolicy": "Always", "ports": [{ "name": "upf1", "containerPort": 2152, "protocol": "UDP" }], "envConfig": { "UE_RANGE": "60.60.0.0/24", "STATIC_IP": "192.168.70.15", }, "command": ["./upf_start.sh", "&"], "kubernetes": { "securityContext": { "privileged": True } }, }], "kubernetesResources": { "pod": { "annotations": { "k8s.v1.cni.cncf.io/networks": '[\n{\n"name" : "n6-network",' '\n"interface": "eth1",\n"ips": []\n}\n]' }, "securityContext": { "runAsUser": 0, "runAsGroup": 0 }, }, "services": [{ "name": "upf-e", "labels": { "juju-app": "upf1" }, "spec": { "selector": { "juju-app": "upf1" }, "ports": [{ "protocol": "TCP", "port": 80, "targetPort": 80 }], "type": "ClusterIP", }, }], }, } # Check if nrf,upf is initialized self.assertIsNone(self.harness.charm.state.natapp_ip) # Initializing the nrf relation natapp_relation_id = self.harness.add_relation("natapp", "natapp") self.harness.add_relation_unit(natapp_relation_id, "natapp/0") self.harness.update_relation_data( natapp_relation_id, "natapp", { "hostname": "natapp", "static_ip": "192.168.70.15" }, ) # Checking if nrf,upf data is stored self.assertEqual(self.harness.charm.state.natapp_ip, "192.168.70.15") # 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_on_natapp_app_relation_changed(self) -> NoReturn: """Test to see if upf app relation is updated.""" self.harness.charm.on.config_changed.emit() self.assertIsNone(self.harness.charm.state.natapp_ip) # Initializing the upf relation natapp_relation_id = self.harness.add_relation("natapp", "upf") self.harness.add_relation_unit(natapp_relation_id, "natapp/0") relation_data = {"static_ip": "192.168.70.15"} self.harness.update_relation_data(natapp_relation_id, "natapp/0", relation_data) # Verifying status self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) # Verifying status message self.assertGreater(len(self.harness.charm.unit.status.message), 0) self.assertTrue( self.harness.charm.unit.status.message.startswith("Waiting for ")) def test_publish_upf_info(self) -> NoReturn: """Test to see if upf relation is updated.""" expected_result = { "private_address": "127.1.1.1", } relation_id = self.harness.add_relation("upf", "smf") relation_data = {"private_address": "127.1.1.1"} self.harness.update_relation_data(relation_id, "upf1", relation_data) relation_data = self.harness.get_relation_data(relation_id, "upf1") self.assertDictEqual(expected_result, relation_data)
class TestCharm(unittest.TestCase): def setUp(self): self.harness = Harness(MongoDBCharm) self.addCleanup(self.harness.cleanup) mongo_resource = { "registrypath": "mongodb:4.4.1", "username": "******", "password": "******" } self.harness.add_oci_resource("mongodb-image", mongo_resource) self.harness.begin() self.peer_rel_id = self.harness.add_relation('mongodb', 'mongodb') @patch('ops.testing._TestingPebbleClient.pull') def test_replica_set_name_can_be_changed(self, _): self.harness.set_leader(True) self.harness.container_pebble_ready("mongodb") # check default replica set name plan = self.harness.get_container_pebble_plan("mongodb") self.assertEqual(replica_set_name(plan), "rs0") # check replica set name can be changed self.harness.update_config({"replica_set_name": "new_name"}) plan = self.harness.get_container_pebble_plan("mongodb") self.assertEqual(replica_set_name(plan), "new_name") @patch("mongoserver.MongoDB.reconfigure_replica_set") def test_replica_set_is_reconfigured_when_peer_joins(self, mock_reconf): self.harness.set_leader(True) self.harness.add_relation_unit(self.peer_rel_id, 'mongodb/1') self.harness.update_relation_data(self.peer_rel_id, 'mongodb/1', {'private-address': '10.0.0.1'}) peers = [ 'mongodb-k8s-0.mongodb-k8s-endpoints', 'mongodb-k8s-1.mongodb-k8s-endpoints' ] mock_reconf.assert_called_once_with(peers) def test_replica_set_uri_data_is_generated_correctly(self): self.harness.set_leader(True) replica_set_uri = self.harness.charm.mongo.replica_set_uri() data = self.harness.get_relation_data(self.peer_rel_id, self.harness.model.app.name) cred = "root:{}".format(data['root_password']) self.assertEqual( replica_set_uri, 'mongodb://{}@mongodb-k8s-0.mongodb-k8s-endpoints:27017/admin'. format(cred)) def test_leader_sets_key_and_root_credentials(self): self.harness.set_leader(False) self.harness.set_leader(True) data = self.harness.get_relation_data(self.peer_rel_id, self.harness.model.app.name) self.assertIsNotNone(data['root_password']) self.assertIsNotNone(data['security_key']) @patch('mongoserver.MongoDB.version') def test_charm_provides_version(self, mock_version): self.harness.set_leader(True) mock_version.return_value = "4.4.1" version = self.harness.charm.mongo.version() self.assertEqual(version, "4.4.1") @patch('mongoserver.MongoDB.is_ready') def test_start_is_deferred_if_monog_is_not_ready(self, is_ready): is_ready.return_value = False self.harness.set_leader(True) with self.assertLogs(level="DEBUG") as logger: self.harness.charm.on.start.emit() is_ready.assert_called() for message in sorted(logger.output): if "DEBUG:ops.framework:Deferring" in message: self.assertIn("StartEvent", message) @patch('mongoserver.MongoDB.initialize_replica_set') @patch('mongoserver.MongoDB.is_ready') def test_start_is_deffered_if_monog_is_not_initialized( self, is_ready, initialize): is_ready.return_value = True initialize.side_effect = RuntimeError("Not Initialized") self.harness.set_leader(True) with self.assertLogs(level="DEBUG") as logger: self.harness.charm.on.start.emit() is_ready.assert_called() self.assertIn( "INFO:charm:Deferring on_start since : error=Not Initialized", sorted(logger.output))
class TestCharm(unittest.TestCase): def setUp(self): self.harness = Harness(RedisCharm) self.addCleanup(self.harness.cleanup) redis_resource = { "registrypath": "redis:6.0", # "username" and "password" are useless, but oci-resource # library fetch() fails if we do not provide them ... "username": "", "password": "" } self.harness.add_oci_resource("redis-image", redis_resource) self.harness.begin() def test_on_start_when_unit_is_not_leader(self): # Given self.harness.set_leader(False) # When self.harness.charm.on.start.emit() # Then self.assertEqual(self.harness.charm.unit.status, ActiveStatus()) @mock.patch.object(RedisClient, 'is_ready') def test_on_start_when_redis_is_not_ready(self, is_ready): # Given self.harness.set_leader(True) is_ready.return_value = False # When self.harness.charm.on.start.emit() # Then is_ready.assert_called_once_with() self.assertEqual(self.harness.charm.unit.status, WaitingStatus("Waiting for Redis ...")) @mock.patch.object(RedisClient, 'is_ready') def test_on_start_when_redis_is_ready(self, is_ready): # Given self.harness.set_leader(True) is_ready.return_value = True # When self.harness.charm.on.start.emit() # Then is_ready.assert_called_once_with() self.assertEqual(self.harness.charm.unit.status, ActiveStatus()) def test_on_stop(self): # When self.harness.charm.on.stop.emit() # Then self.assertEqual(self.harness.charm.unit.status, MaintenanceStatus('Pod is terminating.')) def test_on_config_changed_when_unit_is_not_leader(self): # Given self.harness.set_leader(False) # When self.harness.charm.on.config_changed.emit() # Then self.assertEqual(self.harness.charm.unit.status, ActiveStatus()) @mock.patch.object(RedisClient, 'is_ready') def test_on_config_changed_when_unit_is_leader_and_redis_is_ready( self, is_ready): # Given self.harness.set_leader(True) is_ready.return_value = True # When self.harness.charm.on.config_changed.emit() # Then self.assertIsNotNone(self.harness.charm.state.pod_spec) self.assertEqual(self.harness.charm.unit.status, ActiveStatus()) @mock.patch.object(OCIImageResource, 'fetch') def test_on_config_changed_when_unit_is_leader_but_image_fetch_breaks( self, fetch): # Given self.harness.set_leader(True) fetch.side_effect = OCIImageResourceError("redis-image") # When self.harness.charm.on.config_changed.emit() # Then fetch.assert_called_once_with() self.assertEqual(self.harness.charm.unit.status, BlockedStatus("Error fetching image information.")) def test_on_update_status_when_unit_is_not_leader(self): # Given self.harness.set_leader(False) # When self.harness.charm.on.update_status.emit() # Then self.assertEqual(self.harness.charm.unit.status, ActiveStatus()) @mock.patch.object(RedisClient, 'is_ready') def test_on_update_status_when_redis_is_not_ready(self, is_ready): # Given self.harness.set_leader(True) is_ready.return_value = False # When self.harness.charm.on.update_status.emit() # Then is_ready.assert_called_once_with() self.assertEqual(self.harness.charm.unit.status, WaitingStatus("Waiting for Redis ...")) @mock.patch.object(RedisClient, 'is_ready') def test_on_update_status_when_redis_is_ready(self, is_ready): # Given self.harness.set_leader(True) is_ready.return_value = True # When self.harness.charm.on.update_status.emit() # Then is_ready.assert_called_once_with() self.assertEqual(self.harness.charm.unit.status, ActiveStatus())
class TestDashboardConsumer(unittest.TestCase): def setUp(self): self.harness = Harness(ConsumerCharm, meta=CONSUMER_META) self.harness._backend.model_name = "testing" self.harness._backend.model_uuid = "abcdefgh-1234" self.addCleanup(self.harness.cleanup) self.harness.set_leader(True) self.harness.begin() def test_consumer_does_not_set_dashboard_without_monitoring(self): rel_id = self.harness.add_relation("grafana-dashboard", "consumer") self.harness.add_relation_unit(rel_id, "consumer/0") self.harness.charm.consumer.add_dashboard(DASHBOARD_TMPL) self.assertEqual(self.harness.charm._stored.invalid_events, 1) def test_consumer_sets_dashboard_data(self): rel_id = self.harness.add_relation("grafana-dashboard", "consumer") self.harness.add_relation_unit(rel_id, "consumer/0") mon_rel_id = self.harness.add_relation("monitoring", "consumer") self.harness.add_relation_unit(mon_rel_id, "monitoring/0") self.harness.charm.consumer.add_dashboard(DASHBOARD_TMPL) data = json.loads( self.harness.get_relation_data( rel_id, self.harness.model.app.name)["dashboards"]) return_data = { "monitoring_identifier": "testing_abcdefgh-1234_monitoring", "monitoring_target": "Consumer-tester [ testing / abcdefgh-1234 ]", "monitoring_query": "juju_model='testing',juju_model_uuid='abcdefgh-1234',juju_application='consumer-tester'", "template": "\n\n", "removed": False, "invalidated": False, "invalidated_reason": "", "uuid": "12345678", } self.assertEqual(return_data, data) def test_consumer_can_remove_dashboard(self): rel_id = self.harness.add_relation("grafana-dashboard", "consumer") self.harness.add_relation_unit(rel_id, "consumer/0") mon_rel_id = self.harness.add_relation("monitoring", "consumer") self.harness.add_relation_unit(mon_rel_id, "monitoring/0") self.harness.charm.consumer.add_dashboard(DASHBOARD_TMPL) data = json.loads( self.harness.get_relation_data( rel_id, self.harness.model.app.name)["dashboards"]) return_data = { "monitoring_identifier": "testing_abcdefgh-1234_monitoring", "monitoring_target": "Consumer-tester [ testing / abcdefgh-1234 ]", "monitoring_query": "juju_model='testing',juju_model_uuid='abcdefgh-1234',juju_application='consumer-tester'", "template": "\n\n", "removed": False, "invalidated": False, "invalidated_reason": "", "uuid": "12345678", } self.assertEqual(return_data, data) self.harness.charm.consumer.remove_dashboard() return_data = { "monitoring_identifier": "testing_abcdefgh-1234_monitoring", "monitoring_target": "Consumer-tester [ testing / abcdefgh-1234 ]", "monitoring_query": "juju_model='testing',juju_model_uuid='abcdefgh-1234',juju_application='consumer-tester'", "template": "\n\n", "removed": True, "invalidated": False, "invalidated_reason": "", "uuid": "12345678", } def test_consumer_resends_dashboard_after_monitoring_established(self): rel_id = self.harness.add_relation("grafana-dashboard", "consumer") self.harness.add_relation_unit(rel_id, "consumer/0") self.harness.charm.consumer.add_dashboard(DASHBOARD_TMPL) self.assertEqual(self.harness.charm._stored.invalid_events, 1) mon_rel_id = self.harness.add_relation("monitoring", "consumer") self.harness.add_relation_unit(mon_rel_id, "monitoring/0") data = json.loads( self.harness.get_relation_data( rel_id, self.harness.model.app.name)["dashboards"]) return_data = { "monitoring_identifier": "testing_abcdefgh-1234_monitoring", "monitoring_target": "Consumer-tester [ testing / abcdefgh-1234 ]", "monitoring_query": "juju_model='testing',juju_model_uuid='abcdefgh-1234',juju_application='consumer-tester'", "template": "\n\n", "removed": False, "invalidated": False, "invalidated_reason": "", "uuid": "12345678", } self.assertEqual(return_data, data) def test_consumer_invalidates_dashboard_after_monitoring_established_then_broken( self, ): rel_id = self.harness.add_relation("grafana-dashboard", "consumer") self.harness.add_relation_unit(rel_id, "consumer/0") self.harness.charm.consumer.add_dashboard(DASHBOARD_TMPL) self.assertEqual(self.harness.charm._stored.invalid_events, 1) mon_rel_id = self.harness.add_relation("monitoring", "consumer") self.harness.add_relation_unit(mon_rel_id, "monitoring/0") self.harness.remove_relation(mon_rel_id) data = json.loads( self.harness.get_relation_data( rel_id, self.harness.model.app.name)["dashboards"]) return_data = { "monitoring_identifier": "testing_abcdefgh-1234_monitoring", "monitoring_target": "Consumer-tester [ testing / abcdefgh-1234 ]", "monitoring_query": "juju_model='testing',juju_model_uuid='abcdefgh-1234',juju_application='consumer-tester'", "template": "\n\n", "removed": False, "invalidated": True, "invalidated_reason": "Waiting for a monitoring relation to send dashboard data", "uuid": "12345678", } self.assertEqual(return_data, data) self.assertEqual(self.harness.charm._stored.invalid_events, 1)
class TestEndpointConsumer(unittest.TestCase): def setUp(self): metadata_file = open("metadata.yaml") self.harness = Harness(EndpointConsumerCharm, meta=metadata_file) self.addCleanup(self.harness.cleanup) self.harness.begin() def setup_charm_relations(self, multi=False): """Create relations used by test cases. Args: multi: a boolean indicating if multiple relations must be created. """ rel_ids = [] self.assertEqual(self.harness.charm._stored.num_events, 0) rel_id = self.harness.add_relation(RELATION_NAME, "consumer") rel_ids.append(rel_id) self.harness.update_relation_data( rel_id, "consumer", { "scrape_metadata": json.dumps(SCRAPE_METADATA), "scrape_jobs": json.dumps(SCRAPE_JOBS), }, ) self.harness.add_relation_unit(rel_id, "consumer/0") self.harness.update_relation_data( rel_id, "consumer/0", {"prometheus_scrape_host": "1.1.1.1"}) self.assertEqual(self.harness.charm._stored.num_events, 2) if multi: rel_id = self.harness.add_relation(RELATION_NAME, "other-consumer") rel_ids.append(rel_id) self.harness.update_relation_data( rel_id, "other-consumer", { "scrape_metadata": json.dumps(OTHER_SCRAPE_METADATA), "scrape_jobs": json.dumps(OTHER_SCRAPE_JOBS), }, ) self.harness.add_relation_unit(rel_id, "other-consumer/0") self.harness.update_relation_data( rel_id, "other-consumer/0", {"prometheus_scrape_host": "2.2.2.2"}) self.assertEqual(self.harness.charm._stored.num_events, 4) return rel_ids def validate_jobs(self, jobs): """Valdiate that a list of jobs has the expected fields. Existence for unit labels is not checked since these do not exist for all jobs. Args: jobs: list of jobs where each job is a dictionary. Raises: assertion failures if any job is not as expected. """ for job in jobs: self.assertIn("job_name", job) self.assertIn("static_configs", job) static_configs = job["static_configs"] for static_config in static_configs: self.assertIn("targets", static_config) self.assertIn("labels", static_config) labels = static_config["labels"] self.assertIn("juju_model", labels) self.assertIn("juju_model_uuid", labels) self.assertIn("juju_application", labels) relabel_configs = job["relabel_configs"] self.assertEqual(len(relabel_configs), 1) relabel_config = relabel_configs[0] self.assertEqual( relabel_config.get("source_labels"), [ "juju_model", "juju_model_uuid", "juju_application", "juju_unit" ], ) def test_consumer_notifies_on_new_scrape_relation(self): self.assertEqual(self.harness.charm._stored.num_events, 0) rel_id = self.harness.add_relation(RELATION_NAME, "consumer") self.harness.update_relation_data( rel_id, "consumer", {"scrape_metadata": json.dumps(SCRAPE_METADATA)}) self.assertEqual(self.harness.charm._stored.num_events, 1) def test_consumer_notifies_on_new_scrape_target(self): self.assertEqual(self.harness.charm._stored.num_events, 0) rel_id = self.harness.add_relation(RELATION_NAME, "consumer") self.harness.add_relation_unit(rel_id, "consumer/0") self.harness.update_relation_data( rel_id, "consumer/0", {"prometheus_scrape_host": "1.1.1.1"}) self.assertEqual(self.harness.charm._stored.num_events, 1) def test_consumer_returns_all_static_scrape_labeled_jobs(self): self.setup_charm_relations() jobs = self.harness.charm.prometheus_consumer.jobs() self.assertEqual(len(jobs), len(SCRAPE_JOBS)) self.validate_jobs(jobs) def test_consumer_does_not_unit_label_fully_qualified_targets(self): self.setup_charm_relations() jobs = self.harness.charm.prometheus_consumer.jobs() self.assertEqual(len(jobs), len(SCRAPE_JOBS)) for job in jobs: for static_config in job["static_configs"]: if FULL_TARGET in static_config.get("targets"): self.assertNotIn("juju_unit", static_config.get("labels")) def test_consumer_does_attach_unit_labels_to_wildcard_hosts(self): self.setup_charm_relations() jobs = self.harness.charm.prometheus_consumer.jobs() self.assertEqual(len(jobs), len(SCRAPE_JOBS)) for job in jobs: for static_config in job["static_configs"]: if FULL_TARGET not in static_config.get("targets"): self.assertIn("juju_unit", static_config.get("labels")) def test_consumer_allows_custom_metrics_paths(self): rel_ids = self.setup_charm_relations() self.assertEqual(len(rel_ids), 1) rel_id = rel_ids[0] jobs = self.harness.charm.prometheus_consumer.jobs() for job in jobs: if job.get("metrics_path"): name_suffix = job_name_suffix(job["job_name"], juju_job_labels(job), rel_id) path = named_job_attribute(name_suffix, "metrics_path", "/metrics") self.assertEqual(job["metrics_path"], path) def test_consumer_sanitizes_jobs(self): self.setup_charm_relations() jobs = self.harness.charm.prometheus_consumer.jobs() for job in jobs: job_keys = set(job.keys()) self.assertTrue(job_keys.issubset(ALLOWED_KEYS)) def test_consumer_returns_jobs_for_all_relations(self): self.setup_charm_relations(multi=True) jobs = self.harness.charm.prometheus_consumer.jobs() self.assertEqual(len(jobs), len(SCRAPE_JOBS) + len(OTHER_SCRAPE_JOBS)) def test_consumer_scrapes_each_port_for_wildcard_hosts(self): rel_ids = self.setup_charm_relations() self.assertEqual(len(rel_ids), 1) rel_id = rel_ids[0] jobs = self.harness.charm.prometheus_consumer.jobs() self.assertEqual(len(jobs), len(SCRAPE_JOBS)) ports = wildcard_target_ports(SCRAPE_JOBS) targets = wildcard_targets(jobs, ports) consumers = self.harness.charm.model.get_relation( RELATION_NAME, rel_id) self.assertEqual(len(targets), len(ports) * len(consumers.units)) def test_consumer_handles_default_scrape_job(self): self.assertEqual(self.harness.charm._stored.num_events, 0) rel_id = self.harness.add_relation(RELATION_NAME, "consumer") self.harness.update_relation_data( rel_id, "consumer", { "scrape_metadata": json.dumps(SCRAPE_METADATA), "scrape_jobs": json.dumps(DEFAULT_JOBS), }, ) self.assertEqual(self.harness.charm._stored.num_events, 1) self.harness.add_relation_unit(rel_id, "consumer/0") self.harness.update_relation_data( rel_id, "consumer/0", {"prometheus_scrape_host": "1.1.1.1"}) self.assertEqual(self.harness.charm._stored.num_events, 2) jobs = self.harness.charm.prometheus_consumer.jobs() self.validate_jobs(jobs) def test_consumer_overwrites_juju_topology_labels(self): self.assertEqual(self.harness.charm._stored.num_events, 0) rel_id = self.harness.add_relation(RELATION_NAME, "consumer") self.harness.update_relation_data( rel_id, "consumer", { "scrape_metadata": json.dumps(SCRAPE_METADATA), "scrape_jobs": json.dumps(BAD_JOBS), }, ) self.assertEqual(self.harness.charm._stored.num_events, 1) self.harness.add_relation_unit(rel_id, "consumer/0") self.harness.update_relation_data( rel_id, "consumer/0", {"prometheus_scrape_host": "1.1.1.1"}) self.assertEqual(self.harness.charm._stored.num_events, 2) jobs = self.harness.charm.prometheus_consumer.jobs() self.assertEqual(len(jobs), 1) self.validate_jobs(jobs) bad_labels = juju_job_labels(BAD_JOBS[0]) labels = juju_job_labels(jobs[0]) for label_name, label_value in labels.items(): self.assertNotEqual(label_value, bad_labels[label_name]) def test_consumer_returns_alerts_indexed_by_group_name(self): self.assertEqual(self.harness.charm._stored.num_events, 0) rel_id = self.harness.add_relation(RELATION_NAME, "consumer") self.harness.update_relation_data( rel_id, "consumer", { "scrape_metadata": json.dumps(SCRAPE_METADATA), "alert_rules": json.dumps(ALERT_RULES), }, ) self.harness.add_relation_unit(rel_id, "consumer/0") self.assertEqual(self.harness.charm._stored.num_events, 1) alerts = self.harness.charm.prometheus_consumer.alerts() self.assertEqual(len(alerts), 1) for name, alert_group in alerts.items(): group = next( (group for group in ALERT_RULES["groups"] if group["name"] == name), None) self.assertDictEqual(alert_group, group) def test_consumer_logs_an_error_on_missing_alerting_data(self): self.assertEqual(self.harness.charm._stored.num_events, 0) bad_metadata = {"bad": "metadata"} bad_rules = {"bad": "rule"} rel_id = self.harness.add_relation(RELATION_NAME, "consumer") self.harness.update_relation_data( rel_id, "consumer", { "scrape_metadata": json.dumps(bad_metadata), "alert_rules": json.dumps(bad_rules), }, ) self.harness.add_relation_unit(rel_id, "consumer/0") self.assertEqual(self.harness.charm._stored.num_events, 1) with self.assertLogs(level="ERROR") as logger: _ = self.harness.charm.prometheus_consumer.alerts() messages = sorted(logger.output) self.assertEqual(len(messages), 1) self.assertIn(f"Relation {rel_id} has invalid data", messages[0])
def test_on_install(self): harness = Harness(OPAAuditCharm) self.addCleanup(harness.cleanup) harness.begin() assert harness.charm._on_install({}) is None
class TestDatase(unittest.TestCase): def setUp(self): self.harness = Harness(MySQLOperatorCharm) self.addCleanup(self.harness.cleanup) self.harness.begin() self.peer_relation_id = self.harness.add_relation( "database-peers", "database-peers") self.harness.add_relation_unit(self.peer_relation_id, "mysql/1") self.database_relation_id = self.harness.add_relation( DB_RELATION_NAME, "app") self.harness.add_relation_unit(self.database_relation_id, "app/0") self.charm = self.harness.charm @patch_network_get(private_address="1.1.1.1") @patch("mysqlsh_helpers.MySQL.get_mysql_version", return_value="8.0.29-0ubuntu0.20.04.3") @patch( "mysqlsh_helpers.MySQL.get_cluster_members_addresses", return_value={"2.2.2.1:3306", "2.2.2.3:3306", "2.2.2.2:3306"}, ) @patch("mysqlsh_helpers.MySQL.get_cluster_primary_address", return_value="2.2.2.2:3306") @patch("mysqlsh_helpers.MySQL.create_application_database_and_scoped_user") @patch("relations.database.generate_random_password", return_value="super_secure_password") def test_database_requested( self, _generate_random_password, _create_application_database_and_scoped_user, _get_cluster_primary_address, _get_cluster_members_addresses, _get_mysql_version, ): # run start-up events to enable usage of the helper class self.harness.set_leader(True) self.charm.on.config_changed.emit() # confirm that the relation databag is empty database_relation_databag = self.harness.get_relation_data( self.database_relation_id, self.harness.charm.app) database_relation = self.charm.model.get_relation(DB_RELATION_NAME) app_unit = list(database_relation.units)[0] # simulate cluster initialized by editing the flag self.harness.update_relation_data(self.peer_relation_id, self.charm.app.name, {"units-added-to-cluster": "1"}) self.assertEqual(database_relation_databag, {}) self.assertEqual(database_relation.data.get(app_unit), {}) self.assertEqual(database_relation.data.get(self.charm.unit), {}) # update the app leader unit data to trigger database_requested event self.harness.update_relation_data(self.database_relation_id, "app", {"database": "test_db"}) self.assertEqual( database_relation_databag, { "data": '{"database": "test_db"}', "password": "******", "username": f"relation-{self.database_relation_id}", "endpoints": "2.2.2.2:3306", "version": "8.0.29-0ubuntu0.20.04.3", "read-only-endpoints": "2.2.2.1:3306,2.2.2.3:3306", }, ) _generate_random_password.assert_called_once() _create_application_database_and_scoped_user.assert_called_once() _get_cluster_primary_address.assert_called_once() _get_cluster_members_addresses.assert_called_once() _get_mysql_version.assert_called_once() @patch_network_get(private_address="1.1.1.1") @patch("mysqlsh_helpers.MySQL.delete_user_for_relation") def test_database_broken(self, _delete_user_for_relation): # run start-up events to enable usage of the helper class self.harness.set_leader(True) self.charm.on.config_changed.emit() self.harness.remove_relation(self.database_relation_id) _delete_user_for_relation.assert_called_once_with( self.database_relation_id) @patch_network_get(private_address="1.1.1.1") @patch("mysqlsh_helpers.MySQL.delete_user_for_relation") def test_database_broken_failure(self, _delete_user_for_relation): # run start-up events to enable usage of the helper class self.harness.set_leader(True) self.charm.on.config_changed.emit() _delete_user_for_relation.side_effect = MySQLDeleteUserForRelationError( ) self.harness.remove_relation(self.database_relation_id) _delete_user_for_relation.assert_called_once()
def test_configure_pod(self): harness = Harness(OPAAuditCharm) self.addCleanup(harness.cleanup) harness.begin() assert harness.charm._configure_pod() is None
class TestCharm(unittest.TestCase): def setUp(self): self.harness = Harness(GitlabRunnerCharm) self.addCleanup(self.harness.cleanup) self.harness.begin()
class TestCephISCSIGatewayPeers(unittest.TestCase): def setUp(self): self.harness = Harness(CharmBase, meta=''' name: ceph-iscsi peers: cluster: interface: ceph-iscsi-peer ''') @mock.patch.object(CephISCSIGatewayPeers, 'cluster_bind_address', new_callable=PropertyMock) @mock.patch('socket.getfqdn') def test_on_changed(self, _getfqdn, _cluster_bind_address): our_fqdn = 'ceph-iscsi-0.example' _getfqdn.return_value = our_fqdn # TODO: Replace this with calls to the test harness once # https://github.com/canonical/operator/issues/222 is fixed. _cluster_bind_address.return_value = '192.0.2.1' class TestReceiver(framework.Object): def __init__(self, parent, key): super().__init__(parent, key) self.observed_events = [] def on_ready_peers(self, event): self.observed_events.append(event) self.harness.begin() self.peers = CephISCSIGatewayPeers(self.harness.charm, 'cluster') receiver = TestReceiver(self.harness.framework, 'receiver') self.harness.framework.observe(self.peers.on.ready_peers, receiver) relation_id = self.harness.add_relation('cluster', 'ceph-iscsi') self.harness.add_relation_unit( relation_id, 'ceph-iscsi/1', { 'ingress-address': '192.0.2.2', 'gateway_ready': 'True', 'gateway_fqdn': 'ceph-iscsi-1.example' }) self.assertEqual(len(receiver.observed_events), 1) self.assertIsInstance(receiver.observed_events[0], ReadyPeersEvent) def test_set_admin_password(self): self.harness.set_leader() self.harness.begin() self.peers = CephISCSIGatewayPeers(self.harness.charm, 'cluster') self.harness.add_relation('cluster', 'ceph-iscsi') self.peers.set_admin_password('s3cr3t') rel_data = self.harness.charm.model.get_relation('cluster').data our_app = self.harness.charm.app self.assertEqual(rel_data[our_app]['admin_password'], 's3cr3t') @mock.patch('socket.getfqdn') def test_announce_ready(self, _getfqdn): our_fqdn = 'ceph-iscsi-0.example' _getfqdn.return_value = our_fqdn self.harness.begin() self.peers = CephISCSIGatewayPeers(self.harness.charm, 'cluster') self.harness.add_relation('cluster', 'ceph-iscsi') self.peers.announce_ready() rel_data = self.harness.charm.model.get_relation('cluster').data our_unit = self.harness.charm.unit self.assertEqual(rel_data[our_unit]['gateway_fqdn'], our_fqdn) self.assertEqual(rel_data[our_unit]['gateway_ready'], 'True') @mock.patch.object(CephISCSIGatewayPeers, 'cluster_bind_address', new_callable=PropertyMock) @mock.patch('socket.getfqdn') def test_ready_peer_details(self, _getfqdn, _cluster_bind_address): _getfqdn.return_value = 'ceph-iscsi-0.example' # TODO: Replace this with calls to the test harness once # https://github.com/canonical/operator/issues/222 is fixed. _cluster_bind_address.return_value = '192.0.2.1' self.harness.begin() self.peers = CephISCSIGatewayPeers(self.harness.charm, 'cluster') relation_id = self.harness.add_relation('cluster', 'ceph-iscsi') self.harness.add_relation_unit( relation_id, 'ceph-iscsi/1', { 'ingress-address': '192.0.2.2', 'gateway_ready': 'True', 'gateway_fqdn': 'ceph-iscsi-1.example' }) self.harness.add_relation_unit( relation_id, 'ceph-iscsi/2', { 'ingress-address': '192.0.2.3', 'gateway_ready': 'True', 'gateway_fqdn': 'ceph-iscsi-2.example', }) self.harness.add_relation_unit(relation_id, 'ceph-iscsi/3', {'ingress-address': '192.0.2.4'}) self.peers.ready_peer_details @mock.patch.object(interface_ceph_iscsi_peer.CephISCSIGatewayPeers, 'cluster_bind_address', new_callable=PropertyMock) def test_ready_peer_addresses(self, _cluster_bind_address): # TODO: Replace this with calls to the test harness once # https://github.com/canonical/operator/issues/222 is fixed. _cluster_bind_address.return_value = '192.0.2.1' self.harness.begin() self.peers = CephISCSIGatewayPeers(self.harness.charm, 'cluster') relation_id = self.harness.add_relation('cluster', 'ceph-iscsi') self.harness.add_relation_unit( relation_id, 'ceph-iscsi/1', { 'ingress-address': '192.0.2.2', 'gateway_ready': 'True', 'gateway_fqdn': 'ceph-iscsi-1.example' }) self.harness.add_relation_unit( relation_id, 'ceph-iscsi/2', { 'ingress-address': '192.0.2.3', 'gateway_ready': 'True', 'gateway_fqdn': 'ceph-iscsi-2.example', }) self.assertEqual(['192.0.2.1', '192.0.2.2', '192.0.2.3'], self.peers.peer_addresses)
class TestDBRouter(unittest.TestCase): def setUp(self): self.harness = Harness(MySQLOperatorCharm) self.addCleanup(self.harness.cleanup) self.harness.begin() self.peer_relation_id = self.harness.add_relation( "database-peers", "database-peers") self.harness.add_relation_unit(self.peer_relation_id, "mysql/1") self.db_router_relation_id = self.harness.add_relation( "db-router", "app") self.harness.add_relation_unit(self.db_router_relation_id, "app/0") self.charm = self.harness.charm @patch_network_get(private_address="1.1.1.1") @patch("relations.db_router.generate_random_password", return_value="super_secure_password") @patch("mysqlsh_helpers.MySQL.get_cluster_primary_address", return_value="2.2.2.2") @patch("mysqlsh_helpers.MySQL.does_mysql_user_exist", return_value=False) @patch("mysqlsh_helpers.MySQL.configure_mysqlrouter_user") @patch("mysqlsh_helpers.MySQL.create_application_database_and_scoped_user") def test_db_router_relation_changed( self, _create_application_database_and_scoped_user, _configure_mysqlrouter_user, _does_mysql_user_exist, _get_cluster_primary_address, _generate_random_password, ): # run start-up events to enable usage of the helper class self.harness.set_leader(True) self.charm.on.config_changed.emit() # confirm that the relation databag is empty db_router_relation_databag = self.harness.get_relation_data( self.db_router_relation_id, self.harness.charm.app) db_router_relation = self.charm.model.get_relation("db-router") app_unit = list(db_router_relation.units)[0] self.assertEqual(db_router_relation_databag, {}) self.assertEqual(db_router_relation.data.get(app_unit), {}) self.assertEqual(db_router_relation.data.get(self.charm.unit), {}) # update the app leader unit data to trigger db_router_relation_changed event self.harness.update_relation_data( self.db_router_relation_id, "app/0", { "MRUP_database": "keystone_database", "MRUP_hostname": "1.1.1.2", "MRUP_username": "******", "mysqlrouter_hostname": "1.1.1.3", "mysqlrouter_username": "******", }, ) self.assertEqual(_generate_random_password.call_count, 2) self.assertEqual(_does_mysql_user_exist.call_count, 2) self.assertEqual( sorted(_does_mysql_user_exist.mock_calls), sorted([ call("mysqlrouteruser", "1.1.1.3"), call("keystone_user", "1.1.1.2"), ]), ) _configure_mysqlrouter_user.assert_called_once_with( "mysqlrouteruser", "super_secure_password", "1.1.1.3", "app/0") _create_application_database_and_scoped_user.assert_called_once_with( "keystone_database", "keystone_user", "super_secure_password", "1.1.1.2", "app/0") # confirm that credentials in the mysql leader unit databag is set correctly self.assertEqual( db_router_relation.data.get(app_unit), { "MRUP_database": "keystone_database", "MRUP_hostname": "1.1.1.2", "MRUP_username": "******", "mysqlrouter_hostname": "1.1.1.3", "mysqlrouter_username": "******", }, ) self.assertEqual( db_router_relation.data.get(self.charm.unit), { "db_host": '"2.2.2.2"', "mysqlrouter_password": '******', "mysqlrouter_allowed_units": '"app/0"', "MRUP_password": '******', "MRUP_allowed_units": '"app/0"', }, ) @patch_network_get(private_address="1.1.1.1") @patch("relations.db_router.generate_random_password", return_value="super_secure_password") @patch("mysqlsh_helpers.MySQL.does_mysql_user_exist", return_value=False) @patch("mysqlsh_helpers.MySQL.configure_mysqlrouter_user") @patch("mysqlsh_helpers.MySQL.create_application_database_and_scoped_user") def test_db_router_relation_changed_exceptions( self, _create_application_database_and_scoped_user, _configure_mysqlrouter_user, _does_mysql_user_exist, _generate_random_password, ): # run start-up events to enable usage of the helper class self.harness.set_leader(True) self.charm.on.config_changed.emit() # confirm that the relation databag is empty db_router_relation_databag = self.harness.get_relation_data( self.db_router_relation_id, self.harness.charm.app) db_router_relation = self.charm.model.get_relation("db-router") app_unit = list(db_router_relation.units)[0] self.assertEqual(db_router_relation_databag, {}) self.assertEqual(db_router_relation.data.get(app_unit), {}) self.assertEqual(db_router_relation.data.get(self.charm.unit), {}) # test an exception while configuring mysql users _does_mysql_user_exist.side_effect = MySQLCheckUserExistenceError self.harness.update_relation_data( self.db_router_relation_id, "app/0", { "MRUP_database": "keystone_database", "MRUP_hostname": "1.1.1.2", "MRUP_username": "******", "mysqlrouter_hostname": "1.1.1.3", "mysqlrouter_username": "******", }, ) self.assertTrue( isinstance(self.harness.model.unit.status, BlockedStatus)) _does_mysql_user_exist.reset_mock() # test an exception while creating the mysql router user _configure_mysqlrouter_user.side_effect = MySQLConfigureRouterUserError self.harness.update_relation_data( self.db_router_relation_id, "app/0", { "MRUP_database": "keystone_database", "MRUP_hostname": "1.1.1.2", "MRUP_username": "******", "mysqlrouter_hostname": "1.1.1.3", "mysqlrouter_username": "******", }, ) self.assertTrue( isinstance(self.harness.model.unit.status, BlockedStatus)) _configure_mysqlrouter_user.reset_mock() # test an exception while creating the application database and scoped user _create_application_database_and_scoped_user.side_effect = ( MySQLCreateApplicationDatabaseAndScopedUserError) self.harness.update_relation_data( self.db_router_relation_id, "app/0", { "MRUP_database": "keystone_database", "MRUP_hostname": "1.1.1.2", "MRUP_username": "******", "mysqlrouter_hostname": "1.1.1.3", "mysqlrouter_username": "******", }, ) self.assertTrue( isinstance(self.harness.model.unit.status, BlockedStatus)) _create_application_database_and_scoped_user.reset_mock()
class TestCharm(unittest.TestCase): def setUp(self) -> None: self.harness = Harness(NextcloudOperatorCharm) self.addCleanup(self.harness.cleanup) self.harness.begin() def test__pod_spec(self): self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) pod_spec, _ = self.harness.get_pod_spec() self.assertEqual( BASE_CONFIG['port'], pod_spec.get('containers')[0].get('ports')[0].get('containerPort')) self.assertEqual( BASE_CONFIG['image'], pod_spec.get('containers')[0].get('imageDetails').get('imagePath')) def test__database_relation_data(self): self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) self.assertEqual(self.harness.charm.state.database, {}) # # add relation and update relation data rel_id = self.harness.add_relation('database', 'mysql') rel = self.harness.model.get_relation('database') self.harness.add_relation_unit(rel_id, 'mysql/0') test_relation_data = { 'type': 'mysql', 'host': '0.1.2.3:3306', 'name': 'my-test-db', 'user': '******', 'password': '******', } self.harness.update_relation_data(rel_id, 'mysql/0', test_relation_data) # # check that charm datastore was properly set self.assertEqual(dict(self.harness.charm.state.database), test_relation_data) # # now depart this relation and ensure the datastore is emptied self.harness.charm.on.database_relation_broken.emit(rel) self.assertEqual({}, dict(self.harness.charm.state.database)) def test__update_pod_env_config(self): self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) # test mysql self.harness.charm.state.database = { 'type': 'mysql', 'host': '0.1.2.3:3306', 'name': 'mysql-test-db', 'user': '******', 'password': '******' } expected_config = { 'MYSQL_DATABASE': 'mysql-test-db', 'MYSQL_USER': '******', 'MYSQL_PASSWORD': '******', 'MYSQL_HOST': '0.1.2.3:3306' } pod_spec, _ = self.harness.get_pod_spec() self.harness.charm._update_pod_env_config(pod_spec) self.assertEqual(pod_spec['containers'][0]['envConfig'], expected_config) # test postgresql self.harness.charm.state.database = { 'type': 'postgres', 'host': '0.1.2.3:5432', 'name': 'pg-test-db', 'user': '******', 'password': '******' } expected_config = { 'POSTGRES_DB': 'pg-test-db', 'POSTGRES_USER': '******', 'POSTGRES_PASSWORD': '******', 'POSTGRES_HOST': '0.1.2.3:5432' } pod_spec, _ = self.harness.get_pod_spec() self.harness.charm._update_pod_env_config(pod_spec) self.assertEqual(pod_spec['containers'][0]['envConfig'], expected_config)
class TestCharm(unittest.TestCase): def setUp(self): self.harness = Harness(PrometheusCharm) self.addCleanup(self.harness.cleanup) self.harness.begin() def test_image_path_is_required(self): missing_image_config = { 'prometheus-image-path': '', 'prometheus-image-username': '', 'prometheus-image-password': '' } with self.assertLogs(level='ERROR') as logger: self.harness.update_config(missing_image_config) expected_logs = [ "ERROR:charm:Incomplete Configuration : ['prometheus-image-path']. " "Application will be blocked." ] self.assertEqual(sorted(logger.output), expected_logs) missing = self.harness.charm._check_config() expected = ['prometheus-image-path'] self.assertEqual(missing, expected) def test_password_is_required_when_username_is_set(self): missing_password_config = { 'prometheus-image-path': 'prom/prometheus:latest', 'prometheus-image-username': '******', 'prometheus-image-password': '', } with self.assertLogs(level='ERROR') as logger: self.harness.update_config(missing_password_config) expected_logs = [ "ERROR:charm:Incomplete Configuration : ['prometheus-image-password']. " "Application will be blocked." ] self.assertEqual(sorted(logger.output), expected_logs) missing = self.harness.charm._check_config() expected = ['prometheus-image-password'] self.assertEqual(missing, expected) def test_alerting_config_is_updated_by_alertmanager_relation(self): self.harness.set_leader(True) # check alerting config is empty without alertmanager relation self.harness.update_config(MINIMAL_CONFIG) self.assertEqual(self.harness.charm._stored.alertmanagers, []) rel_id = self.harness.add_relation('alertmanager', 'alertmanager') self.assertIsInstance(rel_id, int) self.harness.add_relation_unit(rel_id, 'alertmanager/0') pod_spec = self.harness.get_pod_spec() self.assertEqual(alerting_config(pod_spec), None) # check alerting config is updated when a alertmanager joins self.harness.update_relation_data(rel_id, 'alertmanager', {'port': '9093'}) self.harness.update_relation_data(rel_id, 'alertmanager/0', {'ingress-address': '192.169.0.1'}) pod_spec = self.harness.get_pod_spec() self.assertEqual(alerting_config(pod_spec), SAMPLE_ALERTING_CONFIG) def test_alerting_config_is_removed_when_alertmanager_departs(self): self.harness.set_leader(True) # ensure there is a non-empty alerting config self.harness.update_config(MINIMAL_CONFIG) rel_id = self.harness.add_relation('alertmanager', 'alertmanager') rel = self.harness.model.get_relation('alertmanager') self.assertIsInstance(rel_id, int) self.harness.add_relation_unit(rel_id, 'alertmanager/0') self.harness.update_relation_data(rel_id, 'alertmanager', {'port': '9093'}) self.harness.update_relation_data(rel_id, 'alertmanager/0', {'ingress-address': '192.169.0.1'}) pod_spec = self.harness.get_pod_spec() self.assertEqual(alerting_config(pod_spec), SAMPLE_ALERTING_CONFIG) # check alerting config is removed when relation departs self.harness.charm.on.alerting_relation_departed.emit(rel) pod_spec = self.harness.get_pod_spec() self.assertEqual(alerting_config(pod_spec), None) def test_grafana_is_provided_port_and_source(self): self.harness.set_leader(True) self.harness.update_config(MINIMAL_CONFIG) rel_id = self.harness.add_relation('grafana-source', 'grafana') self.harness.add_relation_unit(rel_id, 'grafana/0') self.harness.update_relation_data(rel_id, 'grafana/0', {}) data = self.harness.get_relation_data(rel_id, self.harness.model.unit.name) self.assertEqual(int(data['port']), MINIMAL_CONFIG['port']) self.assertEqual(data['source-type'], 'prometheus') def test_default_cli_log_level_is_info(self): self.harness.set_leader(True) self.harness.update_config(MINIMAL_CONFIG) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--log.level'), 'info') def test_invalid_log_level_defaults_to_debug(self): self.harness.set_leader(True) bad_log_config = MINIMAL_CONFIG.copy() bad_log_config['log-level'] = 'bad-level' with self.assertLogs(level='ERROR') as logger: self.harness.update_config(bad_log_config) expected_logs = [ "ERROR:root:Invalid loglevel: bad-level given, " "debug/info/warn/error/fatal allowed. " "defaulting to DEBUG loglevel." ] self.assertEqual(sorted(logger.output), expected_logs) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--log.level'), 'debug') def test_valid_log_level_is_accepted(self): self.harness.set_leader(True) valid_log_config = MINIMAL_CONFIG.copy() valid_log_config['log-level'] = 'warn' self.harness.update_config(valid_log_config) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--log.level'), 'warn') def test_tsdb_compression_is_not_enabled_by_default(self): self.harness.set_leader(True) compress_config = MINIMAL_CONFIG.copy() self.harness.update_config(compress_config) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--storage.tsdb.wal-compression'), None) def test_tsdb_compression_can_be_enabled(self): self.harness.set_leader(True) compress_config = MINIMAL_CONFIG.copy() compress_config['tsdb-wal-compression'] = True self.harness.update_config(compress_config) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--storage.tsdb.wal-compression'), '--storage.tsdb.wal-compression') def test_valid_tsdb_retention_times_can_be_set(self): self.harness.set_leader(True) retention_time_config = MINIMAL_CONFIG.copy() acceptable_units = ['y', 'w', 'd', 'h', 'm', 's'] for unit in acceptable_units: retention_time = '{}{}'.format(1, unit) retention_time_config['tsdb-retention-time'] = retention_time self.harness.update_config(retention_time_config) pod_spec = self.harness.get_pod_spec() self.assertEqual( cli_arg(pod_spec, '--storage.tsdb.retention.time'), retention_time) def test_invalid_tsdb_retention_times_can_not_be_set(self): self.harness.set_leader(True) retention_time_config = MINIMAL_CONFIG.copy() # invalid unit retention_time = '{}{}'.format(1, 'x') retention_time_config['tsdb-retention-time'] = retention_time with self.assertLogs(level='ERROR') as logger: self.harness.update_config(retention_time_config) expected_logs = ["ERROR:charm:Invalid unit x in time spec"] self.assertEqual(sorted(logger.output), expected_logs) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--storage.tsdb.retention.time'), None) # invalid time value retention_time = '{}{}'.format(0, 'd') retention_time_config['tsdb-retention-time'] = retention_time with self.assertLogs(level='ERROR') as logger: self.harness.update_config(retention_time_config) expected_logs = [ "ERROR:charm:Expected positive time spec but got 0" ] self.assertEqual(sorted(logger.output), expected_logs) pod_spec = self.harness.get_pod_spec() self.assertEqual(cli_arg(pod_spec, '--storage.tsdb.retention.time'), None) def test_global_scrape_interval_can_be_set(self): self.harness.set_leader(True) scrapeint_config = MINIMAL_CONFIG.copy() acceptable_units = ['y', 'w', 'd', 'h', 'm', 's'] for unit in acceptable_units: scrapeint_config['scrape-interval'] = '{}{}'.format(1, unit) self.harness.update_config(scrapeint_config) pod_spec = self.harness.get_pod_spec() gconfig = global_config(pod_spec) self.assertEqual(gconfig['scrape_interval'], scrapeint_config['scrape-interval']) def test_global_scrape_timeout_can_be_set(self): self.harness.set_leader(True) scrapetime_config = MINIMAL_CONFIG.copy() acceptable_units = ['y', 'w', 'd', 'h', 'm', 's'] for unit in acceptable_units: scrapetime_config['scrape-timeout'] = '{}{}'.format(1, unit) self.harness.update_config(scrapetime_config) pod_spec = self.harness.get_pod_spec() gconfig = global_config(pod_spec) self.assertEqual(gconfig['scrape_timeout'], scrapetime_config['scrape-timeout']) def test_global_evaluation_interval_can_be_set(self): self.harness.set_leader(True) evalint_config = MINIMAL_CONFIG.copy() acceptable_units = ['y', 'w', 'd', 'h', 'm', 's'] for unit in acceptable_units: evalint_config['evaluation-interval'] = '{}{}'.format(1, unit) self.harness.update_config(evalint_config) pod_spec = self.harness.get_pod_spec() gconfig = global_config(pod_spec) self.assertEqual(gconfig['evaluation_interval'], evalint_config['evaluation-interval']) def test_valid_external_labels_can_be_set(self): self.harness.set_leader(True) label_config = MINIMAL_CONFIG.copy() labels = {'name1': 'value1', 'name2': 'value2'} label_config['external-labels'] = json.dumps(labels) self.harness.update_config(label_config) pod_spec = self.harness.get_pod_spec() gconfig = global_config(pod_spec) self.assertIsNotNone(gconfig['external_labels']) self.assertEqual(labels, gconfig['external_labels']) def test_invalid_external_labels_can_not_be_set(self): self.harness.set_leader(True) label_config = MINIMAL_CONFIG.copy() # label value must be string labels = {'name': 1} label_config['external-labels'] = json.dumps(labels) with self.assertLogs(level='ERROR') as logger: self.harness.update_config(label_config) expected_logs = [ "ERROR:charm:External label keys/values must be strings" ] self.assertEqual(sorted(logger.output), expected_logs) pod_spec = self.harness.get_pod_spec() gconfig = global_config(pod_spec) self.assertIsNone(gconfig.get('external_labels')) def test_default_scrape_config_is_always_set(self): self.harness.set_leader(True) self.harness.update_config(MINIMAL_CONFIG) pod_spec = self.harness.get_pod_spec() prometheus_scrape_config = scrape_config(pod_spec, 'prometheus') self.assertIsNotNone(prometheus_scrape_config, 'No default config found')
class TestKuberneteseInfluxdbCharm(unittest.TestCase): def setUp(self) -> None: """Setup the harness object.""" self.harness = Harness(KubernetesInfluxdbCharm) self.harness.begin() self.harness.add_oci_resource("influxdb2-image") def tearDown(self): """Cleanup the harness.""" self.harness.cleanup() # # Hooks # def test__on_config_changed(self) -> None: self.harness.update_config({"port": "9999"}) self.assertEqual(self.harness.charm.unit.status, ActiveStatus("Pod is ready")) def test__on_config_changed_pebble_api_connection_error_1(self) -> None: self.harness.charm.unit.get_container = mock.MagicMock() self.harness.charm.unit.get_container.return_value.get_plan = mock.MagicMock( side_effect=ConnectionError("connection timeout")) with self.assertLogs(level="DEBUG") as logger: self.harness.update_config({"port": "9999"}) self.assertIn( "DEBUG:charm:The Pebble API is not ready yet. Error message: connection timeout", logger.output, ) self.assertNotIn( "DEBUG:charm:Pebble plan has already been loaded. No need to update the config.", logger.output, ) def test__on_config_changed_pebble_api_connection_error_2(self) -> None: self.harness.charm.unit.get_container = mock.MagicMock() self.harness.charm.unit.get_container.return_value.get_plan.return_value.to_dict = ( mock.MagicMock(return_value={})) self.harness.charm.unit.get_container.return_value.add_layer = mock.MagicMock( side_effect=ConnectionError("connection timeout")) with self.assertLogs(level="DEBUG") as logger: self.harness.update_config({"port": "9999"}) self.assertIn( "DEBUG:charm:The Pebble API is not ready yet. Error message: connection timeout", logger.output, ) self.assertNotIn( "DEBUG:charm:Pebble plan has already been loaded. No need to update the config.", logger.output, ) def test__on_config_changed_same_plan(self) -> None: self.harness.charm.unit.get_container = mock.MagicMock() self.harness.charm.unit.get_container.return_value.get_plan.return_value.to_dict = ( mock.MagicMock(return_value=self.harness.charm._influxdb2_layer())) with self.assertLogs(level="DEBUG") as logger: self.harness.update_config({"port": "9999"}) self.assertIn( "DEBUG:charm:Pebble plan has already been loaded. No need to update the config.", logger.output, ) self.assertNotIn( "DEBUG:charm:The Pebble API is not ready yet. Error message: connection timeout", logger.output, ) # # Test Relations # def test__grafana_source_data(self, expected_reldata: Optional[Dict] = None ) -> None: # Initialize values interface: str = "grafana-source" rel_app: str = "grafana" rel_unit: str = "grafana/0" rel_data: Dict[str, str] = {} expected: Dict[str, str] = {} if expected_reldata is None: # relation not initialized expected_reldata = { key: "" for key in ["private-address", "port", "source-type"] } # Initialize unit state (related to grafana) rel_id = self.harness.add_relation(interface, rel_app) self.harness.add_relation_unit(rel_id, rel_unit) # Trigger the -relation-changed hook, which will call the observed event self.harness.update_relation_data(rel_id, rel_app, rel_data) self.assertIsInstance(rel_id, int) # Verify the relation data set by the influxdb2 charm relation = self.harness.model.get_relation(interface) for key, expected_val in expected.items(): self.assertEqual( relation.data[self.harness.charm.unit].get(key, ""), expected_val) @mock.patch("subprocess.check_output") def test__grafana_source_data_leader( self, mock_check_output: mock.MagicMock) -> None: mock_check_output.return_value = b"10.0.0.1" expected_reldata: Dict[str, str] = { "private-address": "10.0.0.1", "port": "8086", "source-type": "influxdb", } self.harness.set_leader(True) self.test__grafana_source_data(expected_reldata=expected_reldata) # # Test Helpers # def test__influxdb2_layer(self) -> None: expected = { "summary": "influxdb2 layer", "description": "pebble config layer for influxdb2", "services": { "influxdb2": { "override": "replace", "summary": "influxdb2 service", "command": "/entrypoint.sh influxd", "startup": "enabled", "environment": { "DOCKER_INFLUXDB_INIT_MODE": "setup", "DOCKER_INFLUXDB_INIT_USERNAME": "******", "DOCKER_INFLUXDB_INIT_PASSWORD": "******", "DOCKER_INFLUXDB_INIT_ORG": "influxdata", "DOCKER_INFLUXDB_INIT_BUCKET": "default", "DOCKER_INFLUXDB_INIT_RETENTION": "0s", "DOCKER_INFLUXDB_INIT_ADMIN_TOKEN": "asdfasdfasdf", "INFLUXD_BOLT_PATH": "/var/lib/influxdbv2/influxd.bolt", "INFLUXD_ENGINE_PATH": "/var/lib/influxdbv2", "INFLUXD_HTTP_BIND_ADDRESS": ":8086", }, } }, } self.assertEqual(set(self.harness.charm._influxdb2_layer()), set(expected)) def test__is_running(self) -> None: container = self.harness.charm.unit.get_container(WORKLOAD_CONTAINER) service_not_running = self.harness.charm._is_running( container, "influxd") self.assertFalse(service_not_running)
class TestCharm(unittest.TestCase): """Test script for checking relations""" def setUp(self) -> NoReturn: """Test setup""" self.harness = Harness(WebuiCharm) self.harness.set_leader(is_leader=True) self.harness.begin() def test_on_start_without_relations(self) -> NoReturn: """Test installation without any relation.""" self.harness.charm.on.config_changed.emit() # Verifying status self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) # Verifying status message self.assertGreater(len(self.harness.charm.unit.status.message), 0) self.assertTrue( self.harness.charm.unit.status.message.startswith("Waiting for ") ) def test_on_start_with_relations(self) -> NoReturn: """Test installation with any relation.""" expected_result = { "version": 3, "containers": [ { "name": "webui", "imageDetails": self.harness.charm.image.fetch(), "imagePullPolicy": "Always", "ports": [ { "name": "webui", "containerPort": 5000, "protocol": "TCP", } ], "envConfig": { "ALLOW_ANONYMOUS_LOGIN": "******", "GIN_MODE": "release", "MONGODB_URI": "mongodb://mongodb:27017", "MONGODB_HOST": "mongodb", }, "command": ["./webui_start.sh", "&"], } ], } self.harness.charm.on.start.emit() # Check if nrf is initialized self.assertIsNone(self.harness.charm.state.mongodb_host) # Initializing the nrf relation mongodb_relation_id = self.harness.add_relation("mongodb", "mongodb") self.harness.add_relation_unit(mongodb_relation_id, "mongodb/0") self.harness.update_relation_data( mongodb_relation_id, "mongodb", {"hostname": "mongodb", "mongodb_uri": "mongodb://mongodb:27017"}, ) # Checking if nrf data is stored self.assertEqual(self.harness.charm.state.mongodb_host, "mongodb") # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) pod_spec, _ = self.harness.get_pod_spec() self.assertDictEqual(expected_result, pod_spec) def test_on_mongodb_app_relation_changed(self) -> NoReturn: """Test to see if mongo relation is updated.""" self.assertIsNone(self.harness.charm.state.mongodb_host) relation_id = self.harness.add_relation("mongodb", "mongodb") self.harness.add_relation_unit(relation_id, "mongodb/0") self.harness.update_relation_data( relation_id, "mongodb", {"hostname": "mongodb", "mongodb_uri": "mongodb://mongodb:27017"}, ) self.assertEqual(self.harness.charm.state.mongodb_host, "mongodb") # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) # Verifying status message self.assertGreater(len(self.harness.charm.unit.status.message), 0) self.assertFalse( self.harness.charm.unit.status.message.startswith("Waiting for ") )
def harness(): _harness = Harness(SplunkCharm) _harness.set_model_name("testing") _harness.begin() yield _harness _harness.cleanup()
class TestCharm(unittest.TestCase): """Test script for checking pod spec and relations""" def setUp(self) -> NoReturn: """Test setup.""" self.harness = Harness(SmfCharm) self.harness.set_leader(is_leader=True) self.harness.begin() def test_on_start_without_relations(self) -> NoReturn: """Test installation without any relation.""" self.harness.charm.on.config_changed.emit() # Verifying status self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) # Verifying status message self.assertGreater(len(self.harness.charm.unit.status.message), 0) self.assertTrue( self.harness.charm.unit.status.message.startswith("Waiting for ")) def test_on_start_with_relations(self) -> NoReturn: """Test installation with relation.""" self.harness.charm.on.start.emit() expected_result = { "version": 3, "containers": [{ "name": "smf", "imageDetails": self.harness.charm.image.fetch(), "imagePullPolicy": "Always", "ports": [{ "name": "smf", "containerPort": 29502, "protocol": "TCP", }], "envConfig": { "ALLOW_ANONYMOUS_LOGIN": "******", "GIN_MODE": "release", "IPADDR1": "10.45.30.27", "NRF_HOST": "nrf", }, "command": ["./ipscript.sh", "&"], }], } # Check if nrf,upf is initialized self.assertIsNone(self.harness.charm.state.nrf_host) self.assertIsNone(self.harness.charm.state.upf_host) # Initializing the nrf relation nrf_relation_id = self.harness.add_relation("nrf", "nrf") self.harness.add_relation_unit(nrf_relation_id, "nrf/0") self.harness.update_relation_data(nrf_relation_id, "nrf", {"hostname": "nrf"}) # Initializing the upf relation upf_relation_id = self.harness.add_relation("upf", "upf") self.harness.add_relation_unit(upf_relation_id, "upf/0") self.harness.update_relation_data(upf_relation_id, "upf/0", {"private_address": "10.45.30.27"}) # Checking if nrf,upf data is stored self.assertEqual(self.harness.charm.state.nrf_host, "nrf") self.assertEqual(self.harness.charm.state.upf_host, "10.45.30.27") # Verifying status self.assertNotIsInstance(self.harness.charm.unit.status, BlockedStatus) pod_spec, _ = self.harness.get_pod_spec() self.assertDictEqual(expected_result, pod_spec) def test_on_upf_app_relation_changed(self) -> NoReturn: """Test to see if upf app relation is updated.""" self.harness.charm.on.start.emit() self.assertIsNone(self.harness.charm.state.upf_host) # Initializing the upf relation upf_relation_id = self.harness.add_relation("upf", "upf") self.harness.add_relation_unit(upf_relation_id, "upf/0") self.harness.update_relation_data(upf_relation_id, "upf/0", {"private_address": "upf"}) # Verifying status self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) # Verifying status message self.assertGreater(len(self.harness.charm.unit.status.message), 0) self.assertTrue( self.harness.charm.unit.status.message.startswith("Waiting for ")) def test_on_nrf_app_relation_changed(self) -> NoReturn: """Test to see if nfr relation is updated.""" self.harness.charm.on.start.emit() self.assertIsNone(self.harness.charm.state.nrf_host) relation_id = self.harness.add_relation("nrf", "nrf") self.harness.add_relation_unit(relation_id, "nrf/0") self.harness.update_relation_data(relation_id, "nrf", {"hostname": "nrf"}) # Verifying status self.assertIsInstance(self.harness.charm.unit.status, BlockedStatus) # Verifying status message self.assertGreater(len(self.harness.charm.unit.status.message), 0) self.assertTrue( self.harness.charm.unit.status.message.startswith("Waiting for "))
class CharmTest(unittest.TestCase): def setUp(self): # Setup self.harness = Harness(charm.Charm) self.harness.begin() def test__init__works_without_a_hitch(self): # Setup harness = Harness(charm.Charm) # Exercise harness.begin() def test__mysql_on_new_relation_calls_handler(self): with patch.object(charm, 'on_server_new_relation_handler', spect_set=True) as mocked_on_new_server_relation_handler: # Setup server_details = MySQLServerDetails(dict( host=str(uuid4()), port=random.randint(1, 65535), database=str(uuid4()), user=str(uuid4()), password=str(uuid4()), )) # Exercise self.harness.charm.mysql.on.new_relation.emit(server_details) # Assert assert mocked_on_new_server_relation_handler.call_count == 1 args, kwargs = mocked_on_new_server_relation_handler.call_args assert isinstance(args[0], NewMySQLRelationEvent) assert hasattr(args[0], 'server_details') assert args[0].server_details.address == server_details.address assert args[0].server_details.username == server_details.username assert args[0].server_details.database == server_details.database assert args[0].server_details.password == server_details.password assert isinstance(args[1], BoundStoredState) assert isinstance(args[2], adapters.framework.FrameworkAdapter) def test__on_config_changed_calls_handler(self): with patch.object(charm, 'on_config_changed_handler', spect_set=True) as mocked_on_config_changed_handler: # Exercise self.harness.update_config() # Assert assert mocked_on_config_changed_handler.call_count == 1 args, kwargs = mocked_on_config_changed_handler.call_args assert isinstance(args[0], ConfigChangedEvent) assert isinstance(args[1], adapters.framework.FrameworkAdapter) def test__prometheus_client_on_new_server_available_calls_handler(self): with patch.object(charm, 'on_server_new_relation_handler', spect_set=True) as mocked_on_new_server_relation_handler: # Setup server_details = PostgresServerDetails( host=str(uuid4()), port=random.randint(1, 65535), ) # Exercise self.harness.charm.prometheus_client.on.server_available.emit( server_details) # Assert assert mocked_on_new_server_relation_handler.call_count == 1 args, kwargs = mocked_on_new_server_relation_handler.call_args assert isinstance(args[0], ServerAvailableEvent) assert hasattr(args[0], 'server_details') assert args[0].server_details.host == server_details.host assert args[0].server_details.port == server_details.port assert isinstance(args[1], BoundStoredState) assert isinstance(args[2], adapters.framework.FrameworkAdapter)
def test_on_config_changed(self): harness = Harness(OPAAuditCharm) self.addCleanup(harness.cleanup) harness.begin() assert harness.charm._on_config_changed({}) is None
class TestCharm(unittest.TestCase): """Test script for checking relations""" def setUp(self) -> NoReturn: """Test setup.""" self.harness = Harness(NatappCharm) self.harness.set_leader(is_leader=True) self.harness.begin() def test_on_configure_change(self) -> NoReturn: """Test installation with any relation.""" self.harness.charm.on.config_changed.emit() config_data = "192.168.1.216" second_interface = [ {"name": "n6-network", "interface": "eth1", "ips": [config_data]} ] annot = { "annotations": {"k8s.v1.cni.cncf.io/networks": json.dumps(second_interface)} } custom_resource_def = [ { "name": "network-attachment-definitions.k8s.cni.cncf.io", "spec": { "group": "k8s.cni.cncf.io", "scope": "Namespaced", "names": { "kind": "NetworkAttachmentDefinition", "singular": "network-attachment-definition", "plural": "network-attachment-definitions", }, "versions": [{"name": "v1", "served": True, "storage": True}], }, } ] pdn_subnet = "192.168.0.0/16" pdn_ip_range_start = "192.168.1.100" pdn_ip_range_end = "192.168.1.250" pdn_gateway_ip = "192.168.1.1" ipam_body = { "type": "host-local", "subnet": pdn_subnet, "rangeStart": pdn_ip_range_start, "rangeEnd": pdn_ip_range_end, "gateway": pdn_gateway_ip, } config_body = { "cniVersion": "0.3.1", "name": "n6-network", "type": "macvlan", "master": "ens3", "mode": "bridge", "ipam": ipam_body, } custom_resource = { "network-attachment-definitions.k8s.cni.cncf.io": [ { "apiVersion": "k8s.cni.cncf.io/v1", "kind": "NetworkAttachmentDefinition", "metadata": {"name": "n6-network"}, "spec": {"config": json.dumps(config_body)}, } ] } expected_result = { "version": 3, "containers": [ { "name": "natapp", "imageDetails": self.harness.charm.image.fetch(), "imagePullPolicy": "Always", "ports": [ { "name": "natapp", "containerPort": 2601, "protocol": "UDP", } ], "command": ["./start.sh", "&"], "kubernetes": {"securityContext": {"privileged": True}}, } ], "kubernetesResources": { "customResourceDefinitions": custom_resource_def, "customResources": custom_resource, "pod": annot, }, } # 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_natapp_info(self) -> NoReturn: """Test to see if upf relation is updated.""" expected_result = { "hostname": "natapp", "static_ip": "192.168.70.15", } relation_id = self.harness.add_relation("natapp", "upf1") self.harness.add_relation_unit(relation_id, "upf1/0") relation_data = {"hostname": "natapp", "static_ip": "192.168.70.15"} self.harness.update_relation_data(relation_id, "natapp", relation_data) relation_data = self.harness.get_relation_data(relation_id, "natapp") self.assertDictEqual(expected_result, relation_data)
class TestCharm(unittest.TestCase): def setUp(self) -> None: # charm setup self.harness = Harness(GraylogCharm) self.addCleanup(self.harness.cleanup) self.harness.begin() self.harness.add_oci_resource('graylog-image') # patches self.mock_bind_address = \ mock.patch('charm.GraylogCharm.bind_address', new_callable=mock.PropertyMock) self.mock_external_uri = \ mock.patch('charm.GraylogCharm.external_uri', new_callable=mock.PropertyMock) self.mock_bind_address.start() self.mock_external_uri.start() # cleanup self.addCleanup(self.mock_bind_address.stop) self.addCleanup(self.mock_external_uri.stop) def test_pod_spec_port(self): self.harness.set_leader(True) # pretend to have mongo and elasticsearch self.harness.charm._stored.mongodb_uri = 'mongo://test_uri/' self.harness.charm._stored.elasticsearch_uri = 'http://test_es_uri' self.harness.update_config(BASE_CONFIG) self.harness.charm.on.config_changed.emit() spec, _ = self.harness.get_pod_spec() expected_port = 9000 actual_port = spec['containers'][0]['ports'][0]['containerPort'] self.assertEqual(expected_port, actual_port) def test_elasticsearch_and_mongodb_conn_strings(self): self.harness.set_leader(True) self.harness.update_config(BASE_CONFIG) # add the elasticsearch relation es_rel_id = self.harness.add_relation('elasticsearch', 'elasticsearch') mongo_rel_id = self.harness.add_relation('mongodb', 'mongodb') self.harness.add_relation_unit(es_rel_id, 'elasticsearch/0') self.harness.add_relation_unit(mongo_rel_id, 'mongodb/0') # add elasticsearch relation data es_rel_data = { 'ingress-address': '10.183.1.2', 'port': 9200, } self.harness.update_relation_data(es_rel_id, 'elasticsearch/0', es_rel_data) self.assertTrue(self.harness.charm.has_elasticsearch) # add mongodb relation data mongo_rel_data = { 'replica_set_uri': 'mongo://10.0.0.2:14001,10.0.0.3:14002', 'replicated': 'True', 'replica_set_name': 'rs0', } self.harness.update_relation_data(mongo_rel_id, 'mongodb/0', mongo_rel_data) self.assertTrue(self.harness.charm.has_mongodb) # test that elasticsearch-uri properly made it to the _stored variable expected_uri = 'http://10.183.1.2:9200' self.assertEqual(expected_uri, self.harness.charm._stored.elasticsearch_uri) # now emit the relation broken events and make sure the _stored variables are cleared es_rel = self.harness.model.get_relation('elasticsearch') mongo_rel = self.harness.model.get_relation('mongodb') self.harness.charm.on.elasticsearch_relation_broken.emit(es_rel) self.harness.charm.on.mongodb_relation_broken.emit(mongo_rel) self.assertEqual(str(), self.harness.charm._stored.elasticsearch_uri) self.assertEqual(str(), self.harness.charm._stored.mongodb_uri) def test_blocking_without_mongodb_and_elasticsearch(self): self.harness.set_leader(True) with self.assertLogs(level='WARNING') as logger: self.harness.update_config(BASE_CONFIG) msg = 'WARNING:charm:Need both mongodb and Elasticsearch ' \ 'relation for Graylog to function properly. Blocking.' self.assertEqual(sorted(logger.output), [msg]) def test_check_config_with_missing_option(self): self.harness.set_leader(True) missing_password_config = {'port': 9000, 'admin-password': ''} with self.assertLogs(level='WARNING') as logger: self.harness.update_config(missing_password_config) msg = 'ERROR:charm:Need admin-password config option before setting pod spec.' self.assertEqual(sorted(logger.output), [msg]) self.assertEqual( self.harness.model.unit.status, BlockedStatus("Need 'admin-password' config option."))