Merge "Initial support for single instance MongoDB support"

This commit is contained in:
Jenkins 2014-03-05 02:02:19 +00:00 committed by Gerrit Code Review
commit fba8cabea3
13 changed files with 901 additions and 10 deletions

View File

@ -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):

View File

@ -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

View 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)

View 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

View 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()

View File

@ -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

View 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>

View 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}

View 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)

View File

@ -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)

View 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())