#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# An examplary script to operate with Zadara VPSAs
#

import socket
import sys
import urllib2
from lxml import etree
from optparse import OptionParser

class ZadaraVPSAConnection():
 
    def __init__ (self, ip, key, server=None):
        self.ip = ip
        self.key = key
        if server:
            self.hostname = server
        else:
            self.hostname = socket.gethostname()
        
    def _xml_parse_helper(self, data, first_level, search_tuple,
                      first=True):
        """
        Helper for parsing VPSA's XML output.
        
        Returns single item if first==True or list for multiple selection.
        If second argument in search_tuple is None - returns all items with
        appropriate key.
        """
        xml_tree = etree.fromstring(data) 
        objects = xml_tree.find(first_level)
        if objects is None:
            return None
        
        result_list = []
        (key, value) = search_tuple
        for object in objects.getchildren():
            found_value = object.findtext(key)
            if found_value and (found_value == value or value is None):
                if first:
                    return object
                else:
                    result_list.append(object)

        return result_list if result_list else None

    def _find_volume(self, volume_name=None):
        data = urllib2.urlopen('http://' + self.ip + '/api/volumes.xml?access_key=' + self.key + '&page=1&start=0&limit=0').read()
        if volume_name:
            obj = self._xml_parse_helper(data, 'volumes', ('display-name', volume_name))
            if obj is not None:
                return obj.findtext('name')
            else:
                return None
        else:
            objs = self._xml_parse_helper(data, 'volumes', ('display-name', volume_name), first=False)
            lst = []
            for object in objs:
                lst.append((object.findtext('name'), object.findtext('display-name')))
            return lst


    def _find_server(self, server_name):
        data = urllib2.urlopen('http://' + self.ip + '/api/servers.xml?access_key=' + self.key + '&page=1&start=0&limit=0').read()
        obj = self._xml_parse_helper(data, 'servers', ('display-name', server_name))
        if obj is not None:
            return obj.findtext('name')
        else:
            return None

    def _find_raid_group(self, min_size, raid_name):
        data = urllib2.urlopen('http://' + self.ip + '/api/raid_groups.xml?access_key=' + self.key + '&page=1&start=0&limit=0').read()
        objs = self._xml_parse_helper(data, 'raid-groups', ('display-name', raid_name), first=False)
        for object in objs:
            found_value = object.findtext('available')
            if found_value:
                capacity = int(found_value)
                if capacity >= int(min_size):
                    return object.findtext('name')
        return None

    def _parse_return_status(self, resp):
        err_code = resp.getcode()
        if err_code != 200:
            print "Operation failed with error code %s" % err_code
            return err_code

        ret_data = self.resp.read()
        xml_tree = etree.fromstring(ret_data)
        status = xml_tree.findtext('status')
        if status != '0':
            print "Operation failed on VPSA"
            print ret_data
            return -1

        return 0

    def create_vol(self, opts):

        raid_group = self._find_raid_group(int(opts.size), opts.raid)
        if raid_group is None:
            print "ERROR: No free capacity in any RAID Group"
            return -1

        if opts.vol_type is None or opts.vol_type == 'nfs':
            data = ('access_key=' + self.key + \
                    '&display_name=' + opts.volume + \
                    '&virtual_capacity=' + opts.size + \
                    '&raid_group_name[]=' + raid_group + \
                    '&crypt=NO&mode=simple&stripesize=64&force=NO&block=NO' + \
                    '&export_name=' + opts.volume + \
                    '&mount_sync=YES&atime_update=NO')
        elif opts.vol_type == 'block':
            data = ('access_key=' + self.key + \
                    '&display_name=' + opts.volume + \
                    '&virtual_capacity=' + opts.size + \
                    '&raid_group_name[]=' + raid_group + \
                    '&cache=no&crypt=NO&mode=simple&stripesize=64&force=NO&block=YES')
        else:
            print "ERROR: Incorrect volume type %s" % opts.vol_type

        self.req = urllib2.Request('http://' + self.ip + '/api/volumes.xml', data)
        self.resp = urllib2.urlopen(self.req)
        return self._parse_return_status(self.resp)


    def attach_vol(self, opts):

        volume = self._find_volume(opts.volume)
        if volume is None:
            print "ERROR: Volume with name %s not found" % opts.volume
            return -1

        server = self._find_server(self.hostname)
        if server is None:
            print "ERROR: Server with name %s not found" % self.hostname
            return -1

        if opts.vol_type is None or opts.vol_type == 'nfs':
            data = ('access_key=' + self.key + \
                    '&volume_name[]=' + volume + \
                    '&access_type=NFS&read_only=NO&force=NO')
        elif opts.vol_type == 'block':
            data = ('access_key=' + self.key + \
                    '&volume_name[]=' + volume + \
                    '&force=NO')
        else:
            print "ERROR: Incorrect volume type %s" % opts.vol_type

        self.req = urllib2.Request('http://' + self.ip + '/api/servers/' + server + '/volumes.xml', data)
        self.resp = urllib2.urlopen(self.req)
        return self._parse_return_status(self.resp)

    def detach_vol(self, opts):
        volume = self._find_volume(opts.volume)
        if volume is None:
            print "ERROR: Volume with name %s not found" % opts.volume
            return -1

        server = self._find_server(self.hostname)
        if server is None:
            print "ERROR: Server with name %s not found" % self.hostname
            return -1

        data = ('access_key=' + self.key + \
                '&server_name[]=' + server + \
                '&force=NO')

        self.req = urllib2.Request('http://' + self.ip + '/api/volumes/' + volume + '/detach.xml', data)
        self.resp = urllib2.urlopen(self.req)
        return self._parse_return_status(self.resp)
   
    def list_vol(self, opts):
        volumes = self._find_volume()
        for (vol_id, vol_name) in volumes:
            print vol_id, vol_name

def main():
    """Parse options and call the appropriate class/method."""

    usage = "Usage: %prog -i vpsa_ip -k key -o operation [options]"
    parser = OptionParser(usage=usage)
    parser.add_option("-i", "--ip", action="store", type="str", dest="ip", help="VPSA IP")
    parser.add_option("-k", "--key", action="store", type="str", dest="key", help="Access key for the VPSA")
    parser.add_option("-o", "--operation", action="store", type="str", dest="op",
			help="Operations: [create_volume|attach|detach|list_volumes]")
    parser.add_option("-v", "--volume", action="store", type="str", dest="volume", help="Volume name")
    parser.add_option("-r", "--raidgroup", action="store", type="str", dest="raid", help="RAID Group name (optional)")
    parser.add_option("-z", "--size", action="store", type="str", dest="size", help="Volume size, GB")
    parser.add_option("-t", "--type", action="store", type="str", dest="vol_type", help="Volume type: [nfs|block] (default: nfs)")
    parser.add_option("-s", "--server", action="store", type="str", dest="server", help="Server name (optional)")
    (opts, args) = parser.parse_args()

    if opts.ip is None or \
       opts.key is None or \
       opts.op is None:
        print "One of mandatory options (t|i|o) was not specified"
        parser.print_help();
        exit (1)

    server = opts.server if opts.server else None
    z = ZadaraVPSAConnection(opts.ip, opts.key, server)
    operations = dict(create_volume=z.create_vol,
                      attach=z.attach_vol,
                      detach=z.detach_vol,
                      list_volumes=z.list_vol)
    func = operations.get(opts.op, None)
    if func is None:
        print "Unknown operation %s" % opts.op
        exit (1)

    # Run the command
    err_code = func(opts)
    exit(err_code)
        
if __name__ == '__main__':
    main()

