From 0609f85d74c28bd2f86f05e25dd6750da37c62db Mon Sep 17 00:00:00 2001 From: Mariam John Date: Mon, 29 Aug 2016 00:49:34 -0700 Subject: [PATCH] Implement configuration management for DB2 In this feature we implement configuration management for DB2 database manager through Trove. Change-Id: I303b8ddeafe7c67ec80dc8ec9e8489af913e23f7 Implements: blueprint db2-config-group --- ...configuration-groups-ca2164be741d35f9.yaml | 4 + trove/common/configurations.py | 11 + trove/common/template.py | 1 + .../datastore/experimental/db2/manager.py | 27 +- .../datastore/experimental/db2/service.py | 142 +++++- .../datastore/experimental/db2/system.py | 3 + trove/templates/db2/validation-rules.json | 454 ++++++++++++++++++ trove/tests/int_tests.py | 2 +- trove/tests/scenario/helpers/db2_helper.py | 44 ++ .../unittests/guestagent/test_backups.py | 44 +- .../unittests/guestagent/test_db2_manager.py | 48 +- .../tests/unittests/guestagent/test_dbaas.py | 44 +- 12 files changed, 797 insertions(+), 27 deletions(-) create mode 100644 releasenotes/notes/db2-configuration-groups-ca2164be741d35f9.yaml create mode 100644 trove/templates/db2/validation-rules.json create mode 100644 trove/tests/scenario/helpers/db2_helper.py diff --git a/releasenotes/notes/db2-configuration-groups-ca2164be741d35f9.yaml b/releasenotes/notes/db2-configuration-groups-ca2164be741d35f9.yaml new file mode 100644 index 0000000000..ffd1f16ef9 --- /dev/null +++ b/releasenotes/notes/db2-configuration-groups-ca2164be741d35f9.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add support for configuration group management + for DB2 Express-C. diff --git a/trove/common/configurations.py b/trove/common/configurations.py index 423d3b0c01..f9fbccfd60 100644 --- a/trove/common/configurations.py +++ b/trove/common/configurations.py @@ -84,3 +84,14 @@ class VerticaConfParser(object): def parse(self): return self.CODEC.deserialize(self.config).items() + + +class DB2ConfParser(object): + + CODEC = stream_codecs.PropertiesCodec(delimiter='=') + + def __init__(self, config): + self.config = config + + def parse(self): + return self.CODEC.deserialize(self.config).items() diff --git a/trove/common/template.py b/trove/common/template.py index 519805e829..179e72ff0f 100644 --- a/trove/common/template.py +++ b/trove/common/template.py @@ -39,6 +39,7 @@ SERVICE_PARSERS = { 'cassandra': configurations.CassandraConfParser, 'redis': configurations.RedisConfParser, 'vertica': configurations.VerticaConfParser, + 'db2': configurations.DB2ConfParser, } diff --git a/trove/guestagent/datastore/experimental/db2/manager.py b/trove/guestagent/datastore/experimental/db2/manager.py index 792cd90bee..c45ce56201 100644 --- a/trove/guestagent/datastore/experimental/db2/manager.py +++ b/trove/guestagent/datastore/experimental/db2/manager.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from oslo_log import log as logging from trove.common.i18n import _ @@ -42,6 +44,10 @@ class Manager(manager.Manager): def status(self): return self.appStatus + @property + def configuration_manager(self): + return self.app.configuration_manager + def do_prepare(self, context, packages, databases, memory_mb, users, device_path, mount_point, backup_info, config_contents, root_password, overrides, @@ -51,13 +57,18 @@ class Manager(manager.Manager): device = volume.VolumeDevice(device_path) device.unmount_device(device_path) device.format() - device.mount(mount_point) - LOG.debug('Mounted the volume.') + if os.path.exists(mount_point): + device.migrate_data(mount_point) + device.mount(mount_point) + LOG.debug("Mounted the volume.") self.app.update_hostname() self.app.change_ownership(mount_point) self.app.start_db() if backup_info: self._perform_restore(backup_info, context, mount_point) + if config_contents: + self.app.configuration_manager.save_configuration( + config_contents) def restart(self, context): """ @@ -133,3 +144,15 @@ class Manager(manager.Manager): def create_backup(self, context, backup_info): LOG.debug("Creating backup.") backup.backup(context, backup_info) + + def update_overrides(self, context, overrides, remove=False): + LOG.debug("Updating overrides.") + if remove: + self.app.remove_overrides() + else: + self.app.update_overrides(context, overrides) + + def apply_overrides(self, context, overrides): + if overrides: + LOG.debug("Applying overrides: " + str(overrides)) + self.app.apply_overrides(overrides) diff --git a/trove/guestagent/datastore/experimental/db2/service.py b/trove/guestagent/datastore/experimental/db2/service.py index 7e740e247f..732878f824 100644 --- a/trove/guestagent/datastore/experimental/db2/service.py +++ b/trove/guestagent/datastore/experimental/db2/service.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from oslo_log import log as logging from oslo_utils import encodeutils @@ -20,7 +22,11 @@ from trove.common import cfg from trove.common import exception from trove.common.i18n import _ from trove.common import instance as rd_instance +from trove.common.stream_codecs import PropertiesCodec from trove.common import utils as utils +from trove.guestagent.common.configuration import ConfigurationManager +from trove.guestagent.common.configuration import ImportOverrideStrategy +from trove.guestagent.common import guestagent_utils from trove.guestagent.common import operating_system from trove.guestagent.datastore.experimental.db2 import system from trove.guestagent.datastore import service @@ -28,6 +34,9 @@ from trove.guestagent.db import models CONF = cfg.CONF LOG = logging.getLogger(__name__) +MOUNT_POINT = CONF.db2.mount_point +FAKE_CFG = os.path.join(MOUNT_POINT, "db2.cfg.fake") +DB2_DEFAULT_CFG = os.path.join(MOUNT_POINT, "db2_default_dbm.cfg") class DB2App(object): @@ -43,6 +52,87 @@ class DB2App(object): ) LOG.debug("state_change_wait_time = %s." % self.state_change_wait_time) self.status = status + self.dbm_default_config = {} + self.init_config() + + def init_config(self): + if not operating_system.exists(MOUNT_POINT, True): + operating_system.create_directory(MOUNT_POINT, + system.DB2_INSTANCE_OWNER, + system.DB2_INSTANCE_OWNER, + as_root=True) + """ + The database manager configuration file - db2systm is stored under the + /home/db2inst1/sqllib directory. To update the configuration + parameters, DB2 recommends using the command - UPDATE DBM CONFIGURATION + commands instead of directly updating the config file. + + The existing PropertiesCodec implementation has been reused to handle + text-file operations. Configuration overrides are implemented using + the ImportOverrideStrategy of the guestagent configuration manager. + """ + LOG.debug("Initialize DB2 configuration") + revision_dir = ( + guestagent_utils.build_file_path( + os.path.join(MOUNT_POINT, + os.path.dirname(system.DB2_INSTANCE_OWNER)), + ConfigurationManager.DEFAULT_STRATEGY_OVERRIDES_SUB_DIR) + ) + if not operating_system.exists(FAKE_CFG): + operating_system.write_file(FAKE_CFG, '', as_root=True) + operating_system.chown(FAKE_CFG, system.DB2_INSTANCE_OWNER, + system.DB2_INSTANCE_OWNER, as_root=True) + self.configuration_manager = ( + ConfigurationManager(FAKE_CFG, system.DB2_INSTANCE_OWNER, + system.DB2_INSTANCE_OWNER, + PropertiesCodec(delimiter='='), + requires_root=True, + override_strategy=ImportOverrideStrategy( + revision_dir, "cnf")) + ) + ''' + Below we are getting the database manager default configuration and + saving it to the DB2_DEFAULT_CFG file. This is done to help with + correctly resetting the configurations to the original values when + user wants to detach a user-defined configuration group from an + instance. DB2 provides a command to reset the database manager + configuration parameters (RESET DBM CONFIGURATION) but this command + resets all the configuration parameters to the system defaults. When + we build a DB2 guest image there are certain configurations + parameters like SVCENAME which we set so that the instance can start + correctly. Hence resetting this value to the system default will + render the instance in an unstable state. Instead, the recommended + way for resetting a subset of configuration parameters is to save + the output of GET DBM CONFIGURATION of the original configuration + and then call UPDATE DBM CONFIGURATION to reset the value. + http://www.ibm.com/support/knowledgecenter/SSEPGG_10.5.0/ + com.ibm.db2.luw.admin.cmd.doc/doc/r0001970.html + ''' + if not operating_system.exists(DB2_DEFAULT_CFG): + run_command(system.GET_DBM_CONFIGURATION % { + "dbm_config": DB2_DEFAULT_CFG}) + self.process_default_dbm_config() + + def process_default_dbm_config(self): + """ + Once the default database manager configuration is saved to + DB2_DEFAULT_CFG, we try to store the configuration parameters + and values into a dictionary object, dbm_default_config. For + example, a sample content of the database manager configuration + file looks like this: + Buffer pool (DFT_MON_BUFPOOL) = OFF + We need to process this so that we key it on the configuration + parameter DFT_MON_BUFPOOL. + """ + with open(DB2_DEFAULT_CFG) as cfg_file: + for line in cfg_file: + if '=' in line: + item = line.rstrip('\n').split(' = ') + fIndex = item[0].rfind('(') + lIndex = item[0].rfind(')') + if fIndex > -1: + param = item[0][fIndex + 1: lIndex] + self.dbm_default_config.update({param: item[1]}) def update_hostname(self): """ @@ -92,14 +182,8 @@ class DB2App(object): "Command to disable DB2 server on boot failed.")) def start_db_with_conf_changes(self, config_contents): - ''' - Will not be implementing configuration change API for DB2 in - the Kilo release. Currently all that this method does is to start - the DB2 server without any configuration changes. Looks like - this needs to be implemented to enable volume resize on the guest - agent side. - ''' LOG.info(_("Starting DB2 with configuration changes.")) + self.configuration_manager.save_configuration(config_contents) self.start_db(True) def start_db(self, update_db=False): @@ -142,6 +226,50 @@ class DB2App(object): finally: self.status.end_restart() + def update_overrides(self, context, overrides, remove=False): + if overrides: + self.apply_overrides(overrides) + + def remove_overrides(self): + config = self.configuration_manager.get_user_override() + self._reset_config(config) + self.configuration_manager.remove_user_override() + + def apply_overrides(self, overrides): + self._apply_config(overrides) + self.configuration_manager.apply_user_override(overrides) + + def _update_dbm_config(self, param, value): + out, err = run_command( + system.UPDATE_DBM_CONFIGURATION % { + "parameter": param, + "value": value}) + if err: + if err.is_warning(): + LOG.warning(err) + else: + LOG.error(err) + raise RuntimeError(_("Failed to update config %s") % param) + + def _reset_config(self, config): + try: + for k, v in config.iteritems(): + default_cfg_value = self.dbm_default_config[k] + self._update_dbm_config(k, default_cfg_value) + except Exception: + LOG.exception(_("DB2 configuration reset failed.")) + raise RuntimeError(_("DB2 configuration reset failed.")) + LOG.info(_("DB2 configuration reset completed.")) + + def _apply_config(self, config): + try: + for k, v in config.items(): + self._update_dbm_config(k, v) + except Exception: + LOG.exception(_("DB2 configuration apply failed")) + raise RuntimeError(_("DB2 configuration apply failed")) + LOG.info(_("DB2 config apply completed.")) + class DB2AppStatus(service.BaseDbStatus): """ diff --git a/trove/guestagent/datastore/experimental/db2/system.py b/trove/guestagent/datastore/experimental/db2/system.py index 299fc39fde..c65e0900b6 100644 --- a/trove/guestagent/datastore/experimental/db2/system.py +++ b/trove/guestagent/datastore/experimental/db2/system.py @@ -58,3 +58,6 @@ GET_DB_SIZE = ( "db2 call get_dbsize_info(?, ?, ?, -1) ") GET_DB_NAMES = ("find /home/db2inst1/db2inst1/backup/ -type f -name '*.001' |" " grep -Po \"(?<=backup/)[^.']*(?=\.)\"") +GET_DBM_CONFIGURATION = "db2 get dbm configuration > %(dbm_config)s" +UPDATE_DBM_CONFIGURATION = ("db2 update database manager configuration using " + "%(parameter)s %(value)s") diff --git a/trove/templates/db2/validation-rules.json b/trove/templates/db2/validation-rules.json new file mode 100644 index 0000000000..9e3632275b --- /dev/null +++ b/trove/templates/db2/validation-rules.json @@ -0,0 +1,454 @@ +{ + "configuration-parameters": [ + { + "description": "Agent stack size", + "type": "integer", + "name": "AGENT_STACK_SZ", + "restart_required": true + }, + { + "description": "Alternate encryption algorithm for incoming connections at server", + "type": "integer", + "name": "ALTERNATE_AUTH_ENC", + "restart_required": true + }, + { + "description": "Max number of concurrently active databases", + "type": "integer", + "name": "NUMDB", + "restart_required": true + }, + { + "description": "Federated Database Support", + "type": "integer", + "name": "FEDERATED", + "restart_required": false + }, + { + "description": "Transaction processor monitor name", + "type": "string", + "name": "TP_MON_NAME", + "restart_required": true + }, + { + "description": "Default charge-back account", + "type": "string", + "name": "DFT_ACCOUNT_STR", + "restart_required": false + }, + { + "description": "Size of rotating db2diag & notify logs (MB)", + "type": "integer", + "name": "DIAGSIZE", + "restart_required": true + }, + { + "description": "Default database monitor switch for Buffer pool", + "type": "integer", + "name": "DFT_MON_BUFPOOL", + "restart_required": false + }, + { + "description": "Default database monitor switch for Lock", + "type": "integer", + "name": "DFT_MON_LOCK", + "restart_required": false + }, + { + "description": "Default database monitor switch for Sort", + "type": "integer", + "name": "DFT_MON_SORT", + "restart_required": false + }, + { + "description": "Default database monitor switch for Statement", + "type": "integer", + "name": "DFT_MON_STMT", + "restart_required": false + }, + { + "description": "Default database monitor switch for Table", + "type": "integer", + "name": "DFT_MON_TABLE", + "restart_required": false + }, + { + "description": "Default database monitor switch for Timestamp", + "type": "integer", + "name": "DFT_MON_TIMESTAMP", + "restart_required": false + }, + { + "description": "Default database monitor switch for Unit of work", + "type": "integer", + "name": "DFT_MON_UOW", + "restart_required": false + }, + { + "description": "Monitor health of instance and databases", + "type": "integer", + "name": "HEALTH_MON", + "restart_required": false + }, + { + "description": "SYSADM group name", + "type": "string", + "name": "SYSADM_GROUP", + "restart_required": true + }, + { + "description": "SYSCTRL group name", + "type": "string", + "name": "SYSCTRL_GROUP", + "restart_required": true + }, + { + "description": "SYSMAINT group name", + "type": "string", + "name": "SYSMAINT_GROUP", + "restart_required": true + }, + { + "description": "SYSMON group name", + "type": "string", + "name": "SYSMAINT_GROUP", + "restart_required": true + }, + { + "description": "Client Userid-Password Plugin", + "type": "string", + "name": "CLNT_PW_PLUGIN", + "restart_required": true + }, + { + "description": "Client Kerberos Plugin", + "type": "string", + "name": "CLNT_KRB_PLUGIN", + "restart_required": true + }, + { + "description": "Group Plugin", + "type": "string", + "name": "GROUP_PLUGIN", + "restart_required": true + }, + { + "description": "GSS Plugin for Local Authorization", + "type": "string", + "name": "LOCAL_GSSPLUGIN", + "restart_required": true + }, + { + "description": "Server Plugin Mode", + "type": "integer", + "name": "SRV_PLUGIN_MODE", + "restart_required": true + }, + { + "description": "Server List of GSS Plugins", + "type": "string", + "name": "SRVCON_GSSPLUGIN_LIST", + "restart_required": true + }, + { + "description": "Server Userid-Password Plugin", + "type": "string", + "name": "SRVCON_PW_PLUGIN", + "restart_required": true + }, + { + "description": "Server Connection Authentication", + "type": "string", + "name": "SRVCON_AUTH", + "restart_required": true + }, + { + "description": "Database manager authentication", + "type": "integer", + "name": "AUTHENTICATION", + "restart_required": true + }, + { + "description": "Alternate authentication", + "type": "integer", + "name": "ALTERNATE_AUTH_ENC", + "restart_required": true + }, + { + "description": "Cataloging allowed without authority", + "type": "integer", + "name": "CATALOG_NOAUTH", + "restart_required": false + }, + { + "description": "Trust all clients", + "type": "integer", + "name": "TRUST_ALLCLNTS", + "restart_required": true + }, + { + "description": "Trusted client authentication", + "type": "integer", + "name": "TRUST_CLNTAUTH", + "restart_required": true + }, + { + "description": "Bypass federated authentication", + "type": "integer", + "name": "FED_NOAUTH", + "restart_required": false + }, + { + "description": "Database monitor heap size(4KB)", + "type": "integer", + "name": "MON_HEAP_SZ", + "restart_required": false + }, + { + "description": "Java Virtual Machine heap size(4KB)", + "type": "integer", + "name": "JAVA_HEAP_SZ", + "restart_required": true + }, + { + "description": "Audit buffer size(4KB)", + "type": "integer", + "name": "AUDIT_BUF_SZ", + "restart_required": true + }, + { + "description": "Agent stack size", + "type": "integer", + "name": "AGENT_STACK_SZ", + "restart_required": true + }, + { + "description": "Sort heap threshold (4KB)", + "type": "integer", + "name": "SHEAPTHRES", + "restart_required": true + }, + { + "description": "Directory cache support", + "type": "integer", + "name": "DIR_CACHE", + "restart_required": true + }, + { + "description": "Application support layer heap size (4KB)", + "type": "integer", + "name": "ASLHEAPSZ", + "restart_required": true + }, + { + "description": "Max requester I/O block size (bytes)", + "type": "integer", + "name": "RQRIOBLK", + "restart_required": true + }, + { + "description": "Workload impact by throttled utilities", + "type": "integer", + "name": "UTIL_IMPACT_LIM", + "restart_required": true + }, + { + "description": "Priority of agents", + "type": "integer", + "name": "AGENTPRI", + "restart_required": true + }, + { + "description": "Agent pool size", + "type": "integer", + "name": "NUM_POOLAGENTS", + "restart_required": false + }, + { + "description": "Initial number of agents in pool", + "type": "integer", + "name": "NUM_INITAGENTS", + "restart_required": true + }, + { + "description": "Max number of coordinating agents", + "type": "integer", + "name": "MAX_COORDAGENTS", + "restart_required": false + }, + { + "description": "Max number of client connections", + "type": "integer", + "name": "MAX_CONNECTIONS", + "restart_required": false + }, + { + "description": "Keep fenced process", + "type": "integer", + "name": "KEEPFENCED", + "restart_required": true + }, + { + "description": "Number of pooled fenced processes", + "type": "integer", + "name": "FENCED_POOL", + "restart_required": false + }, + { + "description": "Initial number of fenced processes", + "type": "integer", + "name": "NUM_INITFENCED", + "restart_required": true + }, + { + "description": "Index re-creation time and redo index build", + "type": "integer", + "name": "INDEXREC", + "restart_required": false + }, + { + "description": "Transaction manager database name", + "type": "string", + "name": "TM_DATABASE", + "restart_required": true + }, + { + "description": "Transaction resync interval (sec)", + "type": "integer", + "name": "RESYNC_INTERVAL", + "restart_required": true + }, + { + "description": "SPM name", + "type": "string", + "name": "SPM_NAME", + "restart_required": true + }, + { + "description": "SPM log size", + "type": "integer", + "name": "SPM_LOG_FILE_SZ", + "restart_required": true + }, + { + "description": "SPM resync agent limit", + "type": "integer", + "name": "SPM_MAX_RESYNC", + "restart_required": true + }, + { + "description": "Discovery mode", + "type": "integer", + "name": "DISCOVER", + "restart_required": true + }, + { + "description": "Discover server instance", + "type": "integer", + "name": "DISCOVER_INST", + "restart_required": false + }, + { + "description": "SSL server keydb file", + "type": "string", + "name": "SSL_SVR_KEYDB", + "restart_required": true + }, + { + "description": "SSL server stash file", + "type": "string", + "name": "SSL_SVR_STASH", + "restart_required": true + }, + { + "description": "SSL server certificate label", + "type": "string", + "name": "SSL_SVR_LABEL", + "restart_required": true + }, + { + "description": "SSL cipher specs", + "type": "string", + "name": "SSL_CIPHERSPECS", + "restart_required": true + }, + { + "description": "SSL versions", + "type": "string", + "name": "SSL_VERSIONS", + "restart_required": true + }, + { + "description": "SSL client keydb file", + "type": "string", + "name": "SSL_CLNT_KEYDB", + "restart_required": true + }, + { + "description": "SSL client stash file", + "type": "string", + "name": "SSL_CLNT_STASH", + "restart_required": true + }, + { + "description": "Maximum query degree of parallelism", + "type": "integer", + "name": "MAX_QUERYDEGREE", + "restart_required": false + }, + { + "description": "Enable intra-partition parallelism", + "type": "integer", + "name": "INTRA_PARALLEL", + "restart_required": true + }, + { + "description": "No. of int. communication buffers(4KB)", + "type": "integer", + "name": "FCM_NUM_BUFFERS", + "restart_required": false + }, + { + "description": "No. of int. communication channels", + "type": "integer", + "name": "FCM_NUM_CHANNELS", + "restart_required": false + }, + { + "description": "db2start/db2stop timeout (min)", + "type": "integer", + "name": "START_STOP_TIME", + "restart_required": false + }, + { + "description": "WLM dispatcher enabled", + "type": "integer", + "name": "WLM_DISPATCHER", + "restart_required": false + }, + { + "description": "WLM dispatcher concurrency", + "type": "integer", + "name": "WLM_DISP_CONCUR", + "restart_required": false + }, + { + "description": "WLM dispatcher CPU shares enabled", + "type": "integer", + "name": "WLM_DISP_CPU_SHARES", + "restart_required": false + }, + { + "description": "WLM dispatcher min. utilization (%)", + "type": "integer", + "name": "WLM_DISP_MIN_UTIL", + "restart_required": false + }, + { + "description": "Communication buffer exit library list", + "type": "string", + "name": "COMM_EXIT_LIST", + "restart_required": false + } + ] +} diff --git a/trove/tests/int_tests.py b/trove/tests/int_tests.py index ee9b06f06c..b8f90c9293 100644 --- a/trove/tests/int_tests.py +++ b/trove/tests/int_tests.py @@ -210,7 +210,7 @@ register(["user"], user_actions_groups) # 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) + database_actions_groups, user_actions_groups, configuration_groups) register(["cassandra_supported"], common_groups, user_actions_groups, database_actions_groups, backup_groups, configuration_groups, cluster_actions_groups) diff --git a/trove/tests/scenario/helpers/db2_helper.py b/trove/tests/scenario/helpers/db2_helper.py new file mode 100644 index 0000000000..f14de4e754 --- /dev/null +++ b/trove/tests/scenario/helpers/db2_helper.py @@ -0,0 +1,44 @@ +# Copyright 2016 IBM Corporation +# +# 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 Db2Helper(TestHelper): + + def __init__(self, expected_override_name, report): + super(Db2Helper, self).__init__(expected_override_name, report) + + def get_helper_credentials(self): + return {'name': 'lite', 'password': 'litepass', 'database': 'lite'} + + 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_dynamic_group(self): + return {'MON_HEAP_SZ': 40} + + def get_non_dynamic_group(self): + return {'NUMDB': 30} + + def get_invalid_groups(self): + return [{'timezone': 997}, + {"max_worker_processes": 'string_value'}, + {"standard_conforming_strings": 'string_value'}] diff --git a/trove/tests/unittests/guestagent/test_backups.py b/trove/tests/unittests/guestagent/test_backups.py index 6c8a869f6d..bf433f8230 100644 --- a/trove/tests/unittests/guestagent/test_backups.py +++ b/trove/tests/unittests/guestagent/test_backups.py @@ -24,6 +24,8 @@ from trove.guestagent.common import operating_system from trove.guestagent.datastore.experimental.cassandra import ( service as cass_service ) +from trove.guestagent.datastore.experimental.db2 import ( + service as db2_service) from trove.guestagent.strategies.backup import base as backupBase from trove.guestagent.strategies.backup.experimental.postgresql_impl \ import PgBaseBackupUtil @@ -443,7 +445,12 @@ class GuestAgentBackupTest(trove_testtools.TestCase): DECRYPT + PIPE + UNZIP + PIPE + REDISBACKUP_RESTORE) @patch.object(utils, 'execute_with_timeout') - def test_backup_encrypted_db2backup_command(self, *mock): + @patch.object(ImportOverrideStrategy, '_initialize_import_directory') + @patch.multiple(operating_system, exists=DEFAULT, write_file=DEFAULT, + chown=DEFAULT, chmod=DEFAULT) + @patch.object(db2_service, 'run_command') + @patch.object(db2_service.DB2App, 'process_default_dbm_config') + def test_backup_encrypted_db2backup_command(self, *mock, **kwargs): backupBase.BackupRunner.is_encrypted = True backupBase.BackupRunner.encrypt_key = CRYPTO_KEY RunnerClass = utils.import_class(BACKUP_DB2_CLS) @@ -454,7 +461,12 @@ class GuestAgentBackupTest(trove_testtools.TestCase): self.assertIn("gz.enc", bkp.manifest) @patch.object(utils, 'execute_with_timeout') - def test_backup_not_encrypted_db2backup_command(self, *mock): + @patch.object(ImportOverrideStrategy, '_initialize_import_directory') + @patch.multiple(operating_system, exists=DEFAULT, write_file=DEFAULT, + chown=DEFAULT, chmod=DEFAULT) + @patch.object(db2_service, 'run_command') + @patch.object(db2_service.DB2App, 'process_default_dbm_config') + def test_backup_not_encrypted_db2backup_command(self, *mock, **kwargs): backupBase.BackupRunner.is_encrypted = False backupBase.BackupRunner.encrypt_key = CRYPTO_KEY RunnerClass = utils.import_class(BACKUP_DB2_CLS) @@ -463,7 +475,12 @@ class GuestAgentBackupTest(trove_testtools.TestCase): self.assertEqual(DB2BACKUP_CMD + PIPE + ZIP, bkp.command) self.assertIn("gz", bkp.manifest) - def test_restore_decrypted_db2backup_command(self): + @patch.object(ImportOverrideStrategy, '_initialize_import_directory') + @patch.multiple(operating_system, exists=DEFAULT, write_file=DEFAULT, + chown=DEFAULT, chmod=DEFAULT) + @patch.object(db2_service, 'run_command') + @patch.object(db2_service.DB2App, 'process_default_dbm_config') + def test_restore_decrypted_db2backup_command(self, *args, **kwargs): restoreBase.RestoreRunner.is_zipped = True restoreBase.RestoreRunner.is_encrypted = False RunnerClass = utils.import_class(RESTORE_DB2_CLS) @@ -471,7 +488,12 @@ class GuestAgentBackupTest(trove_testtools.TestCase): location="filename", checksum="md5") self.assertEqual(restr.restore_cmd, UNZIP + PIPE + DB2BACKUP_RESTORE) - def test_restore_encrypted_db2backup_command(self): + @patch.object(ImportOverrideStrategy, '_initialize_import_directory') + @patch.multiple(operating_system, exists=DEFAULT, write_file=DEFAULT, + chown=DEFAULT, chmod=DEFAULT) + @patch.object(db2_service, 'run_command') + @patch.object(db2_service.DB2App, 'process_default_dbm_config') + def test_restore_encrypted_db2backup_command(self, *args, **kwargs): restoreBase.RestoreRunner.is_zipped = True restoreBase.RestoreRunner.is_encrypted = True restoreBase.RestoreRunner.encrypt_key = CRYPTO_KEY @@ -946,7 +968,12 @@ class PostgresqlBackupTests(trove_testtools.TestCase): class DB2BackupTests(trove_testtools.TestCase): - def setUp(self): + @patch.object(ImportOverrideStrategy, '_initialize_import_directory') + @patch.multiple(operating_system, exists=DEFAULT, write_file=DEFAULT, + chown=DEFAULT, chmod=DEFAULT) + @patch.object(db2_service, 'run_command') + @patch.object(db2_service.DB2App, 'process_default_dbm_config') + def setUp(self, *args, **kwargs): super(DB2BackupTests, self).setUp() self.exec_timeout_patch = patch.object(utils, 'execute_with_timeout') self.exec_timeout_patch.start() @@ -983,7 +1010,12 @@ class DB2BackupTests(trove_testtools.TestCase): class DB2RestoreTests(trove_testtools.TestCase): - def setUp(self): + @patch.object(ImportOverrideStrategy, '_initialize_import_directory') + @patch.multiple(operating_system, exists=DEFAULT, write_file=DEFAULT, + chown=DEFAULT, chmod=DEFAULT) + @patch.object(db2_service, 'run_command') + @patch.object(db2_service.DB2App, 'process_default_dbm_config') + def setUp(self, *args, **kwargs): super(DB2RestoreTests, self).setUp() self.restore_runner = utils.import_class( diff --git a/trove/tests/unittests/guestagent/test_db2_manager.py b/trove/tests/unittests/guestagent/test_db2_manager.py index 9f1eb5f5f6..6a8b8f9b2f 100644 --- a/trove/tests/unittests/guestagent/test_db2_manager.py +++ b/trove/tests/unittests/guestagent/test_db2_manager.py @@ -12,12 +12,16 @@ # License for the specific language governing permissions and limitations # under the License. +from mock import DEFAULT from mock import MagicMock from mock import patch from testtools.matchers import Is, Equals, Not from trove.common.instance import ServiceStatuses from trove.guestagent import backup +from trove.guestagent.common import configuration +from trove.guestagent.common.configuration import ImportOverrideStrategy +from trove.guestagent.common import operating_system from trove.guestagent.datastore.experimental.db2 import ( manager as db2_manager) from trove.guestagent.datastore.experimental.db2 import ( @@ -30,7 +34,11 @@ from trove.tests.unittests.guestagent.test_datastore_manager import \ class GuestAgentDB2ManagerTest(DatastoreManagerTest): - def setUp(self): + @patch.object(ImportOverrideStrategy, '_initialize_import_directory') + @patch.multiple(operating_system, exists=DEFAULT, write_file=DEFAULT, + chown=DEFAULT, chmod=DEFAULT) + @patch.object(db2_service.DB2App, 'process_default_dbm_config') + def setUp(self, *arg, **kwargs): super(GuestAgentDB2ManagerTest, self).setUp('db2') self.real_status = db2_service.DB2AppStatus.set_status @@ -58,6 +66,9 @@ class GuestAgentDB2ManagerTest(DatastoreManagerTest): self.orig_delete_user = db2_service.DB2Admin.delete_user self.orig_update_hostname = db2_service.DB2App.update_hostname self.orig_backup_restore = backup.restore + self.orig_init_config = db2_service.DB2App.init_config + self.orig_update_overrides = db2_service.DB2App.update_overrides + self.orig_remove_overrides = db2_service.DB2App.remove_overrides def tearDown(self): super(GuestAgentDB2ManagerTest, self).tearDown() @@ -78,6 +89,9 @@ class GuestAgentDB2ManagerTest(DatastoreManagerTest): db2_service.DB2Admin.delete_user = self.orig_delete_user db2_service.DB2App.update_hostname = self.orig_update_hostname backup.restore = self.orig_backup_restore + db2_service.DB2App.init_config = self.orig_init_config + db2_service.DB2App.update_overrides = self.orig_update_overrides + db2_service.DB2App.remove_overrides = self.orig_remove_overrides def test_update_status(self): mock_status = MagicMock() @@ -97,8 +111,9 @@ class GuestAgentDB2ManagerTest(DatastoreManagerTest): def test_prepare_from_backup(self): self._prepare_dynamic(['db2'], backup_id='123backup') + @patch.object(configuration.ConfigurationManager, 'save_configuration') def _prepare_dynamic(self, packages=None, databases=None, users=None, - config_content=None, device_path='/dev/vdb', + config_content='MockContent', device_path='/dev/vdb', is_db_installed=True, backup_id=None, overrides=None): backup_info = {'id': backup_id, @@ -152,6 +167,9 @@ class GuestAgentDB2ManagerTest(DatastoreManagerTest): backup.restore.assert_any_call(self.context, backup_info, '/home/db2inst1/db2inst1') + self.assertTrue( + self.manager.configuration_manager.save_configuration.called + ) def test_restart(self): mock_status = MagicMock() @@ -171,6 +189,12 @@ class GuestAgentDB2ManagerTest(DatastoreManagerTest): db2_service.DB2App.stop_db.assert_any_call( do_not_start_on_reboot=False) + def test_start_db_with_conf_changes(self): + with patch.object(db2_service.DB2App, 'start_db_with_conf_changes'): + self.manager.start_db_with_conf_changes(self.context, 'something') + db2_service.DB2App.start_db_with_conf_changes.assert_any_call( + 'something') + def test_create_database(self): mock_status = MagicMock() self.manager.appStatus = mock_status @@ -228,13 +252,19 @@ class GuestAgentDB2ManagerTest(DatastoreManagerTest): self.assertThat(users, Equals(get_user_mock.return_value)) get_user_mock.assert_any_call(username, hostname) - def test_reset_configuration(self): - try: - configuration = {'config_contents': 'some junk'} - self.manager.reset_configuration(self.context, configuration) - except Exception: - self.fail("reset_configuration raised exception unexpectedly.") - def test_rpc_ping(self): output = self.manager.rpc_ping(self.context) self.assertTrue(output) + + def test_update_update_overrides(self): + configuration = {"DIAGSIZE": 50} + db2_service.DB2App.update_overrides = MagicMock() + self.manager.update_overrides(self.context, configuration, False) + db2_service.DB2App.update_overrides.assert_any_call(self.context, + configuration) + + def test_reset_update_overrides(self): + configuration = {} + db2_service.DB2App.remove_overrides = MagicMock() + self.manager.update_overrides(self.context, configuration, True) + db2_service.DB2App.remove_overrides.assert_any_call() diff --git a/trove/tests/unittests/guestagent/test_dbaas.py b/trove/tests/unittests/guestagent/test_dbaas.py index 9f1559eb6b..588b3c775c 100644 --- a/trove/tests/unittests/guestagent/test_dbaas.py +++ b/trove/tests/unittests/guestagent/test_dbaas.py @@ -3247,7 +3247,12 @@ class VerticaAppTest(trove_testtools.TestCase): class DB2AppTest(trove_testtools.TestCase): - def setUp(self): + @patch.object(ImportOverrideStrategy, '_initialize_import_directory') + @patch.multiple(operating_system, exists=DEFAULT, write_file=DEFAULT, + chown=DEFAULT, chmod=DEFAULT) + @patch.object(db2service, 'run_command') + @patch.object(db2service.DB2App, 'process_default_dbm_config') + def setUp(self, *args, **kwargs): super(DB2AppTest, self).setUp() self.orig_utils_execute_with_timeout = ( db2service.utils.execute_with_timeout) @@ -3258,6 +3263,7 @@ class DB2AppTest(trove_testtools.TestCase): self.appStatus = FakeAppStatus(self.FAKE_ID, rd_instance.ServiceStatuses.NEW) self.db2App = db2service.DB2App(self.appStatus) + self.db2App.init_config() dbaas.CONF.guest_id = self.FAKE_ID def tearDown(self): @@ -3279,7 +3285,12 @@ class DB2AppTest(trove_testtools.TestCase): self.db2App.stop_db() self.assert_reported_status(rd_instance.ServiceStatuses.NEW) - def test_restart_server(self): + @patch.object(ImportOverrideStrategy, '_initialize_import_directory') + @patch.multiple(operating_system, exists=DEFAULT, write_file=DEFAULT, + chown=DEFAULT, chmod=DEFAULT) + @patch.object(db2service, 'run_command') + @patch.object(db2service.DB2App, 'process_default_dbm_config') + def test_restart_server(self, *args, **kwargs): self.appStatus.set_next_status(rd_instance.ServiceStatuses.RUNNING) mock_status = MagicMock(return_value=None) app = db2service.DB2App(mock_status) @@ -3302,6 +3313,35 @@ class DB2AppTest(trove_testtools.TestCase): self.db2App.start_db() self.assert_reported_status(rd_instance.ServiceStatuses.NEW) + @patch.object(ConfigurationManager, 'save_configuration') + @patch.object(db2service.DB2App, 'start_db') + def test_start_db_with_conf_changes(self, start_db, save_cfg): + config = {'DIAGSIZE': '10'} + self.db2App.start_db_with_conf_changes(config) + start_db.assert_called_once_with(True) + save_cfg.assert_called_once_with(config) + + @patch.object(ConfigurationManager, 'apply_user_override') + @patch.object(db2service.DB2App, '_apply_config') + def test_update_overrides(self, apply_user_override, apply_config): + overrides = {'DIAGSIZE': 50} + context = MagicMock() + self.db2App.update_overrides(context, overrides) + apply_user_override.assert_called_once_with(overrides) + apply_config.assert_called_once_with(overrides) + + @patch.object(ConfigurationManager, 'get_user_override') + @patch.object(ConfigurationManager, 'remove_user_override') + @patch.object(db2service.DB2App, '_reset_config') + def test_remove_overrides(self, reset_config, remove_user_override, + get_user_override): + overrides = {'DIAGSIZE': 50} + get_user_override.return_value = overrides + self.db2App.remove_overrides() + get_user_override.assert_called_once() + reset_config.assert_called_once_with(overrides) + remove_user_override.assert_called_once() + class DB2AdminTest(trove_testtools.TestCase):