def test_hooks_enabled_and_disabled(self): harness = Harness(RecordingCharm, meta=''' name: test-charm ''') # Before begin() there are no events. harness.update_config({'value': 'first'}) # By default, after begin the charm is set up to receive events. harness.begin() harness.update_config({'value': 'second'}) self.assertEqual(harness.charm.get_changes(reset=True), [{ 'name': 'config', 'data': { 'value': 'second' } }]) # Once disabled, we won't see config-changed when we make an update harness.disable_hooks() harness.update_config({'third': '3'}) self.assertEqual(harness.charm.get_changes(reset=True), []) harness.enable_hooks() harness.update_config({'value': 'fourth'}) self.assertEqual(harness.charm.get_changes(reset=True), [{ 'name': 'config', 'data': { 'value': 'fourth', 'third': '3' } }])
class TestCharm(unittest.TestCase): def setUp(self): """Setup the test harness.""" self.harness = Harness(PostgreSQLCharm) self.harness.begin() self.harness.disable_hooks() self.maxDiff = None def test_check_for_empty_config_no_image(self): """Check for correctly reported empty required image.""" self.harness.update_config(CONFIG_NO_IMAGE) expected = "required setting(s) empty: image" self.assertEqual(self.harness.charm._check_for_config_problems(), expected) def test_check_for_missing_config_no_image_password(self): """Check for correctly reported empty required image_password.""" self.harness.update_config(CONFIG_NO_IMAGE_PASSWORD) expected = "required setting(s) empty: image_password" self.assertEqual(self.harness.charm._check_for_config_problems(), expected)
def test_set_leader(self): harness = Harness(RecordingCharm) # No event happens here harness.set_leader(False) harness.begin() self.assertFalse(harness.charm.model.unit.is_leader()) harness.set_leader(True) self.assertEqual([{'name': 'leader-elected'}], harness.charm.get_changes(reset=True)) self.assertTrue(harness.charm.model.unit.is_leader()) harness.set_leader(False) self.assertFalse(harness.charm.model.unit.is_leader()) # No hook event when you lose leadership. # TODO: verify if Juju always triggers `leader-settings-changed` if you # lose leadership. self.assertEqual([], harness.charm.get_changes(reset=True)) harness.disable_hooks() harness.set_leader(True) # No hook event if you have disabled them self.assertEqual([], harness.charm.get_changes(reset=True))
class TestInterfaceMssqlCluster(unittest.TestCase): TEST_BIND_ADDRESS = '10.0.0.10' TEST_NODE_NAME = 'test-mssql-node' TEST_MASTER_CERT = { 'master_key_password': '******', 'master_cert': b64encode('test_master_cert'.encode()).decode(), 'master_cert_key': b64encode('test_master_cert_key'.encode()).decode(), 'master_cert_key_password': '******', } TEST_PRIMARY_REPLICA_NAME = 'test-primary-name' TEST_PRIMARY_LOGINS = { 'test-login-1': { 'sid': 'sid1', 'password_hash': 'test-password-hash1', 'roles': ['test-role1'] }, 'test-login-2': { 'sid': 'sid2', 'password_hash': 'test-password-hash2', 'roles': ['test-role2'] }, 'test-login-3': { 'sid': 'sid3', 'password_hash': 'test-password-hash3', 'roles': ['test-role3'] }, 'test-login-4': { 'sid': 'sid4', 'password_hash': 'test-password-hash4', 'roles': ['test-role4'] } } TEST_SECONDARY_LOGINS = { 'test-login-1': { 'sid': 'sid1', 'password_hash': 'test-password-hash1', 'roles': ['test-role1'] }, 'test-login-2': { 'sid': 'sid2', 'password_hash': 'test-password-hash2', 'roles': ['test-role2'] } } def setUp(self): self.harness = Harness(CharmBase, meta=''' name: mssql peers: cluster: interface: mssql-cluster ''') self.addCleanup(self.harness.cleanup) mocked_node_name = mock.patch.object( interface_mssql_cluster.MssqlCluster, 'node_name', new_callable=mock.PropertyMock).start() mocked_node_name.return_value = self.TEST_NODE_NAME mocked_bind_address = mock.patch.object( interface_mssql_cluster.MssqlCluster, 'bind_address', new_callable=mock.PropertyMock).start() mocked_bind_address.return_value = self.TEST_BIND_ADDRESS self.addCleanup(mock.patch.stopall) def test_on_joined(self): self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.state.initialized_nodes = { self.TEST_NODE_NAME: { 'address': self.TEST_BIND_ADDRESS, 'ready_to_cluster': 'true', } } rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') rel_data = self.harness.get_relation_data(rel_id, 'mssql/0') self.assertEqual(rel_data.get('node_name'), self.TEST_NODE_NAME) self.assertEqual(rel_data.get('node_address'), self.TEST_BIND_ADDRESS) self.assertEqual(rel_data.get('ready_to_cluster'), 'true') self.assertIsNone(rel_data.get('clustered')) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'configure_cluster_node') @mock.patch.object(interface_mssql_cluster, 'append_hosts_entry') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'set_sa_password') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'master_cert', new_callable=mock.PropertyMock) def test_on_changed(self, _master_cert, _set_sa_password, _append_hosts_entry, _configure_cluster_node): _master_cert.return_value = self.TEST_MASTER_CERT self.harness.set_leader() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') self.harness.update_relation_data( rel_id, 'mssql/1', { 'node_name': self.TEST_NODE_NAME, 'node_address': self.TEST_BIND_ADDRESS, 'ready_to_cluster': 'true', }) node_state = cluster.state.initialized_nodes.get(self.TEST_NODE_NAME) self.assertIsNotNone(node_state) self.assertEqual(node_state.get('address'), self.TEST_BIND_ADDRESS) self.assertTrue(node_state.get('ready_to_cluster')) self.assertIsNone(node_state.get('clustered')) _set_sa_password.assert_called_once_with() _append_hosts_entry.assert_called_once_with(self.TEST_BIND_ADDRESS, [self.TEST_NODE_NAME]) _configure_cluster_node.assert_called_once_with() @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'configure_cluster_node') @mock.patch.object(interface_mssql_cluster, 'append_hosts_entry') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'set_master_cert') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'master_cert', new_callable=mock.PropertyMock) def test_on_initialized_unit(self, _master_cert, _set_master_cert, _append_hosts_entry, _configure_cluster_node): _master_cert.return_value = None self.harness.set_leader() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') cluster.on.initialized_unit.emit() node_state = cluster.state.initialized_nodes.get(self.TEST_NODE_NAME) self.assertIsNotNone(node_state) self.assertEqual(node_state.get('address'), self.TEST_BIND_ADDRESS) self.assertIsNone(node_state.get('ready_to_cluster')) self.assertIsNone(node_state.get('clustered')) rel_data = self.harness.get_relation_data(rel_id, 'mssql/0') self.assertEqual(rel_data.get('node_name'), self.TEST_NODE_NAME) self.assertEqual(rel_data.get('node_address'), self.TEST_BIND_ADDRESS) self.assertIsNone(rel_data.get('ready_to_cluster')) self.assertIsNone(rel_data.get('clustered')) _append_hosts_entry.assert_called_once_with(self.TEST_BIND_ADDRESS, [self.TEST_NODE_NAME]) _set_master_cert.assert_called_once_with() _configure_cluster_node.assert_not_called() @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'master_cert', new_callable=mock.PropertyMock) def test_configure_master_cert(self, _master_cert, _mssql_db_client): _master_cert.return_value = self.TEST_MASTER_CERT self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.state.initialized_nodes = { self.TEST_NODE_NAME: { 'address': self.TEST_BIND_ADDRESS, 'ready_to_cluster': 'true', } } cluster.configure_master_cert() self.assertTrue(cluster.state.master_cert_configured) _mssql_db_client.assert_called_once_with() mock_ret_value = _mssql_db_client.return_value mock_ret_value.create_master_encryption_key.assert_called_once_with( 'test_key_password') mock_ret_value.setup_master_cert.assert_called_once_with( 'test_master_cert'.encode(), 'test_master_cert_key'.encode(), 'test_cert_key_password') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'configure_secondary_replica') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'configure_primary_replica') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'is_primary_replica', new_callable=mock.PropertyMock) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'configure_master_cert') def test_configure_cluster_node_primary_replica( self, _configure_master_cert, _mssql_db_client, _is_primary_replica, _configure_primary_replica, _configure_secondary_replica): _is_primary_replica.return_value = True self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.state.initialized_nodes[self.TEST_NODE_NAME] = { 'address': self.TEST_BIND_ADDRESS } rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') cluster.configure_cluster_node() _configure_master_cert.assert_called_once_with() _mssql_db_client.assert_called_once_with() mock_ret_value = _mssql_db_client.return_value mock_ret_value.setup_db_mirroring_endpoint.assert_called_once_with() self.assertTrue(cluster.state.initialized_nodes[self.TEST_NODE_NAME] ['ready_to_cluster']) rel_data = self.harness.get_relation_data(rel_id, 'mssql/0') self.assertEqual(rel_data.get('ready_to_cluster'), 'true') _configure_primary_replica.assert_called_once_with() _configure_secondary_replica.assert_not_called() @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'configure_secondary_replica') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'configure_primary_replica') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'is_primary_replica', new_callable=mock.PropertyMock) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'configure_master_cert') def test_configure_cluster_node_secondary_replica( self, _configure_master_cert, _mssql_db_client, _is_primary_replica, _configure_primary_replica, _configure_secondary_replica): _is_primary_replica.return_value = False self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.state.initialized_nodes[self.TEST_NODE_NAME] = { 'address': self.TEST_BIND_ADDRESS } rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') cluster.configure_cluster_node() _configure_master_cert.assert_called_once_with() _mssql_db_client.assert_called_once_with() mock_ret_value = _mssql_db_client.return_value mock_ret_value.setup_db_mirroring_endpoint.assert_called_once_with() self.assertTrue(cluster.state.initialized_nodes[self.TEST_NODE_NAME] ['ready_to_cluster']) rel_data = self.harness.get_relation_data(rel_id, 'mssql/0') self.assertEqual(rel_data.get('ready_to_cluster'), 'true') _configure_primary_replica.assert_not_called() _configure_secondary_replica.assert_called_once_with() @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'create_ag') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'is_ag_ready', new_callable=mock.PropertyMock) def test_configure_primary_replica_ag_not_ready(self, _is_ag_ready, _create_ag): _is_ag_ready.return_value = False self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.configure_primary_replica() _create_ag.assert_called_once_with() @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'create_ag') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'ag_replicas', new_callable=mock.PropertyMock) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'ready_nodes', new_callable=mock.PropertyMock) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'is_ag_ready', new_callable=mock.PropertyMock) def test_configure_primary_replica_ag_ready(self, _is_ag_ready, _ready_nodes, _ag_replicas, _create_ag, _mssql_db_client): _is_ag_ready.return_value = True _ready_nodes.return_value = { 'test-node-1': { 'address': '10.0.0.11' }, 'test-node-2': { 'address': '10.0.0.12' }, 'test-node-3': { 'address': '10.0.0.13' } } _ag_replicas.return_value = ['test-node-1', 'test-node-2'] self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') cluster.configure_primary_replica() _create_ag.assert_not_called() _mssql_db_client.assert_called_once_with() mock_ret_value = _mssql_db_client.return_value mock_ret_value.add_replicas.assert_called_once_with( cluster.AG_NAME, {'test-node-3': { 'address': '10.0.0.13' }}) rel_data = self.harness.get_relation_data(rel_id, 'mssql/0') self.assertIsNotNone(rel_data.get('nonce')) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'sync_logins_from_primary_replica') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'join_existing_ag') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'is_ag_ready', new_callable=mock.PropertyMock) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'ag_primary_replica', new_callable=mock.PropertyMock) def test_configure_secondary_replica(self, _ag_primary_replica, _is_ag_ready, _mssql_db_client, _join_existing_ag, _sync_logins_from_primary_replica): _is_ag_ready.return_value = True _ag_primary_replica.return_value = self.TEST_PRIMARY_REPLICA_NAME _mssql_db_client.return_value.get_ag_replicas.return_value = \ [self.TEST_PRIMARY_REPLICA_NAME, self.TEST_NODE_NAME] self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.configure_secondary_replica() _mssql_db_client.assert_called_once_with( self.TEST_PRIMARY_REPLICA_NAME) mock_ret_value = _mssql_db_client.return_value mock_ret_value.get_ag_replicas.assert_called_once_with(cluster.AG_NAME) _join_existing_ag.assert_called_once_with() _sync_logins_from_primary_replica.assert_called_once_with() @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'ag_primary_replica', new_callable=mock.PropertyMock) def test_sync_logins_from_primary_replica(self, _ag_primary_replica, _mssql_db_client): _ag_primary_replica.return_value = self.TEST_PRIMARY_REPLICA_NAME mocked_primary_db_client = mock.MagicMock() mocked_primary_db_client.get_sql_logins.return_value = \ self.TEST_PRIMARY_LOGINS mocked_this_db_client = mock.MagicMock() mocked_this_db_client.get_sql_logins.return_value = \ self.TEST_SECONDARY_LOGINS _mssql_db_client.side_effect = [ mocked_primary_db_client, mocked_this_db_client ] self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.sync_logins_from_primary_replica() _mssql_db_client.assert_has_calls( [mock.call(self.TEST_PRIMARY_REPLICA_NAME), mock.call()]) mocked_this_db_client.assert_has_calls([mock.call.get_sql_logins()]) mocked_this_db_client.assert_has_calls([ mock.call.get_sql_logins(), mock.call.create_login(name='test-login-3', sid='sid3', password='******', is_hashed_password=True, server_roles=['test-role3']), mock.call.create_login(name='test-login-4', sid='sid4', password='******', is_hashed_password=True, server_roles=['test-role4']) ]) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'ready_nodes', new_callable=mock.PropertyMock) def test_create_ag(self, _ready_nodes, _mssql_db_client): _ready_nodes.return_value = ['node1', 'node2', 'node3'] self.harness.set_leader() self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') cluster.create_ag() self.assertTrue(cluster.state.ag_configured) self.assertEqual(self.harness.charm.unit.status, cluster.UNIT_ACTIVE_STATUS) _mssql_db_client.assert_called_once_with() _mssql_db_client.return_value.create_ag.assert_called_once_with( cluster.AG_NAME, ['node1', 'node2', 'node3']) unit_rel_data = self.harness.get_relation_data(rel_id, 'mssql/0') self.assertEqual(unit_rel_data.get('clustered'), 'true') self.assertIsNotNone(unit_rel_data.get('nonce')) app_rel_data = self.harness.get_relation_data(rel_id, 'mssql') self.assertEqual(app_rel_data.get('ag_ready'), 'true') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') def test_join_existing_ag(self, _mssql_db_client): self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') cluster.join_existing_ag() self.assertTrue(cluster.state.ag_configured) self.assertEqual(self.harness.charm.unit.status, cluster.UNIT_ACTIVE_STATUS) _mssql_db_client.assert_called_once_with() _mssql_db_client.return_value.join_ag.assert_called_once_with( cluster.AG_NAME) unit_rel_data = self.harness.get_relation_data(rel_id, 'mssql/0') self.assertEqual(unit_rel_data.get('clustered'), 'true') @mock.patch.object(interface_mssql_cluster, 'append_hosts_entry') def test_add_to_initialized_nodes(self, _append_hosts_entry): self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.add_to_initialized_nodes(self.TEST_NODE_NAME, self.TEST_BIND_ADDRESS, ready_to_cluster=True, clustered=True) node_state = cluster.state.initialized_nodes.get(self.TEST_NODE_NAME) self.assertIsNotNone(node_state) self.assertEqual(node_state.get('address'), self.TEST_BIND_ADDRESS) self.assertTrue(node_state.get('ready_to_cluster')) self.assertTrue(node_state.get('clustered')) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch('charmhelpers.core.host.pwgen') def test_set_master_cert(self, _pwgen, _mssql_db_client): _pwgen.side_effect = [ 'test-master-key-password', 'test-master-cert-key-password' ] _mssql_db_client.return_value.create_master_cert.return_value = \ ('test-cert'.encode(), 'test-cert-key'.encode()) self.harness.set_leader() self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') cluster.set_master_cert() self.assertTrue(cluster.state.master_cert_configured) _pwgen.assert_has_calls([mock.call(32), mock.call(32)]) _mssql_db_client.assert_called_once_with() mock_ret_value = _mssql_db_client.return_value mock_ret_value.create_master_encryption_key.assert_called_once_with( 'test-master-key-password') mock_ret_value.create_master_cert.assert_called_once_with( 'test-master-cert-key-password') app_rel_data = self.harness.get_relation_data(rel_id, 'mssql') self.assertEqual(app_rel_data.get('master_key_password'), 'test-master-key-password') self.assertEqual(app_rel_data.get('master_cert'), b64encode('test-cert'.encode()).decode()) self.assertEqual(app_rel_data.get('master_cert_key'), b64encode('test-cert-key'.encode()).decode()) self.assertEqual(app_rel_data.get('master_cert_key_password'), 'test-master-cert-key-password') @mock.patch.object(interface_mssql_cluster.secrets, 'choice') def test_set_sa_password(self, _choice): _choice.return_value = 'p' self.harness.set_leader() self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') cluster.set_sa_password() _choice_calls = [] _choice_calls += [mock.call(string.ascii_lowercase)] * 8 _choice_calls += [mock.call(string.ascii_uppercase)] * 8 _choice_calls += [mock.call(string.digits)] * 8 _choice_calls += [mock.call(string.punctuation)] * 8 _choice.assert_has_calls(_choice_calls) app_rel_data = self.harness.get_relation_data(rel_id, 'mssql') self.assertEqual(app_rel_data.get('sa_password'), 'p' * 32) def test_clustered_nodes(self): self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.state.initialized_nodes['node-1'] = { 'address': '10.0.0.11', 'clustered': True } cluster.state.initialized_nodes['node-2'] = { 'address': '10.0.0.12', 'clustered': True } cluster.state.initialized_nodes['node-3'] = {'address': '10.0.0.13'} clustered_nodes = cluster.clustered_nodes self.assertEqual( clustered_nodes, { 'node-1': { 'address': '10.0.0.11', 'clustered': True }, 'node-2': { 'address': '10.0.0.12', 'clustered': True } }) def test_ready_nodes(self): self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.state.initialized_nodes['node-1'] = { 'address': '10.0.0.11', 'ready_to_cluster': True } cluster.state.initialized_nodes['node-2'] = { 'address': '10.0.0.12', 'ready_to_cluster': True, 'clustered': True } cluster.state.initialized_nodes['node-3'] = {'address': '10.0.0.13'} ready_nodes = cluster.ready_nodes self.assertEqual( ready_nodes, { 'node-1': { 'address': '10.0.0.11', 'ready_to_cluster': True }, 'node-2': { 'address': '10.0.0.12', 'ready_to_cluster': True, 'clustered': True } }) def test_master_cert(self): self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') self.harness.update_relation_data( rel_id, 'mssql', { 'master_key_password': '******', 'master_cert': 'test-cert', 'master_cert_key': 'test-cert-key', 'master_cert_key_password': '******', }) master_cert = cluster.master_cert self.assertEqual( master_cert, { 'master_key_password': '******', 'master_cert': 'test-cert', 'master_cert_key': 'test-cert-key', 'master_cert_key_password': '******', }) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'is_ag_ready', new_callable=mock.PropertyMock) def test_ag_primary_replica_ag_configured(self, _is_ag_ready, _mssql_db_client): _is_ag_ready.return_value = True _mssql_db_client.return_value.get_ag_primary_replica.return_value = \ self.TEST_PRIMARY_REPLICA_NAME self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.state.ag_configured = True primary_replica = cluster.ag_primary_replica _mssql_db_client.assert_called_once_with() mock_ret_value = _mssql_db_client.return_value mock_ret_value.get_ag_primary_replica.assert_called_once_with( cluster.AG_NAME) self.assertEqual(primary_replica, self.TEST_PRIMARY_REPLICA_NAME) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'is_ag_ready', new_callable=mock.PropertyMock) def test_ag_primary_replica_no_clustered_nodes(self, _is_ag_ready, _mssql_db_client): _is_ag_ready.return_value = True self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.state.ag_configured = False primary_replica = cluster.ag_primary_replica _mssql_db_client.assert_not_called() self.assertIsNone(primary_replica) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'clustered_nodes', new_callable=mock.PropertyMock) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'is_ag_ready', new_callable=mock.PropertyMock) def test_ag_primary_replica_other_clustered_node(self, _is_ag_ready, _clustered_nodes, _mssql_db_client): _is_ag_ready.return_value = True _mssql_db_client.return_value.get_ag_primary_replica.return_value = \ self.TEST_PRIMARY_REPLICA_NAME _clustered_nodes.return_value = { 'node-1': { 'address': '10.0.0.11', 'clustered': True }, 'node-2': { 'address': '10.0.0.12', 'clustered': True } } self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.state.ag_configured = False primary_replica = cluster.ag_primary_replica _mssql_db_client.assert_called_once_with('node-2') mock_ret_value = _mssql_db_client.return_value mock_ret_value.get_ag_primary_replica.assert_called_once_with( cluster.AG_NAME) self.assertEqual(primary_replica, self.TEST_PRIMARY_REPLICA_NAME) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'ag_primary_replica', new_callable=mock.PropertyMock) def test_is_primary_replica_current_node(self, _ag_primary_replica): _ag_primary_replica.return_value = self.TEST_NODE_NAME self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') self.assertTrue(cluster.is_primary_replica) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'ag_primary_replica', new_callable=mock.PropertyMock) def test_is_primary_replica_other_node(self, _ag_primary_replica): _ag_primary_replica.return_value = 'mssql-primary-replica' self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') self.assertFalse(cluster.is_primary_replica) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'ag_primary_replica', new_callable=mock.PropertyMock) def test_is_primary_replica_leader_node(self, _ag_primary_replica): _ag_primary_replica.return_value = None self.harness.set_leader() self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') self.assertTrue(cluster.is_primary_replica) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'is_ag_ready', new_callable=mock.PropertyMock) def test_ag_replicas_ag_configured(self, _is_ag_ready, _mssql_db_client): _is_ag_ready.return_value = True _mssql_db_client.return_value.get_ag_replicas.return_value = \ ['node-1', 'node-2', 'node-3'] self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.state.ag_configured = True ag_replicas = cluster.ag_replicas _mssql_db_client.assert_called_once_with() mock_ret_value = _mssql_db_client.return_value mock_ret_value.get_ag_replicas.assert_called_once_with(cluster.AG_NAME) self.assertListEqual(ag_replicas, ['node-1', 'node-2', 'node-3']) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'is_ag_ready', new_callable=mock.PropertyMock) def test_ag_replicas_no_clustered_nodes(self, _is_ag_ready, _mssql_db_client): _is_ag_ready.return_value = True self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') ag_replicas = cluster.ag_replicas _mssql_db_client.assert_not_called() self.assertListEqual(ag_replicas, []) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'mssql_db_client') @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'clustered_nodes', new_callable=mock.PropertyMock) @mock.patch.object(interface_mssql_cluster.MssqlCluster, 'is_ag_ready', new_callable=mock.PropertyMock) def test_ag_replicas_other_clustered_node(self, _is_ag_ready, _clustered_nodes, _mssql_db_client): _is_ag_ready.return_value = True _mssql_db_client.return_value.get_ag_replicas.return_value = \ ['node-1', 'node-2'] _clustered_nodes.return_value = { 'node-1': { 'address': '10.0.0.11', 'clustered': True }, 'node-2': { 'address': '10.0.0.12', 'clustered': True } } self.harness.disable_hooks() self.harness.begin() cluster = interface_mssql_cluster.MssqlCluster(self.harness.charm, 'cluster') cluster.state.ag_configured = False ag_replicas = cluster.ag_replicas _mssql_db_client.assert_called_once_with('node-2') mock_ret_value = _mssql_db_client.return_value mock_ret_value.get_ag_replicas.assert_called_once_with(cluster.AG_NAME) self.assertListEqual(ag_replicas, ['node-1', 'node-2'])
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, MaintenanceStatus('')) # 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 TestMongoDB(unittest.TestCase): """MongoDB Charm Unit Tests.""" def setUp(self): """Test setup.""" self.harness = Harness(MongoDBCharm) self.harness.set_leader(is_leader=True) self.harness.begin() # on_install @patch("ops.model.Pod.set_spec") @patch("charm.MongoDBCharm.on_update_status") @patch("oci_image.OCIImageResource.fetch") def test_on_install_leader(self, mock_image_fetch, mock_on_update_status, mock_set_spec): self.harness.charm.on.install.emit() # Assertions mock_image_fetch.assert_called_once() mock_on_update_status.assert_called_once() mock_set_spec.assert_called_once() @patch("oci_image.OCIImageResource.fetch") @patch("charm.MongoDBCharm.on_update_status") def test_on_install_non_leader(self, mock_on_update_status, mock_image_fetch): self.harness.set_leader(is_leader=False) self.harness.charm.on.install.emit() # Assertions mock_image_fetch.assert_not_called() mock_on_update_status.assert_called_once() @patch("charm.MongoDBCharm.on_update_status") @patch("oci_image.OCIImageResource.fetch") def test_on_install_missing_config(self, mock_image_fetch, mock_on_update_status): expected_status = BlockedStatus("missing config standalone") # Change the config to remove a required parameter # Need to disable hooks because we don't want the # config_changed hook to be executed. self.harness.disable_hooks() self.harness.update_config({"standalone": None}) self.harness.enable_hooks() self.harness.charm.on.install.emit() # Assertions mock_on_update_status.assert_not_called() mock_image_fetch.assert_not_called() self.assertEqual(self.harness.charm.unit.status, expected_status) @patch("ops.model.Pod.set_spec") @patch("charm.MongoDBCharm.on_update_status") @patch("oci_image.OCIImageResource.fetch") def test_on_install_error_fetching_image(self, mock_image_fetch, mock_on_update_status, mock_set_spec): expected_status = BlockedStatus("Error fetching image information") mock_image_fetch.side_effect = OCIImageResourceError("mongodb-image") self.harness.charm.on.install.emit() # Assertions mock_on_update_status.assert_not_called() mock_set_spec.assert_not_called() mock_image_fetch.assert_called_once() self.assertEqual(self.harness.charm.unit.status, expected_status) @patch("ops.model.Pod.set_spec") @patch("charm.make_pod_spec") @patch("charm.MongoDBCharm.on_update_status") @patch("oci_image.OCIImageResource.fetch") def test_on_install_leader_no_update_pod_spec_state( self, mock_image_fetch, mock_on_update_status, mock_make_pod_spec, mock_set_spec): pod_spec = {"pod": "spec"} mock_make_pod_spec.return_value = pod_spec self.harness.charm.state.pod_spec = pod_spec self.harness.charm.on.install.emit() # Assertions mock_image_fetch.assert_called_once() mock_on_update_status.assert_called_once() mock_set_spec.assert_not_called() # on_start @patch("charm.MongoDBCharm.on_mongodb_started") @patch("mongo.MongoConnector.ready") @patch("charm.MongoDBCharm.on_update_status") def test_on_start_leader_ready(self, mock_on_update_status, mock_mongo_ready, mock_on_mongodb_started): mock_mongo_ready.return_value = True self.harness.charm.on.start.emit() # Assertions # mock_on_update_status.assert_called_once() mock_on_mongodb_started.assert_called_once() @patch("mongo.MongoConnector.ready") @patch("charm.MongoDBCharm.on_update_status") def test_on_start_leader_not_ready(self, mock_on_update_status, mock_mongo_ready): mock_mongo_ready.return_value = False event_deferal = EventDeferal(self.harness.charm.on_start) self.harness.charm.on_start = event_deferal.fake_event self.harness.charm.on.start.emit() # Assertions # mock_on_update_status.assert_called_once() self.assertTrue(event_deferal.deferred) @patch("mongo.MongoConnector.ready") @patch("charm.MongoDBCharm.on_update_status") def test_on_start_non_leader(self, mock_on_update_status, mock_mongo_ready): self.harness.set_leader(is_leader=False) self.harness.charm.on.start.emit() mock_on_update_status.assert_not_called() mock_mongo_ready.assert_not_called()
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"})
class TestMssqlCharm(unittest.TestCase): def setUp(self): self.harness = Harness(charm.MSSQLCharm) self.addCleanup(self.harness.cleanup) @mock.patch.object(charm, 'apt_install') @mock.patch.object(charm, 'apt_update') @mock.patch.object(charm, 'add_source') @mock.patch.object(charm, 'urlopen') def test_on_install(self, _urlopen, _add_source, _apt_update, _apt_install): gpg_key_url = charm.MSSQLCharm.GPG_KEY_URL apt_repo_url = charm.MSSQLCharm.APT_REPO_URL_MAP['2019'] apt_packages = charm.MSSQLCharm.APT_PACKAGES test_gpg_key = 'test_gpg_key_url' test_apt_repo = 'test_apt_repo' gpg_key_mock = mock.MagicMock() gpg_key_mock.read.return_value = test_gpg_key.encode() apt_repo_mock = mock.MagicMock() apt_repo_mock.read.return_value = test_apt_repo.encode() _urlopen.side_effect = [gpg_key_mock, apt_repo_mock] self.harness.begin() self.harness.charm.on.install.emit() _urlopen.assert_has_calls( [mock.call(gpg_key_url), mock.call(apt_repo_url)]) _add_source.assert_called_once_with(source=test_apt_repo, key=test_gpg_key, fail_invalid=True) _apt_update.assert_called_once_with(fatal=True) _apt_install.assert_called_once_with(packages=apt_packages, fatal=True) @mock.patch.object(charm, 'subprocess') @mock.patch.object(charm, 'service') def test_initialize_mssql_successfully(self, _service, _subprocess): self.harness.update_config({'accept-eula': True}) rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') self.harness.update_relation_data(rel_id, 'mssql', {'sa_password': '******'}) self.harness.begin() self.harness.charm.cluster.on_initialized_unit = mock.MagicMock() self.harness.charm.cluster.on.ready_sa.emit() self.harness.charm.cluster.on_initialized_unit.assert_called_once() _service.assert_called_once_with('stop', charm.MSSQLCharm.SERVICE_NAME) _subprocess.check_call.assert_called_once_with( args=['/opt/mssql/bin/mssql-conf', '-n', 'setup'], env={ 'ACCEPT_EULA': 'Y', 'MSSQL_PID': 'Developer', 'MSSQL_SA_PASSWORD': '******', 'MSSQL_ENABLE_HADR': '1' }) self.assertTrue(self.harness.charm.state.initialized) self.assertEqual(self.harness.charm.unit.status, charm.MSSQLCharm.UNIT_INITIALIZED_UNCLUSTERED_STATUS) def test_initialize_mssql_invalid_config(self): self.harness.update_config({ 'product-id': 'Invalid Product ID', 'accept-eula': True }) rel_id = self.harness.add_relation('cluster', 'mssql') self.harness.add_relation_unit(rel_id, 'mssql/1') self.harness.update_relation_data(rel_id, 'mssql', {'sa_password': '******'}) self.harness.begin() self.harness.charm.cluster.on.ready_sa.emit() self.assertFalse(self.harness.charm.state.initialized) def test_initialize_mssql_empty_sa_password(self): self.harness.update_config({'accept-eula': True}) self.harness.begin() self.harness.charm.cluster.on.ready_sa.emit() self.assertFalse(self.harness.charm.state.initialized) def test_validate_product_id_successfully(self): self.harness.update_config({'product-id': 'Enterprise'}) self.harness.disable_hooks() self.harness.begin() self.assertTrue(self.harness.charm._validate_product_id()) def test_validate_product_id_valid_product_key(self): self.harness.update_config( {'product-id': 'ABC00-ABC11-ABC22-ABC33-ABC44'}) self.harness.disable_hooks() self.harness.begin() self.assertTrue(self.harness.charm._validate_product_id()) def test_validate_product_id_invalid(self): self.harness.update_config({'product-id': 'Invalid Product ID'}) self.harness.disable_hooks() self.harness.begin() self.assertFalse(self.harness.charm._validate_product_id()) def test_validate_config_successfully(self): self.harness.update_config({'accept-eula': True}) self.harness.disable_hooks() self.harness.begin() self.assertTrue(self.harness.charm._validate_config()) def test_validate_config_missing_product_id(self): self.harness.update_config({'product-id': ''}) self.harness.disable_hooks() self.harness.begin() self.assertFalse(self.harness.charm._validate_config()) expected_status = BlockedStatus('Missing configuration: {}'.format( ['product-id'])) self.assertEqual(self.harness.charm.unit.status, expected_status) def test_validate_config_eula_not_accepted(self): self.harness.disable_hooks() self.harness.begin() self.assertFalse(self.harness.charm._validate_config()) self.assertEqual(self.harness.charm.unit.status, BlockedStatus('The MSSQL EULA is not accepted')) def test_validate_config_eula_invalid_product_id(self): self.harness.update_config({ 'accept-eula': True, 'product-id': 'Invalid Product ID' }) self.harness.disable_hooks() self.harness.begin() self.assertFalse(self.harness.charm._validate_config()) self.assertEqual(self.harness.charm.unit.status, BlockedStatus('Invalid MSSQL product id'))