#   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

#   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

#   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. Prepares DBaaS on a Guest container. Prepare the guest machine with a MongoDB installation # If it won't start, but won't die either, kill it by hand so we # don't let a rouge process wander around. # There's nothing more we can do... Update contents of MongoDB configuration file #?\\s*(%s)\\s*=" % params_as_string)