e586638991
Add system.log retrieval. The Cassandra's system/general log is located in the cassandra log directory and is by default called system.log. Cassandra 2.1 and higher uses 'SLF4J' with 'logback' backend. Logging properties can be configured at runtime via the nodetool utility (setlogginglevel) and/or persisted in 'logback.xml' configuration file. Logging can be enabled on per-module/class basis with configurable verbosity levels. The logging in Trove is configured globally (on the root level) - i.e. for all application modules. A new configuration property 'system_log_level' was added to control the log verbosity. It allows the operator define the log level that will be set when the logging is enabled on an instance. All logging will be turned off when disabled. Note: The log level is not changed during prepare. The instances will be provisioned with the default logging setting (INFO level). Note: An XML codec was added to allow parsing XML files into/from Python dicts. Change-Id: Ia57587bf557771ea6d7f00e2fe9d674789b98559 Depends-On: I4a088b7a541c35a6c32d6985727bad65ff491815 Depends-On: I254b81b45e44aeda3049865bee76cd9075312761 Closes-Bug: 1550557
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 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.db import models
|
|
from trove.guestagent import pkg as 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.guestagent.db.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(self, *collection):
|
|
return [item.serialize() for item in collection]
|
|
|
|
@classmethod
|
|
def _get_random_name(self, 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)
|