#!/usr/bin/env python
# Copyright C 2002-2010 by Isilon Systems, Inc. All rights not expressly
# granted herein are reserved.
# Created in the United States of America.
# 
# This digital media is protected by U.S. and international copyright and
# intellectual property laws. Unless otherwise specified, all information
# and screens appearing as part of this digital medium, including
# software, services, documents, text, images, icons, and logos design;
# the selection, assembly, arrangement, and design thereof; and the code
# that enables its presentation, are the sole property of Isilon Systems,
# Inc.
# 
# Except as otherwise required by applicable law, no part of any
# copyrighted material may be reproduced, modified, translated,
# distributed, published, or transmitted by any means, electronic or
# mechanical, for any purpose, without the prior written consent of Isilon
# Systems. 
#  
# Isilon and Isilon Systems are registered trademarks of Isilon Systems,
# Inc.  "Isilon IQ" is a trademark of Isilon Systems, Inc.  All other
# trademarks and registered trademarks are the property of their
# respective holders.

import errno
import getopt
import os
import sys
import syslog
import time

import isi.misc as imisc
import isi.smartpools.diskpools as spdp
import isi.sys.version as sysver
from isi.app.lib import safelockfile

NUM_ATTEMPTS = 10
RETRY_DELAY = 2
DRIVE_PROVISION_LOCK_FILE = "/ifs/.ifsvar/drive_provision.lock"

log = False

def infox(msg):
    print >> sys.stdout, msg
    if log:
        syslog.syslog(syslog.LOG_INFO, msg)
    sys.exit(0)

def errx(msg):
    print >> sys.stderr, msg
    if log:
        syslog.syslog(syslog.LOG_ERR, msg)
    sys.exit(1)

def usagex(msg):
    print >> sys.stdout, msg
    print >> sys.stdout, \
            "usage: isi_evaluate_provision_drive [-s <ident>] <ldnum>"
    sys.exit(2)

def logger(lvl, msg):
    print >> sys.stdout, msg
    if log:
        syslog.syslog(lvl, msg)

def main(argv):
    global log

    try:
        opts, args = getopt.getopt(argv, "s:")
    except getopt.GetoptError, e:
        usagex(str(e))

    log = False
    for o, a in opts:
        if o == "-s":
            log = True
            ident = a
        else:
            assert False, "unhandled option"

    if len(args) != 1:
        usagex("Incorrect number of arguments: %d expected, %d given" %
                (1, len(args)))
    try:
        ldnum = int(args[0])
    except ValueError, e:
        usagex("Invalid ldnum '%s' specified: %s" % (args[0], str(e)))

    if log:
        syslog.openlog(ident, syslog.LOG_PID, syslog.LOG_DAEMON)
        syslog.syslog(syslog.LOG_NOTICE, 
                      "isi_evaluate_provision_drive ldnum %s" % ldnum)

    err_msg = "Error updating node info cache"
    try:
        updated_cache = sysver.update_cache(skip_if_current=True)
    except Exception, e:
        err_msg += " " + str(e)
        updated_cache = False
    if not updated_cache:
        errx(err_msg)

    # There may be other entities writing to the diskpools DB, rendering
    # our in-memory copy stale. Since we're provisioning a specific drive,
    # we'll try up to NUM_ATTEMPTS, delaying RETRY_DELAY in between each
    # attempt.
    attempts = 0
    forced_update = False
    while attempts < NUM_ATTEMPTS:
        try:
            lockfile = safelockfile(DRIVE_PROVISION_LOCK_FILE, "w")
        except IOError as e:
            errx("Error obtaining lock on %s: %s" % 
                    (DRIVE_PROVISION_LOCK_FILE, str(e)))
        try:
            dp_cfg = spdp.open_config()
            if not dp_cfg.fully_upgraded():
                logger(syslog.LOG_NOTICE,
                        "Postponing provisioning of unassigned drives until "
                        "after SmartPools is upgraded")
                return
            if not dp_cfg.purposedb_fully_populated():
                logger(syslog.LOG_NOTICE,
                        "Postponing provisioning of unassigned drives until "
                        "the purpose db is fully populated (should occur after "
                        "all nodes come up")
                return
            try:
                dp_cfg.autoprov_unassigned()
            except spdp.ConfigError:
                if forced_update:
                    raise

                err_msg = "Error updating node info cache"
                try:
                    # once, force an update of node info caches
                    updated_cache = sysver.update_cache(skip_if_current=False)
                except Exception, e:
                    err_msg += " " + str(e)
                    updated_cache = False
                if not updated_cache:
                    errx(err_msg)

                continue

            dp_cfg.save(ignore_underprovisioned=False)
            break
        except OSError, e:
            if e.errno in [errno.ESTALE, errno.EAGAIN, errno.ENOENT]:
                # Sleep for a couple seconds before trying again.
                time.sleep(RETRY_DELAY)
                attempts += 1
                # and enable debugging to /var/log/diskpools.log
                import isi.fs.diskpool as fsdp
                fsdp.set_debug(True)
                continue
            else:
                errx("Error updating diskpool config for ldnum %d: %s" %
                        (ldnum, str(e)))
        except spdp.UnderprovisionedError, e:
            # Provisioning this drive will leave one of more disk pools in
            # the underprovisioned state (most commonly encountered when
            # /ifs is first mounted and the cluster is transitioning from
            # the bootstrap provisioning to user-managed provisioning) so
            # we simply log a message and skip this provisioning action.
            infox("Postponed provisioning of ldnum %d: %s" %
                    (ldnum, str(e)))
            break
        except spdp.ProvisioningError, e:
            # TODO: Generate CELOG alert/event
            errx("Error provisioning drive ldnum %d: %s" %
                    (ldnum, str(e)))
        finally:
            lockfile.close()

    if attempts >= NUM_ATTEMPTS:
        errx("Error provisioning drive ldnum %d: failed %d attempts" %
                (ldnum, attempts))

    return 0

if __name__ == "__main__":
    imisc.setproctitle(os.path.basename(sys.argv[0]))
    sys.exit(main(sys.argv[1:]))

