#!/usr/bin/python2
#
# Copyright 2014-2017 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Refer to the README and COPYING files for full details of the license
#

"""
Usage: fc-scan [-v|-h]

Perform SCSI scan on Fibre Channel scsi_hosts and devices, adding new LUNs and
updating sizes of existing devices. This procedure will not remove existing
LUNs. Must run as root.

Options:
  -v        enable verbose logging
  -h        display this help and exit

Exit codes:
  0         scanned fc_hosts and devices successfully
  1         scanning some hosts or devices failed
"""

import glob
import logging
import os
import sys

from vdsm import utils
from vdsm.common import concurrent

log = logging.getLogger("fc-scan")


class Scan(object):

    def __init__(self, host):
        self.host = host
        self._errors = 0
        self.thread = None

    def start(self):
        self.thread = concurrent.thread(self.run, name=self.host, log=log)
        self.thread.start()

    def wait(self):
        self.thread.join()

    @property
    def succeeded(self):
        return self._errors == 0

    def run(self):
        self._rescan_host()
        self._rescan_devices()

    def _rescan_host(self):
        """
        Scans the host in order to detect new devices
        """
        try:
            path = "/sys/class/scsi_host/%s/scan" % self.host
            log.debug("Scanning %s", path)
            with utils.stopwatch("Scanned %s" % path, log=log):
                fd = os.open(path, os.O_WRONLY)
                try:
                    os.write(fd, "- - -")
                finally:
                    os.close(fd)
        except OSError as e:
            self._errors += 1
            log.error("Scanning %s failed: %s", path, e)
        except Exception:
            self._errors += 1
            log.exception("Scanning %s failed", path)

    def _rescan_devices(self):
        """
        Scans host's devices in order to detect changes in devices size
        """
        for device_path in self._find_host_devices():
            rescan_path = os.path.join(device_path, "../../rescan")
            path = os.path.normpath(rescan_path)
            try:
                log.debug("Rescanning device %s", path)
                with utils.stopwatch("Rescanned device %s" % path, log=log):
                    fd = os.open(path, os.O_WRONLY)
                    try:
                        os.write(fd, "1")
                    finally:
                        os.close(fd)
            except OSError as e:
                self._errors += 1
                log.error("Rescanning %s failed: %s", path, e)
            except Exception:
                self._errors += 1
                log.exception("Rescanning %s failed", path)

    def _find_host_devices(self):
        host_path = os.path.realpath("/sys/class/fc_host/%s/../.." % self.host)
        for device_path in glob.iglob("/sys/class/scsi_device/*"):
            real_device_path = os.path.realpath(device_path)
            if real_device_path.startswith(host_path):
                yield real_device_path


def main(args):
    if '-h' in args:
        print __doc__
        return 0

    logging.basicConfig(
        level=logging.DEBUG if '-v' in args else logging.INFO,
        format="%(name)s: %(message)s")

    hosts = [os.path.basename(path)
             for path in glob.glob("/sys/class/fc_host/host*")]

    if not hosts:
        log.debug("No fc_host found")
        return 0

    scans = []

    for host in hosts:
        s = Scan(host)
        s.start()
        scans.append(s)

    for s in scans:
        s.wait()

    if not all(s.succeeded for s in scans):
        return 1


if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
