# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2014 IBM Corp.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import xcatutils

from oslo.config import cfg
from neutron.openstack.common import log as logging
from neutron.openstack.common.gettextutils import _
from neutron.plugins.zvm.common import exception

CONF = cfg.CONF
LOG = logging.getLogger(__name__)


class zvmUtils(object):
    _MAX_REGRANT_USER_NUMBER = 1000

    def __init__(self):
        self._xcat_url = xcatutils.xCatURL()
        self._zhcp_userid = None
        self._userid_map = {}
        self._xcat_node_name = self._get_xcat_node_name()

    def get_node_from_port(self, port_id):
        return self._get_nic_settings(port_id, get_node=True)

    def get_nic_ids(self):
        addp = ''
        url = self._xcat_url.tabdump("/switch", addp)
        nic_settings = xcatutils.xcat_request("GET", url)
        # remove table header
        nic_settings['data'][0].pop(0)
        # it's possible to return empty array
        return nic_settings['data'][0]

    def _get_nic_settings(self, port_id, field=None, get_node=False):
        """Get NIC information from xCat switch table."""
        LOG.debug(_("Get nic information for port: %s"), port_id)
        addp = '&col=port&value=%s' % port_id + '&attribute=%s' % (
                                                field and field or 'node')
        url = self._xcat_url.gettab("/switch", addp)
        nic_settings = xcatutils.xcat_request("GET", url)
        ret_value = nic_settings['data'][0][0]
        if field is None and not get_node:
            ret_value = self.get_userid_from_node(ret_value)
        return ret_value

    def get_userid_from_node(self, node):
        addp = '&col=node&value=%s&attribute=userid' % node
        url = self._xcat_url.gettab("/zvm", addp)
        user_info = xcatutils.xcat_request("GET", url)
        return user_info['data'][0][0]

    def couple_nic_to_vswitch(self, vswitch_name, switch_port_name,
                                    zhcp, userid, dm=True, immdt=True):
        """Couple nic to vswitch."""
        LOG.debug(_("Connect nic to switch: %s"), vswitch_name)
        vdev = self._get_nic_settings(switch_port_name, "interface")
        if vdev:
            self._couple_nic(zhcp, vswitch_name, userid, vdev, dm, immdt)
        else:
            raise exception.zVMInvalidDataError(msg=('Cannot get vdev for '
                            'user %s, couple to port %s') %
                            (userid, switch_port_name))
        return vdev

    def uncouple_nic_from_vswitch(self, vswitch_name, switch_port_name,
                                    zhcp, userid, dm=True, immdt=True):
        """Uncouple nic from vswitch."""
        LOG.debug(_("Disconnect nic from switch: %s"), vswitch_name)
        vdev = self._get_nic_settings(switch_port_name, "interface")
        self._uncouple_nic(zhcp, userid, vdev, dm, immdt)

    def set_vswitch_port_vlan_id(self, vlan_id, switch_port_name, vdev, zhcp,
                                 vswitch_name):
        userid = self._get_nic_settings(switch_port_name)
        if not userid:
            raise exception.zVMInvalidDataError(msg=('Cannot get userid by '
                            'port %s') % (switch_port_name))
        url = self._xcat_url.xdsh("/%s" % zhcp)
        commands = '/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Set_Extended'
        commands += " -T %s" % userid
        commands += ' -k grant_userid=%s' % userid
        commands += " -k switch_name=%s" % vswitch_name
        commands += " -k user_vlan_id=%s" % vlan_id
        xdsh_commands = 'command=%s' % commands
        body = [xdsh_commands]
        xcatutils.xcat_request("PUT", url, body)

    def grant_user(self, zhcp, vswitch_name, userid):
        """Set vswitch to grant user."""
        url = self._xcat_url.xdsh("/%s" % zhcp)
        commands = '/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Set_Extended'
        commands += " -T %s" % userid
        commands += " -k switch_name=%s" % vswitch_name
        commands += " -k grant_userid=%s" % userid
        xdsh_commands = 'command=%s' % commands
        body = [xdsh_commands]
        xcatutils.xcat_request("PUT", url, body)

    def revoke_user(self, zhcp, vswitch_name, userid):
        """Set vswitch to grant user."""
        url = self._xcat_url.xdsh("/%s" % zhcp)
        commands = '/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Set_Extended'
        commands += " -T %s" % userid
        commands += " -k switch_name=%s" % vswitch_name
        commands += " -k revoke_userid=%s" % userid
        xdsh_commands = 'command=%s' % commands
        body = [xdsh_commands]
        xcatutils.xcat_request("PUT", url, body)

    def _couple_nic(self, zhcp, vswitch_name, userid, vdev, dm, immdt):
        """Couple NIC to vswitch by adding vswitch into user direct."""
        url = self._xcat_url.xdsh("/%s" % zhcp)
        if dm:
            commands = '/opt/zhcp/bin/smcli'
            commands += ' Virtual_Network_Adapter_Connect_Vswitch_DM'
            commands += " -T %s " % userid + "-v %s" % vdev
            commands += " -n %s" % vswitch_name
            xdsh_commands = 'command=%s' % commands
            body = [xdsh_commands]
            xcatutils.xcat_request("PUT", url, body)
        if immdt:
            # the inst must be active, or this call will failed
            commands = '/opt/zhcp/bin/smcli'
            commands += ' Virtual_Network_Adapter_Connect_Vswitch'
            commands += " -T %s " % userid + "-v %s" % vdev
            commands += " -n %s" % vswitch_name
            xdsh_commands = 'command=%s' % commands
            body = [xdsh_commands]
            xcatutils.xcat_request("PUT", url, body)

    def _uncouple_nic(self, zhcp, userid, vdev, dm, immdt):
        """Couple NIC to vswitch by adding vswitch into user direct."""
        url = self._xcat_url.xdsh("/%s" % zhcp)
        if dm:
            commands = '/opt/zhcp/bin/smcli'
            commands += ' Virtual_Network_Adapter_Disconnect_DM'
            commands += " -T %s " % userid + "-v %s" % vdev
            xdsh_commands = 'command=%s' % commands
            body = [xdsh_commands]
            xcatutils.xcat_request("PUT", url, body)
        if immdt:
            # the inst must be active, or this call will failed
            commands = '/opt/zhcp/bin/smcli'
            commands += ' Virtual_Network_Adapter_Disconnect'
            commands += " -T %s " % userid + "-v %s" % vdev
            xdsh_commands = 'command=%s' % commands
            body = [xdsh_commands]
            xcatutils.xcat_request("PUT", url, body)

    def put_user_direct_online(self, zhcp, userid):
        url = self._xcat_url.xdsh("/%s" % zhcp)
        commands = '/opt/zhcp/bin/smcli Static_Image_Changes_Immediate_DM'
        commands += " -T %s" % userid
        xdsh_commands = 'command=%s' % commands
        body = [xdsh_commands]
        xcatutils.xcat_request("PUT", url, body)

    def get_zhcp_userid(self, zhcp):
        if not self._zhcp_userid:
            self._zhcp_userid = self.get_userid_from_node(zhcp)
        return self._zhcp_userid

    def add_vswitch(self, zhcp, name, rdev,
                    controller='*',
                    connection=1, queue_mem=8, router=0, network_type=2, vid=0,
                    port_type=1, update=1, gvrp=2, native_vid=1):
        '''
           connection:0-unspecified 1-Actice 2-non-Active
           router:0-unspecified 1-nonrouter 2-prirouter
           type:0-unspecified 1-IP 2-ethernet
           vid:1-4094 for access port defaut vlan
           port_type:0-unspecified 1-access 2-trunk
           update:0-unspecified 1-create 2-create and add to system
                  configuration file
           gvrp:0-unspecified 1-gvrp 2-nogvrp
        '''
        if (self._does_vswitch_exist(zhcp, name)):
            LOG.info(_('Vswitch %s already exists.'), name)
            return

        # if vid = 0, port_type, gvrp and native_vlanid are not
        # allowed to specified
        if not len(vid):
            vid = 0
            port_type = 0
            gvrp = 0
            native_vid = -1
        else:
            vid = str(vid[0][0]) + '-' + str(vid[0][1])

        userid = self.get_zhcp_userid(zhcp)
        url = self._xcat_url.xdsh("/%s" % zhcp)
        commands = '/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Create'
        commands += " -T %s" % userid
        commands += ' -n %s' % name
        if rdev:
            commands += " -r %s" % rdev.replace(',', ' ')
        #commands += " -a %s" % osa_name
        if controller != '*':
            commands += " -i %s" % controller
        commands += " -c %s" % connection
        commands += " -q %s" % queue_mem
        commands += " -e %s" % router
        commands += " -t %s" % network_type
        commands += " -v %s" % vid
        commands += " -p %s" % port_type
        commands += " -u %s" % update
        commands += " -G %s" % gvrp
        commands += " -V %s" % native_vid
        xdsh_commands = 'command=%s' % commands
        body = [xdsh_commands]

        result = xcatutils.xcat_request("PUT", url, body)
        if (result['errorcode'][0][0] != '0') or \
            (not self._does_vswitch_exist(zhcp, name)):
            raise exception.zvmException(
                msg=("switch: %s add failed, %s") %
                        (name, result['data'][0][0]))
        LOG.info(_('Created vswitch %s done.'), name)

    def _does_vswitch_exist(self, zhcp, vsw):
        userid = self.get_zhcp_userid(zhcp)
        url = self._xcat_url.xdsh("/%s" % zhcp)
        commands = '/opt/zhcp/bin/smcli Virtual_Network_Vswitch_Query'
        commands += " -T %s" % userid
        commands += " -s %s" % vsw
        xdsh_commands = 'command=%s' % commands
        body = [xdsh_commands]
        result = xcatutils.xcat_request("PUT", url, body)

        return (result['errorcode'][0][0] == '0')

    def re_grant_user(self, zhcp):
        """Grant user again after z/VM is re-IPLed"""
        ports_info = self._get_userid_vswitch_vlan_id_mapping(zhcp)
        records_num = 0
        cmd = ''

        def run_command(command):
            xdsh_commands = 'command=%s' % command
            body = [xdsh_commands]
            url = self._xcat_url.xdsh("/%s" % zhcp)
            xcatutils.xcat_request("PUT", url, body)

        for (port_id, port) in ports_info.items():
            if port['userid'] is None or port['vswitch'] is None:
                continue
            if len(port['userid']) == 0 or len(port['vswitch']) == 0:
                continue

            cmd += '/opt/zhcp/bin/smcli '
            cmd += 'Virtual_Network_Vswitch_Set_Extended '
            cmd += '-T %s ' % port['userid']
            cmd += '-k switch_name=%s ' % port['vswitch']
            cmd += '-k grant_userid=%s' % port['userid']
            try:
                if int(port['vlan_id']) in range(1, 4094):
                    cmd += ' -k user_vlan_id=%s\n' % port['vlan_id']
                else:
                    cmd += '\n'
            except ValueError:
                # just in case there are bad records of vlan info which
                # could be a string
                LOG.warn(_("Unknown vlan '%(vlan)s' for user %(user)s."),
                            {'vlan': port['vlan_id'], 'user': port['userid']})
                cmd += '\n'
                continue
            records_num += 1
            if records_num >= self._MAX_REGRANT_USER_NUMBER:
                try:
                    commands = 'echo -e "#!/bin/sh\n%s" > grant.sh' % cmd[:-1]
                    run_command(commands)
                    commands = 'sh grant.sh;rm -f grant.sh'
                    run_command(commands)
                    records_num = 0
                    cmd = ''
                except Exception:
                    LOG.warn(_("Grant user failed"))

        if len(cmd) > 0:
            commands = 'echo -e "#!/bin/sh\n%s" > grant.sh' % cmd[:-1]
            run_command(commands)
            commands = 'sh grant.sh;rm -f grant.sh'
            run_command(commands)
        return ports_info

    def _get_userid_vswitch_vlan_id_mapping(self, zhcp):
        ports_info = self.get_nic_ids()
        ports = {}
        for p in ports_info:
            port_info = p.split(',')
            target_host = port_info[5].strip('"')
            port_vid = port_info[3].strip('"')
            port_id = port_info[2].strip('"')
            vswitch = port_info[1].strip('"')
            nodename = port_info[0].strip('"')
            if target_host == zhcp:
                ports[port_id] = {'nodename': nodename,
                                  'vswitch': vswitch,
                                  'userid': None,
                                  'vlan_id': port_vid}

        def get_all_userid():
            users = {}
            addp = ''
            url = self._xcat_url.tabdump("/zvm", addp)
            all_userids = xcatutils.xcat_request("GET", url)
            header = '#node,hcp,userid,nodetype,parent,comments,disable'
            all_userids['data'][0].remove(header)
            if len(all_userids) > 0:
                for u in all_userids['data'][0]:
                    user_info = u.split(',')
                    userid = user_info[2].strip('"')
                    nodename = user_info[0].strip('"')
                    users[nodename] = {'userid': userid}

            return users

        users = get_all_userid()

        for (port_id, port) in ports.items():
            try:
                ports[port_id]['userid'] = users[port['nodename']]['userid']
            except Exception:
                LOG.info(_("Garbage port found. port id: %s") % port_id)

        return ports

    def update_xcat_switch(self, port, vswitch, vlan):
        """Update information in xCAT switch table."""
        commands = "port=%s" % port
        commands += " switch.switch=%s" % vswitch
        commands += " switch.vlan=%s" % (vlan and vlan or -1)
        url = self._xcat_url.tabch("/switch")
        body = [commands]
        xcatutils.xcat_request("PUT", url, body)

    def create_xcat_mgt_network(self, zhcp, mgt_ip, mgt_mask, mgt_vswitch):
        url = self._xcat_url.xdsh("/%s" % zhcp)
        xdsh_commands = ('command=smcli Virtual_Network_Adapter_Query'
                  ' -T %s -v 0800') % self._xcat_node_name
        body = [xdsh_commands]
        result = xcatutils.xcat_request("PUT", url, body)['data'][0][0]
        code = result.split("\n")
        # return code 212: Adapter does not exist
        new_nic = ''
        if len(code) == 4 and code[1].split(': ')[2] == '212':
            new_nic = ('vmcp define nic 0800 type qdio\n' +
                    'vmcp couple 0800 system %s\n' % (mgt_vswitch))
        elif len(code) == 7:
            status = code[4].split(': ')[2]
            if status == 'Coupled and active':
                # we just assign the IP/mask,
                # no matter if it is assigned or not
                LOG.info(_("Assign IP for NIC 800."))
            else:
                LOG.error(_("NIC 800 staus is unknown."))
                return
        else:
            raise exception.zvmException(
                    msg="Unknown information from SMAPI")

        url = self._xcat_url.xdsh("/%s") % self._xcat_node_name
        cmd = new_nic + ('/usr/bin/perl /usr/sbin/sspqeth2.pl ' +
              '-a %s -d 0800 0801 0802 -e eth2 -m %s -g %s'
              % (mgt_ip, mgt_mask, mgt_ip))
        xdsh_commands = 'command=%s' % cmd
        body = [xdsh_commands]
        xcatutils.xcat_request("PUT", url, body)

    def _get_xcat_node_ip(self):
        addp = '&col=key&value=master&attribute=value'
        url = self._xcat_url.gettab("/site", addp)
        return xcatutils.xcat_request("GET", url)['data'][0][0]

    def _get_xcat_node_name(self):
        xcat_ip = self._get_xcat_node_ip()
        addp = '&col=ip&value=%s&attribute=node' % (xcat_ip)
        url = self._xcat_url.gettab("/hosts", addp)
        return (xcatutils.xcat_request("GET", url)['data'][0][0])

    def query_xcat_uptime(self, zhcp):
        url = self._xcat_url.xdsh("/%s" % zhcp)
        cmd = '/opt/zhcp/bin/smcli Image_Query_Activate_Time'
        cmd += " -T %s" % self.get_userid_from_node(
                                self._xcat_node_name)
        # format 4: yyyy-mm-dd
        cmd += " -f %s" % "4"
        xdsh_commands = 'command=%s' % cmd
        body = [xdsh_commands]
        ret_str = xcatutils.xcat_request("PUT", url, body)['data'][0][0]
        return ret_str.split('on ')[1]

    def query_zvm_uptime(self, zhcp):
        url = self._xcat_url.xdsh("/%s" % zhcp)
        cmd = '/opt/zhcp/bin/smcli System_Info_Query'
        xdsh_commands = 'command=%s' % cmd
        body = [xdsh_commands]
        ret_str = xcatutils.xcat_request("PUT", url, body)['data'][0][0]
        return ret_str.split('\n')[4].split(': ', 3)[2]