diff --git a/trove/tests/int_tests.py b/trove/tests/int_tests.py index da68ef90ea..7947e615a5 100644 --- a/trove/tests/int_tests.py +++ b/trove/tests/int_tests.py @@ -34,6 +34,7 @@ from trove.tests.api import users from trove.tests.api import versions from trove.tests.scenario.groups import backup_group from trove.tests.scenario.groups import cluster_actions_group +from trove.tests.scenario.groups import configuration_group from trove.tests.scenario.groups import database_actions_group from trove.tests.scenario.groups import guest_log_group from trove.tests.scenario.groups import instance_actions_group @@ -120,6 +121,7 @@ proboscis.register(groups=["blackbox_mgmt"], # # Group designations for datastore agnostic int-tests # +# Base groups for all other groups base_groups = [ GROUP_SERVICES_INITIALIZE, flavors.GROUP, @@ -127,12 +129,12 @@ base_groups = [ GROUP_SETUP ] -# cluster groups +# Cluster-based groups cluster_actions_groups = list(base_groups) cluster_actions_groups.extend([cluster_actions_group.GROUP, negative_cluster_actions_group.GROUP]) -# instance groups +# Single-instance based groups instance_create_groups = list(base_groups) instance_create_groups.extend([instance_create_group.GROUP, instance_delete_group.GROUP]) @@ -140,8 +142,8 @@ instance_create_groups.extend([instance_create_group.GROUP, backup_groups = list(instance_create_groups) backup_groups.extend([backup_group.GROUP]) -user_actions_groups = list(instance_create_groups) -user_actions_groups.extend([user_actions_group.GROUP]) +configuration_groups = list(instance_create_groups) +configuration_groups.extend([configuration_group.GROUP]) database_actions_groups = list(instance_create_groups) database_actions_groups.extend([database_actions_group.GROUP]) @@ -155,40 +157,45 @@ instance_actions_groups.extend([instance_actions_group.GROUP]) replication_groups = list(instance_create_groups) replication_groups.extend([replication_group.GROUP]) +user_actions_groups = list(instance_create_groups) +user_actions_groups.extend([user_actions_group.GROUP]) + # groups common to all datastores common_groups = list(instance_actions_groups) common_groups.extend([guest_log_groups]) -# Module based groups +# Register: Module based groups register(["backup"], backup_groups) register(["cluster"], cluster_actions_groups) +register(["configuration"], configuration_groups) register(["database"], database_actions_groups) register(["guest_log"], guest_log_groups) register(["instance", "instance_actions"], instance_actions_groups) register(["instance_create"], instance_create_groups) -register(["user"], user_actions_groups) register(["replication"], replication_groups) +register(["user"], user_actions_groups) -# Datastore based groups - these should contain all functionality -# currently supported by the datastore +# Register: Datastore based groups +# These should contain all functionality currently supported by the datastore +register(["db2_supported"], common_groups, + database_actions_groups, user_actions_groups) register(["cassandra_supported"], common_groups, - backup_groups) -register(["couchbase_supported"], common_groups) + backup_groups, configuration_groups) +register(["couchbase_supported"], common_groups, backup_groups) register(["couchdb_supported"], common_groups) register(["postgresql_supported"], common_groups, - backup_groups, database_actions_groups, user_actions_groups) -register(["mongodb_supported"], common_groups, - backup_groups, cluster_actions_groups, - database_actions_groups, user_actions_groups) -register(["mysql_supported", "mariadb_supported", "percona_supported"], + backup_groups, database_actions_groups, configuration_groups, + user_actions_groups) +register(["mariadb_supported", "mysql_supported", "percona_supported"], common_groups, - backup_groups, database_actions_groups, + backup_groups, configuration_groups, database_actions_groups, replication_groups, user_actions_groups) +register(["mongodb_supported"], common_groups, + backup_groups, cluster_actions_groups, configuration_groups, + database_actions_groups, user_actions_groups) +register(["pxc_supported"], common_groups, + cluster_actions_groups) register(["redis_supported"], common_groups, backup_groups, replication_groups) register(["vertica_supported"], common_groups, cluster_actions_groups) -register(["pxc_supported"], common_groups, - cluster_actions_groups) -register(["db2_supported"], common_groups, - database_actions_groups, user_actions_groups) diff --git a/trove/tests/scenario/groups/configuration_group.py b/trove/tests/scenario/groups/configuration_group.py new file mode 100644 index 0000000000..23184144cc --- /dev/null +++ b/trove/tests/scenario/groups/configuration_group.py @@ -0,0 +1,232 @@ +# 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.scenario.groups import instance_create_group +from trove.tests.scenario.groups.test_group import TestGroup + + +GROUP = "scenario.configuration_group" + + +@test(depends_on_groups=[instance_create_group.GROUP], groups=[GROUP]) +class ConfigurationGroup(TestGroup): + + def __init__(self): + super(ConfigurationGroup, self).__init__( + 'configuration_runners', 'ConfigurationRunner') + + @test + def create_bad_group(self): + """Ensure a group with bad entries fails create.""" + self.test_runner.run_create_bad_group() + + @test + def create_invalid_groups(self): + """Ensure a group with invalid entries fails create.""" + self.test_runner.run_create_invalid_groups() + + @test + def delete_non_existent_group(self): + """Ensure delete non-existent group fails.""" + self.test_runner.run_delete_non_existent_group() + + @test + def delete_bad_group_id(self): + """Ensure delete bad group fails.""" + self.test_runner.run_delete_bad_group_id() + + @test + def attach_non_existent_group(self): + """Ensure attach non-existent group fails.""" + self.test_runner.run_attach_non_existent_group() + + def attach_non_existent_group_to_non_existent_inst(self): + """Ensure attach non-existent group to non-existent inst fails.""" + self.test_runner.run_attach_non_existent_group_to_non_existent_inst() + + @test + def detach_group_with_none_attached(self): + """Test detach with none attached.""" + self.test_runner.run_detach_group_with_none_attached() + + @test + def create_dynamic_group(self): + """Create a group with only dynamic entries.""" + self.test_runner.run_create_dynamic_group() + + @test + def create_non_dynamic_group(self): + """Create a group with only non-dynamic entries.""" + self.test_runner.run_create_non_dynamic_group() + + @test(depends_on=[create_dynamic_group]) + def attach_dynamic_group_to_non_existent_inst(self): + """Ensure attach dynamic group to non-existent inst fails.""" + self.test_runner.run_attach_dynamic_group_to_non_existent_inst() + + @test(depends_on=[create_non_dynamic_group]) + def attach_non_dynamic_group_to_non_existent_inst(self): + """Ensure attach non-dynamic group to non-existent inst fails.""" + self.test_runner.run_attach_non_dynamic_group_to_non_existent_inst() + + @test(depends_on=[create_dynamic_group, create_non_dynamic_group]) + def list_configuration_groups(self): + """Test list configuration groups.""" + self.test_runner.run_list_configuration_groups() + + @test(depends_on=[create_dynamic_group]) + def dynamic_configuration_show(self): + """Test show on dynamic group.""" + self.test_runner.run_dynamic_configuration_show() + + @test(depends_on=[create_non_dynamic_group]) + def non_dynamic_configuration_show(self): + """Test show on non-dynamic group.""" + self.test_runner.run_non_dynamic_configuration_show() + + @test(depends_on=[create_dynamic_group]) + def dynamic_conf_get_unauthorized_user(self): + """Ensure show dynamic fails with unauthorized user.""" + self.test_runner.run_dynamic_conf_get_unauthorized_user() + + @test(depends_on=[create_non_dynamic_group]) + def non_dynamic_conf_get_unauthorized_user(self): + """Ensure show non-dynamic fails with unauthorized user.""" + self.test_runner.run_non_dynamic_conf_get_unauthorized_user() + + @test(depends_on=[create_dynamic_group], + runs_after=[list_configuration_groups]) + def list_dynamic_inst_conf_groups_before(self): + """Count list instances for dynamic group before attach.""" + self.test_runner.run_list_dynamic_inst_conf_groups_before() + + @test(depends_on=[create_dynamic_group], + runs_after=[list_dynamic_inst_conf_groups_before, + attach_non_existent_group, + detach_group_with_none_attached]) + def attach_dynamic_group(self): + """Test attach dynamic group.""" + self.test_runner.run_attach_dynamic_group() + + @test(depends_on=[attach_dynamic_group]) + def list_dynamic_inst_conf_groups_after(self): + """Test list instances for dynamic group after attach.""" + self.test_runner.run_list_dynamic_inst_conf_groups_after() + + @test(depends_on=[attach_dynamic_group], + runs_after=[list_dynamic_inst_conf_groups_after]) + def attach_dynamic_group_again(self): + """Ensure attaching dynamic group again fails.""" + self.test_runner.run_attach_dynamic_group_again() + + @test(depends_on=[attach_dynamic_group], + runs_after=[attach_dynamic_group_again]) + def delete_attached_dynamic_group(self): + """Ensure deleting attached dynamic group fails.""" + self.test_runner.run_delete_attached_dynamic_group() + + @test(depends_on=[attach_dynamic_group], + runs_after=[delete_attached_dynamic_group]) + def update_dynamic_group(self): + """Test update dynamic group.""" + self.test_runner.run_update_dynamic_group() + + @test(depends_on=[create_dynamic_group], + runs_after=[update_dynamic_group]) + def detach_dynamic_group(self): + """Test detach dynamic group.""" + self.test_runner.run_detach_dynamic_group() + + @test(depends_on=[create_non_dynamic_group], + runs_after=[detach_dynamic_group]) + def list_non_dynamic_inst_conf_groups_before(self): + """Count list instances for non-dynamic group before attach.""" + self.test_runner.run_list_non_dynamic_inst_conf_groups_before() + + @test(depends_on=[create_non_dynamic_group], + runs_after=[list_non_dynamic_inst_conf_groups_before, + attach_non_existent_group]) + def attach_non_dynamic_group(self): + """Test attach non-dynamic group.""" + self.test_runner.run_attach_non_dynamic_group() + + @test(depends_on=[attach_non_dynamic_group]) + def list_non_dynamic_inst_conf_groups_after(self): + """Test list instances for non-dynamic group after attach.""" + self.test_runner.run_list_non_dynamic_inst_conf_groups_after() + + @test(depends_on=[attach_non_dynamic_group], + runs_after=[list_non_dynamic_inst_conf_groups_after]) + def attach_non_dynamic_group_again(self): + """Ensure attaching non-dynamic group again fails.""" + self.test_runner.run_attach_non_dynamic_group_again() + + @test(depends_on=[attach_non_dynamic_group], + runs_after=[attach_non_dynamic_group_again]) + def delete_attached_non_dynamic_group(self): + """Ensure deleting attached non-dynamic group fails.""" + self.test_runner.run_delete_attached_non_dynamic_group() + + @test(depends_on=[attach_non_dynamic_group], + runs_after=[delete_attached_non_dynamic_group]) + def update_non_dynamic_group(self): + """Test update non-dynamic group.""" + self.test_runner.run_update_non_dynamic_group() + + @test(depends_on=[attach_non_dynamic_group], + runs_after=[update_non_dynamic_group]) + def detach_non_dynamic_group(self): + """Test detach non-dynamic group.""" + self.test_runner.run_detach_non_dynamic_group() + + @test(runs_after=[create_dynamic_group, create_non_dynamic_group, + update_dynamic_group, update_non_dynamic_group]) + def create_instance_with_conf(self): + """Test create instance with conf group.""" + self.test_runner.run_create_instance_with_conf() + + @test(depends_on=[create_instance_with_conf], + runs_after=[create_dynamic_group, create_non_dynamic_group, + update_dynamic_group, update_non_dynamic_group]) + def wait_for_conf_instance(self): + """Test create instance with conf group completes.""" + self.test_runner.run_wait_for_conf_instance() + + @test(depends_on=[wait_for_conf_instance]) + def delete_conf_instance(self): + """Test delete instance with conf group.""" + self.test_runner.run_delete_conf_instance() + + @test(depends_on=[create_dynamic_group], + runs_after=[list_configuration_groups, detach_dynamic_group, + dynamic_configuration_show, + dynamic_conf_get_unauthorized_user, + attach_dynamic_group_to_non_existent_inst, + delete_conf_instance]) + def delete_dynamic_group(self): + """Test delete dynamic group.""" + self.test_runner.run_delete_dynamic_group() + + @test(depends_on=[create_non_dynamic_group], + runs_after=[list_configuration_groups, detach_non_dynamic_group, + non_dynamic_configuration_show, + non_dynamic_conf_get_unauthorized_user, + attach_non_dynamic_group_to_non_existent_inst, + delete_conf_instance]) + def delete_non_dynamic_group(self): + """Test delete non-dynamic group.""" + self.test_runner.run_delete_non_dynamic_group() diff --git a/trove/tests/scenario/groups/instance_delete_group.py b/trove/tests/scenario/groups/instance_delete_group.py index 76cb603f5a..54d530ef6f 100644 --- a/trove/tests/scenario/groups/instance_delete_group.py +++ b/trove/tests/scenario/groups/instance_delete_group.py @@ -16,6 +16,7 @@ from proboscis import test from trove.tests.scenario.groups import backup_group +from trove.tests.scenario.groups import configuration_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 @@ -30,6 +31,7 @@ GROUP = "scenario.instance_delete_group" @test(depends_on_groups=[instance_create_group.GROUP], groups=[GROUP], runs_after_groups=[backup_group.GROUP_BACKUP, + configuration_group.GROUP, database_actions_group.GROUP, instance_actions_group.GROUP, replication_group.GROUP, diff --git a/trove/tests/scenario/groups/test_group.py b/trove/tests/scenario/groups/test_group.py index 342bc2920b..d582ce87c6 100644 --- a/trove/tests/scenario/groups/test_group.py +++ b/trove/tests/scenario/groups/test_group.py @@ -58,22 +58,40 @@ class TestGroup(object): """Try to load a datastore specific class if it exists; use the default otherwise. """ - try: - # This is for overridden Runner classes - impl = self._build_class_path(module_name, class_prefix, base_name) - return Strategy.get_strategy(impl, namespace) - except ImportError: - pass - try: + # This is for overridden Runner classes + impl = self._build_class_path(module_name, + class_prefix, base_name) + cls = self._load_class('runner', impl, namespace) + + if not cls: # This is for overridden Helper classes module = module_name.replace('test', class_prefix.lower()) impl = self._build_class_path(module, class_prefix, base_name, strip_test=True) - return Strategy.get_strategy(impl, namespace) - except ImportError: + cls = self._load_class('helper', impl, namespace) + + if not cls: # Just import the base class impl = self._build_class_path(module_name, '', base_name) - return Strategy.get_strategy(impl, namespace) + cls = self._load_class(None, impl, namespace) + + return cls + + def _load_class(self, load_type, impl, namespace): + cls = None + if not load_type or load_type in impl.lower(): + try: + cls = Strategy.get_strategy(impl, namespace) + except ImportError as ie: + # Only fail silently if it's something we expect, + # such as a missing override class. Anything else + # shouldn't be suppressed. + l_msg = ie.message.lower() + if load_type not in l_msg or ( + 'no module named' not in l_msg and + 'cannot be found' not in l_msg): + raise + return cls def _build_class_path(self, module_name, class_prefix, class_base, strip_test=False): diff --git a/trove/tests/scenario/helpers/test_helper.py b/trove/tests/scenario/helpers/test_helper.py index db112bd6b2..5bae366c19 100644 --- a/trove/tests/scenario/helpers/test_helper.py +++ b/trove/tests/scenario/helpers/test_helper.py @@ -124,6 +124,18 @@ class TestHelper(object): self._build_data_fns() + ################# + # Utility methods + ################# + def get_class_name(self): + """Builds a string of the expected class name, plus the actual one + being used if it's not the same. + """ + class_name_str = "'%s'" % self._expected_override_name + if self._expected_override_name != self.__class__.__name__: + class_name_str += ' (using %s)' % self.__class__.__name__ + return class_name_str + ################ # Client related ################ @@ -135,7 +147,9 @@ class TestHelper(object): return self.create_client(host, *args, **kwargs) def create_client(self, host, *args, **kwargs): - """Create a datastore client.""" + """Create a datastore client. This is datastore specific, so this + method should be overridden if datastore access is desired. + """ raise SkipTest('No client defined') def get_helper_credentials(self): @@ -336,6 +350,7 @@ class TestHelper(object): def get_invalid_groups(self): """Return a list of configuration groups with invalid values. + An empty list indicates that no 'invalid' tests should be run. """ return [] diff --git a/trove/tests/scenario/runners/configuration_runners.py b/trove/tests/scenario/runners/configuration_runners.py new file mode 100644 index 0000000000..a0c2bbfb43 --- /dev/null +++ b/trove/tests/scenario/runners/configuration_runners.py @@ -0,0 +1,532 @@ +# 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 datetime import datetime +import json +from proboscis import SkipTest + +from trove.common.utils import generate_uuid +from trove.tests.config import CONFIG +from trove.tests.scenario.runners.test_runners import TestRunner +from trove.tests.util.check import CollectionCheck +from trove.tests.util.check import TypeCheck +from trove.tests.util import create_dbaas_client +from trove.tests.util.users import Requirements +from troveclient.compat import exceptions + + +class ConfigurationRunner(TestRunner): + + def __init__(self): + super(ConfigurationRunner, self).__init__(sleep_time=10) + self.dynamic_group_name = 'dynamic_test_group' + self.dynamic_group_id = None + self.dynamic_inst_count = 0 + self.non_dynamic_group_name = 'non_dynamic_test_group' + self.non_dynamic_group_id = None + self.non_dynamic_inst_count = 0 + self.initial_group_count = 0 + self.additional_group_count = 0 + self.other_client = None + self.config_id_for_inst = None + self.config_inst_id = None + + def run_create_bad_group(self, + expected_exception=exceptions.UnprocessableEntity, + expected_http_code=422): + bad_group = {'unknown_datastore_key': 'bad_value'} + self.assert_action_on_conf_group_failure( + bad_group, expected_exception, expected_http_code) + + def assert_action_on_conf_group_failure( + self, group_values, expected_exception, expected_http_code): + json_def = json.dumps(group_values) + self.assert_raises( + expected_exception, expected_http_code, + self.auth_client.configurations.create, + 'conf_group', + json_def, + 'Group with Bad or Invalid entries', + datastore=self.instance_info.dbaas_datastore, + datastore_version=self.instance_info.dbaas_datastore_version) + + def run_create_invalid_groups( + self, expected_exception=exceptions.UnprocessableEntity, + expected_http_code=422): + invalid_groups = self.test_helper.get_invalid_groups() + if invalid_groups: + for invalid_group in invalid_groups: + self.assert_action_on_conf_group_failure( + invalid_group, + expected_exception, expected_http_code) + elif invalid_groups is None: + raise SkipTest("No invalid configuration values defined in %s." % + self.test_helper.get_class_name()) + else: + raise SkipTest("Datastore has no invalid configuration values.") + + def run_delete_non_existent_group( + self, expected_exception=exceptions.NotFound, + expected_http_code=404): + self.assert_group_delete_failure( + None, expected_exception, expected_http_code) + + def run_delete_bad_group_id( + self, expected_exception=exceptions.NotFound, + expected_http_code=404): + self.assert_group_delete_failure( + generate_uuid(), expected_exception, expected_http_code) + + def run_attach_non_existent_group( + self, expected_exception=exceptions.NotFound, + expected_http_code=404): + self.assert_instance_modify_failure( + self.instance_info.id, generate_uuid(), + expected_exception, expected_http_code) + + def run_attach_non_existent_group_to_non_existent_inst( + self, expected_exception=exceptions.NotFound, + expected_http_code=404): + self.assert_instance_modify_failure( + generate_uuid(), generate_uuid(), + expected_exception, expected_http_code) + + def run_detach_group_with_none_attached(self, + expected_states=['ACTIVE'], + expected_http_code=202): + self.assert_instance_modify( + self.instance_info.id, None, + expected_states, expected_http_code) + # run again, just to make sure + self.assert_instance_modify( + self.instance_info.id, None, + expected_states, expected_http_code) + + def run_create_dynamic_group(self, expected_http_code=200): + self.initial_group_count = len(self.auth_client.configurations.list()) + values = self.test_helper.get_dynamic_group() + if values: + self.dynamic_group_id = self.assert_create_group( + self.dynamic_group_name, + 'a fully dynamic group should not require restart', + values, expected_http_code) + self.additional_group_count += 1 + elif values is None: + raise SkipTest("No dynamic group defined in %s." % + self.test_helper.get_class_name()) + else: + raise SkipTest("Datastore has no dynamic configuration values.") + + def assert_create_group(self, name, description, values, + expected_http_code): + json_def = json.dumps(values) + result = self.auth_client.configurations.create( + name, + json_def, + description, + datastore=self.instance_info.dbaas_datastore, + datastore_version=self.instance_info.dbaas_datastore_version) + self.assert_client_code(expected_http_code) + + with TypeCheck('Configuration', result) as configuration: + configuration.has_field('name', basestring) + configuration.has_field('description', basestring) + configuration.has_field('values', dict) + configuration.has_field('datastore_name', basestring) + configuration.has_field('datastore_version_id', unicode) + configuration.has_field('datastore_version_name', basestring) + + self.assert_equal(name, result.name) + self.assert_equal(description, result.description) + self.assert_equal(values, result.values) + + return result.id + + def run_create_non_dynamic_group(self, expected_http_code=200): + values = self.test_helper.get_non_dynamic_group() + if values: + self.non_dynamic_group_id = self.assert_create_group( + self.non_dynamic_group_name, + 'a group containing non-dynamic properties should always ' + 'require restart', + values, expected_http_code) + self.additional_group_count += 1 + elif values is None: + raise SkipTest("No non-dynamic group defined in %s." % + self.test_helper.get_class_name()) + else: + raise SkipTest("Datastore has no non-dynamic configuration " + "values.") + + def run_attach_dynamic_group_to_non_existent_inst( + self, expected_exception=exceptions.NotFound, + expected_http_code=404): + if self.dynamic_group_id: + self.assert_instance_modify_failure( + generate_uuid(), self.dynamic_group_id, + expected_exception, expected_http_code) + + def run_attach_non_dynamic_group_to_non_existent_inst( + self, expected_exception=exceptions.NotFound, + expected_http_code=404): + if self.non_dynamic_group_id: + self.assert_instance_modify_failure( + generate_uuid(), self.non_dynamic_group_id, + expected_exception, expected_http_code) + + def run_list_configuration_groups(self): + configuration_list = self.auth_client.configurations.list() + self.assert_configuration_list( + configuration_list, + self.initial_group_count + self.additional_group_count) + + def assert_configuration_list(self, configuration_list, expected_count): + self.assert_equal(expected_count, len(configuration_list), + 'Unexpected number of configurations found') + if expected_count: + configuration_names = [conf.name for conf in configuration_list] + if self.dynamic_group_id: + self.assert_true( + self.dynamic_group_name in configuration_names) + if self.non_dynamic_group_id: + self.assert_true( + self.non_dynamic_group_name in configuration_names) + + def run_dynamic_configuration_show(self): + if self.dynamic_group_id: + self.assert_configuration_show(self.dynamic_group_id, + self.dynamic_group_name) + else: + raise SkipTest("No dynamic group created.") + + def assert_configuration_show(self, config_id, config_name): + result = self.auth_client.configurations.get(config_id) + self.assert_equal(config_id, result.id, "Unexpected config id") + self.assert_equal(config_name, result.name, "Unexpected config name") + + # check the result field types + with TypeCheck("configuration", result) as check: + check.has_field("id", basestring) + check.has_field("name", basestring) + check.has_field("description", basestring) + check.has_field("values", dict) + check.has_field("created", basestring) + check.has_field("updated", basestring) + check.has_field("instance_count", int) + + # check for valid timestamps + self.assert_true(self._is_valid_timestamp(result.created), + 'Created timestamp %s is invalid' % result.created) + self.assert_true(self._is_valid_timestamp(result.updated), + 'Updated timestamp %s is invalid' % result.updated) + + with CollectionCheck("configuration_values", result.values) as check: + # check each item has the correct type according to the rules + for (item_key, item_val) in result.values.iteritems(): + print("item_key: %s" % item_key) + print("item_val: %s" % item_val) + param = ( + self.auth_client.configuration_parameters.get_parameter( + self.instance_info.dbaas_datastore, + self.instance_info.dbaas_datastore_version, + item_key)) + if param.type == 'integer': + check.has_element(item_key, int) + if param.type == 'string': + check.has_element(item_key, basestring) + if param.type == 'boolean': + check.has_element(item_key, bool) + + def _is_valid_timestamp(self, time_string): + try: + datetime.strptime(time_string, "%Y-%m-%dT%H:%M:%S") + except ValueError: + return False + return True + + def run_non_dynamic_configuration_show(self): + if self.non_dynamic_group_id: + self.assert_configuration_show(self.non_dynamic_group_id, + self.non_dynamic_group_name) + else: + raise SkipTest("No non-dynamic group created.") + + def run_dynamic_conf_get_unauthorized_user( + self, expected_exception=exceptions.NotFound, + expected_http_code=404): + self.assert_conf_get_unauthorized_user(self.dynamic_group_id, + expected_exception, + expected_http_code) + + def assert_conf_get_unauthorized_user( + self, config_id, expected_exception=exceptions.NotFound, + expected_http_code=404): + self._create_other_client() + self.assert_raises( + expected_exception, None, + self.other_client.configurations.get, config_id) + # we're using a different client, so we'll check the return code + # on it explicitly, instead of depending on 'assert_raises' + self.assert_client_code(expected_http_code, client=self.other_client) + + def _create_other_client(self): + if not self.other_client: + requirements = Requirements(is_admin=False) + other_user = CONFIG.users.find_user( + requirements, black_list=[self.instance_info.user.auth_user]) + self.other_client = create_dbaas_client(other_user) + + def run_non_dynamic_conf_get_unauthorized_user( + self, expected_exception=exceptions.NotFound, + expected_http_code=404): + self.assert_conf_get_unauthorized_user(self.dynamic_group_id, + expected_exception, + expected_http_code) + + def run_list_dynamic_inst_conf_groups_before(self): + if self.dynamic_group_id: + self.dynamic_inst_count = len( + self.auth_client.configurations.instances( + self.dynamic_group_id)) + + def assert_conf_instance_list(self, group_id, expected_count): + conf_instance_list = self.auth_client.configurations.instances( + group_id) + self.assert_equal(expected_count, len(conf_instance_list), + 'Unexpected number of configurations found') + if expected_count: + conf_instance_ids = [inst.id for inst in conf_instance_list] + self.assert_true( + self.instance_info.id in conf_instance_ids) + + def run_attach_dynamic_group( + self, expected_states=['ACTIVE'], expected_http_code=202): + if self.dynamic_group_id: + self.assert_instance_modify( + self.instance_info.id, self.dynamic_group_id, + expected_states, expected_http_code) + + def run_list_dynamic_inst_conf_groups_after(self): + if self.dynamic_group_id: + self.assert_conf_instance_list(self.dynamic_group_id, + self.dynamic_inst_count + 1) + + def run_attach_dynamic_group_again( + self, expected_exception=exceptions.BadRequest, + expected_http_code=400): + # The exception here should probably be UnprocessableEntity or + # something else other than BadRequest as the request really is + # valid. + if self.dynamic_group_id: + self.assert_instance_modify_failure( + self.instance_info.id, self.dynamic_group_id, + expected_exception, expected_http_code) + + def run_delete_attached_dynamic_group( + self, expected_exception=exceptions.BadRequest, + expected_http_code=400): + # The exception here should probably be UnprocessableEntity or + # something else other than BadRequest as the request really is + # valid. + if self.dynamic_group_id: + self.assert_group_delete_failure( + self.dynamic_group_id, expected_exception, expected_http_code) + + def run_update_dynamic_group( + self, expected_states=['ACTIVE'], expected_http_code=202): + if self.dynamic_group_id: + values = json.dumps(self.test_helper.get_dynamic_group()) + self.assert_update_group( + self.instance_info.id, self.dynamic_group_id, values, + expected_states, expected_http_code) + + def assert_update_group( + self, instance_id, group_id, values, + expected_states, expected_http_code, restart_inst=False): + self.auth_client.configurations.update(group_id, values) + self.assert_instance_action( + instance_id, expected_states, expected_http_code) + if restart_inst: + self._restart_instance(instance_id) + + def run_detach_dynamic_group( + self, expected_states=['ACTIVE'], expected_http_code=202): + if self.dynamic_group_id: + self.assert_instance_modify( + self.instance_info.id, None, + expected_states, expected_http_code) + + def run_list_non_dynamic_inst_conf_groups_before(self): + if self.non_dynamic_group_id: + self.non_dynamic_inst_count = len( + self.auth_client.configurations.instances( + self.non_dynamic_group_id)) + + def run_attach_non_dynamic_group( + self, expected_states=['RESTART_REQUIRED'], + expected_http_code=202): + if self.non_dynamic_group_id: + self.assert_instance_modify( + self.instance_info.id, self.non_dynamic_group_id, + expected_states, expected_http_code, restart_inst=True) + + def run_list_non_dynamic_inst_conf_groups_after(self): + if self.non_dynamic_group_id: + self.assert_conf_instance_list(self.non_dynamic_group_id, + self.non_dynamic_inst_count + 1) + + def run_attach_non_dynamic_group_again( + self, expected_exception=exceptions.BadRequest, + expected_http_code=400): + if self.non_dynamic_group_id: + self.assert_instance_modify_failure( + self.instance_info.id, self.non_dynamic_group_id, + expected_exception, expected_http_code) + + def run_delete_attached_non_dynamic_group( + self, expected_exception=exceptions.BadRequest, + expected_http_code=400): + if self.non_dynamic_group_id: + self.assert_group_delete_failure( + self.non_dynamic_group_id, expected_exception, + expected_http_code) + + def run_update_non_dynamic_group( + self, expected_states=['RESTART_REQUIRED'], + expected_http_code=202): + if self.non_dynamic_group_id: + values = json.dumps(self.test_helper.get_non_dynamic_group()) + self.assert_update_group( + self.instance_info.id, self.non_dynamic_group_id, values, + expected_states, expected_http_code, restart_inst=True) + + def run_detach_non_dynamic_group( + self, expected_states=['RESTART_REQUIRED'], + expected_http_code=202): + if self.non_dynamic_group_id: + self.assert_instance_modify( + self.instance_info.id, None, expected_states, + expected_http_code, restart_inst=True) + + def assert_instance_modify( + self, instance_id, group_id, expected_states, expected_http_code, + restart_inst=False): + self.auth_client.instances.modify(instance_id, configuration=group_id) + self.assert_instance_action( + instance_id, expected_states, expected_http_code) + + # Verify the group has been attached. + instance = self.get_instance(instance_id) + if group_id: + group = self.auth_client.configurations.get(group_id) + self.assert_equal( + group.id, instance.configuration['id'], + "Attached group does not have the expected ID") + self.assert_equal( + group.name, instance.configuration['name'], + "Attached group does not have the expected name") + else: + self.assert_false( + hasattr(instance, 'configuration'), + "The configuration group was not detached from the instance.") + + if restart_inst: + self._restart_instance(instance_id) + + def assert_instance_modify_failure( + self, instance_id, group_id, expected_exception, + expected_http_code): + self.assert_raises( + expected_exception, expected_http_code, + self.auth_client.instances.modify, + instance_id, configuration=group_id) + + def run_delete_dynamic_group(self, expected_http_code=202): + if self.dynamic_group_id: + self.assert_group_delete(self.dynamic_group_id, + expected_http_code) + + def assert_group_delete(self, group_id, expected_http_code): + self.auth_client.configurations.delete(group_id) + self.assert_client_code(expected_http_code) + + def run_delete_non_dynamic_group(self, expected_http_code=202): + if self.non_dynamic_group_id: + self.assert_group_delete(self.non_dynamic_group_id, + expected_http_code) + + def assert_group_delete_failure(self, group_id, expected_exception, + expected_http_code): + self.assert_raises( + expected_exception, expected_http_code, + self.auth_client.configurations.delete, group_id) + + def _restart_instance( + self, instance_id, expected_states=['REBOOT', 'ACTIVE'], + expected_http_code=202): + self.auth_client.instances.restart(instance_id) + self.assert_instance_action(instance_id, expected_states, + expected_http_code) + + def run_create_instance_with_conf(self): + self.config_id_for_inst = ( + self.dynamic_group_id or self.non_dynamic_group_id) + if self.config_id_for_inst: + self.config_inst_id = self.assert_create_instance_with_conf( + self.config_id_for_inst) + else: + raise SkipTest("No groups (dynamic or non-dynamic) defined in %s." + % self.test_helper.get_class_name()) + + def assert_create_instance_with_conf(self, config_id): + # test that a new instance will apply the configuration on create + result = self.auth_client.instances.create( + "TEST_" + str(datetime.now()) + "_config", + self.instance_info.dbaas_flavor_href, + self.instance_info.volume, + [], [], availability_zone="nova", + configuration=config_id) + self.assert_client_code(200) + self.assert_equal("BUILD", result.status, 'Unexpected inst status') + return result.id + + def run_wait_for_conf_instance( + # we can't specify all the states here, as it may go active + # before this test runs + self, final_state=['ACTIVE'], + expected_http_code=200): + if self.config_inst_id: + self.assert_instance_action(self.config_inst_id, final_state, + expected_http_code) + inst = self.auth_client.instances.get(self.config_inst_id) + self.assert_equal(self.config_id_for_inst, + inst.configuration['id']) + else: + raise SkipTest("No instance created with a configuration group.") + + def run_delete_conf_instance( + self, expected_states=['SHUTDOWN'], + expected_http_code=202): + if self.config_inst_id: + self.assert_delete_conf_instance( + self.config_inst_id, expected_states, expected_http_code) + else: + raise SkipTest("No instance created with a configuration group.") + + def assert_delete_conf_instance( + self, instance_id, expected_state, expected_http_code): + self.auth_client.instances.delete(instance_id) + self.assert_client_code(expected_http_code) + self.assert_all_gone(instance_id, expected_state)