# coding=utf-8

"""
SNMPCollector for Netscaler Metrics

NetScaler is a network appliance manufactured by Citrix providing level 4 load
balancing, firewall, proxy and VPN functions.

"""

import os
import re
import struct

import sys
import time

import diamond.metric

# Fix Path for locating the SNMPCollector
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../", "snmp")))

from snmp import SNMPCollector as parent_SNMPCollector


class NetscalerSNMPCollector(parent_SNMPCollector):
    """
    SNMPCollector for Netscaler Metrics
    """

    """
    EntityProtocolType ::=
    INTEGER{    http(0),
                ftp(1),
                tcp(2),
                udp(3),
                sslBridge(4),
                monitor(5),
                monitorUdp(6),
                nntp(7),
                httpserver(8),
                httpclient(9),
                rpcserver(10),
                rpcclient(11),
                nat(12),
                any(13),
                ssl(14),
                dns(15),
                adns(16),
                snmp(17),
                ha(18),
                monitorPing(19),
                sslOtherTcp(20),
                aaa(21),
                secureMonitor(23),
                sslvpnUdp(24),
                rip(25),
                dnsClient(26),
                rpcServer(27),
                rpcClient(28),
                dhcrpa(36),
                sipudp(39),
                dnstcp(44),
                adnstcp(45),
                rtsp(46),
                push(48),
                sslPush(49),
                dhcpClient(50),
                radius(51),
                serviceUnknown(62) }

    EntityState ::=
    INTEGER{    down(1),
                unknown(2),
                busy(3),
                outOfService(4),
                transitionToOutOfService(5),
                up(7),
                transitionToOutOfServiceDown(8) }
    """

    NETSCALER_SYSTEM_GUAGES = {
        "cpuUsage": "1.3.6.1.4.1.5951.4.1.1.41.1.0",
        "memUsage": "1.3.6.1.4.1.5951.4.1.1.41.2.0",
        "surgeQueue": "1.3.6.1.4.1.5951.4.1.1.46.15.0",
        "establishedServerConnections": "1.3.6.1.4.1.5951.4.1.1.46.10.0",
        "establishedClientConnections": "1.3.6.1.4.1.5951.4.1.1.46.12.0",
    }

    NETSCALER_SYSTEM_COUNTERS = {"httpTotRequests": "1.3.6.1.4.1.5951.4.1.1.48.67.0"}

    NETSCALER_VSERVER_NAMES = "1.3.6.1.4.1.5951.4.1.3.1.1.1"

    NETSCALER_VSERVER_TYPE = "1.3.6.1.4.1.5951.4.1.3.1.1.4"

    NETSCALER_VSERVER_STATE = "1.3.6.1.4.1.5951.4.1.3.1.1.5"

    NETSCALER_VSERVER_GUAGES = {
        "vsvrRequestRate": "1.3.6.1.4.1.5951.4.1.3.1.1.43",
        "vsvrRxBytesRate": "1.3.6.1.4.1.5951.4.1.3.1.1.44",
        "vsvrTxBytesRate": "1.3.6.1.4.1.5951.4.1.3.1.1.45",
        "vsvrCurServicesUp": "1.3.6.1.4.1.5951.4.1.3.1.1.41",
        "vsvrCurServicesDown": "1.3.6.1.4.1.5951.4.1.3.1.1.37",
        "vsvrCurServicesUnknown": "1.3.6.1.4.1.5951.4.1.3.1.1.38",
        "vsvrCurServicesTransToOutOfSvc": "1.3.6.1.4.1.5951.4.1.3.1.1.40",
    }

    NETSCALER_SERVICE_NAMES = "1.3.6.1.4.1.5951.4.1.2.1.1.1"

    NETSCALER_SERVICE_TYPE = "1.3.6.1.4.1.5951.4.1.2.1.1.4"

    NETSCALER_SERVICE_STATE = "1.3.6.1.4.1.5951.4.1.2.1.1.5"

    NETSCALER_SERVICE_GUAGES = {
        "svcRequestRate": "1.3.6.1.4.1.5951.4.1.2.1.1.42",
        "svcSurgeCount": "1.3.6.1.4.1.5951.4.1.2.1.1.10",
        "svcEstablishedConn": "1.3.6.1.4.1.5951.4.1.2.1.1.8",
        "svcActiveConn": "1.3.6.1.4.1.5951.4.1.2.1.1.9",
        "svcCurClntConnections": "1.3.6.1.4.1.5951.4.1.2.1.1.41",
    }

    MAX_VALUE = 18446744073709551615

    def get_default_config_help(self):
        config_help = super(NetscalerSNMPCollector, self).get_default_config_help()
        config_help.update(
            {
                "host": "netscaler dns address",
                "port": "Netscaler port to collect snmp data",
                "community": "SNMP community",
                "exclude_service_type": "list of service types to exclude  (see MIB EntityProtocolType)",
                "exclude_vserver_type": "list of vserver types to exclude  (see MIB EntityProtocolType)",
            }
        )

        return config_help

    def get_default_config(self):
        """
        Returns the default collector settings
        """
        config = super(NetscalerSNMPCollector, self).get_default_config()
        config.update(
            {
                "path": "netscaler",
                "timeout": 15,
                "exclude_service_type": [],
                "exclude_vserver_type": [],
                "exclude_service_state": [],
                "exclude_vserver_state": [],
            }
        )

        return config

    def get_string_index_oid(self, s):
        """Turns a string into an oid format is length of name followed by
        name chars in ascii"""
        return (len(self.get_bytes(s)),) + self.get_bytes(s)

    def get_bytes(self, s):
        """Turns a string into a list of byte values"""
        return struct.unpack("%sB" % len(s), s)

    def collect_snmp(self, device, host, port, community):
        """
        Collect Netscaler SNMP stats from device
        """
        # Log
        self.log.info("Collecting Netscaler statistics from: %s", device)

        # Set timestamp
        timestamp = time.time()

        # Collect Netscaler System OIDs
        for k, v in self.NETSCALER_SYSTEM_GUAGES.items():
            # Get Metric Name and Value
            metric_name = ".".join([k])
            metric_value = int(self.get(v, host, port, community)[v])

            # Get Metric Path
            metric_path = ".".join(["devices", device, "system", metric_name])

            # Create Metric
            metric = diamond.metric.Metric(metric_path, metric_value, timestamp, 0)

            # Publish Metric
            self.publish_metric(metric)

        # Collect Netscaler System Counter OIDs
        for k, v in self.NETSCALER_SYSTEM_COUNTERS.items():
            # Get Metric Name and Value
            metric_name = ".".join([k])

            # Get Metric Path
            metric_path = ".".join(["devices", device, "system", metric_name])

            # Get Metric Value
            metric_value = self.derivative(
                metric_path, int(self.get(v, host, port, community)[v]), self.MAX_VALUE
            )

            # Create Metric
            metric = diamond.metric.Metric(metric_path, metric_value, timestamp, 0)

            # Publish Metric
            self.publish_metric(metric)

        # Collect Netscaler Services
        service_names = [
            v.strip("'")
            for v in self.walk(
                self.NETSCALER_SERVICE_NAMES, host, port, community
            ).values()
        ]

        for serviceName in service_names:
            # Get Service Name in OID form
            service_name_oid = self.get_string_index_oid(serviceName)

            # Get Service Type
            service_type_oid = ".".join(
                [self.NETSCALER_SERVICE_TYPE, self._convert_from_oid(service_name_oid)]
            )
            service_type = int(
                self.get(service_type_oid, host, port, community)[
                    service_type_oid
                ].strip("'")
            )

            # Filter excluded service types
            if service_type in map(
                lambda v: int(v), self.config.get("exclude_service_type")
            ):
                continue

            # Get Service State
            service_state_oid = ".".join(
                [self.NETSCALER_SERVICE_STATE, self._convert_from_oid(service_name_oid)]
            )
            service_state = int(
                self.get(service_state_oid, host, port, community)[
                    service_state_oid
                ].strip("'")
            )

            # Filter excluded service states
            if service_state in map(
                lambda v: int(v), self.config.get("exclude_service_state")
            ):
                continue

            for k, v in self.NETSCALER_SERVICE_GUAGES.items():
                service_guage_oid = ".".join(
                    [v, self._convert_from_oid(service_name_oid)]
                )

                # Get Metric Name
                metric_name = ".".join([re.sub(r"\.|\\", "_", serviceName), k])

                # Get Metric Value
                metric_value = int(
                    self.get(service_guage_oid, host, port, community)[
                        service_guage_oid
                    ].strip("'")
                )

                # Get Metric Path
                metric_path = ".".join(["devices", device, "service", metric_name])

                # Create Metric
                metric = diamond.metric.Metric(metric_path, metric_value, timestamp, 0)

                # Publish Metric
                self.publish_metric(metric)

        # Collect Netscaler Vservers
        vserver_names = [
            v.strip("'")
            for v in self.walk(
                self.NETSCALER_VSERVER_NAMES, host, port, community
            ).values()
        ]

        for vserverName in vserver_names:
            # Get Vserver Name in OID form
            vserver_name_oid = self.get_string_index_oid(vserverName)

            # Get Vserver Type
            vserver_type_oid = ".".join(
                [self.NETSCALER_VSERVER_TYPE, self._convert_from_oid(vserver_name_oid)]
            )
            vserver_type = int(
                self.get(vserver_type_oid, host, port, community)[
                    vserver_type_oid
                ].strip("'")
            )

            # filter excluded vserver types
            if vserver_type in map(
                lambda v: int(v), self.config.get("exclude_vserver_type")
            ):
                continue

            # Get Service State
            vserver_state_oid = ".".join(
                [self.NETSCALER_VSERVER_STATE, self._convert_from_oid(vserver_name_oid)]
            )
            vserver_state = int(
                self.get(vserver_state_oid, host, port, community)[
                    vserver_state_oid
                ].strip("'")
            )

            # Filter excluded vserver state
            if vserver_state in map(
                lambda v: int(v), self.config.get("exclude_vserver_state")
            ):
                continue

            for k, v in self.NETSCALER_VSERVER_GUAGES.items():
                vserver_guage_oid = ".".join(
                    [v, self._convert_from_oid(vserver_name_oid)]
                )

                # Get Metric Name
                metric_name = ".".join([re.sub(r"\.|\\", "_", vserverName), k])

                # Get Metric Value
                metric_value = int(
                    self.get(vserver_guage_oid, host, port, community)[
                        vserver_guage_oid
                    ].strip("'")
                )

                # Get Metric Path
                metric_path = ".".join(["devices", device, "vserver", metric_name])

                # Create Metric
                metric = diamond.metric.Metric(metric_path, metric_value, timestamp, 0)

                # Publish Metric
                self.publish_metric(metric)
