From aeadbfc974fc515ef85059c48277a56d0ec9037e Mon Sep 17 00:00:00 2001 From: Petr Malik Date: Thu, 20 Aug 2015 12:48:53 -0400 Subject: [PATCH] Add instance create int-tests Add generic integration tests for instance create. Also moved some global constants to the base runner. Note: Did some additional cleanup work on other test groups as well. Broke down some negative tests into more self-contained units. Added a comment string on tests that did not have it. Had to fix database_actions test to work for users defining hosts (like MySQL users). Change-Id: I7c1413fa5742c276454e26ebbbeae8e02569a392 --- trove/tests/int_tests.py | 21 +- trove/tests/scenario/groups/backup_group.py | 5 +- .../scenario/groups/cluster_actions_group.py | 8 +- .../scenario/groups/database_actions_group.py | 79 ++++- .../scenario/groups/instance_actions_group.py | 9 +- .../scenario/groups/instance_create_group.py | 73 +++++ .../scenario/groups/instance_delete_group.py | 15 +- .../groups/negative_cluster_actions_group.py | 8 +- .../scenario/groups/replication_group.py | 7 +- trove/tests/scenario/groups/test_group.py | 18 +- .../scenario/groups/user_actions_group.py | 137 ++++++-- .../scenario/helpers/cassandra_helper.py | 40 +++ .../tests/scenario/helpers/mongodb_helper.py | 41 +++ trove/tests/scenario/helpers/mysql_helper.py | 45 +++ trove/tests/scenario/helpers/redis_helper.py | 9 + trove/tests/scenario/helpers/test_helper.py | 35 ++ .../runners/cluster_actions_runners.py | 6 +- .../runners/database_actions_runners.py | 90 +++--- .../runners/instance_create_runners.py | 300 ++++++++++++++++++ trove/tests/scenario/runners/test_runners.py | 3 +- .../scenario/runners/user_actions_runners.py | 186 ++++++----- 21 files changed, 926 insertions(+), 209 deletions(-) create mode 100644 trove/tests/scenario/groups/instance_create_group.py create mode 100644 trove/tests/scenario/helpers/cassandra_helper.py create mode 100644 trove/tests/scenario/helpers/mongodb_helper.py create mode 100644 trove/tests/scenario/helpers/mysql_helper.py create mode 100644 trove/tests/scenario/runners/instance_create_runners.py diff --git a/trove/tests/int_tests.py b/trove/tests/int_tests.py index 9ca0d9679d..a3625628e7 100644 --- a/trove/tests/int_tests.py +++ b/trove/tests/int_tests.py @@ -38,6 +38,7 @@ from trove.tests.scenario.groups import backup_group from trove.tests.scenario.groups import cluster_actions_group from trove.tests.scenario.groups import database_actions_group from trove.tests.scenario.groups import instance_actions_group +from trove.tests.scenario.groups import instance_create_group from trove.tests.scenario.groups import instance_delete_group from trove.tests.scenario.groups import negative_cluster_actions_group from trove.tests.scenario.groups import replication_group @@ -119,37 +120,39 @@ proboscis.register(groups=["blackbox_mgmt"], # # Group designations for datastore agnostic int-tests # -initial_groups = [ +instance_create_groups = [ GROUP_SERVICES_INITIALIZE, flavors.GROUP, versions.GROUP, - instances.GROUP_START_SIMPLE, + instance_create_group.GROUP, instance_delete_group.GROUP ] -backup_groups = list(initial_groups) + +backup_groups = list(instance_create_groups) backup_groups.extend([backup_group.GROUP]) -user_actions_groups = list(initial_groups) +user_actions_groups = list(instance_create_groups) user_actions_groups.extend([user_actions_group.GROUP]) -database_actions_groups = list(initial_groups) +database_actions_groups = list(instance_create_groups) database_actions_groups.extend([database_actions_group.GROUP]) -cluster_actions_groups = list(initial_groups) +cluster_actions_groups = list(instance_create_groups) cluster_actions_groups.extend([cluster_actions_group.GROUP, negative_cluster_actions_group.GROUP]) -instance_actions_groups = list(initial_groups) +instance_actions_groups = list(instance_create_groups) instance_actions_groups.extend([instance_actions_group.GROUP]) -replication_groups = list(initial_groups) +replication_groups = list(instance_create_groups) replication_groups.extend([replication_group.GROUP]) # Module based groups register(["backup"], backup_groups) register(["cluster"], cluster_actions_groups) -register(["database"], database_actions_group) +register(["database"], database_actions_groups) register(["instance_actions"], instance_actions_groups) +register(["instance_create"], instance_create_groups) register(["user"], user_actions_groups) register(["replication"], replication_groups) diff --git a/trove/tests/scenario/groups/backup_group.py b/trove/tests/scenario/groups/backup_group.py index ceae82dcac..2004ab8a71 100644 --- a/trove/tests/scenario/groups/backup_group.py +++ b/trove/tests/scenario/groups/backup_group.py @@ -15,7 +15,7 @@ from proboscis import test -from trove.tests.api.instances import WaitForGuestInstallationToFinish +from trove.tests.scenario.groups import instance_create_group from trove.tests.scenario.groups.test_group import TestGroup @@ -25,8 +25,7 @@ GROUP_BACKUP_LIST = "scenario.backup_list_group" GROUP_RESTORE = "scenario.restore_group" -@test(depends_on_classes=[WaitForGuestInstallationToFinish], - groups=[GROUP]) +@test(depends_on_groups=[instance_create_group.GROUP], groups=[GROUP]) class BackupGroup(TestGroup): """Test Backup and Restore functionality.""" diff --git a/trove/tests/scenario/groups/cluster_actions_group.py b/trove/tests/scenario/groups/cluster_actions_group.py index ec5fbda983..93730f84d8 100644 --- a/trove/tests/scenario/groups/cluster_actions_group.py +++ b/trove/tests/scenario/groups/cluster_actions_group.py @@ -15,16 +15,13 @@ from proboscis import test -from trove.tests.api.instances import GROUP_START_SIMPLE -from trove.tests.api.instances import WaitForGuestInstallationToFinish from trove.tests.scenario.groups.test_group import TestGroup GROUP = "scenario.cluster_actions_group" -@test(depends_on_groups=[GROUP_START_SIMPLE], groups=[GROUP], - runs_after=[WaitForGuestInstallationToFinish]) +@test(groups=[GROUP]) class ClusterActionsGroup(TestGroup): def __init__(self): @@ -33,12 +30,15 @@ class ClusterActionsGroup(TestGroup): @test def cluster_create(self): + """Create a cluster.""" self.test_runner.run_cluster_create() @test(depends_on=[cluster_create]) def test_cluster_communication(self): + """Validate the cluster data and properties.""" self.test_runner.run_cluster_communication() @test(depends_on=[cluster_create], runs_after=[test_cluster_communication]) def cluster_delete(self): + """Delete an existing cluster.""" self.test_runner.run_cluster_delete() diff --git a/trove/tests/scenario/groups/database_actions_group.py b/trove/tests/scenario/groups/database_actions_group.py index 232ca357fc..7519507fc9 100644 --- a/trove/tests/scenario/groups/database_actions_group.py +++ b/trove/tests/scenario/groups/database_actions_group.py @@ -15,49 +15,94 @@ from proboscis import test -from trove.tests.api.instances import GROUP_START_SIMPLE -from trove.tests.api.instances import WaitForGuestInstallationToFinish +from trove.tests.scenario.groups import instance_create_group from trove.tests.scenario.groups.test_group import TestGroup -from trove.tests.scenario.groups import user_actions_group - -GROUP = "dbaas.api.database_actions_tests" -@test(depends_on_groups=[GROUP_START_SIMPLE], groups=[GROUP], - runs_after=[WaitForGuestInstallationToFinish], - runs_after_groups=[user_actions_group.GROUP]) +GROUP = "scenario.database_actions_group" + + +@test(depends_on_groups=[instance_create_group.GROUP], groups=[GROUP]) class DatabaseActionsGroup(TestGroup): def __init__(self): super(DatabaseActionsGroup, self).__init__( 'database_actions_runners', 'DatabaseActionsRunner') + self.instance_create_runner = self.get_runner( + 'instance_create_runners', 'InstanceCreateRunner') @test + def create_initialized_instance(self): + """Create an instance with initial databases.""" + self.instance_create_runner.run_initialized_instance_create( + with_dbs=True, with_users=False, configuration_id=None) + + @test(runs_after=[create_initialized_instance]) def create_databases(self): + """Create databases on an existing instance.""" self.test_runner.run_databases_create() @test(depends_on=[create_databases]) def list_databases(self): + """List the created databases.""" self.test_runner.run_databases_list() @test(depends_on=[create_databases], runs_after=[list_databases]) - def negative_create_database(self): - self.test_runner.run_negative_database_create() + def create_database_with_no_attributes(self): + """Ensure creating a database with blank specification fails.""" + self.test_runner.run_database_create_with_no_attributes() @test(depends_on=[create_databases], - runs_after=[negative_create_database]) + runs_after=[create_database_with_no_attributes]) + def create_database_with_blank_name(self): + """Ensure creating a database with blank name fails.""" + self.test_runner.run_database_create_with_blank_name() + + @test(depends_on=[create_databases], + runs_after=[create_database_with_blank_name]) + def create_existing_database(self): + """Ensure creating an existing database fails.""" + self.test_runner.run_existing_database_create() + + @test(depends_on=[create_databases], + runs_after=[create_existing_database]) def delete_database(self): + """Delete the created databases.""" self.test_runner.run_database_delete() - @test - def nonexisting_database_delete(self): + @test(runs_after=[delete_database]) + def delete_nonexisting_database(self): + """Delete non-existing databases.""" self.test_runner.run_nonexisting_database_delete() - @test - def system_database_create(self): + @test(runs_after=[delete_nonexisting_database]) + def create_system_database(self): + """Ensure creating a system database fails.""" self.test_runner.run_system_database_create() - @test - def system_database_delete(self): + @test(runs_after=[create_system_database]) + def delete_system_database(self): + """Ensure deleting a system database fails.""" self.test_runner.run_system_database_delete() + + @test(depends_on=[create_initialized_instance], + runs_after=[delete_system_database]) + def wait_for_instances(self): + """Waiting for all instances to become active.""" + self.instance_create_runner.wait_for_created_instances() + + @test(depends_on=[wait_for_instances]) + def add_initialized_instance_data(self): + """Add data to the initialized instance.""" + self.instance_create_runner.run_add_initialized_instance_data() + + @test(runs_after=[add_initialized_instance_data]) + def validate_initialized_instance(self): + """Validate the initialized instance data and properties.""" + self.instance_create_runner.run_validate_initialized_instance() + + @test(runs_after=[validate_initialized_instance]) + def delete_initialized_instance(self): + """Delete the initialized instance.""" + self.instance_create_runner.run_initialized_instance_delete() diff --git a/trove/tests/scenario/groups/instance_actions_group.py b/trove/tests/scenario/groups/instance_actions_group.py index 292f7c5eff..240c52305e 100644 --- a/trove/tests/scenario/groups/instance_actions_group.py +++ b/trove/tests/scenario/groups/instance_actions_group.py @@ -15,16 +15,14 @@ from proboscis import test -from trove.tests.api.instances import GROUP_START_SIMPLE -from trove.tests.api.instances import WaitForGuestInstallationToFinish +from trove.tests.scenario.groups import instance_create_group from trove.tests.scenario.groups.test_group import TestGroup GROUP = "scenario.instance_actions_group" -@test(depends_on_groups=[GROUP_START_SIMPLE], groups=[GROUP], - runs_after=[WaitForGuestInstallationToFinish]) +@test(depends_on_groups=[instance_create_group.GROUP], groups=[GROUP]) class InstanceActionsGroup(TestGroup): def __init__(self): @@ -33,12 +31,15 @@ class InstanceActionsGroup(TestGroup): @test def instance_restart(self): + """Restart an existing instance.""" self.test_runner.run_instance_restart() @test(depends_on=[instance_restart]) def instance_resize_volume(self): + """Resize attached volume.""" self.test_runner.run_instance_resize_volume() @test(depends_on=[instance_resize_volume]) def instance_resize_flavor(self): + """Resize instance flavor.""" self.test_runner.run_instance_resize_flavor() diff --git a/trove/tests/scenario/groups/instance_create_group.py b/trove/tests/scenario/groups/instance_create_group.py new file mode 100644 index 0000000000..de3bdb3a64 --- /dev/null +++ b/trove/tests/scenario/groups/instance_create_group.py @@ -0,0 +1,73 @@ +# Copyright 2015 Tesora Inc. +# All Rights Reserved. +# +# 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. + +from proboscis import test + +from trove.tests.api.instances import InstanceSetup +from trove.tests import PRE_INSTANCES +from trove.tests.scenario.groups.test_group import TestGroup + + +GROUP = "scenario.instance_create_group" + + +@test(depends_on_classes=[InstanceSetup], runs_after_groups=[PRE_INSTANCES], + groups=[GROUP]) +class InstanceCreateGroup(TestGroup): + + def __init__(self): + super(InstanceCreateGroup, self).__init__( + 'instance_create_runners', 'InstanceCreateRunner') + + @test + def create_empty_instance(self): + """Create an empty instance.""" + self.test_runner.run_empty_instance_create() + + @test(runs_after=[create_empty_instance]) + def create_initial_configuration(self): + """Create a configuration group for a new initialized instance.""" + self.test_runner.run_initial_configuration_create() + + @test(runs_after=[create_initial_configuration]) + def create_initialized_instance(self): + """Create an instance with initial properties.""" + self.test_runner.run_initialized_instance_create() + + @test(runs_after=[create_initialized_instance]) + def wait_for_instances(self): + """Waiting for all instances to become active.""" + self.test_runner.wait_for_created_instances() + + @test(depends_on=[wait_for_instances]) + def add_initialized_instance_data(self): + """Add data to the initialized instance.""" + self.test_runner.run_add_initialized_instance_data() + + @test(runs_after=[add_initialized_instance_data]) + def validate_initialized_instance(self): + """Validate the initialized instance data and properties.""" + self.test_runner.run_validate_initialized_instance() + + @test(runs_after=[validate_initialized_instance]) + def delete_initialized_instance(self): + """Delete the initialized instance.""" + self.test_runner.run_initialized_instance_delete() + + @test(depends_on=[create_initial_configuration, + delete_initialized_instance]) + def delete_initial_configuration(self): + """Delete the initial configuration group.""" + self.test_runner.run_initial_configuration_delete() diff --git a/trove/tests/scenario/groups/instance_delete_group.py b/trove/tests/scenario/groups/instance_delete_group.py index d5290aa2b6..76cb603f5a 100644 --- a/trove/tests/scenario/groups/instance_delete_group.py +++ b/trove/tests/scenario/groups/instance_delete_group.py @@ -15,19 +15,25 @@ from proboscis import test -from trove.tests.api.instances import GROUP_START_SIMPLE from trove.tests.scenario.groups import backup_group +from trove.tests.scenario.groups import database_actions_group from trove.tests.scenario.groups import instance_actions_group +from trove.tests.scenario.groups import instance_create_group from trove.tests.scenario.groups import replication_group from trove.tests.scenario.groups.test_group import TestGroup +from trove.tests.scenario.groups import user_actions_group GROUP = "scenario.instance_delete_group" -@test(depends_on_groups=[GROUP_START_SIMPLE], groups=[GROUP], - runs_after_groups=[backup_group.GROUP_BACKUP, replication_group.GROUP, - instance_actions_group.GROUP]) +@test(depends_on_groups=[instance_create_group.GROUP], + groups=[GROUP], + runs_after_groups=[backup_group.GROUP_BACKUP, + database_actions_group.GROUP, + instance_actions_group.GROUP, + replication_group.GROUP, + user_actions_group.GROUP]) class InstanceDeleteGroup(TestGroup): def __init__(self): @@ -36,4 +42,5 @@ class InstanceDeleteGroup(TestGroup): @test def instance_delete(self): + """Delete an existing instance.""" self.test_runner.run_instance_delete() diff --git a/trove/tests/scenario/groups/negative_cluster_actions_group.py b/trove/tests/scenario/groups/negative_cluster_actions_group.py index 9e07f032f7..6257a71307 100644 --- a/trove/tests/scenario/groups/negative_cluster_actions_group.py +++ b/trove/tests/scenario/groups/negative_cluster_actions_group.py @@ -15,15 +15,13 @@ from proboscis import test -from trove.tests.api.instances import GROUP_START_SIMPLE -from trove.tests.api.instances import WaitForGuestInstallationToFinish from trove.tests.scenario.groups.test_group import TestGroup + GROUP = "scenario.negative_cluster_actions_group" -@test(depends_on_groups=[GROUP_START_SIMPLE], groups=[GROUP], - runs_after=[WaitForGuestInstallationToFinish]) +@test(groups=[GROUP]) class NegativeClusterActionsGroup(TestGroup): def __init__(self): @@ -32,8 +30,10 @@ class NegativeClusterActionsGroup(TestGroup): @test def create_constrained_size_cluster(self): + """Ensure creating a cluster with wrong number of nodes fails.""" self.test_runner.run_create_constrained_size_cluster() @test def create_heterogeneous_cluster(self): + """Ensure creating a cluster with unequal nodes fails.""" self.test_runner.run_create_heterogeneous_cluster() diff --git a/trove/tests/scenario/groups/replication_group.py b/trove/tests/scenario/groups/replication_group.py index 690d4f79e3..811cda497d 100644 --- a/trove/tests/scenario/groups/replication_group.py +++ b/trove/tests/scenario/groups/replication_group.py @@ -15,15 +15,14 @@ from proboscis import test -from trove.tests.api.instances import GROUP_START_SIMPLE -from trove.tests.scenario.groups import backup_group +from trove.tests.scenario.groups import instance_create_group from trove.tests.scenario.groups.test_group import TestGroup + GROUP = "scenario.replication_group" -@test(depends_on_groups=[GROUP_START_SIMPLE], groups=[GROUP], - runs_after=[backup_group.GROUP_BACKUP]) +@test(depends_on_groups=[instance_create_group.GROUP], groups=[GROUP]) class ReplicationGroup(TestGroup): """Test Replication functionality.""" diff --git a/trove/tests/scenario/groups/test_group.py b/trove/tests/scenario/groups/test_group.py index 2a362dea5a..438b5f1697 100644 --- a/trove/tests/scenario/groups/test_group.py +++ b/trove/tests/scenario/groups/test_group.py @@ -29,15 +29,29 @@ class TestGroup(object): TEST_HELPER_BASE_NAME = 'TestHelper' def __init__(self, runner_module_name, runner_base_name, *args, **kwargs): + self._test_runner = self.get_runner( + runner_module_name, runner_base_name, *args, **kwargs) + + def get_runner(self, runner_module_name, runner_base_name, + *args, **kwargs): class_prefix = self._get_test_datastore() runner_cls = self._load_dynamic_class( runner_module_name, class_prefix, runner_base_name, self.TEST_RUNNERS_NS) - self._test_runner = runner_cls(*args, **kwargs) + runner = runner_cls(*args, **kwargs) helper_cls = self._load_dynamic_class( self.TEST_HELPER_MODULE_NAME, class_prefix, self.TEST_HELPER_BASE_NAME, self.TEST_HELPERS_NS) - self._test_runner._test_helper = helper_cls(self._build_class_name( + runner._test_helper = helper_cls(self._build_class_name( + class_prefix, self.TEST_HELPER_BASE_NAME, strip_test=True)) + return runner + + def get_helper(self): + class_prefix = self._get_test_datastore() + helper_cls = self._load_dynamic_class( + self.TEST_HELPER_MODULE_NAME, class_prefix, + self.TEST_HELPER_BASE_NAME, self.TEST_HELPERS_NS) + return helper_cls(self._build_class_name( class_prefix, self.TEST_HELPER_BASE_NAME, strip_test=True)) def _get_test_datastore(self): diff --git a/trove/tests/scenario/groups/user_actions_group.py b/trove/tests/scenario/groups/user_actions_group.py index 92d3846e57..13eccbf892 100644 --- a/trove/tests/scenario/groups/user_actions_group.py +++ b/trove/tests/scenario/groups/user_actions_group.py @@ -15,80 +15,159 @@ from proboscis import test -from trove.tests.api.instances import GROUP_START_SIMPLE -from trove.tests.api.instances import WaitForGuestInstallationToFinish -from trove.tests.scenario.groups import instance_actions_group +from trove.tests.scenario.groups import instance_create_group from trove.tests.scenario.groups.test_group import TestGroup -GROUP = "dbaas.api.user_actions_tests" + +GROUP = "scenario.user_actions_group" -@test(depends_on_groups=[GROUP_START_SIMPLE], groups=[GROUP], - runs_after=[WaitForGuestInstallationToFinish], - runs_after_groups=[instance_actions_group.GROUP]) +@test(depends_on_groups=[instance_create_group.GROUP], groups=[GROUP]) class UserActionsGroup(TestGroup): def __init__(self): super(UserActionsGroup, self).__init__( 'user_actions_runners', 'UserActionsRunner') + self.instance_create_runner = self.get_runner( + 'instance_create_runners', 'InstanceCreateRunner') + self.database_actions_runner = self.get_runner( + 'database_actions_runners', 'DatabaseActionsRunner') @test + def create_initialized_instance(self): + """Create an instance with initial users.""" + self.instance_create_runner.run_initialized_instance_create( + with_dbs=False, with_users=True, configuration_id=None) + + @test(runs_after=[create_initialized_instance]) + def create_user_databases(self): + """Create user databases on an existing instance.""" + # These databases may be referenced by the users (below) so we need to + # create them first. + self.database_actions_runner.run_databases_create() + + @test(runs_after=[create_user_databases]) def create_users(self): + """Create users on an existing instance.""" self.test_runner.run_users_create() @test(depends_on=[create_users]) def show_user(self): + """Show created users.""" self.test_runner.run_user_show() @test(depends_on=[create_users], runs_after=[show_user]) def list_users(self): + """List the created users.""" self.test_runner.run_users_list() @test(depends_on=[create_users], runs_after=[list_users]) - def negative_create_user(self): - self.test_runner.run_negative_user_create() + def create_user_with_no_attributes(self): + """Ensure creating a user with blank specification fails.""" + self.test_runner.run_user_create_with_no_attributes() @test(depends_on=[create_users], - runs_after=[list_users]) - def negative_user_attribute_update(self): - self.test_runner.run_negative_user_attribute_update() + runs_after=[create_user_with_no_attributes]) + def create_user_with_blank_name(self): + """Ensure creating a user with blank name fails.""" + self.test_runner.run_user_create_with_blank_name() @test(depends_on=[create_users], - runs_after=[negative_user_attribute_update]) - def user_attribute_update(self): + runs_after=[create_user_with_blank_name]) + def create_user_with_blank_password(self): + """Ensure creating a user with blank password fails.""" + self.test_runner.run_user_create_with_blank_password() + + @test(depends_on=[create_users], + runs_after=[create_user_with_blank_password]) + def create_existing_user(self): + """Ensure creating an existing user fails.""" + self.test_runner.run_existing_user_create() + + @test(depends_on=[create_users], + runs_after=[create_existing_user]) + def update_user_with_no_attributes(self): + """Ensure updating a user with blank specification fails.""" + self.test_runner.run_user_update_with_no_attributes() + + @test(depends_on=[create_users], + runs_after=[update_user_with_no_attributes]) + def update_user_with_blank_name(self): + """Ensure updating a user with blank name fails.""" + self.test_runner.run_user_update_with_blank_name() + + @test(depends_on=[create_users], + runs_after=[update_user_with_blank_name]) + def update_user_with_existing_name(self): + """Ensure updating a user with an existing name fails.""" + self.test_runner.run_user_update_with_existing_name() + + @test(depends_on=[create_users], + runs_after=[update_user_with_existing_name]) + def update_user_attributes(self): + """Update an existing user.""" self.test_runner.run_user_attribute_update() @test(depends_on=[create_users], - runs_after=[user_attribute_update]) + runs_after=[update_user_attributes]) def delete_user(self): + """Delete the created users.""" self.test_runner.run_user_delete() - @test - def nonexisting_user_show(self): + @test(runs_after=[delete_user]) + def show_nonexisting_user(self): + """Delete non-existing users.""" self.test_runner.run_nonexisting_user_show() - @test - def nonexisting_user_attribute_update(self): + @test(runs_after=[show_nonexisting_user]) + def update_nonexisting_user(self): + """Ensure updating a non-existing user fails.""" self.test_runner.run_nonexisting_user_update() - @test - def nonexisting_user_delete(self): + @test(runs_after=[update_nonexisting_user]) + def delete_nonexisting_user(self): + """Ensure deleting a non-existing user fails.""" self.test_runner.run_nonexisting_user_delete() - @test - def system_user_create(self): + @test(runs_after=[delete_nonexisting_user]) + def create_system_user(self): + """Ensure creating a system user fails.""" self.test_runner.run_system_user_create() - @test - def system_user_show(self): + @test(runs_after=[create_system_user]) + def show_system_user(self): + """Ensure showing a system user fails.""" self.test_runner.run_system_user_show() - @test - def system_user_attribute_update(self): + @test(runs_after=[show_system_user]) + def update_system_user(self): + """Ensure updating a system user fails.""" self.test_runner.run_system_user_attribute_update() - @test - def system_user_delete(self): + @test(runs_after=[update_system_user]) + def delete_system_user(self): + """Ensure deleting a system user fails.""" self.test_runner.run_system_user_delete() + + @test(runs_after=[delete_system_user]) + def delete_user_databases(self): + """Delete the user databases.""" + self.database_actions_runner.run_database_delete() + + @test(depends_on=[create_initialized_instance], + runs_after=[delete_user_databases]) + def wait_for_instances(self): + """Waiting for all instances to become active.""" + self.instance_create_runner.wait_for_created_instances() + + @test(depends_on=[wait_for_instances]) + def validate_initialized_instance(self): + """Validate the initialized instance data and properties.""" + self.instance_create_runner.run_validate_initialized_instance() + + @test(runs_after=[validate_initialized_instance]) + def delete_initialized_instance(self): + """Delete the initialized instance.""" + self.instance_create_runner.run_initialized_instance_delete() diff --git a/trove/tests/scenario/helpers/cassandra_helper.py b/trove/tests/scenario/helpers/cassandra_helper.py new file mode 100644 index 0000000000..4e19fe35ba --- /dev/null +++ b/trove/tests/scenario/helpers/cassandra_helper.py @@ -0,0 +1,40 @@ +# Copyright 2015 Tesora Inc. +# All Rights Reserved. +# +# 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. + +from trove.tests.scenario.helpers.test_helper import TestHelper + + +class CassandrabHelper(TestHelper): + + def __init__(self, expected_override_name): + super(CassandrabHelper, self).__init__(expected_override_name) + + def get_valid_database_definitions(self): + return [{"name": 'db1'}, {"name": 'db2'}] + + def get_valid_user_definitions(self): + return [{'name': 'user1', 'password': 'password1', + 'databases': []}, + {'name': 'user2', 'password': 'password1', + 'databases': [{'name': 'db1'}]}, + {'name': 'user3', 'password': 'password1', + 'databases': [{'name': 'db1'}, {'name': 'db2'}]}] + + def get_non_dynamic_group(self): + return {'sstable_preemptive_open_interval_in_mb': 40} + + def get_invalid_groups(self): + return [{'sstable_preemptive_open_interval_in_mb': -1}, + {'sstable_preemptive_open_interval_in_mb': 'string_value'}] diff --git a/trove/tests/scenario/helpers/mongodb_helper.py b/trove/tests/scenario/helpers/mongodb_helper.py new file mode 100644 index 0000000000..3f11be6d4a --- /dev/null +++ b/trove/tests/scenario/helpers/mongodb_helper.py @@ -0,0 +1,41 @@ +# Copyright 2015 Tesora Inc. +# All Rights Reserved. +# +# 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. + +from trove.tests.scenario.helpers.test_helper import TestHelper + + +class MongodbHelper(TestHelper): + + def __init__(self, expected_override_name): + super(MongodbHelper, self).__init__(expected_override_name) + + def get_valid_database_definitions(self): + return [{"name": 'db1'}, {"name": 'db2'}] + + def get_valid_user_definitions(self): + return [{'name': 'db0.user1', 'password': 'password1', + 'databases': []}, + {'name': 'db0.user2', 'password': 'password1', + 'databases': [{'name': 'db1'}]}, + {'name': 'db1.user3', 'password': 'password1', + 'databases': [{'name': 'db1'}, {'name': 'db2'}]}] + + def get_non_dynamic_group(self): + return {'systemLog.verbosity': 4} + + def get_invalid_groups(self): + return [{'net.maxIncomingConnections': -1}, + {'storage.mmapv1.nsSize': 4096}, + {'storage.journal.enabled': 'string_value'}] diff --git a/trove/tests/scenario/helpers/mysql_helper.py b/trove/tests/scenario/helpers/mysql_helper.py new file mode 100644 index 0000000000..cc2bef6878 --- /dev/null +++ b/trove/tests/scenario/helpers/mysql_helper.py @@ -0,0 +1,45 @@ +# Copyright 2015 Tesora Inc. +# All Rights Reserved. +# +# 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. + +from trove.tests.scenario.helpers.test_helper import TestHelper + + +class MysqlHelper(TestHelper): + + def __init__(self, expected_override_name): + super(MysqlHelper, self).__init__(expected_override_name) + + def get_valid_database_definitions(self): + return [{'name': 'db1', 'character_set': 'latin2', + 'collate': 'latin2_general_ci'}, + {'name': 'db2'}] + + def get_valid_user_definitions(self): + return [{'name': 'user1', 'password': 'password1', 'databases': [], + 'host': '127.0.0.1'}, + {'name': 'user2', 'password': 'password1', + 'databases': [{'name': 'db1'}], 'host': '0.0.0.0'}, + {'name': 'user3', 'password': 'password1', + 'databases': [{'name': 'db1'}, {'name': 'db2'}]}] + + def get_dynamic_group(self): + return {'key_buffer_size': 10485760, + 'join_buffer_size': 10485760} + + def get_non_dynamic_group(self): + return {'innodb_buffer_pool_size': 10485760} + + def get_invalid_groups(self): + return [{'key_buffer_size': 4}, {"join_buffer_size": 'string_value'}] diff --git a/trove/tests/scenario/helpers/redis_helper.py b/trove/tests/scenario/helpers/redis_helper.py index 1f0f2a4928..e0ff8115c8 100644 --- a/trove/tests/scenario/helpers/redis_helper.py +++ b/trove/tests/scenario/helpers/redis_helper.py @@ -153,3 +153,12 @@ class RedisHelper(TestHelper): TestRunner.assert_equal(expected_value, value, "Unexpected value '%s' returned from Redis " "key '%s'" % (value, key)) + + def get_dynamic_group(self): + return {'hz': 15} + + def get_non_dynamic_group(self): + return {'databases': 24} + + def get_invalid_groups(self): + return [{'hz': 600}, {'databases': -1}, {'databases': 'string_value'}] diff --git a/trove/tests/scenario/helpers/test_helper.py b/trove/tests/scenario/helpers/test_helper.py index e30dc2c341..3dc35165f2 100644 --- a/trove/tests/scenario/helpers/test_helper.py +++ b/trove/tests/scenario/helpers/test_helper.py @@ -192,3 +192,38 @@ class TestHelper(object): specific overrides could increase (or decrease) this delay. """ sleep(30) + + def get_valid_database_definitions(self): + """Return a list of valid database JSON definitions. + These definitions will be used by tests that create databases. + Return an empty list if the datastore does not support databases. + """ + return list() + + def get_valid_user_definitions(self): + """Return a list of valid user JSON definitions. + These definitions will be used by tests that create users. + Return an empty list if the datastore does not support users. + """ + return list() + + def get_dynamic_group(self): + """Return a definition of a dynamic configuration group. + A dynamic group should contain only properties that do not require + database restart. + Return an empty dict if the datastore does not have any. + """ + return dict() + + def get_non_dynamic_group(self): + """Return a definition of a non-dynamic configuration group. + A non-dynamic group has to include at least one property that requires + database restart. + Return an empty dict if the datastore does not have any. + """ + return dict() + + def get_invalid_groups(self): + """Return a list of configuration groups with invalid values. + """ + return [] diff --git a/trove/tests/scenario/runners/cluster_actions_runners.py b/trove/tests/scenario/runners/cluster_actions_runners.py index 6c0163ffa5..ddbab86171 100644 --- a/trove/tests/scenario/runners/cluster_actions_runners.py +++ b/trove/tests/scenario/runners/cluster_actions_runners.py @@ -60,6 +60,10 @@ class ClusterActionsRunner(TestRunner): return cluster_id def run_cluster_communication(self): + # TODO(pmalik): This will need to be generalized + # (using a datastore test_helper) to add and verify data. + # Creating and checking databases like this would not work with + # datastores that do not support them (Redis). databases = [] databases.append({"name": 'somenewdb'}) cluster = self.auth_client.clusters.get(self.cluster_id) @@ -73,7 +77,7 @@ class ClusterActionsRunner(TestRunner): for instance in cluster_instances: databases_after = self.auth_client.databases.list( cluster_instances[0].id) - asserts.assert_true(len(databases_before) < len(databases_after)) + self.assert_true(len(databases_before) < len(databases_after)) def run_cluster_delete( self, expected_last_instance_state='SHUTDOWN', diff --git a/trove/tests/scenario/runners/database_actions_runners.py b/trove/tests/scenario/runners/database_actions_runners.py index c2da8f62ef..83da206483 100644 --- a/trove/tests/scenario/runners/database_actions_runners.py +++ b/trove/tests/scenario/runners/database_actions_runners.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from proboscis import asserts - from trove.tests.scenario.runners.test_runners import TestRunner from troveclient.compat import exceptions @@ -28,15 +26,13 @@ class DatabaseActionsRunner(TestRunner): # more appropriate anyways. def run_databases_create(self, expected_http_code=202): - databases = [{"name": 'database1'}, - {"name": 'database2'}, - {"name": 'database3'}] + databases = self.test_helper.get_valid_database_definitions() self.db_defs = self.assert_databases_create( self.instance_info.id, databases, expected_http_code) def assert_databases_create(self, instance_id, serial_databases_def, expected_http_code): - self.rd_client.databases.create(instance_id, serial_databases_def) + self.auth_client.databases.create(instance_id, serial_databases_def) self.assert_client_code(expected_http_code) return serial_databases_def @@ -46,15 +42,15 @@ class DatabaseActionsRunner(TestRunner): def assert_databases_list(self, instance_id, expected_database_defs, expected_http_code, limit=2): - full_list = self.rd_client.databases.list(instance_id) + full_list = self.auth_client.databases.list(instance_id) self.assert_client_code(expected_http_code) listed_databases = {database.name: database for database in full_list} - asserts.assert_is_none(full_list.next, - "Unexpected pagination in the list.") + self.assert_is_none(full_list.next, + "Unexpected pagination in the list.") for database_def in expected_database_defs: database_name = database_def['name'] - asserts.assert_true( + self.assert_true( database_name in listed_databases, "Database not included in the 'database-list' output: %s" % database_name) @@ -62,41 +58,49 @@ class DatabaseActionsRunner(TestRunner): # Check that the system (ignored) databases are not included in the # output. system_databases = self.get_system_databases() - asserts.assert_false( + self.assert_false( any(name in listed_databases for name in system_databases), "System databases should not be included in the 'database-list' " "output.") # Test list pagination. - list_page = self.rd_client.databases.list(instance_id, limit=limit) + list_page = self.auth_client.databases.list(instance_id, limit=limit) self.assert_client_code(expected_http_code) - asserts.assert_true(len(list_page) <= limit) - asserts.assert_is_not_none(list_page.next, "List page is missing.") + self.assert_true(len(list_page) <= limit) + if len(full_list) > limit: + self.assert_is_not_none(list_page.next, "List page is missing.") + else: + self.assert_is_none(list_page.next, "An extra page in the list.") marker = list_page.next self.assert_pagination_match(list_page, full_list, 0, limit) - self.assert_pagination_match( - list_page[-1:], full_list, limit - 1, limit) + if marker: + self.assert_equal(list_page[-1], marker.name, + "Pagination marker should be the last element " + "in the page.") + list_page = self.auth_client.databases.list( + instance_id, marker=marker) + self.assert_client_code(expected_http_code) + self.assert_pagination_match( + list_page, full_list, limit, len(full_list)) - list_page = self.rd_client.databases.list(instance_id, marker=marker) - self.assert_client_code(expected_http_code) - self.assert_pagination_match( - list_page, full_list, limit, len(full_list)) - - def run_negative_database_create( + def run_database_create_with_no_attributes( self, expected_exception=exceptions.BadRequest, expected_http_code=400): - # Test with no attribites. self.assert_databases_create_failure( self.instance_info.id, {}, expected_exception, expected_http_code) - # Test with empty database name attribute. + def run_database_create_with_blank_name( + self, expected_exception=exceptions.BadRequest, + expected_http_code=400): self.assert_databases_create_failure( self.instance_info.id, {'name': ''}, expected_exception, expected_http_code) - # Test creating an existing database. + def run_existing_database_create( + self, expected_exception=exceptions.BadRequest, + expected_http_code=400): self.assert_databases_create_failure( self.instance_info.id, self.db_defs[0], expected_exception, expected_http_code) @@ -105,8 +109,11 @@ class DatabaseActionsRunner(TestRunner): self, instance_id, serial_databases_def, expected_exception, expected_http_code): self.assert_raises( - expected_exception, expected_http_code, - self.rd_client.databases.create, instance_id, serial_databases_def) + expected_exception, + expected_http_code, + self.auth_client.databases.create, + instance_id, + serial_databases_def) def run_system_database_create( self, expected_exception=exceptions.BadRequest, @@ -115,13 +122,11 @@ class DatabaseActionsRunner(TestRunner): # return Forbidden 403 instead. The current error messages are # confusing (talking about a malformed request). system_databases = self.get_system_databases() + database_defs = [{'name': name} for name in system_databases] if system_databases: - for name in system_databases: - database_def = {'name': name, 'password': 'password1', - 'databases': []} - self.assert_databases_create_failure( - self.instance_info.id, database_def, - expected_exception, expected_http_code) + self.assert_databases_create_failure( + self.instance_info.id, database_defs, + expected_exception, expected_http_code) def run_database_delete(self, expected_http_code=202): for database_def in self.db_defs: @@ -134,12 +139,12 @@ class DatabaseActionsRunner(TestRunner): instance_id, database_name, expected_http_code): - self.rd_client.databases.delete(instance_id, database_name) + self.auth_client.databases.delete(instance_id, database_name) self.assert_client_code(expected_http_code) - for database in self.rd_client.databases.list(instance_id): + for database in self.auth_client.databases.list(instance_id): if database.name == database_name: - asserts.fail( + self.fail( "Database still listed after delete: %s" % database_name) @@ -166,19 +171,8 @@ class DatabaseActionsRunner(TestRunner): self, instance_id, database_name, expected_exception, expected_http_code): self.assert_raises(expected_exception, expected_http_code, - self.rd_client.databases.delete, + self.auth_client.databases.delete, instance_id, database_name) def get_system_databases(self): return self.get_datastore_config_property('ignore_dbs') - - -class MysqlDatabaseActionsRunner(DatabaseActionsRunner): - - def get_system_databases(self): - # It seems the client does not like this name. - # Does this particular name actually still have to be ignored after - # all the datadir changes? - return [name for name - in self.get_datastore_config_property('ignore_dbs') - if name != '#mysql50#lost+found'] diff --git a/trove/tests/scenario/runners/instance_create_runners.py b/trove/tests/scenario/runners/instance_create_runners.py new file mode 100644 index 0000000000..f5cb572924 --- /dev/null +++ b/trove/tests/scenario/runners/instance_create_runners.py @@ -0,0 +1,300 @@ +# Copyright 2015 Tesora Inc. +# All Rights Reserved. +# +# 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 json + +from proboscis import SkipTest + +from trove.tests.api.instances import CheckInstance, InstanceTestInfo +from trove.tests.config import CONFIG +from trove.tests.scenario.helpers.test_helper import DataType +from trove.tests.scenario.runners.test_runners import TestRunner + + +class InstanceCreateRunner(TestRunner): + + def __init__(self): + super(InstanceCreateRunner, self).__init__() + self.init_inst_id = None + self.init_inst_dbs = None + self.init_inst_users = None + self.init_inst_host = None + self.init_inst_data = None + self.init_config_group_id = None + + def run_empty_instance_create( + self, expected_states=['BUILD', 'ACTIVE'], expected_http_code=200): + # TODO(pmalik): Instance create should return 202 Accepted (cast) + # rather than 200 OK (call). + name = self.instance_info.name + flavor = self._get_instance_flavor() + trove_volume_size = CONFIG.get('trove_volume_size', 1) + + info = self.assert_instance_create( + name, flavor, trove_volume_size, [], [], None, None, + CONFIG.dbaas_datastore, CONFIG.dbaas_datastore_version, + expected_states, expected_http_code) + + # Update the shared instance info. + self.instance_info.databases = info.databases + self.instance_info.users = info.users + self.instance_info.dbaas_datastore = info.dbaas_datastore + self.instance_info.dbaas_datastore_version = (info. + dbaas_datastore_version) + self.instance_info.dbaas_flavor_href = info.dbaas_flavor_href + self.instance_info.volume = info.volume + self.instance_info.id = info.id + + def run_initial_configuration_create(self, expected_http_code=200): + dynamic_config = self.test_helper.get_dynamic_group() + non_dynamic_config = self.test_helper.get_non_dynamic_group() + values = dynamic_config or non_dynamic_config + if values: + json_def = json.dumps(values) + result = self.auth_client.configurations.create( + 'initial_configuration_for_instance_create', + json_def, + "Configuration group used by instance create tests.", + datastore=self.instance_info.dbaas_datastore, + datastore_version=self.instance_info.dbaas_datastore_version) + self.assert_client_code(expected_http_code) + + self.init_config_group_id = result.id + else: + raise SkipTest("No groups defined.") + + def run_initialized_instance_create( + self, with_dbs=True, with_users=True, configuration_id=None, + expected_states=['BUILD', 'ACTIVE'], expected_http_code=200): + # TODO(pmalik): Instance create should return 202 Accepted (cast) + # rather than 200 OK (call). + name = self.instance_info.name + flavor = self._get_instance_flavor() + trove_volume_size = CONFIG.get('trove_volume_size', 1) + self.init_inst_dbs = (self.test_helper.get_valid_database_definitions() + if with_dbs else []) + self.init_inst_users = (self.test_helper.get_valid_user_definitions() + if with_users else []) + if configuration_id: + self.init_config_group_id = configuration_id + + if (self.init_inst_dbs or self.init_inst_users or + self.init_config_group_id): + info = self.assert_instance_create( + name, flavor, trove_volume_size, + self.init_inst_dbs, self.init_inst_users, + self.init_config_group_id, None, + CONFIG.dbaas_datastore, CONFIG.dbaas_datastore_version, + expected_states, expected_http_code) + + self.init_inst_id = info.id + else: + # There is no need to run this test as it's effectively the same as + # the empty instance test. + raise SkipTest("No testable initial properties provided.") + + def _get_instance_flavor(self): + if self.EPHEMERAL_SUPPORT: + flavor_name = CONFIG.values.get('instance_eph_flavor_name', + 'eph.rd-tiny') + else: + flavor_name = CONFIG.values.get('instance_flavor_name', 'm1.tiny') + + return self.get_flavor(flavor_name) + + def _get_flavor_href(self, flavor): + return self.auth_client.find_flavor_self_href(flavor) + + def assert_instance_create( + self, name, flavor, trove_volume_size, + database_definitions, user_definitions, + configuration_id, root_password, datastore, datastore_version, + expected_states, expected_http_code): + """This assert method executes a 'create' call and verifies the server + response. It neither waits for the instance to become available + nor it performs any other validations itself. + It has been designed this way to increase test granularity + (other tests may run while the instance is building) and also to allow + its reuse in other runners . + """ + + databases = database_definitions + users = [{'name': item['name'], 'password': item['password']} + for item in user_definitions] + + instance_info = InstanceTestInfo() + instance_info.name = name + instance_info.databases = databases + instance_info.users = users + instance_info.dbaas_datastore = CONFIG.dbaas_datastore + instance_info.dbaas_datastore_version = CONFIG.dbaas_datastore_version + instance_info.dbaas_flavor_href = self._get_flavor_href(flavor) + if self.VOLUME_SUPPORT: + instance_info.volume = {'size': trove_volume_size} + else: + instance_info.volume = None + + self.report.log("Testing create instance: %s" + % {'name': name, + 'flavor': flavor.id, + 'volume': trove_volume_size, + 'databases': databases, + 'users': users, + 'configuration': configuration_id, + 'root password': root_password, + 'datastore': datastore, + 'datastore version': datastore_version}) + + instance = self.get_existing_instance() + if instance: + self.report.log("Using an existing instance: %s" % instance.id) + self.assert_equal(expected_states[-1], instance.status, + "Given instance is in a bad state.") + else: + self.report.log("Creating a new instance.") + instance = self.auth_client.instances.create( + instance_info.name, + instance_info.dbaas_flavor_href, + instance_info.volume, + instance_info.databases, + instance_info.users, + configuration=configuration_id, + availability_zone="nova", + datastore=instance_info.dbaas_datastore, + datastore_version=instance_info.dbaas_datastore_version) + self.assert_instance_action( + instance.id, expected_states[0:1], expected_http_code) + + instance_info.id = instance.id + + with CheckInstance(instance._info) as check: + check.flavor() + check.datastore() + check.links(instance._info['links']) + if self.VOLUME_SUPPORT: + check.volume() + self.assert_equal(trove_volume_size, + instance._info['volume']['size'], + "Unexpected Trove volume size") + + self.assert_equal(instance_info.name, instance._info['name'], + "Unexpected instance name") + self.assert_equal(flavor.id, + int(instance._info['flavor']['id']), + "Unexpected instance flavor") + self.assert_equal(instance_info.dbaas_datastore, + instance._info['datastore']['type'], + "Unexpected instance datastore version") + self.assert_equal(instance_info.dbaas_datastore_version, + instance._info['datastore']['version'], + "Unexpected instance datastore version") + self.assert_configuration_group(instance_info.id, configuration_id) + + return instance_info + + def wait_for_created_instances(self, expected_states=['BUILD', 'ACTIVE']): + instances = [self.instance_info.id] + if self.init_inst_id: + instances.append(self.init_inst_id) + self.assert_all_instance_states(instances, expected_states) + + def run_add_initialized_instance_data(self): + self.init_inst_data = DataType.small + self.init_inst_host = self.get_instance_host(self.instance_info.id) + self.test_helper.add_data(self.init_inst_data, self.init_inst_host) + + def run_validate_initialized_instance(self): + if self.init_inst_id: + self.assert_instance_properties( + self.init_inst_id, self.init_inst_dbs, self.init_inst_users, + self.init_config_group_id, self.init_inst_data) + + def assert_instance_properties( + self, instance_id, expected_dbs_definitions, + expected_user_definitions, expected_config_group_id, + expected_data_type): + if expected_dbs_definitions: + self.assert_database_list(instance_id, expected_dbs_definitions) + else: + self.report.log("No databases to validate for instance: %s" + % instance_id) + if expected_user_definitions: + self.assert_user_list(instance_id, expected_user_definitions) + else: + self.report.log("No users to validate for instance: %s" + % instance_id) + self.assert_configuration_group(instance_id, expected_config_group_id) + + if self.init_inst_host: + self.test_helper.verify_data( + expected_data_type, self.init_inst_host) + else: + self.report.log("No data to validate for instance: %s" + % instance_id) + + def assert_configuration_group(self, instance_id, expected_group_id): + instance = self.get_instance(instance_id) + if expected_group_id: + self.assert_equal(expected_group_id, instance.configuration['id'], + "Wrong configuration group attached") + else: + self.assert_false(hasattr(instance, 'configuration'), + "No configuration group expected") + + def assert_database_list(self, instance_id, expected_databases): + expected_names = self._get_names(expected_databases) + full_list = self.auth_client.databases.list(instance_id) + self.assert_is_none(full_list.next, + "Unexpected pagination in the database list.") + listed_names = [database.name for database in full_list] + self.assert_list_elements_equal(expected_names, listed_names, + "Mismatch in instance databases.") + + def _get_names(self, definitions): + return [item['name'] for item in definitions] + + def assert_user_list(self, instance_id, expected_users): + expected_names = self._get_names(expected_users) + full_list = self.auth_client.users.list(instance_id) + self.assert_is_none(full_list.next, + "Unexpected pagination in the user list.") + listed_names = [user.name for user in full_list] + self.assert_list_elements_equal(expected_names, listed_names, + "Mismatch in instance users.") + + # Verify that user definitions include only created databases. + all_databases = self._get_names( + self.test_helper.get_valid_database_definitions()) + for user in expected_users: + self.assert_is_sublist( + self._get_names(user['databases']), all_databases, + "Definition of user '%s' specifies databases not included in " + "the list of initial databases." % user['name']) + + def run_initialized_instance_delete(self, expected_states=['SHUTDOWN'], + expected_http_code=202): + if self.init_inst_id: + self.auth_client.instances.delete(self.init_inst_id) + self.assert_client_code(expected_http_code) + self.assert_all_gone(self.init_inst_id, expected_states[-1]) + else: + raise SkipTest("Cleanup is not required.") + + def run_initial_configuration_delete(self, expected_http_code=202): + if self.init_config_group_id: + self.auth_client.configurations.delete(self.init_config_group_id) + self.assert_client_code(expected_http_code) + else: + raise SkipTest("Cleanup is not required.") diff --git a/trove/tests/scenario/runners/test_runners.py b/trove/tests/scenario/runners/test_runners.py index 905ea2b20b..8de955fec7 100644 --- a/trove/tests/scenario/runners/test_runners.py +++ b/trove/tests/scenario/runners/test_runners.py @@ -62,13 +62,14 @@ class TestRunner(object): EPHEMERAL_SUPPORT = not VOLUME_SUPPORT and CONFIG.get('device_path', None) ROOT_PARTITION = not (VOLUME_SUPPORT or CONFIG.get('device_path', None)) + report = CONFIG.get_report() + def __init__(self, sleep_time=10, timeout=1200): self.def_sleep_time = sleep_time self.def_timeout = timeout self.instance_info = instance_info self.auth_client = create_dbaas_client(self.instance_info.user) self.unauth_client = None - self.report = CONFIG.get_report() self._test_helper = None @classmethod diff --git a/trove/tests/scenario/runners/user_actions_runners.py b/trove/tests/scenario/runners/user_actions_runners.py index 88e7d0fc10..a891a883a8 100644 --- a/trove/tests/scenario/runners/user_actions_runners.py +++ b/trove/tests/scenario/runners/user_actions_runners.py @@ -13,10 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. -from proboscis import asserts +import urllib from trove.tests.scenario.runners.test_runners import TestRunner from troveclient.compat import exceptions +from troveclient.openstack.common.apiclient.exceptions import ValidationError class UserActionsRunner(TestRunner): @@ -28,18 +29,13 @@ class UserActionsRunner(TestRunner): # more appropriate anyways. def run_users_create(self, expected_http_code=202): - users = [{'name': 'nodbguy', 'password': 'password1', - 'databases': []}, - {'name': 'singledbguy', 'password': 'password1', - 'databases': [{'name': 'db1'}]}, - {'name': 'multidbguy', 'password': 'password1', - 'databases': [{'name': 'db1', 'name': 'db2'}]}] + users = self.test_helper.get_valid_user_definitions() self.user_defs = self.assert_users_create( self.instance_info.id, users, expected_http_code) def assert_users_create(self, instance_id, serial_users_def, expected_http_code): - self.rd_client.users.create(instance_id, serial_users_def) + self.auth_client.users.create(instance_id, serial_users_def) self.assert_client_code(expected_http_code) return serial_users_def @@ -51,16 +47,19 @@ class UserActionsRunner(TestRunner): def assert_user_show(self, instance_id, expected_user_def, expected_http_code): user_name = expected_user_def['name'] - queried_user = self.rd_client.users.get(instance_id, user_name, '%') + user_host = expected_user_def.get('host') + + queried_user = self.auth_client.users.get( + instance_id, user_name, user_host) self.assert_client_code(expected_http_code) self._assert_user_matches(queried_user, expected_user_def) def _assert_user_matches(self, user, expected_user_def): user_name = expected_user_def['name'] - asserts.assert_equal(user.name, expected_user_def['name'], - "Mismatch of names for user: %s" % user_name) - asserts.assert_equal(user.databases, expected_user_def['databases'], - "Mismatch of databases for user: %s" % user_name) + self.assert_equal(user.name, expected_user_def['name'], + "Mismatch of names for user: %s" % user_name) + self.assert_equal(user.databases, expected_user_def['databases'], + "Mismatch of databases for user: %s" % user_name) def run_users_list(self, expected_http_code=200): self.assert_users_list( @@ -68,15 +67,15 @@ class UserActionsRunner(TestRunner): def assert_users_list(self, instance_id, expected_user_defs, expected_http_code, limit=2): - full_list = self.rd_client.users.list(instance_id) + full_list = self.auth_client.users.list(instance_id) self.assert_client_code(expected_http_code) listed_users = {user.name: user for user in full_list} - asserts.assert_is_none(full_list.next, - "Unexpected pagination in the list.") + self.assert_is_none(full_list.next, + "Unexpected pagination in the list.") for user_def in expected_user_defs: user_name = user_def['name'] - asserts.assert_true( + self.assert_true( user_name in listed_users, "User not included in the 'user-list' output: %s" % user_name) @@ -84,34 +83,43 @@ class UserActionsRunner(TestRunner): # Check that the system (ignored) users are not included in the output. system_users = self.get_system_users() - asserts.assert_false( + self.assert_false( any(name in listed_users for name in system_users), "System users should not be included in the 'user-list' output.") # Test list pagination. - list_page = self.rd_client.users.list(instance_id, limit=limit) + list_page = self.auth_client.users.list(instance_id, limit=limit) self.assert_client_code(expected_http_code) - asserts.assert_true(len(list_page) <= limit) - asserts.assert_is_not_none(list_page.next, "List page is missing.") + self.assert_true(len(list_page) <= limit) + if len(full_list) > limit: + self.assert_is_not_none(list_page.next, "List page is missing.") + else: + self.assert_is_none(list_page.next, "An extra page in the list.") marker = list_page.next self.assert_pagination_match(list_page, full_list, 0, limit) - self.assert_pagination_match( - list_page[-1:], full_list, limit - 1, limit) + if marker: + last_user = list_page[-1] + expected_marker = urllib.quote( + '%s@%s' % (last_user.name, last_user.host)) + self.assert_equal(expected_marker, marker, + "Pagination marker should be the last element " + "in the page.") + list_page = self.auth_client.users.list(instance_id, marker=marker) + self.assert_client_code(expected_http_code) + self.assert_pagination_match( + list_page, full_list, limit, len(full_list)) - list_page = self.rd_client.users.list(instance_id, marker=marker) - self.assert_client_code(expected_http_code) - self.assert_pagination_match( - list_page, full_list, limit, len(full_list)) - - def run_negative_user_create( + def run_user_create_with_no_attributes( self, expected_exception=exceptions.BadRequest, expected_http_code=400): - # Test with no attribites. self.assert_users_create_failure( self.instance_info.id, {}, expected_exception, expected_http_code) + def run_user_create_with_blank_name( + self, expected_exception=exceptions.BadRequest, + expected_http_code=400): # Test with missing user name attribute. self.assert_users_create_failure( self.instance_info.id, @@ -124,6 +132,9 @@ class UserActionsRunner(TestRunner): {'name': '', 'password': 'password1', 'databases': []}, expected_exception, expected_http_code) + def run_user_create_with_blank_password( + self, expected_exception=exceptions.BadRequest, + expected_http_code=400): # Test with missing password attribute. self.assert_users_create_failure( self.instance_info.id, @@ -136,7 +147,9 @@ class UserActionsRunner(TestRunner): {'name': 'nodbguy', 'password': 'password1'}, expected_exception, expected_http_code) - # Test creating an existing user. + def run_existing_user_create( + self, expected_exception=exceptions.BadRequest, + expected_http_code=400): self.assert_users_create_failure( self.instance_info.id, self.user_defs[0], expected_exception, expected_http_code) @@ -149,48 +162,52 @@ class UserActionsRunner(TestRunner): # confusing (talking about a malformed request). system_users = self.get_system_users() if system_users: - for name in system_users: - user_def = {'name': name, 'password': 'password1', - 'databases': []} - self.assert_users_create_failure( - self.instance_info.id, user_def, - expected_exception, expected_http_code) + user_defs = [{'name': name, 'password': 'password1', + 'databases': []} for name in system_users] + self.assert_users_create_failure( + self.instance_info.id, user_defs, + expected_exception, expected_http_code) def assert_users_create_failure( self, instance_id, serial_users_def, expected_exception, expected_http_code): self.assert_raises( expected_exception, expected_http_code, - self.rd_client.users.create, instance_id, serial_users_def) + self.auth_client.users.create, instance_id, serial_users_def) - def run_negative_user_attribute_update( - self, expected_exception=exceptions.BadRequest, - expected_http_code=400): - # Test some basic invalid attributes on an existing user. - - # Test with no attribites. + def run_user_update_with_no_attributes(self): # Note: this is caught on the client-side. self.assert_user_attribute_update_failure( - self.instance_info.id, 'nodbguy', {}, Exception, None) + self.instance_info.id, self.user_defs[0], + {}, ValidationError, None) - # Test with empty user name. + def run_user_update_with_blank_name( + self, expected_exception=exceptions.BadRequest, + expected_http_code=400): self.assert_user_attribute_update_failure( - self.instance_info.id, 'nodbguy', {'name': ''}, + self.instance_info.id, self.user_defs[0], {'name': ''}, expected_exception, expected_http_code) - # Test updating an existing user with a conflicting name. + def run_user_update_with_existing_name( + self, expected_exception=exceptions.BadRequest, + expected_http_code=400): self.assert_user_attribute_update_failure( - self.instance_info.id, self.user_defs[0]['name'], - {'name': self.user_defs[1]['name']}, + self.instance_info.id, self.user_defs[0], + {'name': self.user_defs[0]['name']}, expected_exception, expected_http_code) def assert_user_attribute_update_failure( - self, instance_id, user_name, update_attribites, + self, instance_id, user_def, update_attribites, expected_exception, expected_http_code): + user_name, user_host = self._get_user_name_host_pair(user_def) + self.assert_raises( expected_exception, expected_http_code, - self.rd_client.users.update_attributes, instance_id, - user_name, update_attribites) + self.auth_client.users.update_attributes, instance_id, + user_name, update_attribites, user_host) + + def _get_user_name_host_pair(self, user_def): + return user_def['name'], user_def.get('host') def run_system_user_attribute_update( self, expected_exception=exceptions.BadRequest, @@ -201,21 +218,23 @@ class UserActionsRunner(TestRunner): system_users = self.get_system_users() if system_users: for name in system_users: - update_attribites = {'name': name, 'password': 'password2'} + user_def = {'name': name, 'password': 'password2'} self.assert_user_attribute_update_failure( - self.instance_info.id, name, update_attribites, + self.instance_info.id, user_def, user_def, expected_exception, expected_http_code) def run_user_attribute_update(self, expected_http_code=202): update_attribites = {'name': 'dblessguy', 'password': 'password2'} self.assert_user_attribute_update( - self.instance_info.id, 'nodbguy', update_attribites, - expected_http_code) + self.instance_info.id, self.user_defs[0], + update_attribites, expected_http_code) - def assert_user_attribute_update(self, instance_id, user_name, + def assert_user_attribute_update(self, instance_id, user_def, update_attribites, expected_http_code): - self.rd_client.users.update_attributes( - instance_id, user_name, update_attribites) + user_name, user_host = self._get_user_name_host_pair(user_def) + + self.auth_client.users.update_attributes( + instance_id, user_name, update_attribites, user_host) self.assert_client_code(expected_http_code) # Update the stored definitions with the new value. @@ -232,31 +251,36 @@ class UserActionsRunner(TestRunner): def run_user_delete(self, expected_http_code=202): for user_def in self.user_defs: self.assert_user_delete( - self.instance_info.id, user_def['name'], expected_http_code) + self.instance_info.id, user_def, expected_http_code) - def assert_user_delete(self, instance_id, user_name, expected_http_code): - self.rd_client.users.delete(instance_id, user_name) + def assert_user_delete(self, instance_id, user_def, expected_http_code): + user_name, user_host = self._get_user_name_host_pair(user_def) + + self.auth_client.users.delete(instance_id, user_name, user_host) self.assert_client_code(expected_http_code) self.assert_raises(exceptions.NotFound, 404, - self.rd_client.users.get, - instance_id, user_name, '%') + self.auth_client.users.get, + instance_id, user_name, user_host) - for user in self.rd_client.users.list(instance_id): + for user in self.auth_client.users.list(instance_id): if user.name == user_name: - asserts.fail("User still listed after delete: %s" % user_name) + self.fail("User still listed after delete: %s" % user_name) def run_nonexisting_user_show( self, expected_exception=exceptions.NotFound, expected_http_code=404): - self.assert_user_show_failure(self.instance_info.id, 'nonexistingusr', - expected_exception, expected_http_code) + self.assert_user_show_failure( + self.instance_info.id, {'name': 'nonexistingusr'}, + expected_exception, expected_http_code) - def assert_user_show_failure(self, instance_id, user_name, + def assert_user_show_failure(self, instance_id, user_def, expected_exception, expected_http_code): + user_name, user_host = self._get_user_name_host_pair(user_def) + self.assert_raises( expected_exception, expected_http_code, - self.rd_client.users.get, instance_id, user_name, '%') + self.auth_client.users.get, instance_id, user_name, user_host) def run_system_user_show( self, expected_exception=exceptions.BadRequest, @@ -268,27 +292,31 @@ class UserActionsRunner(TestRunner): if system_users: for name in system_users: self.assert_user_show_failure( - self.instance_info.id, name, + self.instance_info.id, {'name': name}, expected_exception, expected_http_code) def run_nonexisting_user_update(self, expected_http_code=404): # Test valid update on a non-existing user. + user_def = {'name': 'justashadow'} self.assert_user_attribute_update_failure( - self.instance_info.id, 'nonexistingusr', {'name': 'justashadow'}, + self.instance_info.id, user_def, user_def, exceptions.NotFound, expected_http_code) def run_nonexisting_user_delete( self, expected_exception=exceptions.NotFound, expected_http_code=404): - self.assert_user_delete_failure(self.instance_info.id, 'justashadow', - expected_exception, expected_http_code) + self.assert_user_delete_failure( + self.instance_info.id, {'name': 'justashadow'}, + expected_exception, expected_http_code) def assert_user_delete_failure( - self, instance_id, user_name, + self, instance_id, user_def, expected_exception, expected_http_code): + user_name, user_host = self._get_user_name_host_pair(user_def) + self.assert_raises(expected_exception, expected_http_code, - self.rd_client.users.delete, - instance_id, user_name) + self.auth_client.users.delete, + instance_id, user_name, user_host) def run_system_user_delete( self, expected_exception=exceptions.BadRequest, @@ -300,7 +328,7 @@ class UserActionsRunner(TestRunner): if system_users: for name in system_users: self.assert_user_delete_failure( - self.instance_info.id, name, + self.instance_info.id, {'name': name}, expected_exception, expected_http_code) def get_system_users(self):