#!/usr/bin/env python

# Copyright (C) 2011 Red Hat, Inc.
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
#
# Author: Andy Grover <agrover at redhat com>

from lsm.iplugin import IStorageAreaNetwork, INetworkAttachedStorage
from lsm.pluginrunner import PluginRunner
import sys
from lsm.data import (Pool, Volume, System, Capabilities, Initiator,
                      FileSystem, Snapshot)
from lsm.common import (LsmError, ErrorNumber, uri_parse)
import urllib2
import json
import time
import urlparse
import socket
from lsm.version import VERSION

default_user = "admin"
default_port = 18700
path = "/targetrpc"


class TargetdStorage(IStorageAreaNetwork, INetworkAttachedStorage):
    def startup(self, uri, password, timeout, flags=0):
        self.uri = uri_parse(uri)
        self.password = password
        self.tmo = timeout
        self.rpc_id = 1

        user = self.uri.get('username', default_user)
        port = self.uri.get('port', default_port)

        self.host_with_port = "%s:%s" % (self.uri['host'], port)
        if self.uri['scheme'].lower() == 'targetd+ssl':
            self.scheme = 'https'
        else:
            self.scheme = 'http'

        self.url = urlparse.urlunsplit(
            (self.scheme, self.host_with_port, path, None, None))

        auth = ('%s:%s' % (user, self.password)).encode('base64')[:-1]
        self.headers = {'Content-Type': 'application/json',
                        'Authorization': 'Basic %s' % (auth,)}

        self.system = System("targetd", "targetd storage appliance",
                             System.STATUS_UNKNOWN)

    def set_time_out(self, ms, flags=0):
        self.tmo = ms

    def get_time_out(self, flags=0):
        return self.tmo

    def shutdown(self, flags=0):
        pass

    def capabilities(self, system, flags=0):
        cap = Capabilities()
        cap.set(Capabilities.BLOCK_SUPPORT)
        cap.set(Capabilities.FS_SUPPORT)
        cap.set(Capabilities.VOLUMES)
        cap.set(Capabilities.VOLUME_CREATE)
        cap.set(Capabilities.VOLUME_REPLICATE)
        cap.set(Capabilities.VOLUME_REPLICATE_COPY)
        cap.set(Capabilities.VOLUME_DELETE)
        cap.set(Capabilities.VOLUME_OFFLINE)
        cap.set(Capabilities.VOLUME_ONLINE)
        cap.set(Capabilities.INITIATORS)
        cap.set(Capabilities.VOLUME_INITIATOR_GRANT)
        cap.set(Capabilities.VOLUME_INITIATOR_REVOKE)
        cap.set(Capabilities.VOLUME_ACCESSIBLE_BY_INITIATOR)
        cap.set(Capabilities.INITIATORS_GRANTED_TO_VOLUME)
        cap.set(Capabilities.FS)
        cap.set(Capabilities.FS_CREATE)
        cap.set(Capabilities.FS_DELETE)
        cap.set(Capabilities.FS_CLONE)
        cap.set(Capabilities.FS_SNAPSHOT_CREATE)
        cap.set(Capabilities.FS_SNAPSHOT_DELETE)
        cap.set(Capabilities.FS_SNAPSHOTS)
        return cap

    def plugin_info(self, flags=0):
        return "Linux LIO target support", VERSION

    def systems(self, flags=0):
        # verify we're online
        self._jsonrequest("pool_list")

        return [self.system]

    def job_status(self, job_id, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def job_free(self, job_id, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def volumes(self, flags=0):
        volumes = []
        for p_name in (p['name'] for p in self._jsonrequest("pool_list") if
                       p['type'] == 'block'):
            for vol in self._jsonrequest("vol_list", dict(pool=p_name)):
                volumes.append(Volume(vol['uuid'], vol['name'], vol['uuid'],
                                      512, vol['size'] / 512, Volume.STATUS_OK,
                                      self.system.id, p_name))
        return volumes

    def pools(self, flags=0):
        pools = []
        for pool in self._jsonrequest("pool_list"):
            pools.append(Pool(pool['name'], pool['name'], pool['size'],
                              pool['free_size'], 'targetd'))
        return pools

    def initiators(self, flags=0):
        inits = []
        for init in set(
                i['initiator_wwn'] for i in self._jsonrequest("export_list")):
            inits.append(Initiator(init, Initiator.TYPE_ISCSI, init))

        return inits

    def _get_volume(self, pool_id, volume_name):
        vol = [v for v in self._jsonrequest("vol_list", dict(pool=pool_id))
               if v['name'] == volume_name][0]

        return Volume(vol['uuid'], vol['name'], vol['uuid'],
                      512, vol['size'] / 512, Volume.STATUS_OK, self.system.id,
                      pool_id)

    def _get_fs(self, pool_id, fs_name):
        fs = self.fs()
        for f in fs:
            if f.name == fs_name and f.pool_id == pool_id:
                return f
        return None

    def _get_ss(self, fs, ss_name):
        ss = self.fs_snapshots(fs)
        for s in ss:
            if s.name == ss_name:
                return s
        return None

    def volume_create(self, pool, volume_name, size_bytes, provisioning,
                      flags=0):
        self._jsonrequest("vol_create", dict(pool=pool.id,
                                             name=volume_name, size=size_bytes))

        return None, self._get_volume(pool.id, volume_name)

    def volume_delete(self, volume, flags=0):
        self._jsonrequest("vol_destroy",
                          dict(pool=volume.pool_id, name=volume.name))

    def volume_replicate(self, pool, rep_type, volume_src, name, flags=0):
        if rep_type != Volume.REPLICATE_COPY:
            raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

        #pool id is optional, use volume src as default
        pool_id = volume_src.pool_id
        if pool:
            pool_id = pool.id

        self._jsonrequest("vol_copy",
                          dict(pool=pool_id, vol_orig=volume_src.name,
                               vol_new=name))

        return None, self._get_volume(pool_id, name)

    def volume_replicate_range_block_size(self, system, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def volume_replicate_range(self, rep_type, volume_src, volume_dest,
                               ranges, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def volume_online(self, volume, flags=0):
        vol_list = self._jsonrequest("vol_list", dict(pool=volume.pool_id))

        return volume.name in [vol['name'] for vol in vol_list]

    def volume_offline(self, volume, flags=0):
        return not self.volume_online(volume)

    def volume_resize(self, volume, new_size_bytes, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def access_group_grant(self, group, volume, access, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def access_group_revoke(self, group, volume, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def access_group_list(self, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def access_group_create(self, name, initiator_id, id_type, system_id,
                            flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def access_group_del(self, group, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def access_group_add_initiator(self, group, initiator_id, id_type, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def access_group_del_initiator(self, group, initiator, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def volumes_accessible_by_access_group(self, group, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def access_groups_granted_to_volume(self, volume, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def iscsi_chap_auth_inbound(self, initiator, user, password, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def initiator_grant(self, initiator_id, initiator_type, volume, access,
                        flags=0):
        if initiator_type != Initiator.TYPE_ISCSI:
            raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

        # find lowest unused lun and use that
        used_luns = [x['lun'] for x in self._jsonrequest("export_list")]
        lun = 0
        while True:
            if lun in used_luns:
                lun += 1
            else:
                break

        self._jsonrequest("export_create",
                          dict(pool=volume.pool_id,
                               vol=volume.name,
                               initiator_wwn=initiator_id, lun=lun))

    def initiator_revoke(self, initiator, volume, flags=0):
        self._jsonrequest("export_destroy",
                          dict(pool=volume.pool_id,
                               vol=volume.name,
                               initiator_wwn=initiator.id))

    def volumes_accessible_by_initiator(self, initiator, flags=0):
        exports = [x for x in self._jsonrequest("export_list")
                   if initiator.id == x['initiator_wwn']]

        vols = []
        for export in exports:
            vols.append(Volume(export['vol_uuid'], export['vol_name'],
                               export['vol_uuid'], 512,
                               export['vol_size'] / 512,
                               Volume.STATUS_OK, self.system.id,
                               export['pool']))

        return vols

    def initiators_granted_to_volume(self, volume, flags=0):
        exports = [x for x in self._jsonrequest("export_list")
                   if volume.id == x['vol_uuid']]

        inits = []
        for export in exports:
            name = export['initiator_wwn']
            inits.append(Initiator(name, Initiator.TYPE_ISCSI, name))

        return inits

    def volume_child_dependency(self, volume, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def volume_child_dependency_rm(self, volume, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def fs(self, flags=0):
        rc = []
        for fs in self._jsonrequest("fs_list"):
            #self, id, name, total_space, free_space, pool_id, system_id
            rc.append(FileSystem(fs['uuid'], fs['name'], fs['total_space'],
                                 fs['free_space'], fs['pool'], self.system.id))
        return rc

    def fs_delete(self, fs, flags=0):
        self._jsonrequest("fs_destroy", dict(uuid=fs.id))

    def fs_resize(self, fs, new_size_bytes, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def fs_create(self, pool, name, size_bytes, flags=0):
        self._jsonrequest("fs_create", dict(pool_name=pool.id, name=name,
                                            size_bytes=size_bytes))

        return None, self._get_fs(pool.name, name)

    def fs_clone(self, src_fs, dest_fs_name, snapshot=None, flags=0):

        ss_id = None
        if snapshot:
            ss_id = snapshot.id

        self._jsonrequest("fs_clone", dict(fs_uuid=src_fs.id,
                                           dest_fs_name=dest_fs_name,
                                           snapshot_id=ss_id))

        return None, self._get_fs(src_fs.pool_id, dest_fs_name)

    def file_clone(self, fs, src_file_name, dest_file_name, snapshot=None,
                   flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def fs_snapshots(self, fs, flags=0):
        rc = []
        for ss in self._jsonrequest("ss_list", dict(fs_uuid=fs.id)):
            #id, name, timestamp
            rc.append(Snapshot(ss['uuid'], ss['name'], ss['timestamp']))
        return rc

    def fs_snapshot_create(self, fs, snapshot_name, files, flags=0):

        self._jsonrequest("fs_snapshot", dict(fs_uuid=fs.id,
                                              dest_ss_name=snapshot_name))

        return None, self._get_ss(fs, snapshot_name)

    def fs_snapshot_delete(self, fs, snapshot, flags=0):
        self._jsonrequest("fs_snapshot_delete", dict(fs_uuid=fs.id,
                                                     ss_uuid=snapshot.id))

    def fs_snapshot_revert(self, fs, snapshot, files, restore_files,
                           all_files=False, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def fs_child_dependency(self, fs, files, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def fs_child_dependency_rm(self, fs, files, flags=0):
        raise LsmError(ErrorNumber.NO_SUPPORT, "Not supported")

    def _jsonrequest(self, method, params=None):
        data = json.dumps(dict(id=self.rpc_id, method=method,
                               params=params, jsonrpc="2.0"))
        self.rpc_id += 1

        try:
            request = urllib2.Request(self.url, data, self.headers)
            response_obj = urllib2.urlopen(request)
        except socket.error:
            if self.scheme == 'https':
                raise
            print "socket error, retrying with SSL"
            url = urlparse.urlunsplit(
                ("https", self.host_with_port, path, None, None))
            request = urllib2.Request(url, data, self.headers)
            response_obj = urllib2.urlopen(request)

        response_data = response_obj.read()
        response = json.loads(response_data)
        if response.get('error', None) is None:
            return response.get('result')
        else:
            if response['error']['code'] <= 0:
                #error_text = "%s:%s" % (str(response['error']['code']),
                #                        response['error'].get('message', ''))

                raise LsmError(abs(int(response['error']['code'])),
                               response['error'].get('message', ''))
            else:  # +code is async execution id
                print "Async completion, polling for results"
                async_code = response['error']['code']
                while True:
                    time.sleep(1)
                    results = self._jsonrequest('async_list')
                    status = results.get(str(async_code), None)
                    if status:
                        if status[0]:
                            print "%d has error %d" % (async_code, status[0])
                            break
                        else:
                            print "%d still going, %d%% complete" % \
                                  (async_code, status[1])
                    else:
                        print "%s done" % async_code
                        break


if __name__ == '__main__':
    PluginRunner(TargetdStorage, sys.argv).run()
