Merge "Initial support for single instance MongoDB support"
This commit is contained in:
commit
fba8cabea3
@ -219,7 +219,8 @@ common_opts = [
|
|||||||
default={'mysql': '2f3ff068-2bfb-4f70-9a9d-a6bb65bc084b',
|
default={'mysql': '2f3ff068-2bfb-4f70-9a9d-a6bb65bc084b',
|
||||||
'redis': 'b216ffc5-1947-456c-a4cf-70f94c05f7d0',
|
'redis': 'b216ffc5-1947-456c-a4cf-70f94c05f7d0',
|
||||||
'cassandra': '459a230d-4e97-4344-9067-2a54a310b0ed',
|
'cassandra': '459a230d-4e97-4344-9067-2a54a310b0ed',
|
||||||
'couchbase': 'fa62fe68-74d9-4779-a24e-36f19602c415'},
|
'couchbase': 'fa62fe68-74d9-4779-a24e-36f19602c415',
|
||||||
|
'mongodb': 'c8c907af-7375-456f-b929-b637ff9209ee'},
|
||||||
help='Unique ID to tag notification events.'),
|
help='Unique ID to tag notification events.'),
|
||||||
cfg.StrOpt('nova_proxy_admin_user', default='',
|
cfg.StrOpt('nova_proxy_admin_user', default='',
|
||||||
help="Admin username used to connect to nova.", secret=True),
|
help="Admin username used to connect to nova.", secret=True),
|
||||||
@ -259,6 +260,12 @@ common_opts = [
|
|||||||
'large tokens (typically those generated by the '
|
'large tokens (typically those generated by the '
|
||||||
'Keystone v3 API with big service catalogs'),
|
'Keystone v3 API with big service catalogs'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
CONF.register_opts(path_opts)
|
||||||
|
CONF.register_opts(common_opts)
|
||||||
|
|
||||||
# Datastore specific option groups
|
# Datastore specific option groups
|
||||||
|
|
||||||
# Mysql
|
# Mysql
|
||||||
@ -363,6 +370,26 @@ couchbase_opts = [
|
|||||||
"volumes if volume support is enabled"),
|
"volumes if volume support is enabled"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# MongoDB
|
||||||
|
mongodb_group = cfg.OptGroup(
|
||||||
|
'mongodb', title='MongoDB options',
|
||||||
|
help="Oslo option group designed for MongoDB datastore")
|
||||||
|
mongodb_opts = [
|
||||||
|
cfg.ListOpt('tcp_ports', default=["2500", "27017"],
|
||||||
|
help='List of TCP ports and/or port ranges to open'
|
||||||
|
' in the security group (only applicable '
|
||||||
|
'if trove_security_groups_support is True)'),
|
||||||
|
cfg.ListOpt('udp_ports', default=[],
|
||||||
|
help='List of UPD ports and/or port ranges to open'
|
||||||
|
' in the security group (only applicable '
|
||||||
|
'if trove_security_groups_support is True)'),
|
||||||
|
cfg.StrOpt('backup_strategy', default=None,
|
||||||
|
help='Default strategy to perform backups.'),
|
||||||
|
cfg.StrOpt('mount_point', default='/var/lib/mongodb',
|
||||||
|
help="Filesystem path for mounting "
|
||||||
|
"volumes if volume support is enabled"),
|
||||||
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
CONF.register_opts(path_opts)
|
CONF.register_opts(path_opts)
|
||||||
@ -373,12 +400,14 @@ CONF.register_group(percona_group)
|
|||||||
CONF.register_group(redis_group)
|
CONF.register_group(redis_group)
|
||||||
CONF.register_group(cassandra_group)
|
CONF.register_group(cassandra_group)
|
||||||
CONF.register_group(couchbase_group)
|
CONF.register_group(couchbase_group)
|
||||||
|
CONF.register_group(mongodb_group)
|
||||||
|
|
||||||
CONF.register_opts(mysql_opts, mysql_group)
|
CONF.register_opts(mysql_opts, mysql_group)
|
||||||
CONF.register_opts(percona_opts, percona_group)
|
CONF.register_opts(percona_opts, percona_group)
|
||||||
CONF.register_opts(redis_opts, redis_group)
|
CONF.register_opts(redis_opts, redis_group)
|
||||||
CONF.register_opts(cassandra_opts, cassandra_group)
|
CONF.register_opts(cassandra_opts, cassandra_group)
|
||||||
CONF.register_opts(couchbase_opts, couchbase_group)
|
CONF.register_opts(couchbase_opts, couchbase_group)
|
||||||
|
CONF.register_opts(mongodb_opts, mongodb_group)
|
||||||
|
|
||||||
|
|
||||||
def custom_parser(parsername, parser):
|
def custom_parser(parsername, parser):
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import fcntl
|
import fcntl
|
||||||
import struct
|
import struct
|
||||||
|
0
trove/guestagent/datastore/mongodb/__init__.py
Normal file
0
trove/guestagent/datastore/mongodb/__init__.py
Normal file
164
trove/guestagent/datastore/mongodb/manager.py
Normal file
164
trove/guestagent/datastore/mongodb/manager.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
# Copyright (c) 2014 Mirantis, 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 os
|
||||||
|
|
||||||
|
from trove.common import cfg
|
||||||
|
from trove.common import exception
|
||||||
|
from trove.guestagent import dbaas
|
||||||
|
from trove.guestagent import volume
|
||||||
|
from trove.guestagent.common import operating_system
|
||||||
|
from trove.guestagent.datastore.mongodb import service as mongo_service
|
||||||
|
from trove.guestagent.datastore.mongodb import system
|
||||||
|
from trove.openstack.common import log as logging
|
||||||
|
from trove.openstack.common.gettextutils import _
|
||||||
|
from trove.openstack.common import periodic_task
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
ERROR_MSG = _("Not supported")
|
||||||
|
|
||||||
|
|
||||||
|
class Manager(periodic_task.PeriodicTasks):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.status = mongo_service.MongoDbAppStatus()
|
||||||
|
self.app = mongo_service.MongoDBApp(self.status)
|
||||||
|
|
||||||
|
@periodic_task.periodic_task(ticks_between_runs=3)
|
||||||
|
def update_status(self, context):
|
||||||
|
"""Update the status of the MongoDB service"""
|
||||||
|
self.status.update()
|
||||||
|
|
||||||
|
def prepare(self, context, packages, databases, memory_mb, users,
|
||||||
|
device_path=None, mount_point=None, backup_info=None,
|
||||||
|
config_contents=None, root_password=None, overrides=None):
|
||||||
|
"""Makes ready DBAAS on a Guest container."""
|
||||||
|
|
||||||
|
LOG.debug(_("Prepare MongoDB instance"))
|
||||||
|
|
||||||
|
self.status.begin_install()
|
||||||
|
self.app.install_if_needed(packages)
|
||||||
|
self.app.stop_db()
|
||||||
|
self.app.clear_storage()
|
||||||
|
mount_point = system.MONGODB_MOUNT_POINT
|
||||||
|
if device_path:
|
||||||
|
device = volume.VolumeDevice(device_path)
|
||||||
|
device.format()
|
||||||
|
if os.path.exists(system.MONGODB_MOUNT_POINT):
|
||||||
|
device.migrate_data(mount_point)
|
||||||
|
device.mount(mount_point)
|
||||||
|
self.app.update_owner(mount_point)
|
||||||
|
|
||||||
|
LOG.debug(_("Mounted the volume %(path)s as %(mount)s") %
|
||||||
|
{'path': device_path, "mount": mount_point})
|
||||||
|
|
||||||
|
if mount_point:
|
||||||
|
config_contents = self.app.update_config_contents(
|
||||||
|
config_contents, {
|
||||||
|
'dbpath': mount_point,
|
||||||
|
'bind_ip': operating_system.get_ip_address()
|
||||||
|
})
|
||||||
|
|
||||||
|
self.app.start_db_with_conf_changes(config_contents)
|
||||||
|
LOG.info(_('"prepare" call has finished.'))
|
||||||
|
|
||||||
|
def restart(self, context):
|
||||||
|
self.app.restart()
|
||||||
|
|
||||||
|
def start_db_with_conf_changes(self, context, config_contents):
|
||||||
|
self.app.start_db_with_conf_changes(config_contents)
|
||||||
|
|
||||||
|
def stop_db(self, context, do_not_start_on_reboot=False):
|
||||||
|
self.app.stop_db(do_not_start_on_reboot=do_not_start_on_reboot)
|
||||||
|
|
||||||
|
def reset_configuration(self, context, configuration):
|
||||||
|
self.app.reset_configuration(configuration)
|
||||||
|
|
||||||
|
def get_filesystem_stats(self, context, fs_path):
|
||||||
|
"""Gets the filesystem stats for the path given """
|
||||||
|
return dbaas.get_filesystem_volume_stats(system.MONGODB_MOUNT_POINT)
|
||||||
|
|
||||||
|
def change_passwords(self, context, users):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def update_attributes(self, context, username, hostname, user_attrs):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def create_database(self, context, databases):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def create_user(self, context, users):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def delete_database(self, context, database):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def delete_user(self, context, user):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def get_user(self, context, username, hostname):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def grant_access(self, context, username, hostname, databases):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def revoke_access(self, context, username, hostname, database):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def list_access(self, context, username, hostname):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def list_databases(self, context, limit=None, marker=None,
|
||||||
|
include_marker=False):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def list_users(self, context, limit=None, marker=None,
|
||||||
|
include_marker=False):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def enable_root(self, context):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def is_root_enabled(self, context):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def _perform_restore(self, backup_info, context, restore_location, app):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def create_backup(self, context, backup_info):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def mount_volume(self, context, device_path=None, mount_point=None):
|
||||||
|
device = volume.VolumeDevice(device_path)
|
||||||
|
device.mount(mount_point, write_to_fstab=False)
|
||||||
|
LOG.debug(_("Mounted the volume."))
|
||||||
|
|
||||||
|
def unmount_volume(self, context, device_path=None, mount_point=None):
|
||||||
|
device = volume.VolumeDevice(device_path)
|
||||||
|
device.unmount(mount_point)
|
||||||
|
LOG.debug(_("Unmounted the volume."))
|
||||||
|
|
||||||
|
def resize_fs(self, context, device_path=None, mount_point=None):
|
||||||
|
device = volume.VolumeDevice(device_path)
|
||||||
|
device.resize_fs(mount_point)
|
||||||
|
LOG.debug(_("Resized the filesystem"))
|
||||||
|
|
||||||
|
def update_overrides(self, context, overrides, remove=False):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
||||||
|
|
||||||
|
def apply_overrides(self, context, overrides):
|
||||||
|
raise exception.TroveError(ERROR_MSG)
|
229
trove/guestagent/datastore/mongodb/service.py
Normal file
229
trove/guestagent/datastore/mongodb/service.py
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
# Copyright (c) 2014 Mirantis, 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 re
|
||||||
|
|
||||||
|
from trove.common import cfg
|
||||||
|
from trove.common import utils as utils
|
||||||
|
from trove.common import exception
|
||||||
|
from trove.common import instance as rd_instance
|
||||||
|
from trove.common.exception import ProcessExecutionError
|
||||||
|
from trove.guestagent.datastore import service
|
||||||
|
from trove.guestagent.datastore.mongodb import system
|
||||||
|
from trove.openstack.common import log as logging
|
||||||
|
from trove.guestagent.common import operating_system
|
||||||
|
from trove.openstack.common.gettextutils import _
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class MongoDBApp(object):
|
||||||
|
"""Prepares DBaaS on a Guest container."""
|
||||||
|
|
||||||
|
def __init__(self, status):
|
||||||
|
self.state_change_wait_time = CONF.state_change_wait_time
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
def install_if_needed(self, packages):
|
||||||
|
"""Prepare the guest machine with a MongoDB installation"""
|
||||||
|
LOG.info(_("Preparing Guest as MongoDB"))
|
||||||
|
if not system.PACKAGER.pkg_is_installed(packages):
|
||||||
|
LOG.debug(_("Installing packages: %s") % str(packages))
|
||||||
|
system.PACKAGER.pkg_install(packages, {}, system.TIME_OUT)
|
||||||
|
LOG.info(_("Finished installing MongoDB server"))
|
||||||
|
|
||||||
|
def _enable_db_on_boot(self):
|
||||||
|
LOG.info(_("Enabling MongoDB on boot"))
|
||||||
|
try:
|
||||||
|
mongodb_service = operating_system.service_discovery(
|
||||||
|
system.SERVICE_CANDIDATES)
|
||||||
|
utils.execute_with_timeout(mongodb_service['cmd_enable'],
|
||||||
|
shell=True)
|
||||||
|
except KeyError:
|
||||||
|
raise RuntimeError(_("MongoDB service is not discovered."))
|
||||||
|
|
||||||
|
def _disable_db_on_boot(self):
|
||||||
|
LOG.info(_("Disabling MongoDB on boot"))
|
||||||
|
try:
|
||||||
|
mongodb_service = operating_system.service_discovery(
|
||||||
|
system.SERVICE_CANDIDATES)
|
||||||
|
utils.execute_with_timeout(mongodb_service['cmd_disable'],
|
||||||
|
shell=True)
|
||||||
|
except KeyError:
|
||||||
|
raise RuntimeError("MongoDB service is not discovered.")
|
||||||
|
|
||||||
|
def stop_db(self, update_db=False, do_not_start_on_reboot=False):
|
||||||
|
LOG.info(_("Stopping MongoDB"))
|
||||||
|
if do_not_start_on_reboot:
|
||||||
|
self._disable_db_on_boot()
|
||||||
|
|
||||||
|
try:
|
||||||
|
mongodb_service = operating_system.service_discovery(
|
||||||
|
system.SERVICE_CANDIDATES)
|
||||||
|
utils.execute_with_timeout(mongodb_service['cmd_stop'],
|
||||||
|
shell=True)
|
||||||
|
except KeyError:
|
||||||
|
raise RuntimeError(_("MongoDB service is not discovered."))
|
||||||
|
|
||||||
|
if not self.status.wait_for_real_status_to_change_to(
|
||||||
|
rd_instance.ServiceStatuses.SHUTDOWN,
|
||||||
|
self.state_change_wait_time, update_db):
|
||||||
|
LOG.error(_("Could not stop MongoDB"))
|
||||||
|
self.status.end_install_or_restart()
|
||||||
|
raise RuntimeError(_("Could not stop MongoDB"))
|
||||||
|
|
||||||
|
def restart(self):
|
||||||
|
LOG.info(_("Restarting MongoDB"))
|
||||||
|
try:
|
||||||
|
self.status.begin_restart()
|
||||||
|
self.stop_db()
|
||||||
|
self.start_db()
|
||||||
|
finally:
|
||||||
|
self.status.end_install_or_restart()
|
||||||
|
|
||||||
|
def start_db(self, update_db=False):
|
||||||
|
LOG.info(_("Starting MongoDB"))
|
||||||
|
|
||||||
|
self._enable_db_on_boot()
|
||||||
|
|
||||||
|
try:
|
||||||
|
mongodb_service = operating_system.service_discovery(
|
||||||
|
system.SERVICE_CANDIDATES)
|
||||||
|
utils.execute_with_timeout(mongodb_service['cmd_start'],
|
||||||
|
shell=True)
|
||||||
|
except ProcessExecutionError:
|
||||||
|
pass
|
||||||
|
except KeyError:
|
||||||
|
raise RuntimeError("MongoDB service is not discovered.")
|
||||||
|
|
||||||
|
if not self.status.wait_for_real_status_to_change_to(
|
||||||
|
rd_instance.ServiceStatuses.RUNNING,
|
||||||
|
self.state_change_wait_time, update_db):
|
||||||
|
LOG.error(_("Start up of MongoDB failed"))
|
||||||
|
# If it won't start, but won't die either, kill it by hand so we
|
||||||
|
# don't let a rouge process wander around.
|
||||||
|
try:
|
||||||
|
out, err = utils.execute_with_timeout(
|
||||||
|
system.FIND_PID, shell=True)
|
||||||
|
pid = "".join(out.split(" ")[1:2])
|
||||||
|
utils.execute_with_timeout(
|
||||||
|
system.MONGODB_KILL % pid, shell=True)
|
||||||
|
except exception.ProcessExecutionError as p:
|
||||||
|
LOG.error("Error killing stalled MongoDB start command.")
|
||||||
|
LOG.error(p)
|
||||||
|
# There's nothing more we can do...
|
||||||
|
self.status.end_install_or_restart()
|
||||||
|
raise RuntimeError("Could not start MongoDB")
|
||||||
|
|
||||||
|
def start_db_with_conf_changes(self, config_contents):
|
||||||
|
LOG.info(_("Starting MongoDB with configuration changes"))
|
||||||
|
LOG.info(_("Configuration contents:\n %s") % config_contents)
|
||||||
|
if self.status.is_running:
|
||||||
|
LOG.error(_("Cannot start MongoDB with configuration changes. "
|
||||||
|
"MongoDB state == %s!") % self.status)
|
||||||
|
raise RuntimeError("MongoDB is not stopped.")
|
||||||
|
self._write_config(config_contents)
|
||||||
|
self.start_db(True)
|
||||||
|
|
||||||
|
def reset_configuration(self, configuration):
|
||||||
|
config_contents = configuration['config_contents']
|
||||||
|
LOG.info(_("Resetting configuration"))
|
||||||
|
self._write_config(config_contents)
|
||||||
|
|
||||||
|
def update_config_contents(self, config_contents, parameters):
|
||||||
|
if not config_contents:
|
||||||
|
config_contents = self._read_config()
|
||||||
|
|
||||||
|
contents = self._delete_config_parameters(config_contents,
|
||||||
|
parameters.keys())
|
||||||
|
for param, value in parameters.iteritems():
|
||||||
|
if param and value:
|
||||||
|
contents = self._add_config_parameter(contents,
|
||||||
|
param, value)
|
||||||
|
|
||||||
|
return contents
|
||||||
|
|
||||||
|
def _write_config(self, config_contents):
|
||||||
|
"""
|
||||||
|
Update contents of MongoDB configuration file
|
||||||
|
"""
|
||||||
|
LOG.info(_("Updating MongoDB config"))
|
||||||
|
if config_contents:
|
||||||
|
LOG.info(_("Writing %s") % system.TMP_CONFIG)
|
||||||
|
with open(system.TMP_CONFIG, 'w') as t:
|
||||||
|
t.write(config_contents)
|
||||||
|
|
||||||
|
LOG.info(_("Moving %(a)s to %(b)s")
|
||||||
|
% {'a': system.TMP_CONFIG, 'b': system.CONFIG})
|
||||||
|
utils.execute_with_timeout("mv", system.TMP_CONFIG, system.CONFIG,
|
||||||
|
run_as_root=True, root_helper="sudo")
|
||||||
|
else:
|
||||||
|
LOG.info(_("Empty config_contents. Do nothing"))
|
||||||
|
|
||||||
|
def _read_config(self):
|
||||||
|
try:
|
||||||
|
with open(system.CONFIG, 'r') as f:
|
||||||
|
return f.read()
|
||||||
|
except IOError:
|
||||||
|
LOG.info(_("Config file %s not found") % system.CONFIG)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def _delete_config_parameters(self, config_contents, parameters):
|
||||||
|
if not config_contents:
|
||||||
|
return None
|
||||||
|
|
||||||
|
params_as_string = '|'.join(parameters)
|
||||||
|
p = re.compile("\\s*#?\\s*(%s)\\s*=" % params_as_string)
|
||||||
|
contents_as_list = config_contents.splitlines()
|
||||||
|
filtered = filter(lambda line: not p.match(line), contents_as_list)
|
||||||
|
return '\n'.join(filtered)
|
||||||
|
|
||||||
|
def _add_config_parameter(self, config_contents, parameter, value):
|
||||||
|
return (config_contents or '') + "\n%s = %s" % (parameter, value)
|
||||||
|
|
||||||
|
def update_owner(self, path):
|
||||||
|
LOG.info(_("Set owner to 'mongodb' for %s ") % system.CONFIG)
|
||||||
|
utils.execute_with_timeout("chown", "-R", "mongodb", path,
|
||||||
|
run_as_root=True, root_helper="sudo")
|
||||||
|
LOG.info(_("Set group to 'mongodb' for %s ") % system.CONFIG)
|
||||||
|
utils.execute_with_timeout("chgrp", "-R", "mongodb", path,
|
||||||
|
run_as_root=True, root_helper="sudo")
|
||||||
|
|
||||||
|
def clear_storage(self):
|
||||||
|
mount_point = "/var/lib/mongodb/*"
|
||||||
|
try:
|
||||||
|
cmd = "sudo rm -rf %s" % mount_point
|
||||||
|
utils.execute_with_timeout(cmd, shell=True)
|
||||||
|
except exception.ProcessExecutionError as e:
|
||||||
|
LOG.error(_("Process execution %s") % e)
|
||||||
|
|
||||||
|
|
||||||
|
class MongoDbAppStatus(service.BaseDbStatus):
|
||||||
|
def _get_actual_db_status(self):
|
||||||
|
try:
|
||||||
|
status_check = (system.CMD_STATUS %
|
||||||
|
operating_system.get_ip_address())
|
||||||
|
out, err = utils.execute_with_timeout(status_check, shell=True)
|
||||||
|
if not err and "connected to:" in out:
|
||||||
|
return rd_instance.ServiceStatuses.RUNNING
|
||||||
|
else:
|
||||||
|
return rd_instance.ServiceStatuses.SHUTDOWN
|
||||||
|
except exception.ProcessExecutionError as e:
|
||||||
|
LOG.error(_("Process execution %s") % e)
|
||||||
|
return rd_instance.ServiceStatuses.SHUTDOWN
|
||||||
|
except OSError as e:
|
||||||
|
LOG.error(_("OS Error %s") % e)
|
||||||
|
return rd_instance.ServiceStatuses.SHUTDOWN
|
30
trove/guestagent/datastore/mongodb/system.py
Normal file
30
trove/guestagent/datastore/mongodb/system.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Copyright (c) 2014 Mirantis, 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.guestagent import pkg
|
||||||
|
|
||||||
|
MONGODB_MOUNT_POINT = "/var/lib/mongodb"
|
||||||
|
# After changing bind address mongodb accepts connection
|
||||||
|
# on real IP, not on the localhost
|
||||||
|
CMD_STATUS = "mongostat --host %s -n 1 | grep connected"
|
||||||
|
|
||||||
|
TMP_CONFIG = "/tmp/mongodb.conf.tmp"
|
||||||
|
CONFIG = "/etc/mongodb.conf"
|
||||||
|
SERVICE_CANDIDATES = ["mongodb", "mongod"]
|
||||||
|
MONGODB_KILL = "sudo kill %s"
|
||||||
|
FIND_PID = "ps xau | grep mongod"
|
||||||
|
TIME_OUT = 1000
|
||||||
|
|
||||||
|
PACKAGER = pkg.Package()
|
@ -37,6 +37,7 @@ defaults = {
|
|||||||
'redis': 'trove.guestagent.datastore.redis.manager.Manager',
|
'redis': 'trove.guestagent.datastore.redis.manager.Manager',
|
||||||
'cassandra': 'trove.guestagent.datastore.cassandra.manager.Manager',
|
'cassandra': 'trove.guestagent.datastore.cassandra.manager.Manager',
|
||||||
'couchbase': 'trove.guestagent.datastore.couchbase.manager.Manager',
|
'couchbase': 'trove.guestagent.datastore.couchbase.manager.Manager',
|
||||||
|
'mongodb': 'trove.guestagent.datastore.mongodb.manager.Manager',
|
||||||
}
|
}
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
95
trove/templates/mongodb/config.template
Normal file
95
trove/templates/mongodb/config.template
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# mongodb.conf
|
||||||
|
|
||||||
|
smallfiles = false
|
||||||
|
|
||||||
|
# Where to store the data.
|
||||||
|
dbpath=/var/lib/mongodb
|
||||||
|
|
||||||
|
#where to log
|
||||||
|
logpath=/var/log/mongodb/mongodb.log
|
||||||
|
|
||||||
|
logappend=true
|
||||||
|
|
||||||
|
bind_ip = 127.0.0.1
|
||||||
|
#port = 27017
|
||||||
|
|
||||||
|
# Enable journaling, http://www.mongodb.org/display/DOCS/Journaling
|
||||||
|
journal=true
|
||||||
|
|
||||||
|
# Enables periodic logging of CPU utilization and I/O wait
|
||||||
|
#cpu = true
|
||||||
|
|
||||||
|
# Turn on/off security. Off is currently the default
|
||||||
|
#noauth = true
|
||||||
|
#auth = true
|
||||||
|
|
||||||
|
# Verbose logging output.
|
||||||
|
#verbose = true
|
||||||
|
|
||||||
|
# Inspect all client data for validity on receipt (useful for
|
||||||
|
# developing drivers)
|
||||||
|
#objcheck = true
|
||||||
|
|
||||||
|
# Enable db quota management
|
||||||
|
#quota = true
|
||||||
|
|
||||||
|
# Set oplogging level where n is
|
||||||
|
# 0=off (default)
|
||||||
|
# 1=W
|
||||||
|
# 2=R
|
||||||
|
# 3=both
|
||||||
|
# 7=W+some reads
|
||||||
|
#oplog = 0
|
||||||
|
|
||||||
|
# Diagnostic/debugging option
|
||||||
|
#nocursors = true
|
||||||
|
|
||||||
|
# Ignore query hints
|
||||||
|
#nohints = true
|
||||||
|
|
||||||
|
# Disable the HTTP interface (Defaults to localhost:27018).
|
||||||
|
#nohttpinterface = true
|
||||||
|
|
||||||
|
# Turns off server-side scripting. This will result in greatly limited
|
||||||
|
# functionality
|
||||||
|
#noscripting = true
|
||||||
|
|
||||||
|
# Turns off table scans. Any query that would do a table scan fails.
|
||||||
|
#notablescan = true
|
||||||
|
|
||||||
|
# Disable data file preallocation.
|
||||||
|
#noprealloc = true
|
||||||
|
|
||||||
|
# Specify .ns file size for new databases.
|
||||||
|
# nssize = <size>
|
||||||
|
|
||||||
|
# Accout token for Mongo monitoring server.
|
||||||
|
#mms-token = <token>
|
||||||
|
|
||||||
|
# Server name for Mongo monitoring server.
|
||||||
|
#mms-name = <server-name>
|
||||||
|
|
||||||
|
# Ping interval for Mongo monitoring server.
|
||||||
|
#mms-interval = <seconds>
|
||||||
|
|
||||||
|
# Replication Options
|
||||||
|
|
||||||
|
# in replicated mongo databases, specify here whether this is a slave or master
|
||||||
|
#slave = true
|
||||||
|
#source = master.example.com
|
||||||
|
# Slave only: specify a single database to replicate
|
||||||
|
#only = master.example.com
|
||||||
|
# or
|
||||||
|
#master = true
|
||||||
|
#source = slave.example.com
|
||||||
|
|
||||||
|
# Address of a server to pair with.
|
||||||
|
#pairwith = <server:port>
|
||||||
|
# Address of arbiter server.
|
||||||
|
#arbiter = <server:port>
|
||||||
|
# Automatically resync if slave data is stale
|
||||||
|
#autoresync
|
||||||
|
# Custom size for replication operation log.
|
||||||
|
#oplogSize = <MB>
|
||||||
|
# Size limit for in-memory storage of op ids.
|
||||||
|
#opIdMem = <bytes>
|
93
trove/templates/mongodb/heat.template
Normal file
93
trove/templates/mongodb/heat.template
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
HeatTemplateFormatVersion: '2012-12-12'
|
||||||
|
Description: Instance creation template for mongodb
|
||||||
|
Parameters:
|
||||||
|
Flavor:
|
||||||
|
Type: String
|
||||||
|
VolumeSize:
|
||||||
|
Type: Number
|
||||||
|
Default : '1'
|
||||||
|
InstanceId:
|
||||||
|
Type: String
|
||||||
|
ImageId:
|
||||||
|
Type: String
|
||||||
|
DatastoreManager:
|
||||||
|
Type: String
|
||||||
|
AvailabilityZone:
|
||||||
|
Type: String
|
||||||
|
Default: nova
|
||||||
|
TenantId:
|
||||||
|
Type: String
|
||||||
|
Resources:
|
||||||
|
{% for port in ports %}
|
||||||
|
{{ port.name }}:
|
||||||
|
Type: OS::Neutron::Port
|
||||||
|
Properties:
|
||||||
|
network_id: "{{ port.net_id }}"
|
||||||
|
security_groups: [{Ref: DBaaSSG}]
|
||||||
|
{% if port.fixed_ip %}
|
||||||
|
fixed_ips: [{"ip_address": "{{ port.fixed_ip }}"}]
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
BaseInstance:
|
||||||
|
Type: AWS::EC2::Instance
|
||||||
|
Metadata:
|
||||||
|
AWS::CloudFormation::Init:
|
||||||
|
config:
|
||||||
|
files:
|
||||||
|
/etc/guest_info:
|
||||||
|
content:
|
||||||
|
Fn::Join:
|
||||||
|
- ''
|
||||||
|
- ["[DEFAULT]\nguest_id=", {Ref: InstanceId},
|
||||||
|
"\ndatastore_manager=", {Ref: DatastoreManager},
|
||||||
|
"\ntenant_id=", {Ref: TenantId}]
|
||||||
|
mode: '000644'
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
Properties:
|
||||||
|
ImageId: {Ref: ImageId}
|
||||||
|
InstanceType: {Ref: Flavor}
|
||||||
|
AvailabilityZone: {Ref: AvailabilityZone}
|
||||||
|
{% if ifaces %}
|
||||||
|
NetworkInterfaces: [{{ ifaces|join(', ') }}]
|
||||||
|
{% else %}
|
||||||
|
SecurityGroups: [{Ref: DBaaSSG}]
|
||||||
|
{% endif %}
|
||||||
|
UserData:
|
||||||
|
Fn::Base64:
|
||||||
|
Fn::Join:
|
||||||
|
- ''
|
||||||
|
- ["#!/bin/bash -v\n",
|
||||||
|
"/opt/aws/bin/cfn-init\n",
|
||||||
|
"sudo service trove-guest start\n"]
|
||||||
|
{% if volume_support %}
|
||||||
|
DataVolume:
|
||||||
|
Type: AWS::EC2::Volume
|
||||||
|
Properties:
|
||||||
|
Size: {Ref: VolumeSize}
|
||||||
|
AvailabilityZone: {Ref: AvailabilityZone}
|
||||||
|
Tags:
|
||||||
|
- {Key: Usage, Value: Test}
|
||||||
|
MountPoint:
|
||||||
|
Type: AWS::EC2::VolumeAttachment
|
||||||
|
Properties:
|
||||||
|
InstanceId: {Ref: BaseInstance}
|
||||||
|
VolumeId: {Ref: DataVolume}
|
||||||
|
Device: /dev/vdb
|
||||||
|
{% endif %}
|
||||||
|
DBaaSSG:
|
||||||
|
Type: AWS::EC2::SecurityGroup
|
||||||
|
Properties:
|
||||||
|
GroupDescription: Default Security group for MongoDB
|
||||||
|
SecurityGroupIngress:
|
||||||
|
- IpProtocol: "tcp"
|
||||||
|
FromPort: "27017"
|
||||||
|
ToPort: "27017"
|
||||||
|
CidrIp: "0.0.0.0/0"
|
||||||
|
DatabaseIPAddress:
|
||||||
|
Type: AWS::EC2::EIP
|
||||||
|
DatabaseIPAssoc :
|
||||||
|
Type: AWS::EC2::EIPAssociation
|
||||||
|
Properties:
|
||||||
|
InstanceId: {Ref: BaseInstance}
|
||||||
|
EIP: {Ref: DatabaseIPAddress}
|
0
trove/templates/mongodb/override.config.template
Normal file
0
trove/templates/mongodb/override.config.template
Normal file
@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
|
|
||||||
import testtools
|
import testtools
|
||||||
import mock
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from trove.common import template
|
from trove.common import template
|
||||||
@ -68,13 +67,6 @@ class HeatTemplateLoadTest(testtools.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(HeatTemplateLoadTest, self).setUp()
|
super(HeatTemplateLoadTest, self).setUp()
|
||||||
self.fException = mock.Mock(side_effect=
|
|
||||||
lambda *args, **kwargs:
|
|
||||||
_raise(template.jinja2.
|
|
||||||
TemplateNotFound("Test")))
|
|
||||||
|
|
||||||
def _raise(ex):
|
|
||||||
raise ex
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(HeatTemplateLoadTest, self).tearDown()
|
super(HeatTemplateLoadTest, self).tearDown()
|
||||||
@ -86,6 +78,11 @@ class HeatTemplateLoadTest(testtools.TestCase):
|
|||||||
|
|
||||||
def test_heat_template_load_success(self):
|
def test_heat_template_load_success(self):
|
||||||
mysql_tmpl = template.load_heat_template('mysql')
|
mysql_tmpl = template.load_heat_template('mysql')
|
||||||
|
#TODO(denis_makogon): use it when redis template would be added
|
||||||
|
#redis_tmplt = template.load_heat_template('redis')
|
||||||
cassandra_tmpl = template.load_heat_template('cassandra')
|
cassandra_tmpl = template.load_heat_template('cassandra')
|
||||||
|
mongo_tmpl = template.load_heat_template('mongodb')
|
||||||
self.assertIsNotNone(mysql_tmpl)
|
self.assertIsNotNone(mysql_tmpl)
|
||||||
self.assertIsNotNone(cassandra_tmpl)
|
self.assertIsNotNone(cassandra_tmpl)
|
||||||
|
self.assertIsNotNone(mongo_tmpl)
|
||||||
|
# self.assertIsNotNone(redis_tmpl)
|
||||||
|
@ -51,6 +51,8 @@ from trove.guestagent.datastore.mysql.service import MySqlApp
|
|||||||
from trove.guestagent.datastore.mysql.service import MySqlAppStatus
|
from trove.guestagent.datastore.mysql.service import MySqlAppStatus
|
||||||
from trove.guestagent.datastore.mysql.service import KeepAliveConnection
|
from trove.guestagent.datastore.mysql.service import KeepAliveConnection
|
||||||
from trove.guestagent.datastore.couchbase import service as couchservice
|
from trove.guestagent.datastore.couchbase import service as couchservice
|
||||||
|
from trove.guestagent.datastore.mongodb import service as mongo_service
|
||||||
|
from trove.guestagent.datastore.mongodb import system as mongo_system
|
||||||
from trove.guestagent.db import models
|
from trove.guestagent.db import models
|
||||||
from trove.instance.models import InstanceServiceStatus
|
from trove.instance.models import InstanceServiceStatus
|
||||||
from trove.tests.unittests.util import util
|
from trove.tests.unittests.util import util
|
||||||
@ -71,7 +73,7 @@ FAKE_USER = [{"_name": "random", "_password": "guesswhat",
|
|||||||
conductor_api.API.heartbeat = Mock()
|
conductor_api.API.heartbeat = Mock()
|
||||||
|
|
||||||
|
|
||||||
class FakeAppStatus(MySqlAppStatus):
|
class FakeAppStatus(BaseDbStatus):
|
||||||
|
|
||||||
def __init__(self, id, status):
|
def __init__(self, id, status):
|
||||||
self.id = id
|
self.id = id
|
||||||
@ -929,6 +931,9 @@ class ServiceRegistryTest(testtools.TestCase):
|
|||||||
self.assertEqual(test_dict.get('couchbase'),
|
self.assertEqual(test_dict.get('couchbase'),
|
||||||
'trove.guestagent.datastore.couchbase.manager'
|
'trove.guestagent.datastore.couchbase.manager'
|
||||||
'.Manager')
|
'.Manager')
|
||||||
|
self.assertEqual('trove.guestagent.datastore.mongodb.'
|
||||||
|
'manager.Manager',
|
||||||
|
test_dict.get('mongodb'))
|
||||||
|
|
||||||
def test_datastore_registry_with_existing_manager(self):
|
def test_datastore_registry_with_existing_manager(self):
|
||||||
datastore_registry_ext_test = {
|
datastore_registry_ext_test = {
|
||||||
@ -952,6 +957,8 @@ class ServiceRegistryTest(testtools.TestCase):
|
|||||||
self.assertEqual(test_dict.get('couchbase'),
|
self.assertEqual(test_dict.get('couchbase'),
|
||||||
'trove.guestagent.datastore.couchbase.manager'
|
'trove.guestagent.datastore.couchbase.manager'
|
||||||
'.Manager')
|
'.Manager')
|
||||||
|
self.assertEqual('trove.guestagent.datastore.mongodb.manager.Manager',
|
||||||
|
test_dict.get('mongodb'))
|
||||||
|
|
||||||
def test_datastore_registry_with_blank_dict(self):
|
def test_datastore_registry_with_blank_dict(self):
|
||||||
datastore_registry_ext_test = dict()
|
datastore_registry_ext_test = dict()
|
||||||
@ -972,6 +979,8 @@ class ServiceRegistryTest(testtools.TestCase):
|
|||||||
self.assertEqual(test_dict.get('couchbase'),
|
self.assertEqual(test_dict.get('couchbase'),
|
||||||
'trove.guestagent.datastore.couchbase.manager'
|
'trove.guestagent.datastore.couchbase.manager'
|
||||||
'.Manager')
|
'.Manager')
|
||||||
|
self.assertEqual('trove.guestagent.datastore.mongodb.manager.Manager',
|
||||||
|
test_dict.get('mongodb'))
|
||||||
|
|
||||||
|
|
||||||
class KeepAliveConnectionTest(testtools.TestCase):
|
class KeepAliveConnectionTest(testtools.TestCase):
|
||||||
@ -1621,3 +1630,151 @@ class CouchbaseAppTest(testtools.TestCase):
|
|||||||
self.assertTrue(couchservice.packager.pkg_is_installed.called)
|
self.assertTrue(couchservice.packager.pkg_is_installed.called)
|
||||||
self.assertTrue(self.couchbaseApp.initial_setup.called)
|
self.assertTrue(self.couchbaseApp.initial_setup.called)
|
||||||
self.assert_reported_status(rd_instance.ServiceStatuses.NEW)
|
self.assert_reported_status(rd_instance.ServiceStatuses.NEW)
|
||||||
|
|
||||||
|
|
||||||
|
class MongoDBAppTest(testtools.TestCase):
|
||||||
|
|
||||||
|
def fake_mongodb_service_discovery(self, candidates):
|
||||||
|
return {
|
||||||
|
'cmd_start': 'start',
|
||||||
|
'cmd_stop': 'stop',
|
||||||
|
'cmd_enable': 'enable',
|
||||||
|
'cmd_disable': 'disable'
|
||||||
|
}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(MongoDBAppTest, self).setUp()
|
||||||
|
self.orig_utils_execute_with_timeout = (mongo_service.
|
||||||
|
utils.execute_with_timeout)
|
||||||
|
self.orig_time_sleep = time.sleep
|
||||||
|
self.orig_packager = mongo_system.PACKAGER
|
||||||
|
self.orig_service_discovery = operating_system.service_discovery
|
||||||
|
|
||||||
|
operating_system.service_discovery = (
|
||||||
|
self.fake_mongodb_service_discovery)
|
||||||
|
util.init_db()
|
||||||
|
self.FAKE_ID = str(uuid4())
|
||||||
|
InstanceServiceStatus.create(instance_id=self.FAKE_ID,
|
||||||
|
status=rd_instance.ServiceStatuses.NEW)
|
||||||
|
self.appStatus = FakeAppStatus(self.FAKE_ID,
|
||||||
|
rd_instance.ServiceStatuses.NEW)
|
||||||
|
self.mongoDbApp = mongo_service.MongoDBApp(self.appStatus)
|
||||||
|
time.sleep = Mock()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(MongoDBAppTest, self).tearDown()
|
||||||
|
mongo_service.utils.execute_with_timeout = (
|
||||||
|
self.orig_utils_execute_with_timeout)
|
||||||
|
time.sleep = self.orig_time_sleep
|
||||||
|
mongo_system.PACKAGER = self.orig_packager
|
||||||
|
operating_system.service_discovery = self.orig_service_discovery
|
||||||
|
InstanceServiceStatus.find_by(instance_id=self.FAKE_ID).delete()
|
||||||
|
|
||||||
|
def assert_reported_status(self, expected_status):
|
||||||
|
service_status = InstanceServiceStatus.find_by(
|
||||||
|
instance_id=self.FAKE_ID)
|
||||||
|
self.assertEqual(expected_status, service_status.status)
|
||||||
|
|
||||||
|
def test_stopdb(self):
|
||||||
|
mongo_service.utils.execute_with_timeout = Mock()
|
||||||
|
self.appStatus.set_next_status(
|
||||||
|
rd_instance.ServiceStatuses.SHUTDOWN)
|
||||||
|
|
||||||
|
self.mongoDbApp.stop_db()
|
||||||
|
self.assert_reported_status(rd_instance.ServiceStatuses.NEW)
|
||||||
|
|
||||||
|
def test_stop_db_with_db_update(self):
|
||||||
|
|
||||||
|
mongo_service.utils.execute_with_timeout = Mock()
|
||||||
|
self.appStatus.set_next_status(
|
||||||
|
rd_instance.ServiceStatuses.SHUTDOWN)
|
||||||
|
|
||||||
|
self.mongoDbApp.stop_db(True)
|
||||||
|
self.assertTrue(conductor_api.API.heartbeat.called_once_with(
|
||||||
|
self.FAKE_ID, {'service_status': 'shutdown'}))
|
||||||
|
|
||||||
|
def test_stop_db_error(self):
|
||||||
|
|
||||||
|
mongo_service.utils.execute_with_timeout = Mock()
|
||||||
|
self.appStatus.set_next_status(rd_instance.ServiceStatuses.RUNNING)
|
||||||
|
self.mongoDbApp.state_change_wait_time = 1
|
||||||
|
self.assertRaises(RuntimeError, self.mongoDbApp.stop_db)
|
||||||
|
|
||||||
|
def test_restart(self):
|
||||||
|
|
||||||
|
self.appStatus.set_next_status(rd_instance.ServiceStatuses.RUNNING)
|
||||||
|
self.mongoDbApp.stop_db = Mock()
|
||||||
|
self.mongoDbApp.start_db = Mock()
|
||||||
|
|
||||||
|
self.mongoDbApp.restart()
|
||||||
|
|
||||||
|
self.assertTrue(self.mongoDbApp.stop_db.called)
|
||||||
|
self.assertTrue(self.mongoDbApp.start_db.called)
|
||||||
|
|
||||||
|
self.assertTrue(conductor_api.API.heartbeat.called_once_with(
|
||||||
|
self.FAKE_ID, {'service_status': 'shutdown'}))
|
||||||
|
|
||||||
|
self.assertTrue(conductor_api.API.heartbeat.called_once_with(
|
||||||
|
self.FAKE_ID, {'service_status': 'running'}))
|
||||||
|
|
||||||
|
def test_start_db(self):
|
||||||
|
|
||||||
|
mongo_service.utils.execute_with_timeout = Mock()
|
||||||
|
self.appStatus.set_next_status(rd_instance.ServiceStatuses.RUNNING)
|
||||||
|
|
||||||
|
self.mongoDbApp.start_db()
|
||||||
|
self.assert_reported_status(rd_instance.ServiceStatuses.NEW)
|
||||||
|
|
||||||
|
def test_start_db_with_update(self):
|
||||||
|
|
||||||
|
mongo_service.utils.execute_with_timeout = Mock()
|
||||||
|
self.appStatus.set_next_status(rd_instance.ServiceStatuses.RUNNING)
|
||||||
|
|
||||||
|
self.mongoDbApp.start_db(True)
|
||||||
|
self.assertTrue(conductor_api.API.heartbeat.called_once_with(
|
||||||
|
self.FAKE_ID, {'service_status': 'running'}))
|
||||||
|
|
||||||
|
def test_start_db_runs_forever(self):
|
||||||
|
|
||||||
|
mongo_service.utils.execute_with_timeout = Mock(
|
||||||
|
return_value=["ubuntu 17036 0.0 0.1 618960 "
|
||||||
|
"29232 pts/8 Sl+ Jan29 0:07 mongod", ""])
|
||||||
|
self.mongoDbApp.state_change_wait_time = 1
|
||||||
|
self.appStatus.set_next_status(rd_instance.ServiceStatuses.SHUTDOWN)
|
||||||
|
|
||||||
|
self.assertRaises(RuntimeError, self.mongoDbApp.start_db)
|
||||||
|
self.assertTrue(conductor_api.API.heartbeat.called_once_with(
|
||||||
|
self.FAKE_ID, {'service_status': 'shutdown'}))
|
||||||
|
|
||||||
|
def test_start_db_error(self):
|
||||||
|
|
||||||
|
self.mongoDbApp._enable_db_on_boot = Mock()
|
||||||
|
from trove.common.exception import ProcessExecutionError
|
||||||
|
mocked = Mock(side_effect=ProcessExecutionError('Error'))
|
||||||
|
mongo_service.utils.execute_with_timeout = mocked
|
||||||
|
|
||||||
|
self.assertRaises(RuntimeError, self.mongoDbApp.start_db)
|
||||||
|
|
||||||
|
def test_start_db_with_conf_changes_db_is_running(self):
|
||||||
|
|
||||||
|
self.mongoDbApp.start_db = Mock()
|
||||||
|
|
||||||
|
self.appStatus.status = rd_instance.ServiceStatuses.RUNNING
|
||||||
|
self.assertRaises(RuntimeError,
|
||||||
|
self.mongoDbApp.start_db_with_conf_changes,
|
||||||
|
Mock())
|
||||||
|
|
||||||
|
def test_install_when_db_installed(self):
|
||||||
|
packager_mock = mock()
|
||||||
|
when(packager_mock).pkg_is_installed(any()).thenReturn(True)
|
||||||
|
mongo_system.PACKAGER = packager_mock
|
||||||
|
self.mongoDbApp.install_if_needed(['package'])
|
||||||
|
self.assert_reported_status(rd_instance.ServiceStatuses.NEW)
|
||||||
|
|
||||||
|
def test_install_when_db_not_installed(self):
|
||||||
|
packager_mock = mock()
|
||||||
|
when(packager_mock).pkg_is_installed(any()).thenReturn(False)
|
||||||
|
mongo_system.PACKAGER = packager_mock
|
||||||
|
self.mongoDbApp.install_if_needed(['package'])
|
||||||
|
verify(packager_mock).pkg_install(any(), {}, any())
|
||||||
|
self.assert_reported_status(rd_instance.ServiceStatuses.NEW)
|
||||||
|
95
trove/tests/unittests/guestagent/test_mongodb_manager.py
Normal file
95
trove/tests/unittests/guestagent/test_mongodb_manager.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Copyright 2012 OpenStack Foundation
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import testtools
|
||||||
|
from mockito import verify, when, unstub, any, mock
|
||||||
|
from trove.common.context import TroveContext
|
||||||
|
from trove.guestagent import volume
|
||||||
|
from trove.guestagent.datastore.mongodb import service as mongo_service
|
||||||
|
from trove.guestagent.datastore.mongodb import manager as mongo_manager
|
||||||
|
from trove.guestagent.volume import VolumeDevice
|
||||||
|
|
||||||
|
|
||||||
|
class GuestAgentMongoDBManagerTest(testtools.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(GuestAgentMongoDBManagerTest, self).setUp()
|
||||||
|
self.context = TroveContext()
|
||||||
|
self.manager = mongo_manager.Manager()
|
||||||
|
self.origin_MongoDbAppStatus = mongo_service.MongoDbAppStatus
|
||||||
|
self.origin_os_path_exists = os.path.exists
|
||||||
|
self.origin_format = volume.VolumeDevice.format
|
||||||
|
self.origin_migrate_data = volume.VolumeDevice.migrate_data
|
||||||
|
self.origin_mount = volume.VolumeDevice.mount
|
||||||
|
self.origin_stop_db = mongo_service.MongoDBApp.stop_db
|
||||||
|
self.origin_start_db = mongo_service.MongoDBApp.start_db
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(GuestAgentMongoDBManagerTest, self).tearDown()
|
||||||
|
mongo_service.MongoDbAppStatus = self.origin_MongoDbAppStatus
|
||||||
|
os.path.exists = self.origin_os_path_exists
|
||||||
|
volume.VolumeDevice.format = self.origin_format
|
||||||
|
volume.VolumeDevice.migrate_data = self.origin_migrate_data
|
||||||
|
volume.VolumeDevice.mount = self.origin_mount
|
||||||
|
mongo_service.MongoDBApp.stop_db = self.origin_stop_db
|
||||||
|
mongo_service.MongoDBApp.start_db = self.origin_start_db
|
||||||
|
unstub()
|
||||||
|
|
||||||
|
def test_update_status(self):
|
||||||
|
self.manager.status = mock()
|
||||||
|
self.manager.update_status(self.context)
|
||||||
|
verify(self.manager.status).update()
|
||||||
|
|
||||||
|
def test_prepare_from_backup(self):
|
||||||
|
self._prepare_dynamic(backup_id='backup_id_123abc')
|
||||||
|
|
||||||
|
def _prepare_dynamic(self, device_path='/dev/vdb', is_db_installed=True,
|
||||||
|
backup_id=None):
|
||||||
|
|
||||||
|
# covering all outcomes is starting to cause trouble here
|
||||||
|
backup_info = {'id': backup_id,
|
||||||
|
'location': 'fake-location',
|
||||||
|
'type': 'MongoDBDump',
|
||||||
|
'checksum': 'fake-checksum'} if backup_id else None
|
||||||
|
|
||||||
|
mock_status = mock()
|
||||||
|
self.manager.status = mock_status
|
||||||
|
when(mock_status).begin_install().thenReturn(None)
|
||||||
|
|
||||||
|
when(VolumeDevice).format().thenReturn(None)
|
||||||
|
when(VolumeDevice).migrate_data(any()).thenReturn(None)
|
||||||
|
when(VolumeDevice).mount().thenReturn(None)
|
||||||
|
|
||||||
|
mock_app = mock()
|
||||||
|
self.manager.app = mock_app
|
||||||
|
when(mock_app).stop_db().thenReturn(None)
|
||||||
|
when(mock_app).start_db().thenReturn(None)
|
||||||
|
when(mock_app).clear_storage().thenReturn(None)
|
||||||
|
when(os.path).exists(any()).thenReturn(is_db_installed)
|
||||||
|
|
||||||
|
# invocation
|
||||||
|
self.manager.prepare(context=self.context, databases=None,
|
||||||
|
packages=['package'],
|
||||||
|
memory_mb='2048', users=None,
|
||||||
|
device_path=device_path,
|
||||||
|
mount_point='/var/lib/mongodb',
|
||||||
|
backup_info=backup_info)
|
||||||
|
# verification/assertion
|
||||||
|
verify(mock_status).begin_install()
|
||||||
|
verify(VolumeDevice).format()
|
||||||
|
verify(mock_app).stop_db()
|
||||||
|
verify(VolumeDevice).migrate_data(any())
|
||||||
|
verify(mock_app).install_if_needed(any())
|
Loading…
Reference in New Issue
Block a user