# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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
import signal

from oslo_log import log as logging

from eventlet.green import subprocess
from trove.common import cfg, utils
from trove.common.strategies.strategy import Strategy

CONF = cfg.CONF

LOG = logging.getLogger(__name__)


class BackupError(Exception):
    """Error running the Backup Command."""


class UnknownBackupType(Exception):
    """Unknown backup type."""


class BackupRunner(Strategy):
    """Base class for Backup Strategy implementations."""
    __strategy_type__ = 'backup_runner'
    __strategy_ns__ = 'trove.guestagent.strategies.backup'

    # The actual system call to run the backup
    cmd = None
    is_zipped = CONF.backup_use_gzip_compression
    is_encrypted = CONF.backup_use_openssl_encryption
    encrypt_key = CONF.backup_aes_cbc_key

    def __init__(self, filename, **kwargs):
        self.base_filename = filename
        self.process = None
        self.pid = None
        kwargs.update({'filename': filename})
        self.command = self.cmd % kwargs
        super(BackupRunner, self).__init__()

    @property
    def backup_type(self):
        return type(self).__name__

    def _run(self):
        LOG.debug("BackupRunner running cmd: %s", self.command)
        self.process = subprocess.Popen(self.command, shell=True,
                                        stdout=subprocess.PIPE,
                                        stderr=subprocess.PIPE,
                                        preexec_fn=os.setsid)
        self.pid = self.process.pid

    def __enter__(self):
        """Start up the process."""
        self._run_pre_backup()
        self._run()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """Clean up everything."""
        # NOTE(zhaochao): all child processes should always be killed even the
        # context exits by an exception.
        if getattr(self, 'process', None):
            try:
                # Send a sigterm to the session leader, so that all
                # child processes are killed and cleaned up on terminate
                # (Ensures zombie processes aren't left around on a FAILURE)
                # https://bugs.launchpad.net/trove/+bug/1253850
                os.killpg(self.process.pid, signal.SIGTERM)
                self.process.terminate()
            except OSError:
                # Already stopped
                pass

            if exc_type is not None:
                return False

            utils.raise_if_process_errored(self.process, BackupError)
            if not self.check_process():
                raise BackupError

        self._run_post_backup()

        return True

    def metadata(self):
        """Hook for subclasses to store metadata from the backup."""
        return {}

    @property
    def filename(self):
        """Subclasses may overwrite this to declare a format (.tar)."""
        return self.base_filename

    @property
    def manifest(self):
        return "%s%s%s" % (self.filename,
                           self.zip_manifest,
                           self.encrypt_manifest)

    @property
    def zip_cmd(self):
        return ' | gzip' if self.is_zipped else ''

    @property
    def zip_manifest(self):
        return '.gz' if self.is_zipped else ''

    @property
    def encrypt_cmd(self):
        return (' | openssl enc -aes-256-cbc -salt -pass pass:%s' %
                self.encrypt_key) if self.is_encrypted else ''

    @property
    def encrypt_manifest(self):
        return '.enc' if self.is_encrypted else ''

    def check_process(self):
        """Hook for subclasses to check process for errors."""
        return True

    def read(self, chunk_size):
        return self.process.stdout.read(chunk_size)

    def _run_pre_backup(self):
        pass

    def _run_post_backup(self):
        pass
