trove/trove/guestagent/datastore/redis/service.py

283 lines
10 KiB
Python

# Copyright (c) 2013 Rackspace
# 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 utils as utils
from trove.common import exception
from trove.common import instance as rd_instance
from trove.guestagent import pkg
from trove.guestagent.common import operating_system
from trove.guestagent.datastore import service
from trove.guestagent.datastore.redis import system
from trove.openstack.common import log as logging
from trove.common.i18n import _
LOG = logging.getLogger(__name__)
TMP_REDIS_CONF = '/tmp/redis.conf.tmp'
TIME_OUT = 1200
CONF = cfg.CONF
packager = pkg.Package()
def _load_redis_options():
"""
Reads the redis config file for all redis options.
Right now this does not do any smart parsing and returns only key
value pairs as a str, str.
So: 'foo bar baz' becomes {'foo' : 'bar baz'}
"""
options = {}
LOG.debug("Loading Redis options.")
with open(system.REDIS_CONFIG, 'r') as fd:
for opt in fd.readlines():
opt = opt.rstrip().split(' ')
options.update({opt[0]: ' '.join(opt[1:])})
return options
class RedisAppStatus(service.BaseDbStatus):
"""
Handles all of the status updating for the redis guest agent.
"""
@classmethod
def get(cls):
"""
Gets an instance of the RedisAppStatus class.
"""
if not cls._instance:
cls._instance = RedisAppStatus()
return cls._instance
def _get_actual_db_status(self):
"""
Gets the actual status of the Redis instance
First it attempts to make a connection to the redis instance
by making a PING request.
If PING does not return PONG we do a ps
to see if the process is blocked or hung.
This implementation stinks but redis-cli only returns 0
at this time.
http://redis.googlecode.com/svn/trunk/redis-cli.c
If we raise another exception.ProcessExecutionError while
running ps.
We attempt to locate the PID file and see if the process
is crashed or shutdown.
Remeber by default execute_with_timeout raises this exception
if a non 0 status code is returned from the cmd called.
"""
options = _load_redis_options()
out = ""
err = ""
try:
if 'requirepass' in options:
LOG.debug('Password is set, running ping with password.')
out, err = utils.execute_with_timeout(
system.REDIS_CLI,
'-a',
options['requirepass'],
'PING',
run_as_root=True,
root_helper='sudo')
else:
LOG.debug('Password not set, running ping without password.')
out, err = utils.execute_with_timeout(
system.REDIS_CLI,
'PING',
run_as_root=True,
root_helper='sudo')
LOG.info(_('Redis Service Status is RUNNING.'))
return rd_instance.ServiceStatuses.RUNNING
except exception.ProcessExecutionError:
LOG.exception(_('Process execution error on redis-cli.'))
if 'PONG' not in out:
try:
out, err = utils.execute_with_timeout('/bin/ps', '-C',
'redis-server', 'h')
pid = out.split()[0]
LOG.debug('Redis pid: %s.' % (pid))
LOG.info(_('Redis Service Status is BLOCKED.'))
return rd_instance.ServiceStatuses.BLOCKED
except exception.ProcessExecutionError:
pid_file = options.get('pidfile',
'/var/run/redis/redis-server.pid')
if os.path.exists(pid_file):
LOG.info(_('Redis Service Status is CRASHED.'))
return rd_instance.ServiceStatuses.CRASHED
else:
LOG.info(_('Redis Service Status is SHUTDOWN.'))
return rd_instance.ServiceStatuses.SHUTDOWN
class RedisApp(object):
"""
Handles installation and configuration of redis
on a trove instance.
"""
def __init__(self, status, state_change_wait_time=None):
"""
Sets default status and state_change_wait_time
"""
if state_change_wait_time:
self.state_change_wait_time = state_change_wait_time
else:
self.state_change_wait_time = CONF.state_change_wait_time
self.status = status
def install_if_needed(self, packages):
"""
Install redis if needed do nothing if it is already installed.
"""
LOG.info(_('Preparing Guest as Redis Server.'))
if not packager.pkg_is_installed(packages):
LOG.info(_('Installing Redis.'))
self._install_redis(packages)
LOG.info(_('Redis installed completely.'))
def complete_install_or_restart(self):
"""
finalize status updates for install or restart.
"""
LOG.debug("Complete install or restart called.")
self.status.end_install_or_restart()
def _install_redis(self, packages):
"""
Install the redis server.
"""
LOG.debug('Installing redis server.')
msg = "Creating %s." % system.REDIS_CONF_DIR
LOG.debug(msg)
utils.execute_with_timeout('mkdir',
'-p',
system.REDIS_CONF_DIR,
run_as_root=True,
root_helper='sudo')
pkg_opts = {}
packager.pkg_install(packages, pkg_opts, TIME_OUT)
self.start_redis()
LOG.debug('Finished installing redis server.')
def _enable_redis_on_boot(self):
"""
Enables redis on boot.
"""
LOG.info(_('Enabling Redis on boot.'))
try:
redis_service = operating_system.service_discovery(
system.SERVICE_CANDIDATES)
utils.execute_with_timeout(
redis_service['cmd_enable'], shell=True)
except KeyError:
raise RuntimeError(_(
"Command to enable Redis on boot not found."))
def _disable_redis_on_boot(self):
"""
Disables redis on boot.
"""
LOG.info(_("Disabling Redis on boot."))
try:
redis_service = operating_system.service_discovery(
system.SERVICE_CANDIDATES)
utils.execute_with_timeout(
redis_service['cmd_disable'], shell=True)
except KeyError:
raise RuntimeError(
"Command to disable Redis on boot not found.")
def stop_db(self, update_db=False, do_not_start_on_reboot=False):
"""
Stops the redis application on the trove instance.
"""
LOG.info(_('Stopping redis.'))
if do_not_start_on_reboot:
self._disable_redis_on_boot()
cmd = 'sudo %s' % (system.REDIS_CMD_STOP)
utils.execute_with_timeout(cmd,
shell=True)
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 Redis.'))
self.status.end_install_or_restart()
def restart(self):
"""
Restarts the redis daemon.
"""
LOG.debug("Restarting Redis daemon.")
try:
self.status.begin_restart()
self.stop_db()
self.start_redis()
finally:
self.status.end_install_or_restart()
def write_config(self, config_contents):
"""
Write the redis config.
"""
LOG.debug("Writing Redis config.")
with open(TMP_REDIS_CONF, 'w') as fd:
fd.write(config_contents)
utils.execute_with_timeout('mv',
TMP_REDIS_CONF,
system.REDIS_CONFIG,
run_as_root=True,
root_helper='sudo')
def start_db_with_conf_changes(self, config_contents):
LOG.info(_('Starting redis with conf changes.'))
if self.status.is_running:
format = 'Cannot start_db_with_conf_changes because status is %s.'
LOG.debug(format, self.status)
raise RuntimeError(format % self.status)
LOG.info(_("Initiating config."))
self.write_config(config_contents)
self.start_redis(True)
def reset_configuration(self, configuration):
config_contents = configuration['config_contents']
LOG.info(_("Resetting configuration."))
self.write_config(config_contents)
def start_redis(self, update_db=False):
"""
Start the redis daemon.
"""
LOG.info(_("Starting redis."))
self._enable_redis_on_boot()
try:
cmd = 'sudo %s' % (system.REDIS_CMD_START)
utils.execute_with_timeout(cmd,
shell=True)
except exception.ProcessExecutionError:
pass
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 redis failed."))
try:
utils.execute_with_timeout('pkill', '-9',
'redis-server',
run_as_root=True,
root_helper='sudo')
except exception.ProcessExecutionError:
LOG.exception(_('Error killing stalled redis start command.'))
self.status.end_install_or_restart()