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',
|
||||
'redis': 'b216ffc5-1947-456c-a4cf-70f94c05f7d0',
|
||||
'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.'),
|
||||
cfg.StrOpt('nova_proxy_admin_user', default='',
|
||||
help="Admin username used to connect to nova.", secret=True),
|
||||
@ -259,6 +260,12 @@ common_opts = [
|
||||
'large tokens (typically those generated by the '
|
||||
'Keystone v3 API with big service catalogs'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
CONF.register_opts(path_opts)
|
||||
CONF.register_opts(common_opts)
|
||||
|
||||
# Datastore specific option groups
|
||||
|
||||
# Mysql
|
||||
@ -363,6 +370,26 @@ couchbase_opts = [
|
||||
"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.register_opts(path_opts)
|
||||
@ -373,12 +400,14 @@ CONF.register_group(percona_group)
|
||||
CONF.register_group(redis_group)
|
||||
CONF.register_group(cassandra_group)
|
||||
CONF.register_group(couchbase_group)
|
||||
CONF.register_group(mongodb_group)
|
||||
|
||||
CONF.register_opts(mysql_opts, mysql_group)
|
||||
CONF.register_opts(percona_opts, percona_group)
|
||||
CONF.register_opts(redis_opts, redis_group)
|
||||
CONF.register_opts(cassandra_opts, cassandra_group)
|
||||
CONF.register_opts(couchbase_opts, couchbase_group)
|
||||
CONF.register_opts(mongodb_opts, mongodb_group)
|
||||
|
||||
|
||||
def custom_parser(parsername, parser):
|
||||
|
@ -12,6 +12,7 @@
|
||||
# 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 fcntl
|
||||
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',
|
||||
'cassandra': 'trove.guestagent.datastore.cassandra.manager.Manager',
|
||||
'couchbase': 'trove.guestagent.datastore.couchbase.manager.Manager',
|
||||
'mongodb': 'trove.guestagent.datastore.mongodb.manager.Manager',
|
||||
}
|
||||
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 mock
|
||||
import re
|
||||
|
||||
from trove.common import template
|
||||
@ -68,13 +67,6 @@ class HeatTemplateLoadTest(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
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):
|
||||
super(HeatTemplateLoadTest, self).tearDown()
|
||||
@ -86,6 +78,11 @@ class HeatTemplateLoadTest(testtools.TestCase):
|
||||
|
||||
def test_heat_template_load_success(self):
|
||||
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')
|
||||
mongo_tmpl = template.load_heat_template('mongodb')
|
||||
self.assertIsNotNone(mysql_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 KeepAliveConnection
|
||||
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.instance.models import InstanceServiceStatus
|
||||
from trove.tests.unittests.util import util
|
||||
@ -71,7 +73,7 @@ FAKE_USER = [{"_name": "random", "_password": "guesswhat",
|
||||
conductor_api.API.heartbeat = Mock()
|
||||
|
||||
|
||||
class FakeAppStatus(MySqlAppStatus):
|
||||
class FakeAppStatus(BaseDbStatus):
|
||||
|
||||
def __init__(self, id, status):
|
||||
self.id = id
|
||||
@ -929,6 +931,9 @@ class ServiceRegistryTest(testtools.TestCase):
|
||||
self.assertEqual(test_dict.get('couchbase'),
|
||||
'trove.guestagent.datastore.couchbase.manager'
|
||||
'.Manager')
|
||||
self.assertEqual('trove.guestagent.datastore.mongodb.'
|
||||
'manager.Manager',
|
||||
test_dict.get('mongodb'))
|
||||
|
||||
def test_datastore_registry_with_existing_manager(self):
|
||||
datastore_registry_ext_test = {
|
||||
@ -952,6 +957,8 @@ class ServiceRegistryTest(testtools.TestCase):
|
||||
self.assertEqual(test_dict.get('couchbase'),
|
||||
'trove.guestagent.datastore.couchbase.manager'
|
||||
'.Manager')
|
||||
self.assertEqual('trove.guestagent.datastore.mongodb.manager.Manager',
|
||||
test_dict.get('mongodb'))
|
||||
|
||||
def test_datastore_registry_with_blank_dict(self):
|
||||
datastore_registry_ext_test = dict()
|
||||
@ -972,6 +979,8 @@ class ServiceRegistryTest(testtools.TestCase):
|
||||
self.assertEqual(test_dict.get('couchbase'),
|
||||
'trove.guestagent.datastore.couchbase.manager'
|
||||
'.Manager')
|
||||
self.assertEqual('trove.guestagent.datastore.mongodb.manager.Manager',
|
||||
test_dict.get('mongodb'))
|
||||
|
||||
|
||||
class KeepAliveConnectionTest(testtools.TestCase):
|
||||
@ -1621,3 +1630,151 @@ class CouchbaseAppTest(testtools.TestCase):
|
||||
self.assertTrue(couchservice.packager.pkg_is_installed.called)
|
||||
self.assertTrue(self.couchbaseApp.initial_setup.called)
|
||||
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