Example #1
0
 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)
Example #3
0
 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))
Example #4
0
class TestInterfaceMssqlCluster(unittest.TestCase):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        _mssql_db_client.assert_not_called()
        self.assertIsNone(primary_replica)

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

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

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

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

        self.assertTrue(cluster.is_primary_replica)

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

        self.assertFalse(cluster.is_primary_replica)

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

        self.assertTrue(cluster.is_primary_replica)

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

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

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

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

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

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

        _mssql_db_client.assert_called_once_with('node-2')
        mock_ret_value = _mssql_db_client.return_value
        mock_ret_value.get_ag_replicas.assert_called_once_with(cluster.AG_NAME)
        self.assertListEqual(ag_replicas, ['node-1', 'node-2'])
Example #5
0
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'))
Example #6
0
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()
Example #7
0
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"})
Example #8
0
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'))