269 lines
10 KiB
Python
269 lines
10 KiB
Python
# Copyright (c) 2013 eBay Software Foundation
|
|
# 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 json
|
|
import os
|
|
import stat
|
|
import subprocess
|
|
import tempfile
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_utils import netutils
|
|
import pexpect
|
|
import six
|
|
|
|
from trove.common import cfg
|
|
from trove.common.db import models
|
|
from trove.common import exception
|
|
from trove.common.i18n import _
|
|
from trove.common import instance as rd_instance
|
|
from trove.common import utils as utils
|
|
from trove.guestagent.common import operating_system
|
|
from trove.guestagent.datastore.experimental.couchbase import system
|
|
from trove.guestagent.datastore import service
|
|
from trove.guestagent import pkg
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
packager = pkg.Package()
|
|
|
|
|
|
class CouchbaseApp(object):
|
|
"""
|
|
Handles installation and configuration of couchbase
|
|
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 couchbase if needed, do nothing if it is already installed.
|
|
"""
|
|
LOG.info(_('Preparing Guest as Couchbase Server.'))
|
|
if not packager.pkg_is_installed(packages):
|
|
LOG.debug('Installing Couchbase.')
|
|
self._install_couchbase(packages)
|
|
|
|
def initial_setup(self):
|
|
self.ip_address = netutils.get_my_ipv4()
|
|
mount_point = CONF.couchbase.mount_point
|
|
try:
|
|
LOG.info(_('Couchbase Server change data dir path.'))
|
|
operating_system.chown(mount_point, 'couchbase', 'couchbase',
|
|
as_root=True)
|
|
pwd = CouchbaseRootAccess.get_password()
|
|
utils.execute_with_timeout(
|
|
(system.cmd_node_init
|
|
% {'data_path': mount_point,
|
|
'IP': self.ip_address,
|
|
'PWD': pwd}), shell=True)
|
|
operating_system.remove(system.INSTANCE_DATA_DIR, force=True,
|
|
as_root=True)
|
|
LOG.debug('Couchbase Server initialize cluster.')
|
|
utils.execute_with_timeout(
|
|
(system.cmd_cluster_init
|
|
% {'IP': self.ip_address, 'PWD': pwd}),
|
|
shell=True)
|
|
utils.execute_with_timeout(system.cmd_set_swappiness, shell=True)
|
|
utils.execute_with_timeout(system.cmd_update_sysctl_conf,
|
|
shell=True)
|
|
LOG.info(_('Couchbase Server initial setup finished.'))
|
|
except exception.ProcessExecutionError:
|
|
LOG.exception(_('Error performing initial Couchbase setup.'))
|
|
raise RuntimeError(_("Couchbase Server initial setup failed"))
|
|
|
|
def _install_couchbase(self, packages):
|
|
"""
|
|
Install the Couchbase Server.
|
|
"""
|
|
LOG.debug('Installing Couchbase Server. Creating %s' %
|
|
system.COUCHBASE_CONF_DIR)
|
|
operating_system.create_directory(system.COUCHBASE_CONF_DIR,
|
|
as_root=True)
|
|
pkg_opts = {}
|
|
packager.pkg_install(packages, pkg_opts, system.TIME_OUT)
|
|
self.start_db()
|
|
LOG.debug('Finished installing Couchbase Server.')
|
|
|
|
def stop_db(self, update_db=False, do_not_start_on_reboot=False):
|
|
self.status.stop_db_service(
|
|
system.SERVICE_CANDIDATES, self.state_change_wait_time,
|
|
disable_on_boot=do_not_start_on_reboot, update_db=update_db)
|
|
|
|
def restart(self):
|
|
self.status.restart_db_service(
|
|
system.SERVICE_CANDIDATES, self.state_change_wait_time)
|
|
|
|
def start_db(self, update_db=False):
|
|
self.status.start_db_service(
|
|
system.SERVICE_CANDIDATES, self.state_change_wait_time,
|
|
enable_on_boot=True, update_db=update_db)
|
|
|
|
def enable_root(self, root_password=None):
|
|
return CouchbaseRootAccess.enable_root(root_password)
|
|
|
|
def start_db_with_conf_changes(self, config_contents):
|
|
LOG.info(_("Starting Couchbase with configuration changes.\n"
|
|
"Configuration contents:\n %s.") % config_contents)
|
|
if self.status.is_running:
|
|
LOG.error(_("Cannot start Couchbase with configuration changes. "
|
|
"Couchbase state == %s.") % self.status)
|
|
raise RuntimeError(_("Couchbase is not stopped."))
|
|
self._write_config(config_contents)
|
|
self.start_db(True)
|
|
|
|
def reset_configuration(self, configuration):
|
|
config_contents = configuration['config_contents']
|
|
LOG.debug("Resetting configuration.")
|
|
self._write_config(config_contents)
|
|
|
|
def _write_config(self, config_contents):
|
|
"""
|
|
Update contents of Couchbase configuration file
|
|
"""
|
|
return
|
|
|
|
|
|
class CouchbaseAppStatus(service.BaseDbStatus):
|
|
"""
|
|
Handles all of the status updating for the couchbase guest agent.
|
|
"""
|
|
|
|
def _get_actual_db_status(self):
|
|
self.ip_address = netutils.get_my_ipv4()
|
|
pwd = None
|
|
try:
|
|
pwd = CouchbaseRootAccess.get_password()
|
|
return self._get_status_from_couchbase(pwd)
|
|
except exception.ProcessExecutionError:
|
|
# log the exception, but continue with native config approach
|
|
LOG.exception(_("Error getting the Couchbase status."))
|
|
|
|
try:
|
|
out, err = utils.execute_with_timeout(
|
|
system.cmd_get_password_from_config, shell=True)
|
|
except exception.ProcessExecutionError:
|
|
LOG.exception(_("Error getting the root password from the "
|
|
"native Couchbase config file."))
|
|
return rd_instance.ServiceStatuses.SHUTDOWN
|
|
|
|
config_pwd = out.strip() if out is not None else None
|
|
if not config_pwd or config_pwd == pwd:
|
|
LOG.debug("The root password from the native Couchbase config "
|
|
"file is either empty or already matches the "
|
|
"stored value.")
|
|
return rd_instance.ServiceStatuses.SHUTDOWN
|
|
|
|
try:
|
|
status = self._get_status_from_couchbase(config_pwd)
|
|
except exception.ProcessExecutionError:
|
|
LOG.exception(_("Error getting Couchbase status using the "
|
|
"password parsed from the native Couchbase "
|
|
"config file."))
|
|
return rd_instance.ServiceStatuses.SHUTDOWN
|
|
|
|
# if the parsed root password worked, update the stored value to
|
|
# avoid having to consult/parse the couchbase config file again.
|
|
LOG.debug("Updating the stored value for the Couchbase "
|
|
"root password.")
|
|
CouchbaseRootAccess().write_password_to_file(config_pwd)
|
|
return status
|
|
|
|
def _get_status_from_couchbase(self, pwd):
|
|
out, err = utils.execute_with_timeout(
|
|
(system.cmd_couchbase_status %
|
|
{'IP': self.ip_address, 'PWD': pwd}),
|
|
shell=True)
|
|
server_stats = json.loads(out)
|
|
if not err and server_stats["clusterMembership"] == "active":
|
|
return rd_instance.ServiceStatuses.RUNNING
|
|
else:
|
|
return rd_instance.ServiceStatuses.SHUTDOWN
|
|
|
|
def cleanup_stalled_db_services(self):
|
|
utils.execute_with_timeout(system.cmd_kill)
|
|
|
|
|
|
class CouchbaseRootAccess(object):
|
|
|
|
@classmethod
|
|
def enable_root(cls, root_password=None):
|
|
user = models.DatastoreUser.root(password=root_password)
|
|
|
|
if root_password:
|
|
CouchbaseRootAccess().write_password_to_file(root_password)
|
|
else:
|
|
CouchbaseRootAccess().set_password(user.password)
|
|
return user.serialize()
|
|
|
|
def set_password(self, root_password):
|
|
self.ip_address = netutils.get_my_ipv4()
|
|
child = pexpect.spawn(system.cmd_reset_pwd % {'IP': self.ip_address})
|
|
try:
|
|
child.expect('.*password.*')
|
|
child.sendline(root_password)
|
|
child.expect('.*(yes/no).*')
|
|
child.sendline('yes')
|
|
child.expect('.*successfully.*')
|
|
except pexpect.TIMEOUT:
|
|
child.delayafterclose = 1
|
|
child.delayafterterminate = 1
|
|
try:
|
|
child.close(force=True)
|
|
except pexpect.ExceptionPexpect:
|
|
# Close fails to terminate a sudo process on some OSes.
|
|
subprocess.call(['sudo', 'kill', str(child.pid)])
|
|
|
|
self.write_password_to_file(root_password)
|
|
|
|
def write_password_to_file(self, root_password):
|
|
operating_system.create_directory(system.COUCHBASE_CONF_DIR,
|
|
as_root=True)
|
|
try:
|
|
tempfd, tempname = tempfile.mkstemp()
|
|
os.fchmod(tempfd, stat.S_IRUSR | stat.S_IWUSR)
|
|
if isinstance(root_password, six.text_type):
|
|
root_password = root_password.encode('utf-8')
|
|
os.write(tempfd, root_password)
|
|
os.fchmod(tempfd, stat.S_IRUSR)
|
|
os.close(tempfd)
|
|
except OSError as err:
|
|
message = _("An error occurred in saving password "
|
|
"(%(errno)s). %(strerror)s.") % {
|
|
"errno": err.errno,
|
|
"strerror": err.strerror}
|
|
LOG.exception(message)
|
|
raise RuntimeError(message)
|
|
|
|
operating_system.move(tempname, system.pwd_file, as_root=True)
|
|
|
|
@staticmethod
|
|
def get_password():
|
|
pwd = "password"
|
|
if os.path.exists(system.pwd_file):
|
|
with open(system.pwd_file) as file:
|
|
pwd = file.readline().strip()
|
|
return pwd
|