#!/usr/bin/env python

# oio-blob-rebuilder.py
# Copyright (C) 2015-2018 OpenIO SAS, as part of OpenIO SDS
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import argparse
import sys

from oio.rebuilder.blob_rebuilder import BlobRebuilder, DEFAULT_REBUILDER_TUBE
from oio.common.utils import get_logger


def make_arg_parser():
    log_parser = argparse.ArgumentParser(add_help=False)
    levels = ['DEBUG', 'INFO', 'WARN', 'ERROR']
    log_parser.add_argument('--log-level', choices=levels,
                            help="Log level")
    log_parser.add_argument('--log-syslog-prefix',
                            help="Syslog prefix")
    log_parser.add_argument('--log-facility',
                            help="Log facility")
    log_parser.add_argument('--log-address',
                            help="Log address")

    descr = """
Rebuild chunks that were on the specified volume, or chunks listed in
the input file. If no input file is provided, the list of chunks is
obtained by requesting the associated rdir service. In that case,
it is necessary to declare an incident (with 'openio volume admin incident')
before running this tool. This tool can also keep listening to a beanstalkd
tube for broken chunks events.
"""
    parser = argparse.ArgumentParser(description=descr, parents=[log_parser])
    parser.add_argument('namespace', help="Namespace")
    parser.add_argument('--volume',
                        help="Id of the volume to rebuild (IP:PORT)")
    parser.add_argument('--dry-run', action='store_true',
                        help="Display actions but do nothing")
    parser.add_argument('--rdir-fetch-limit', type=int,
                        help="Maximum of entries returned in "
                             "each rdir response (100)")
    parser.add_argument('--report-interval', type=int,
                        help="Report interval in seconds (3600)")
    parser.add_argument('--workers', type=int,
                        help="Number of workers (1)")
    parser.add_argument('--chunks-per-second', type=int,
                        help="Max chunks per second per worker (30)")
    parser.add_argument('-q', '--quiet', action='store_true',
                        help="Don't print log on console")
    parser.add_argument('--allow-same-rawx', action='store_true',
                        help="Allow rebuilding a chunk on the original rawx")
    dft_help = "Try to delete faulty chunks after they have been rebuilt " \
               "elsewhere. This option is useful if the chunks you are " \
               "rebuilding are not actually missing but are corrupted."
    parser.add_argument('--delete-faulty-chunks', action='store_true',
                        help=dft_help)
    ifile_help = "Read chunks from this file instead of rdir. " \
                 "Each line should be formatted like " \
                 "'container_id|content_id|short_chunk_id_or_position'."
    parser.add_argument('--input-file', nargs='?',
                        help=ifile_help)
    beanstalk_help = "Listen to broken chunks events from a beanstalkd tube " \
                     "instead of querying rdir."
    parser.add_argument('--beanstalkd',
                        metavar='IP:PORT',
                        help=beanstalk_help)
    parser.add_argument(
        '--beanstalkd-tube',
        help='The beanstalk tube to use (default: "oio-rebuild")')

    return parser


def main():
    args = make_arg_parser().parse_args()

    if not any((args.volume, args.input_file, args.beanstalkd)):
        raise ValueError('Missing VOLUME, INPUT_FILE or beanstalkd address')

    conf = {}
    conf['allow_same_rawx'] = args.allow_same_rawx
    conf['dry_run'] = args.dry_run
    conf['namespace'] = args.namespace

    if args.log_level is not None:
        conf['log_level'] = args.log_level
    if args.log_facility is not None:
        conf['log_facility'] = args.log_facility
    if args.log_address is not None:
        conf['log_address'] = args.log_address
    if args.log_syslog_prefix is not None:
        conf['syslog_prefix'] = args.log_syslog_prefix
    else:
        conf['syslog_prefix'] = 'OIO,%s,blob-rebuilder,%s' % \
            (args.namespace,
             args.volume or
             args.input_file or
             ('tube:' + (args.beanstalkd_tube or DEFAULT_REBUILDER_TUBE)))

    logger = get_logger(conf, 'log', not args.quiet)

    if args.beanstalkd_tube is not None:
        conf['beanstalkd_tube'] = args.beanstalkd_tube
    if args.rdir_fetch_limit is not None:
        conf['rdir_fetch_limit'] = args.rdir_fetch_limit
    if args.report_interval is not None:
        conf['report_interval'] = args.report_interval
    if args.workers is not None:
        conf['workers'] = args.workers
    if args.chunks_per_second is not None:
        conf['items_per_second'] = args.chunks_per_second

    success = False
    try:
        blob_rebuilder = BlobRebuilder(
            conf, logger, args.volume, input_file=args.input_file,
            try_chunk_delete=args.delete_faulty_chunks,
            beanstalkd_addr=args.beanstalkd)
        if args.volume:
            success = blob_rebuilder.rebuilder_pass_with_lock()
        else:
            success = blob_rebuilder.rebuilder_pass()
    except KeyboardInterrupt:
        logger.info('Exiting')
    except Exception as exc:
        logger.exception('ERROR in rebuilder: %s', exc)
    if not success:
        sys.exit(1)


if __name__ == '__main__':
    main()
