#!/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
from lsm.pluginrunner import PluginRunner
import sys
from lsm.data import (Pool, Volume, System, Capabilities, Initiator)
from lsm.common import (LsmError, ErrorNumber, uri_parse)
import urllib2
import json
import time
import urlparse
import socket

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

class TargetdStorage(IStorageAreaNetwork):

    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.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)
        return cap

    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 pool in (pool['name'] for pool in self._jsonrequest("pool_list")):
            for vol in self._jsonrequest("vol_list", dict(pool=pool)):
                volumes.append(Volume(vol['uuid'], vol['name'], vol['uuid'],
                                      512, vol['size']/512, Volume.STATUS_OK,
                                      self.system.id, pool))
        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 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 _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:
                raise Exception(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()
