797 lines
36 KiB
Python
797 lines
36 KiB
Python
# Copyright 2012 OpenStack Foundation
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import os
|
|
import random
|
|
import string
|
|
|
|
from mock import ANY
|
|
from mock import call
|
|
from mock import DEFAULT
|
|
from mock import MagicMock
|
|
from mock import Mock
|
|
from mock import NonCallableMagicMock
|
|
from mock import patch
|
|
from oslo_utils import netutils
|
|
from testtools import ExpectedException
|
|
|
|
from trove.common.db.cassandra import models
|
|
from trove.common import exception
|
|
from trove.common.instance import ServiceStatuses
|
|
from trove.guestagent import backup
|
|
from trove.guestagent.common.configuration import ImportOverrideStrategy
|
|
from trove.guestagent.common import operating_system
|
|
from trove.guestagent.datastore.experimental.cassandra import (
|
|
manager as cass_manager)
|
|
from trove.guestagent.datastore.experimental.cassandra import (
|
|
service as cass_service)
|
|
from trove.guestagent import pkg
|
|
from trove.guestagent import volume
|
|
from trove.tests.unittests.guestagent.test_datastore_manager import \
|
|
DatastoreManagerTest
|
|
from trove.tests.unittests import trove_testtools
|
|
|
|
|
|
class GuestAgentCassandraDBManagerTest(DatastoreManagerTest):
|
|
|
|
__MOUNT_POINT = '/var/lib/cassandra'
|
|
|
|
__N_GAK = '_get_available_keyspaces'
|
|
__N_GLU = '_get_listed_users'
|
|
__N_BU = '_build_user'
|
|
__N_RU = '_rename_user'
|
|
__N_AUP = '_alter_user_password'
|
|
__N_CAU = 'trove.common.db.cassandra.models.CassandraUser'
|
|
__N_CU = '_create_user'
|
|
__N_GFA = '_grant_full_access_on_keyspace'
|
|
__N_DU = '_drop_user'
|
|
|
|
__ACCESS_MODIFIERS = ('ALTER', 'CREATE', 'DROP', 'MODIFY', 'SELECT')
|
|
__CREATE_DB_FORMAT = (
|
|
"CREATE KEYSPACE \"{}\" WITH REPLICATION = "
|
|
"{{ 'class' : 'SimpleStrategy', 'replication_factor' : 1 }};"
|
|
)
|
|
__DROP_DB_FORMAT = "DROP KEYSPACE \"{}\";"
|
|
__CREATE_USR_FORMAT = "CREATE USER '{}' WITH PASSWORD %s NOSUPERUSER;"
|
|
__ALTER_USR_FORMAT = "ALTER USER '{}' WITH PASSWORD %s;"
|
|
__DROP_USR_FORMAT = "DROP USER '{}';"
|
|
__GRANT_FORMAT = "GRANT {} ON KEYSPACE \"{}\" TO '{}';"
|
|
__REVOKE_FORMAT = "REVOKE ALL PERMISSIONS ON KEYSPACE \"{}\" FROM '{}';"
|
|
__LIST_PERMISSIONS_FORMAT = "LIST ALL PERMISSIONS NORECURSIVE;"
|
|
__LIST_PERMISSIONS_OF_FORMAT = "LIST ALL PERMISSIONS OF '{}' NORECURSIVE;"
|
|
__LIST_DB_FORMAT = "SELECT * FROM system.schema_keyspaces;"
|
|
__LIST_USR_FORMAT = "LIST USERS;"
|
|
|
|
@patch.object(ImportOverrideStrategy, '_initialize_import_directory')
|
|
@patch('trove.guestagent.datastore.experimental.cassandra.service.LOG')
|
|
def setUp(self, *args, **kwargs):
|
|
super(GuestAgentCassandraDBManagerTest, self).setUp('cassandra')
|
|
|
|
conn_patcher = patch.multiple(cass_service.CassandraConnection,
|
|
_connect=DEFAULT,
|
|
is_active=Mock(return_value=True))
|
|
self.addCleanup(conn_patcher.stop)
|
|
conn_patcher.start()
|
|
|
|
self.real_status = cass_service.CassandraAppStatus.set_status
|
|
|
|
class FakeInstanceServiceStatus(object):
|
|
status = ServiceStatuses.NEW
|
|
|
|
def save(self):
|
|
pass
|
|
|
|
cass_service.CassandraAppStatus.set_status = MagicMock(
|
|
return_value=FakeInstanceServiceStatus())
|
|
self.context = trove_testtools.TroveTestContext(self)
|
|
self.manager = cass_manager.Manager()
|
|
self.manager._app = cass_service.CassandraApp()
|
|
self.manager._admin = cass_service.CassandraAdmin(
|
|
models.CassandraUser('Test'))
|
|
self.admin = self.manager._admin
|
|
self.admin._CassandraAdmin__client = MagicMock()
|
|
self.conn = self.admin._CassandraAdmin__client
|
|
self.pkg = cass_service.packager
|
|
self.origin_os_path_exists = os.path.exists
|
|
self.origin_format = volume.VolumeDevice.format
|
|
self.origin_migrate_data = volume.VolumeDevice.migrate_data
|
|
self.origin_mount = volume.VolumeDevice.mount
|
|
self.origin_mount_points = volume.VolumeDevice.mount_points
|
|
self.origin_stop_db = cass_service.CassandraApp.stop_db
|
|
self.origin_start_db = cass_service.CassandraApp.start_db
|
|
self.origin_install_db = cass_service.CassandraApp._install_db
|
|
self.original_get_ip = netutils.get_my_ipv4
|
|
self.orig_make_host_reachable = (
|
|
cass_service.CassandraApp.apply_initial_guestagent_configuration)
|
|
|
|
def tearDown(self):
|
|
super(GuestAgentCassandraDBManagerTest, self).tearDown()
|
|
cass_service.packager = self.pkg
|
|
os.path.exists = self.origin_os_path_exists
|
|
volume.VolumeDevice.format = self.origin_format
|
|
volume.VolumeDevice.migrate_data = self.origin_migrate_data
|
|
volume.VolumeDevice.mount = self.origin_mount
|
|
volume.VolumeDevice.mount_points = self.origin_mount_points
|
|
cass_service.CassandraApp.stop_db = self.origin_stop_db
|
|
cass_service.CassandraApp.start_db = self.origin_start_db
|
|
cass_service.CassandraApp._install_db = self.origin_install_db
|
|
netutils.get_my_ipv4 = self.original_get_ip
|
|
cass_service.CassandraApp.apply_initial_guestagent_configuration = (
|
|
self.orig_make_host_reachable)
|
|
cass_service.CassandraAppStatus.set_status = self.real_status
|
|
|
|
def test_update_status(self):
|
|
mock_status = MagicMock()
|
|
self.manager.app.status = mock_status
|
|
self.manager.update_status(self.context)
|
|
mock_status.update.assert_any_call()
|
|
|
|
def test_prepare_pkg(self):
|
|
self._prepare_dynamic(['cassandra'])
|
|
|
|
def test_prepare_no_pkg(self):
|
|
self._prepare_dynamic([])
|
|
|
|
def test_prepare_db_not_installed(self):
|
|
self._prepare_dynamic([], is_db_installed=False)
|
|
|
|
def test_prepare_db_not_installed_no_package(self):
|
|
self._prepare_dynamic([],
|
|
is_db_installed=True)
|
|
|
|
@patch.object(backup, 'restore')
|
|
def test_prepare_db_restore(self, restore):
|
|
backup_info = {'id': 'backup_id',
|
|
'instance_id': 'fake-instance-id',
|
|
'location': 'fake-location',
|
|
'type': 'InnoBackupEx',
|
|
'checksum': 'fake-checksum'}
|
|
|
|
self._prepare_dynamic(['cassandra'], is_db_installed=False,
|
|
backup_info=backup_info)
|
|
restore.assert_called_once_with(
|
|
self.context, backup_info, self.__MOUNT_POINT)
|
|
|
|
@patch.multiple(operating_system, enable_service_on_boot=DEFAULT,
|
|
disable_service_on_boot=DEFAULT)
|
|
@patch('trove.guestagent.datastore.experimental.cassandra.service.LOG')
|
|
def test_superuser_password_reset(
|
|
self, _, enable_service_on_boot, disable_service_on_boot):
|
|
fake_status = MagicMock()
|
|
fake_status.is_running = False
|
|
|
|
test_app = cass_service.CassandraApp()
|
|
test_app.status = fake_status
|
|
with patch.multiple(
|
|
test_app,
|
|
start_db=DEFAULT,
|
|
stop_db=DEFAULT,
|
|
restart=DEFAULT,
|
|
_CassandraApp__disable_remote_access=DEFAULT,
|
|
_CassandraApp__enable_remote_access=DEFAULT,
|
|
_CassandraApp__disable_authentication=DEFAULT,
|
|
_CassandraApp__enable_authentication=DEFAULT,
|
|
_CassandraApp__reset_user_password_to_default=DEFAULT,
|
|
secure=DEFAULT) as calls:
|
|
|
|
test_app._reset_admin_password()
|
|
|
|
disable_service_on_boot.assert_called_once_with(
|
|
test_app.service_candidates)
|
|
calls[
|
|
'_CassandraApp__disable_remote_access'
|
|
].assert_called_once_with()
|
|
calls[
|
|
'_CassandraApp__disable_authentication'
|
|
].assert_called_once_with()
|
|
calls['start_db'].assert_called_once_with(update_db=False,
|
|
enable_on_boot=False),
|
|
calls[
|
|
'_CassandraApp__enable_authentication'
|
|
].assert_called_once_with()
|
|
|
|
pw_reset_mock = calls[
|
|
'_CassandraApp__reset_user_password_to_default'
|
|
]
|
|
pw_reset_mock.assert_called_once_with(test_app._ADMIN_USER)
|
|
calls['secure'].assert_called_once_with(
|
|
update_user=pw_reset_mock.return_value)
|
|
calls['restart'].assert_called_once_with()
|
|
calls['stop_db'].assert_called_once_with()
|
|
calls[
|
|
'_CassandraApp__enable_remote_access'
|
|
].assert_called_once_with()
|
|
enable_service_on_boot.assert_called_once_with(
|
|
test_app.service_candidates)
|
|
|
|
@patch('trove.guestagent.datastore.experimental.cassandra.service.LOG')
|
|
def test_change_cluster_name(self, _):
|
|
fake_status = MagicMock()
|
|
fake_status.is_running = True
|
|
|
|
test_app = cass_service.CassandraApp()
|
|
test_app.status = fake_status
|
|
with patch.multiple(
|
|
test_app,
|
|
start_db=DEFAULT,
|
|
stop_db=DEFAULT,
|
|
restart=DEFAULT,
|
|
_update_cluster_name_property=DEFAULT,
|
|
_CassandraApp__reset_cluster_name=DEFAULT) as calls:
|
|
|
|
sample_name = NonCallableMagicMock()
|
|
test_app.change_cluster_name(sample_name)
|
|
calls['_CassandraApp__reset_cluster_name'].assert_called_once_with(
|
|
sample_name)
|
|
calls['_update_cluster_name_property'].assert_called_once_with(
|
|
sample_name)
|
|
calls['restart'].assert_called_once_with()
|
|
|
|
@patch.object(cass_service, 'CONF', DEFAULT)
|
|
@patch('trove.guestagent.datastore.experimental.cassandra.service.LOG')
|
|
def test_apply_post_restore_updates(self, _, conf_mock):
|
|
fake_status = MagicMock()
|
|
fake_status.is_running = False
|
|
|
|
test_app = cass_service.CassandraApp()
|
|
test_app.status = fake_status
|
|
with patch.multiple(
|
|
test_app,
|
|
start_db=DEFAULT,
|
|
stop_db=DEFAULT,
|
|
_update_cluster_name_property=DEFAULT,
|
|
_reset_admin_password=DEFAULT,
|
|
change_cluster_name=DEFAULT) as calls:
|
|
backup_info = {'instance_id': 'old_id'}
|
|
conf_mock.guest_id = 'new_id'
|
|
test_app._apply_post_restore_updates(backup_info)
|
|
calls['_update_cluster_name_property'].assert_called_once_with(
|
|
'old_id')
|
|
calls['_reset_admin_password'].assert_called_once_with()
|
|
calls['start_db'].assert_called_once_with(update_db=False)
|
|
calls['change_cluster_name'].assert_called_once_with('new_id')
|
|
calls['stop_db'].assert_called_once_with()
|
|
|
|
def _prepare_dynamic(self, packages,
|
|
config_content='MockContent', device_path='/dev/vdb',
|
|
is_db_installed=True, backup_info=None,
|
|
is_root_enabled=False,
|
|
overrides=None):
|
|
|
|
mock_status = MagicMock()
|
|
mock_app = MagicMock()
|
|
mock_app.status = mock_status
|
|
self.manager._app = mock_app
|
|
|
|
mock_status.begin_install = MagicMock(return_value=None)
|
|
mock_app.install_if_needed = MagicMock(return_value=None)
|
|
mock_app.init_storage_structure = MagicMock(return_value=None)
|
|
mock_app.write_config = MagicMock(return_value=None)
|
|
mock_app.apply_initial_guestagent_configuration = MagicMock(
|
|
return_value=None)
|
|
mock_app.restart = MagicMock(return_value=None)
|
|
mock_app.start_db = MagicMock(return_value=None)
|
|
mock_app.stop_db = MagicMock(return_value=None)
|
|
mock_app._remove_system_tables = MagicMock(return_value=None)
|
|
os.path.exists = MagicMock(return_value=True)
|
|
volume.VolumeDevice.format = MagicMock(return_value=None)
|
|
volume.VolumeDevice.migrate_data = MagicMock(return_value=None)
|
|
volume.VolumeDevice.mount = MagicMock(return_value=None)
|
|
volume.VolumeDevice.mount_points = MagicMock(return_value=[])
|
|
|
|
with patch.object(pkg.Package, 'pkg_is_installed',
|
|
return_value=is_db_installed):
|
|
# invocation
|
|
self.manager.prepare(context=self.context, packages=packages,
|
|
config_contents=config_content,
|
|
databases=None,
|
|
memory_mb='2048', users=None,
|
|
device_path=device_path,
|
|
mount_point=self.__MOUNT_POINT,
|
|
backup_info=backup_info,
|
|
overrides=None,
|
|
cluster_config=None)
|
|
|
|
# verification/assertion
|
|
mock_status.begin_install.assert_any_call()
|
|
mock_app.install_if_needed.assert_any_call(packages)
|
|
mock_app._remove_system_tables.assert_any_call()
|
|
mock_app.init_storage_structure.assert_any_call('/var/lib/cassandra')
|
|
mock_app.apply_initial_guestagent_configuration.assert_any_call(
|
|
cluster_name=None)
|
|
mock_app.start_db.assert_any_call(update_db=False)
|
|
mock_app.stop_db.assert_any_call()
|
|
if backup_info:
|
|
mock_app._apply_post_restore_updates.assert_called_once_with(
|
|
backup_info)
|
|
|
|
def test_keyspace_validation(self):
|
|
valid_name = self._get_random_name(32)
|
|
db = models.CassandraSchema(valid_name)
|
|
self.assertEqual(valid_name, db.name)
|
|
with ExpectedException(ValueError):
|
|
models.CassandraSchema(self._get_random_name(33))
|
|
|
|
def test_user_validation(self):
|
|
valid_name = self._get_random_name(65535)
|
|
usr = models.CassandraUser(valid_name, 'password')
|
|
self.assertEqual(valid_name, usr.name)
|
|
self.assertEqual('password', usr.password)
|
|
with ExpectedException(ValueError):
|
|
models.CassandraUser(self._get_random_name(65536))
|
|
|
|
@classmethod
|
|
def _serialize_collection(cls, *collection):
|
|
return [item.serialize() for item in collection]
|
|
|
|
@classmethod
|
|
def _get_random_name(cls, size,
|
|
chars=string.ascii_letters + string.digits):
|
|
return ''.join(random.choice(chars) for _ in range(size))
|
|
|
|
def test_create_database(self):
|
|
db1 = models.CassandraSchema('db1')
|
|
db2 = models.CassandraSchema('db2')
|
|
db3 = models.CassandraSchema(self._get_random_name(32))
|
|
|
|
self.manager.create_database(self.context,
|
|
self._serialize_collection(db1, db2, db3))
|
|
self.conn.execute.assert_has_calls([
|
|
call(self.__CREATE_DB_FORMAT, (db1.name,)),
|
|
call(self.__CREATE_DB_FORMAT, (db2.name,)),
|
|
call(self.__CREATE_DB_FORMAT, (db3.name,))
|
|
])
|
|
|
|
def test_delete_database(self):
|
|
db = models.CassandraSchema(self._get_random_name(32))
|
|
self.manager.delete_database(self.context, db.serialize())
|
|
self.conn.execute.assert_called_once_with(
|
|
self.__DROP_DB_FORMAT, (db.name,))
|
|
|
|
def test_create_user(self):
|
|
usr1 = models.CassandraUser('usr1')
|
|
usr2 = models.CassandraUser('usr2', '')
|
|
usr3 = models.CassandraUser(self._get_random_name(1025), 'password')
|
|
|
|
self.manager.create_user(self.context,
|
|
self._serialize_collection(usr1, usr2, usr3))
|
|
self.conn.execute.assert_has_calls([
|
|
call(self.__CREATE_USR_FORMAT, (usr1.name,), (usr1.password,)),
|
|
call(self.__CREATE_USR_FORMAT, (usr2.name,), (usr2.password,)),
|
|
call(self.__CREATE_USR_FORMAT, (usr3.name,), (usr3.password,))
|
|
])
|
|
|
|
def test_delete_user(self):
|
|
usr = models.CassandraUser(self._get_random_name(1025), 'password')
|
|
self.manager.delete_user(self.context, usr.serialize())
|
|
self.conn.execute.assert_called_once_with(
|
|
self.__DROP_USR_FORMAT, (usr.name,))
|
|
|
|
def test_change_passwords(self):
|
|
usr1 = models.CassandraUser('usr1')
|
|
usr2 = models.CassandraUser('usr2', '')
|
|
usr3 = models.CassandraUser(self._get_random_name(1025), 'password')
|
|
|
|
self.manager.change_passwords(self.context, self._serialize_collection(
|
|
usr1, usr2, usr3))
|
|
self.conn.execute.assert_has_calls([
|
|
call(self.__ALTER_USR_FORMAT, (usr1.name,), (usr1.password,)),
|
|
call(self.__ALTER_USR_FORMAT, (usr2.name,), (usr2.password,)),
|
|
call(self.__ALTER_USR_FORMAT, (usr3.name,), (usr3.password,))
|
|
])
|
|
|
|
def test_alter_user_password(self):
|
|
usr1 = models.CassandraUser('usr1')
|
|
usr2 = models.CassandraUser('usr2', '')
|
|
usr3 = models.CassandraUser(self._get_random_name(1025), 'password')
|
|
|
|
self.admin.alter_user_password(usr1)
|
|
self.admin.alter_user_password(usr2)
|
|
self.admin.alter_user_password(usr3)
|
|
self.conn.execute.assert_has_calls([
|
|
call(self.__ALTER_USR_FORMAT, (usr1.name,), (usr1.password,)),
|
|
call(self.__ALTER_USR_FORMAT, (usr2.name,), (usr2.password,)),
|
|
call(self.__ALTER_USR_FORMAT, (usr3.name,), (usr3.password,))
|
|
])
|
|
|
|
def test_grant_access(self):
|
|
usr1 = models.CassandraUser('usr1')
|
|
usr2 = models.CassandraUser('usr1', 'password')
|
|
db1 = models.CassandraSchema('db1')
|
|
db2 = models.CassandraSchema('db2')
|
|
db3 = models.CassandraSchema('db3')
|
|
|
|
self.manager.grant_access(self.context, usr1.name, None, [db1.name,
|
|
db2.name])
|
|
self.manager.grant_access(self.context, usr2.name, None, [db3.name])
|
|
|
|
expected = []
|
|
for modifier in self.__ACCESS_MODIFIERS:
|
|
expected.append(call(self.__GRANT_FORMAT,
|
|
(modifier, db1.name, usr1.name)))
|
|
expected.append(call(self.__GRANT_FORMAT,
|
|
(modifier, db3.name, usr2.name)))
|
|
|
|
self.conn.execute.assert_has_calls(
|
|
expected,
|
|
any_order=True)
|
|
|
|
def test_revoke_access(self):
|
|
usr1 = models.CassandraUser('usr1')
|
|
usr2 = models.CassandraUser('usr1', 'password')
|
|
db1 = models.CassandraSchema('db1')
|
|
db2 = models.CassandraSchema('db2')
|
|
|
|
self.manager.revoke_access(self.context, usr1.name, None, db1.name)
|
|
self.manager.revoke_access(self.context, usr2.name, None, db2.name)
|
|
self.conn.execute.assert_has_calls([
|
|
call(self.__REVOKE_FORMAT, (db1.name, usr1.name)),
|
|
call(self.__REVOKE_FORMAT, (db2.name, usr2.name))
|
|
])
|
|
|
|
def test_get_available_keyspaces(self):
|
|
self.manager.list_databases(self.context)
|
|
self.conn.execute.assert_called_once_with(
|
|
self.__LIST_DB_FORMAT)
|
|
|
|
def test_list_databases(self):
|
|
db1 = models.CassandraSchema('db1')
|
|
db2 = models.CassandraSchema('db2')
|
|
db3 = models.CassandraSchema(self._get_random_name(32))
|
|
|
|
with patch.object(self.admin, self.__N_GAK, return_value={db1, db2,
|
|
db3}):
|
|
found = self.manager.list_databases(self.context)
|
|
self.assertEqual(2, len(found))
|
|
self.assertEqual(3, len(found[0]))
|
|
self.assertIsNone(found[1])
|
|
self.assertIn(db1.serialize(), found[0])
|
|
self.assertIn(db2.serialize(), found[0])
|
|
self.assertIn(db3.serialize(), found[0])
|
|
|
|
with patch.object(self.admin, self.__N_GAK, return_value=set()):
|
|
found = self.manager.list_databases(self.context)
|
|
self.assertEqual(([], None), found)
|
|
|
|
def test_get_acl(self):
|
|
r0 = NonCallableMagicMock(username='user1', resource='<all keyspaces>',
|
|
permission='SELECT')
|
|
r1 = NonCallableMagicMock(username='user2', resource='<keyspace ks1>',
|
|
permission='SELECT')
|
|
r2 = NonCallableMagicMock(username='user2', resource='<keyspace ks2>',
|
|
permission='SELECT')
|
|
r3 = NonCallableMagicMock(username='user2', resource='<keyspace ks2>',
|
|
permission='ALTER')
|
|
r4 = NonCallableMagicMock(username='user3', resource='<table ks2.t1>',
|
|
permission='SELECT')
|
|
r5 = NonCallableMagicMock(username='user3', resource='',
|
|
permission='ALTER')
|
|
r6 = NonCallableMagicMock(username='user3', resource='<keyspace ks2>',
|
|
permission='')
|
|
r7 = NonCallableMagicMock(username='user3', resource='',
|
|
permission='')
|
|
r8 = NonCallableMagicMock(username='user3', resource='<keyspace ks1>',
|
|
permission='DELETE')
|
|
r9 = NonCallableMagicMock(username='user4', resource='<all keyspaces>',
|
|
permission='UPDATE')
|
|
r10 = NonCallableMagicMock(username='user4', resource='<keyspace ks1>',
|
|
permission='DELETE')
|
|
|
|
available_ks = {models.CassandraSchema('ks1'),
|
|
models.CassandraSchema('ks2'),
|
|
models.CassandraSchema('ks3')}
|
|
|
|
mock_result_set = [r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r9, r9, r10]
|
|
execute_mock = MagicMock(return_value=mock_result_set)
|
|
mock_client = MagicMock(execute=execute_mock)
|
|
|
|
with patch.object(self.admin,
|
|
self.__N_GAK, return_value=available_ks) as gak_mock:
|
|
acl = self.admin._get_acl(mock_client)
|
|
execute_mock.assert_called_once_with(
|
|
self.__LIST_PERMISSIONS_FORMAT)
|
|
gak_mock.assert_called_once_with(mock_client)
|
|
|
|
self.assertEqual({'user1': {'ks1': {'SELECT'},
|
|
'ks2': {'SELECT'},
|
|
'ks3': {'SELECT'}},
|
|
'user2': {'ks1': {'SELECT'},
|
|
'ks2': {'SELECT', 'ALTER'}},
|
|
'user3': {'ks1': {'DELETE'}},
|
|
'user4': {'ks1': {'UPDATE', 'DELETE'},
|
|
'ks2': {'UPDATE'},
|
|
'ks3': {'UPDATE'}}
|
|
},
|
|
acl)
|
|
|
|
mock_result_set = [r1, r2, r3]
|
|
execute_mock = MagicMock(return_value=mock_result_set)
|
|
mock_client = MagicMock(execute=execute_mock)
|
|
|
|
with patch.object(self.admin,
|
|
self.__N_GAK, return_value=available_ks) as gak_mock:
|
|
acl = self.admin._get_acl(mock_client, username='user2')
|
|
execute_mock.assert_called_once_with(
|
|
self.__LIST_PERMISSIONS_OF_FORMAT.format('user2'))
|
|
gak_mock.assert_not_called()
|
|
|
|
self.assertEqual({'user2': {'ks1': {'SELECT'},
|
|
'ks2': {'SELECT', 'ALTER'}}}, acl)
|
|
|
|
mock_result_set = []
|
|
execute_mock = MagicMock(return_value=mock_result_set)
|
|
mock_client = MagicMock(execute=execute_mock)
|
|
|
|
with patch.object(self.admin,
|
|
self.__N_GAK, return_value=available_ks) as gak_mock:
|
|
acl = self.admin._get_acl(mock_client, username='nonexisting')
|
|
execute_mock.assert_called_once_with(
|
|
self.__LIST_PERMISSIONS_OF_FORMAT.format('nonexisting'))
|
|
gak_mock.assert_not_called()
|
|
|
|
self.assertEqual({}, acl)
|
|
|
|
def test_get_listed_users(self):
|
|
usr1 = models.CassandraUser(self._get_random_name(1025))
|
|
usr2 = models.CassandraUser(self._get_random_name(1025))
|
|
usr3 = models.CassandraUser(self._get_random_name(1025))
|
|
db1 = models.CassandraSchema('db1')
|
|
db2 = models.CassandraSchema('db2')
|
|
usr1.databases.append(db1.serialize())
|
|
usr3.databases.append(db2.serialize())
|
|
|
|
rv_1 = NonCallableMagicMock()
|
|
rv_1.configure_mock(name=usr1.name, super=False)
|
|
rv_2 = NonCallableMagicMock()
|
|
rv_2.configure_mock(name=usr2.name, super=False)
|
|
rv_3 = NonCallableMagicMock()
|
|
rv_3.configure_mock(name=usr3.name, super=True)
|
|
|
|
with patch.object(self.conn, 'execute', return_value=iter(
|
|
[rv_1, rv_2, rv_3])):
|
|
with patch.object(self.admin, '_get_acl',
|
|
return_value={usr1.name: {db1.name: {'SELECT'},
|
|
db2.name: {}},
|
|
usr3.name: {db2.name: {'SELECT'}}}
|
|
):
|
|
usrs = self.manager.list_users(self.context)
|
|
self.conn.execute.assert_has_calls([
|
|
call(self.__LIST_USR_FORMAT),
|
|
], any_order=True)
|
|
self.assertIn(usr1.serialize(), usrs[0])
|
|
self.assertIn(usr2.serialize(), usrs[0])
|
|
self.assertIn(usr3.serialize(), usrs[0])
|
|
|
|
def test_list_access(self):
|
|
usr1 = models.CassandraUser('usr1')
|
|
usr2 = models.CassandraUser('usr2')
|
|
usr3 = models.CassandraUser(self._get_random_name(1025), 'password')
|
|
db1 = models.CassandraSchema('db1').serialize()
|
|
db2 = models.CassandraSchema('db2').serialize()
|
|
usr2.databases.append(db1)
|
|
usr3.databases.append(db1)
|
|
usr3.databases.append(db2)
|
|
|
|
with patch.object(self.admin, self.__N_GLU, return_value={usr1, usr2,
|
|
usr3}):
|
|
usr1_dbs = self.manager.list_access(self.context, usr1.name, None)
|
|
usr2_dbs = self.manager.list_access(self.context, usr2.name, None)
|
|
usr3_dbs = self.manager.list_access(self.context, usr3.name, None)
|
|
self.assertEqual([], usr1_dbs)
|
|
self.assertEqual([db1], usr2_dbs)
|
|
self.assertEqual([db1, db2], usr3_dbs)
|
|
|
|
with patch.object(self.admin, self.__N_GLU, return_value=set()):
|
|
with ExpectedException(exception.UserNotFound):
|
|
self.manager.list_access(self.context, usr3.name, None)
|
|
|
|
def test_list_users(self):
|
|
usr1 = models.CassandraUser('usr1')
|
|
usr2 = models.CassandraUser('usr2')
|
|
usr3 = models.CassandraUser(self._get_random_name(1025), 'password')
|
|
|
|
with patch.object(self.admin, self.__N_GLU, return_value={usr1, usr2,
|
|
usr3}):
|
|
found = self.manager.list_users(self.context)
|
|
self.assertEqual(2, len(found))
|
|
self.assertEqual(3, len(found[0]))
|
|
self.assertIsNone(found[1])
|
|
self.assertIn(usr1.serialize(), found[0])
|
|
self.assertIn(usr2.serialize(), found[0])
|
|
self.assertIn(usr3.serialize(), found[0])
|
|
|
|
with patch.object(self.admin, self.__N_GLU, return_value=set()):
|
|
self.assertEqual(([], None), self.manager.list_users(self.context))
|
|
|
|
def test_get_user(self):
|
|
usr1 = models.CassandraUser('usr1')
|
|
usr2 = models.CassandraUser('usr2')
|
|
usr3 = models.CassandraUser(self._get_random_name(1025), 'password')
|
|
|
|
with patch.object(self.admin, self.__N_GLU, return_value={usr1, usr2,
|
|
usr3}):
|
|
found = self.manager.get_user(self.context, usr2.name, None)
|
|
self.assertEqual(usr2.serialize(), found)
|
|
|
|
with patch.object(self.admin, self.__N_GLU, return_value=set()):
|
|
self.assertIsNone(
|
|
self.manager.get_user(self.context, usr2.name, None))
|
|
|
|
@patch.object(cass_service.CassandraAdmin, '_deserialize_keyspace',
|
|
side_effect=lambda p1: p1)
|
|
def test_rename_user(self, ks_deserializer):
|
|
usr = models.CassandraUser('usr')
|
|
db1 = models.CassandraSchema('db1').serialize()
|
|
db2 = models.CassandraSchema('db2').serialize()
|
|
usr.databases.append(db1)
|
|
usr.databases.append(db2)
|
|
|
|
new_user = models.CassandraUser('new_user')
|
|
with patch(self.__N_CAU, return_value=new_user):
|
|
with patch.object(self.admin, self.__N_BU, return_value=usr):
|
|
with patch.object(self.admin, self.__N_CU) as create:
|
|
with patch.object(self.admin, self.__N_GFA) as grant:
|
|
with patch.object(self.admin, self.__N_DU) as drop:
|
|
usr_attrs = {'name': 'user', 'password': 'trove'}
|
|
self.manager.update_attributes(self.context,
|
|
usr.name, None,
|
|
usr_attrs)
|
|
create.assert_called_once_with(ANY, new_user)
|
|
grant.assert_has_calls([call(ANY, db1, ANY),
|
|
call(ANY, db2, ANY)])
|
|
drop.assert_called_once_with(ANY, usr)
|
|
|
|
def test_update_attributes(self):
|
|
usr = models.CassandraUser('usr', 'pwd')
|
|
|
|
with patch.object(self.admin, self.__N_BU, return_value=usr):
|
|
usr_attrs = {'name': usr.name, 'password': usr.password}
|
|
with patch.object(self.admin, self.__N_RU) as rename:
|
|
with patch.object(self.admin, self.__N_AUP) as alter:
|
|
self.manager.update_attributes(self.context, usr.name,
|
|
None, usr_attrs)
|
|
self.assertEqual(0, rename.call_count)
|
|
self.assertEqual(0, alter.call_count)
|
|
|
|
usr_attrs = {'name': 'user', 'password': 'password'}
|
|
with patch.object(self.admin, self.__N_RU) as rename:
|
|
with patch.object(self.admin, self.__N_AUP) as alter:
|
|
self.manager.update_attributes(self.context, usr.name,
|
|
None, usr_attrs)
|
|
rename.assert_called_once_with(ANY, usr, usr_attrs['name'],
|
|
usr_attrs['password'])
|
|
self.assertEqual(0, alter.call_count)
|
|
|
|
usr_attrs = {'name': 'user', 'password': usr.password}
|
|
with patch.object(self.admin, self.__N_RU) as rename:
|
|
with patch.object(self.admin, self.__N_AUP) as alter:
|
|
self.manager.update_attributes(self.context, usr.name,
|
|
None, usr_attrs)
|
|
rename.assert_called_once_with(ANY, usr, usr_attrs['name'],
|
|
usr_attrs['password'])
|
|
self.assertEqual(0, alter.call_count)
|
|
|
|
usr_attrs = {'name': 'user'}
|
|
with patch.object(self.admin, self.__N_RU) as rename:
|
|
with patch.object(self.admin, self.__N_AUP) as alter:
|
|
with ExpectedException(
|
|
exception.UnprocessableEntity, "Updating username "
|
|
"requires specifying a password as well."):
|
|
self.manager.update_attributes(self.context, usr.name,
|
|
None, usr_attrs)
|
|
self.assertEqual(0, rename.call_count)
|
|
self.assertEqual(0, alter.call_count)
|
|
|
|
usr_attrs = {'name': usr.name, 'password': 'password'}
|
|
with patch.object(self.admin, self.__N_RU) as rename:
|
|
with patch.object(self.admin, self.__N_AUP) as alter:
|
|
self.manager.update_attributes(self.context, usr.name,
|
|
None, usr_attrs)
|
|
alter.assert_called_once_with(ANY, usr)
|
|
self.assertEqual(0, rename.call_count)
|
|
|
|
usr_attrs = {'password': usr.password}
|
|
with patch.object(self.admin, self.__N_RU) as rename:
|
|
with patch.object(self.admin, self.__N_AUP) as alter:
|
|
self.manager.update_attributes(self.context, usr.name,
|
|
None, usr_attrs)
|
|
self.assertEqual(0, rename.call_count)
|
|
self.assertEqual(0, alter.call_count)
|
|
|
|
usr_attrs = {'password': 'trove'}
|
|
with patch.object(self.admin, self.__N_RU) as rename:
|
|
with patch.object(self.admin, self.__N_AUP) as alter:
|
|
self.manager.update_attributes(self.context, usr.name,
|
|
None, usr_attrs)
|
|
alter.assert_called_once_with(ANY, usr)
|
|
self.assertEqual(0, rename.call_count)
|
|
|
|
def test_update_overrides(self):
|
|
cfg_mgr_mock = MagicMock()
|
|
self.manager._app.configuration_manager = cfg_mgr_mock
|
|
overrides = NonCallableMagicMock()
|
|
self.manager.update_overrides(Mock(), overrides)
|
|
cfg_mgr_mock.apply_user_override.assert_called_once_with(overrides)
|
|
cfg_mgr_mock.remove_user_override.assert_not_called()
|
|
|
|
def test_remove_overrides(self):
|
|
cfg_mgr_mock = MagicMock()
|
|
self.manager._app.configuration_manager = cfg_mgr_mock
|
|
self.manager.update_overrides(Mock(), {}, remove=True)
|
|
cfg_mgr_mock.remove_user_override.assert_called_once_with()
|
|
cfg_mgr_mock.apply_user_override.assert_not_called()
|
|
|
|
def test_apply_overrides(self):
|
|
self.assertIsNone(
|
|
self.manager.apply_overrides(Mock(), NonCallableMagicMock()))
|
|
|
|
@patch('trove.guestagent.datastore.experimental.cassandra.service.LOG')
|
|
def test_enable_root(self, _):
|
|
with patch.object(self.manager._app, 'is_root_enabled',
|
|
return_value=False):
|
|
with patch.object(cass_service.CassandraAdmin,
|
|
'_create_superuser') as create_mock:
|
|
self.manager.enable_root(self.context)
|
|
create_mock.assert_called_once_with(ANY)
|
|
|
|
with patch.object(self.manager._app, 'is_root_enabled',
|
|
return_value=True):
|
|
with patch.object(cass_service.CassandraAdmin,
|
|
'alter_user_password') as alter_mock:
|
|
self.manager.enable_root(self.context)
|
|
alter_mock.assert_called_once_with(ANY)
|
|
|
|
@patch('trove.guestagent.datastore.experimental.cassandra.service.LOG')
|
|
def test_is_root_enabled(self, _):
|
|
trove_admin = Mock()
|
|
trove_admin.configure_mock(name=self.manager._app._ADMIN_USER)
|
|
other_admin = Mock()
|
|
other_admin.configure_mock(name='someuser')
|
|
|
|
with patch.object(cass_service.CassandraAdmin,
|
|
'list_superusers', return_value=[]):
|
|
self.assertFalse(self.manager.is_root_enabled(self.context))
|
|
|
|
with patch.object(cass_service.CassandraAdmin,
|
|
'list_superusers', return_value=[trove_admin]):
|
|
self.assertFalse(self.manager.is_root_enabled(self.context))
|
|
|
|
with patch.object(cass_service.CassandraAdmin,
|
|
'list_superusers', return_value=[other_admin]):
|
|
self.assertTrue(self.manager.is_root_enabled(self.context))
|
|
|
|
with patch.object(cass_service.CassandraAdmin,
|
|
'list_superusers',
|
|
return_value=[trove_admin, other_admin]):
|
|
self.assertTrue(self.manager.is_root_enabled(self.context))
|
|
|
|
def test_guest_log_enable(self):
|
|
self._assert_guest_log_enable(False, 'INFO')
|
|
self._assert_guest_log_enable(True, 'OFF')
|
|
|
|
def _assert_guest_log_enable(self, disable, expected_level):
|
|
with patch.multiple(
|
|
self.manager._app,
|
|
logback_conf_manager=DEFAULT,
|
|
_run_nodetool_command=DEFAULT
|
|
) as app_mocks:
|
|
self.assertFalse(self.manager.guest_log_enable(
|
|
Mock(), Mock(), disable))
|
|
|
|
(app_mocks['logback_conf_manager'].apply_system_override.
|
|
assert_called_once_with(
|
|
{'configuration': {'root': {'@level': expected_level}}}))
|
|
app_mocks['_run_nodetool_command'].assert_called_once_with(
|
|
'setlogginglevel', 'root', expected_level)
|