# Copyright 2015 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.


from ceilometer.compute.virt import inspector as virt_inspector
from ceilometer.i18n import _
from oslo_config import cfg
from oslo_log import log
from oslo_utils import timeutils
from oslo_utils import units

from ceilometer_zvm.compute.virt.zvm import utils as zvmutils


zvm_ops = [
    cfg.StrOpt('zvm_xcat_server',
               default=None,
               help='Host name or IP address of xCAT management_node'),
    cfg.StrOpt('zvm_xcat_username',
               default=None,
               help='xCAT username'),
    cfg.StrOpt('zvm_xcat_password',
               default=None,
               secret=True,
               help='Password of the xCAT user'),
    cfg.IntOpt('zvm_xcat_connection_timeout',
               default=600,
               help="The number of seconds wait for xCAT MN response"),
    cfg.StrOpt('xcat_zhcp_nodename',
               default='zhcp',
               help='xCat zHCP nodename in xCAT '),
    cfg.StrOpt('zvm_host',
               default=None,
               help='z/VM host that managed by xCAT MN.'),
    cfg.StrOpt('zvm_xcat_master',
               default='xcat',
               help='The xCAT MM node name'),
    cfg.IntOpt('cache_update_interval',
               default=600,
               help="Cached data update interval"),
    cfg.StrOpt('zvm_xcat_ca_file',
               default=None,
               help="CA file for https connection to xcat"),
]


CONF = cfg.CONF
CONF.register_opts(zvm_ops, group='zvm')

LOG = log.getLogger(__name__)


class ZVMInspector(virt_inspector.Inspector):

    def __init__(self):
        self.cache = zvmutils.CacheData()
        self.cache_expiration = timeutils.utcnow_ts()

        self.instances = {}
        self.zhcp_info = {
            'nodename': CONF.zvm.xcat_zhcp_nodename,
            'hostname': zvmutils.get_node_hostname(
                            CONF.zvm.xcat_zhcp_nodename),
            'userid': zvmutils.get_userid(CONF.zvm.xcat_zhcp_nodename)
        }

    def _update_inst_cpu_mem_stat(self, instances):
        inst_pis = zvmutils.image_performance_query(
                                self.zhcp_info['nodename'], instances.values())

        for inst_name, userid in instances.items():
            if userid not in inst_pis.keys():
                # Not performance data returned for this virtual machine
                continue

            with zvmutils.expect_invalid_xcat_resp_data():
                guest_cpus = int(inst_pis[userid]['guest_cpus'])
                used_cpu_time = inst_pis[userid]['used_cpu_time']
                used_cpu_time = int(used_cpu_time.partition(' ')[0]) * units.k
                used_memory = inst_pis[userid]['used_memory']
                used_memory = int(used_memory.partition(' ')[0]) / units.Ki

            inst_stat = {'nodename': inst_name,
                         'userid': userid,
                         'guest_cpus': guest_cpus,
                         'used_cpu_time': used_cpu_time,
                         'used_memory': used_memory}

            self.cache.set('cpumem', inst_stat)

    def _update_inst_nic_stat(self, instances):
        vsw_dict = zvmutils.virutal_network_vswitch_query_iuo_stats(
                                                    self.zhcp_info['nodename'])
        with zvmutils.expect_invalid_xcat_resp_data():
            for vsw in vsw_dict['vswitches']:
                for nic in vsw['nics']:
                    for inst_name, userid in instances.items():
                        if nic['userid'].upper() == userid.upper():
                            nic_entry = {
                                'vswitch_name': vsw['vswitch_name'],
                                'nic_vdev': nic['vdev'],
                                'nic_fr_rx': int(nic['nic_fr_rx']),
                                'nic_fr_tx': int(nic['nic_fr_tx']),
                                'nic_fr_rx_dsc': int(nic['nic_fr_rx_dsc']),
                                'nic_fr_tx_dsc': int(nic['nic_fr_tx_dsc']),
                                'nic_fr_rx_err': int(nic['nic_fr_rx_err']),
                                'nic_fr_tx_err': int(nic['nic_fr_tx_err']),
                                'nic_rx': int(nic['nic_rx']),
                                'nic_tx': int(nic['nic_tx'])}
                            inst_stat = self.cache.get('vnics', inst_name)
                            if inst_stat is None:
                                inst_stat = {
                                    'nodename': inst_name,
                                    'userid': userid,
                                    'nics': [nic_entry]
                                }
                            else:
                                inst_stat['nics'].append(nic_entry)
                            self.cache.set('vnics', inst_stat)

    def _update_cache(self, meter, instances={}):
        if instances == {}:
            self.cache.clear()
            self.cache_expiration = (timeutils.utcnow_ts() +
                                     CONF.zvm.cache_update_interval)
            instances = self.instances = zvmutils.list_instances(
                                                                self.zhcp_info)
        if meter == 'cpumem':
            self._update_inst_cpu_mem_stat(instances)
        if meter == 'vnics':
            self._update_inst_nic_stat(instances)

    def _check_expiration_and_update_cache(self, meter):
        now = timeutils.utcnow_ts()
        if now >= self.cache_expiration:
            self._update_cache(meter)

    def _get_inst_stat(self, meter, instance):
        inst_name = zvmutils.get_inst_name(instance)
        # zvm inspector can not get instance info in shutdown stat
        if zvmutils.get_inst_power_state(instance) == 0x04:
            msg = _("Can not get vm info in shutdown state "
                    "for %s") % inst_name
            raise virt_inspector.InstanceShutOffException(msg)

        self._check_expiration_and_update_cache(meter)

        inst_stat = self.cache.get(meter, inst_name)

        if inst_stat is None:
            userid = (self.instances.get(inst_name) or
                        zvmutils.get_userid(inst_name))
            self._update_cache(meter, {inst_name: userid})
            inst_stat = self.cache.get(meter, inst_name)

        if inst_stat is None:
            msg = _("Can not get vm info for %s") % inst_name
            raise virt_inspector.InstanceNotFoundException(msg)
        else:
            return inst_stat

    def inspect_cpus(self, instance):
        inst_stat = self._get_inst_stat('cpumem', instance)
        return virt_inspector.CPUStats(number=inst_stat['guest_cpus'],
                                       time=inst_stat['used_cpu_time'])

    def inspect_memory_usage(self, instance, duration=None):
        inst_stat = self._get_inst_stat('cpumem', instance)
        return virt_inspector.MemoryUsageStats(usage=inst_stat['used_memory'])

    def inspect_vnics(self, instance):
        inst_stat = self._get_inst_stat('vnics', instance)
        for nic in inst_stat['nics']:
            nic_id = '_'.join((nic['vswitch_name'], inst_stat['userid'],
                               nic['nic_vdev']))
            interface = virt_inspector.Interface(
                name=nic_id,
                mac=None,
                fref=None,
                parameters=None)
            stats = virt_inspector.InterfaceStats(
                rx_bytes=nic['nic_rx'],
                rx_packets=nic['nic_fr_rx'],
                tx_bytes=nic['nic_tx'],
                tx_packets=nic['nic_fr_tx'],
                rx_drop=nic['nic_fr_rx_dsc'],
                tx_drop=nic['nic_fr_tx_dsc'],
                rx_errors=nic['nic_fr_rx_err'],
                tx_errors=nic['nic_fr_tx_err'])
            yield (interface, stats)
