# Copyright (C) 2005-2021 Splunk Inc. All Rights Reserved. 
# Core Python Imports
import sys
import logging
import logging.handlers
import re
from httplib2 import ServerNotFoundError
import socket
import time
import json
import datetime

# Splunkd imports
import splunk
import splunk.rest as rest
import splunk.util as util
import lxml.etree as et
from splunk.clilib.bundle_paths import make_splunkhome_path

# TA and SA Imports
sys.path.append(make_splunkhome_path(['etc', 'apps', 'SA-Hydra-inframon', 'bin']))
sys.path.append(make_splunkhome_path(['etc', 'apps', 'Splunk_TA_vmware_inframon', 'bin']))
from hydra_inframon.six import iteritems
import ta_vmware_inframon.simple_vsphere_utils as vsu
from ta_vmware_inframon.models import TAVMwareCollectionStanza, TAVMwareVCenterForwarderStanza, \
    SSLCertificate, PoolStanza, TemplateStanza
from hydra_inframon.models import SplunkStoredCredential, HydraNodeStanza, HydraCacheStanza, HydraSessionStanza

# defining global constants
REST_ROOT_PATH = '/services'
local_host_path = splunk.mergeHostPath()
max_worker_process = 30

STATUS_CODES = {
    400: 'Bad Request',
    401: 'Unauthorized',
    402: 'Payment Required',
    403: 'Forbidden',
    404: 'Not Found',
    405: 'Method Not Allowed',
    406: 'Not Acceptable',
    407: 'Proxy Authentication Required',
    408: 'Request Timeout',
    409: 'Conflict',
    411: 'Length Required',
    500: 'Internal Server Error',
    503: 'Service Unavailable'
}


class RestError(Exception):
    """
    REST Error.
    """

    def __init__(self, status, message):
        self.status = status
        self.reason = STATUS_CODES.get(
            status,
            'Unknown Error',
        )
        self.message = message
        err_msg = 'REST Error [%(status)s]: %(reason)s -- %(message)s' % {
            'status': self.status,
            'reason': self.reason,
            'message': self.message
        }
        super(RestError, self).__init__(err_msg)


def setup_logger(logger=None, log_format='%(asctime)s %(levelname)s [ConfigSetUp] %(message)s',
                 level=logging.INFO, log_name="splunk_for_vmware_setup.log", logger_name="splunk_for_vmware_setup"):
    """
    Setup a logger suitable for splunkd consumption
    """
    if logger is None:
        logger = logging.getLogger(logger_name)

    # Prevent the log messages from being duplicated in the python.log file
    logger.propagate = False
    logger.setLevel(level)

    file_handler = logging.handlers.RotatingFileHandler(make_splunkhome_path(
        ['var', 'log', 'splunk', log_name]), maxBytes=2500000, backupCount=5)
    formatter = logging.Formatter(log_format)
    file_handler.setFormatter(formatter)

    logger.handlers = []
    logger.addHandler(file_handler)

    logger.debug("Init splunk for vmware setup logger")

    return logger


def splunk_rest_request(path, logger, local_session_key=None, session_key=None, getargs=None, postargs=None,
                        method='GET',
                        raise_all_errors=False, raw_result=False, timeout=30, jsonargs=None):
    """
    This is mostly a shameful copy of splunk.rest.simpleRequest.
    The difference lies in the automagic header/cert attachment that
    happens in splunkweb and messes with the splunkweb cherrypy.session.
    Also we don't auto magic any session keys

    Makes an HTTP call to the main splunk REST endpoint

    path: the URI to fetch
            If given a relative URI, then the method will normalize to the splunkd
            default of "/services/...".
            If given an absolute HTTP(S) URI, then the method will use as-is.
            If given a 'file://' URI, then the method will attempt to read the file
            from the local filesystem.  Only files under $SPLUNK_HOME are supported,
            so paths are 'chrooted' from $SPLUNK_HOME.

    getargs: dict of k/v pairs that are always appended to the URL

    postargs: dict of k/v pairs that get placed into the body of the 
            request. If postargs is provided, then the HTTP method is auto
            assigned to POST.

    method: the HTTP verb - [GET | POST | DELETE | PUT]

    raise_all_errors: indicates if the method should raise an exception
            if the server HTTP response code is >= 400

    raw_result: don't raise an exception if a non 200 response is received;
            return the actual response

    Return:

            This method will return a tuple of (server_response, server_content)

            server_response: a dict of HTTP status information
            server_content: the body content
    """
    logger.debug("Called splunk_rest_request method of rest_utility()")
    # strip spaces
    path = path.strip(' ')
    # if absolute URI, pass along as-is
    if path.startswith('http'):
        uri = path

    # if file:// protocol, try to read file and return
    # the serverStatus is just an empty dict; file contents are in server_response
    elif path.startswith('file://'):
        raise Exception(
            "Not supported for this method, use splunk.rest.simpleRequest instead")

    else:
        # prepend convenience root path
        if not path.startswith(REST_ROOT_PATH):
            path = REST_ROOT_PATH + '/' + path.strip('/')

        # setup args
        host = splunk.getDefault('host')
        if ':' in host:
            host = '[%s]' % host

        uri = '%s://%s:%s/%s' % \
              (splunk.getDefault('protocol'), host,
               splunk.getDefault('port'), path.strip('/'))

    if getargs:
        getargs = dict([(k, v) for (k, v) in iteritems(getargs) if v is not None])
        uri += '?' + util.urlencodeDict(getargs)

    # proxy mode bypasses all header passing
    headers = {}
    session_source = 'direct'

    if session_key:
        headers['Authorization'] = 'Splunk %s' % session_key

    payload = ''
    if postargs or jsonargs and method in ('GET', 'POST', 'PUT'):
        if method == 'GET':
            method = 'POST'
        if jsonargs:
            # if a JSON body was given, use it for the payload and ignore the postargs
            payload = jsonargs
        else:
            payload = util.urlencodeDict(postargs)
    #
    # make request
    #
    t1 = None
    if logger.level <= logging.DEBUG:
        if uri.lower().find('login') > -1:
            logpayload = '[REDACTED]'
        else:
            logpayload = payload
        logger.debug(
            'splunk_rest_request >>>\n\tmethod=%s\n\turi=%s\n\tbody=%s', method, uri, logpayload)
        logger.debug('splunk_rest_request > %s %s [%s] session_source=%s' % (
            method, uri, logpayload, session_source))
        t1 = time.time()

    # Certificate validation status from inframon_ta_vmware_config_ssl.conf

    stanzas = SSLCertificate.all(sessionKey=local_session_key)
    cert_validation = not stanzas[0].validate_ssl_certificate

    if cert_validation:
        logger.info(
            "SSL certificate validation disabled for collection configuration")
    else:
        logger.info(
            "SSL certificate validation enabled for collection configuration")

    # Add wait and tries to check if the HTTP server is up and running
    tries = 4
    wait = 10
    server_response = None
    server_content = None
    try:
        import httplib2
        for a_try in range(tries):
            h = httplib2.Http(
                timeout=timeout, disable_ssl_certificate_validation=cert_validation)
            server_response, server_content = h.request(
                uri, method, headers=headers, body=payload)
            if server_response is None:
                if a_try < tries:
                    time.sleep(wait)
            else:
                break
    except socket.error as e:
        raise splunk.SplunkdConnectionException(str(e))
    except socket.timeout as e:
        raise splunk.SplunkdConnectionException('Timed out while waiting for splunkd daemon to respond. Splunkd may be hung. (timeout=30)')
    except AttributeError as e:
        raise splunk.SplunkdConnectionException('Unable to establish connection with splunkd deamon. (%s)' % e)

    server_response.messages = []

    if logger.level <= logging.DEBUG:
        logger.debug('simpleRequest < server responded status=%s responseTime=%.4fs',
                     server_response.status, time.time() - t1)

    # Don't raise exceptions for different status codes or try and parse the response
    if raw_result:
        return server_response, server_content

    #
    # we only throw exceptions in limited cases; for most HTTP errors, splunkd
    # will return messages in the body, which we parse, so we don't want to
    # halt everything and raise exceptions; it is up to the client to figure
    # out the best course of action
    #
    if server_response.status == 401:
        # SPL-20915
        logger.debug(
            'splunk_rest_request - Authentication failed; session_key=%s', session_key)
        raise splunk.AuthenticationFailed

    elif server_response.status == 402:
        raise splunk.LicenseRestriction

    elif server_response.status == 403:
        raise splunk.AuthorizationFailed(extendedMessages=uri)

    elif server_response.status == 404:

        # Some 404 responses, such as those for expired jobs which were originally
        # run by the scheduler return extra data about the original resource.
        # In this case we add that additional info into the exception object
        # as the resource_info parameter so others might use it.
        try:
            body = et.fromstring(server_content)
            resource_info = body.find('dict')
            if resource_info is not None:
                raise splunk.ResourceNotFound(
                    uri, format.nodeToPrimitive(resource_info))
            else:
                raise splunk.ResourceNotFound(
                    uri, extendedMessages=rest.extractMessages(body))
        except et.XMLSyntaxError:
            pass

        raise splunk.ResourceNotFound(uri)

    elif server_response.status == 201:
        try:
            body = et.fromstring(server_content)
            server_response.messages = rest.extractMessages(body)
        except et.XMLSyntaxError as e:
            # do nothing, just continue, no messages to extract if there is no xml
            pass
        except Exception as e:
            # warn if some other type of error occurred.
            logger.warn(
                "exception trying to parse server_content returned from a 201 response.")

    elif server_response.status < 200 or server_response.status > 299:

        # service may return messages in the body; try to parse them
        try:
            body = et.fromstring(server_content)
            server_response.messages = rest.extractMessages(body)
        except:
            pass

        if raise_all_errors and server_response.status > 399:

            if server_response.status == 500:
                raise splunk.InternalServerError((None,
                                                   server_response.messages))
            elif server_response.status == 400:
                raise splunk.BadRequest((None, server_response.messages))
            else:
                raise splunk.RESTException((server_response.status,
                                             server_response.messages))

    # return the headers and body content
    return server_response, server_content


def get_remote_session_key(username, password, local_session_key, host_path, logger):
    """
    Get a remote session key from the auth system
    If fails return None
    """

    uri = splunk.mergeHostPath(host_path) + '/services/auth/login'
    args = {'username': username, 'password': password}
    server_response = None
    server_content = None
    try:
        server_response, server_content = splunk_rest_request(
            uri, logger, local_session_key=local_session_key, postargs=args)

    except splunk.AuthenticationFailed:
        return None

    if server_response and server_response.status != 200:
        logger.error(
            'get_remote_session_key - unable to login; check credentials')
        rest.extractMessages(et.fromstring(server_content))
        return None

    root = et.fromstring(server_content)
    session_key = root.findtext('sessionKey')

    return session_key


def validate_dcn(node_path, username, password, heads, pool_name, local_session_key, logger):
    """

    :param node_path: dcn managment port url
    :param username: dcn splunk username
    :param password: dcn splunk password
    :param heads: dcn worker process
    :param pool_name: pool name in which dcn belongs
    :param local_session_key:
    :param logger:
    :return: result of validation as a response
    """

    credential_validation = False
    last_connectivity_checked = datetime.datetime.utcnow()
    # addon_validation checking condition will be changed latter (as package structure is not decided yet.)
    addon_validation = False

    if not pool_name == [None]:
        if isinstance(pool_name, list):
            pool_name = pool_name[0]

        pool_name = pool_name.strip()
        if len(pool_name) == 0:
            logger.error("Pool name cannot be empty for node:{0}.".format(node_path))
            response = {"status": "invalid", "message": "Pool name cannot be empty.",
                        "credential_validation": credential_validation, "addon_validation": addon_validation,
                        "last_connectivity_checked": last_connectivity_checked}
            return response

        pool_stanza = PoolStanza.from_name(pool_name, "Splunk_TA_vmware_inframon", host_path=local_host_path,
                                           session_key=local_session_key)
        if not pool_stanza:
            logger.error("[pool={0}]Given pool for node:{1} doesn't exist.".format(pool_name, node_path))
            response = {"status": "invalid", "message": "Given pool doesn't exist.",
                        "addon_validation": addon_validation,
                        "credential_validation": credential_validation,
                        "last_connectivity_checked": last_connectivity_checked}
            return response
    else:
        logger.error("pool name is not passed for node:{0}".format(node_path))
        response = {"status": "invalid", "message": "Empty pool name is not allowed.",
                    "credential_validation": credential_validation, "addon_validation": addon_validation,
                    "last_connectivity_checked": last_connectivity_checked}
        return response

    if not node_path == [None]:
        # node_path could be list or string (list in case of coming in args , string if coming as target).
        if isinstance(node_path, list):
            node_path = node_path[0]

        validated_node_path = re.search(
            "^\s*https?:\/\/[A-Za-z0-9\.\-_]+:\d+\/?\s*$", node_path)
        if validated_node_path is None:
            logger.error("[pool={0}]Node name passed is not valid.".format(pool_name))
            response = {"status": "invalid", "message": "Node name passed is not valid.",
                        "credential_validation": credential_validation, "addon_validation": addon_validation,
                        "last_connectivity_checked": last_connectivity_checked}
            return response
    else:
        logger.error("[pool={0}]No node name passed in data, cannot save nothing!".format(pool_name))
        response = {"status": "invalid", "message": "Empty node name is not allowed.",
                    "credential_validation": credential_validation, "addon_validation": addon_validation,
                    "last_connectivity_checked": last_connectivity_checked}
        return response

    if not username == [None]:
        if isinstance(username, list):
            username = username[0]
        username = username.strip()
        validated_node_username = re.search("^[^\r\n\t\/\s]+$", username)
        if validated_node_username is None:
            logger.error(
                "[pool={0}]Node username passed for node:{1} is not valid.".format(pool_name, node_path))
            response = {"status": "invalid", "message": "Username is not valid.",
                        "credential_validation": credential_validation, "addon_validation": addon_validation,
                        "last_connectivity_checked": last_connectivity_checked}
            return response
    else:
        logger.error(
            "[pool={0}]No username passed for node:{1}".format(pool_name, node_path))
        response = {"status": "invalid", "message": "Empty username is not allowed.",
                    "credential_validation": credential_validation, "addon_validation": addon_validation,
                    "last_connectivity_checked": last_connectivity_checked}
        return response

    if not heads == [None]:
        if isinstance(heads, list):
            heads = heads[0]
        validated_heads = re.search("^(([1-2][0-9])|[1-9]|30)$", heads)
        if validated_heads is None:
            logger.error("[pool={0}]Heads is not valid for node:{1}.".format(pool_name, node_path))
            response = {"status": "invalid", "message": "Heads is not valid.",
                        "credential_validation": credential_validation, "addon_validation": addon_validation,
                        "last_connectivity_checked": last_connectivity_checked}
            return response
    else:
        logger.error(
            "[pool={0}]No heads passed for node:{1}".format(pool_name, node_path))
        response = {"status": "invalid", "message": "Empty heads is not allowed.",
                    "credential_validation": credential_validation, "addon_validation": addon_validation,
                    "last_connectivity_checked": last_connectivity_checked}
        return response

    if not password == [None]:
        if isinstance(password, list):
            password = password[0]
        password = password.strip()
        if len(password) == 0:
            logger.error("[pool={0}]Password is not valid for node:{1}.".format(pool_name, node_path))
            response = {"status": "invalid", "message": "Password is not valid.",
                        "credential_validation": credential_validation, "addon_validation": addon_validation,
                        "last_connectivity_checked": last_connectivity_checked}
            return response
    else:
        logger.error("[pool={0}]No password passed for node:{1}.".format(pool_name, node_path))
        response = {"status": "invalid", "message": "Empty password is not allowed.",
                    "credential_validation": credential_validation, "addon_validation": addon_validation,
                    "last_connectivity_checked": last_connectivity_checked}
        return response

    last_connectivity_checked = datetime.datetime.utcnow()
    try:
        remote_session_key = get_remote_session_key(
            username, password, local_session_key, node_path, logger)

        if remote_session_key is None:
            response = {"status": "valid", "message": "Could reach host, but login failed",
                        "credential_validation": credential_validation, "addon_validation": addon_validation,
                        "last_connectivity_checked": last_connectivity_checked}

        else:
            # Okay credentials are good, now we can check that the apps are there
            server_response, server_content = splunk_rest_request(
                path=node_path + '/services/apps/local', logger=logger, local_session_key=local_session_key,
                session_key=remote_session_key, getargs={'count': '0'})
            apps = rest.format.parseFeedDocument(server_content)
            # required_apps is to be changed latter.
            required_apps = ["SA-Hydra-inframon", "Splunk_TA_vmware_inframon"]

            installed_count = 0
            installed_apps = []
            for app in apps:
                contents = app.toPrimitive()
                if 'label' in contents:
                    installed_apps.append(contents['label'])
                if app.title in required_apps:
                    installed_count += 1

            credential_validation = True
            if installed_count == len(required_apps):
                addon_validation = True
                response = {"status": "valid", "message": "Everything is valid",
                            "credential_validation": credential_validation, "addon_validation": addon_validation,
                            "last_connectivity_checked": last_connectivity_checked}
            else:
                logger.error(
                    "[pool={2}]Username/password are good for node:{0}, but apps are not there, it had installed_apps='{1}'".
                        format(node_path, str(installed_apps), pool_name))
                addon_validation = False
                response = {"status": "badapps", "message": "Username/password are good but apps are not there",
                            "credential_validation": credential_validation, "addon_validation": addon_validation,
                            "last_connectivity_checked": last_connectivity_checked}

    except ServerNotFoundError:
        logger.error("[pool=%s]Could not reach host=%s", pool_name, node_path)
        response = {"status": "unreachable", "message": "Could not reach host",
                    "credential_validation": credential_validation, "addon_validation": addon_validation,
                    "last_connectivity_checked": last_connectivity_checked}
    except splunk.SplunkdConnectionException:
        logger.error("[pool=%s]Could not find splunkd on node=%s", pool_name, node_path)
        response = {"status": "unreachable", "message": "Could not reach host",
                    "credential_validation": credential_validation, "addon_validation": addon_validation,
                    "last_connectivity_checked": last_connectivity_checked}
    except splunk.AuthenticationFailed:
        logger.error(
            "[pool=%s]Could not log into splunkd on node=%s, credentials are definitely bad", pool_name, node_path)
        response = {"status": "invalid_creds", "message": "Could not authenticate with remote splunkd",
                    "credential_validation": credential_validation, "addon_validation": addon_validation,
                    "last_connectivity_checked": last_connectivity_checked}
    except Exception:
        logger.exception("[pool={1}]Could not reach host={0}.".format(node_path, pool_name))
        response = {"status": "unreachable", "message": "Could not reach host",
                    "credential_validation": credential_validation, "addon_validation": addon_validation,
                    "last_connectivity_checked": last_connectivity_checked}

    return response


def validate_vcenter(vc_path, username, password, pool_name, local_session_key, logger, check_connection_only=False):
    """

    :param vc_path: vCenter path
    :param username: vCenter username
    :param password: vCenter password
    :param pool_name: vCenter pool_name
    :param local_session_key: local session key of current Splunk session
    :param check_connection_only: flag denoting whether to validate vc using stanzas from conf or check connection using the provided credentials
    :return: returns the result of validation as a response
    """
    credential_validation = False
    last_connectivity_checked = datetime.datetime.utcnow()
    
    if check_connection_only:
        dbg_info = 'Check_connection_only'
    else:
        if not pool_name == [None]:
            if isinstance(pool_name, list):
                pool_name = pool_name[0]
            pool_name = pool_name.strip()

            if len(pool_name) == 0:
                logger.error("for vCenter:{0} Pool name cannot be empty.".format(vc_path))
                response = {"status": "invalid", "message": "Pool name cannot be empty.",
                            "credential_validation": credential_validation,
                            "last_connectivity_checked": last_connectivity_checked}
                return response

            pool_stanza = PoolStanza.from_name(pool_name, "Splunk_TA_vmware_inframon", host_path=local_host_path,
                                            session_key=local_session_key)
            if not pool_stanza:
                logger.error("for vCenter:{0} Given pool doesn't exist.".format(vc_path))
                response = {"status": "invalid", "message": "Given pool doesn't exist.",
                            "credential_validation": credential_validation,
                            "last_connectivity_checked": last_connectivity_checked}
                return response
        else:
            logger.error("for vCenter:{0} No pool_name passed".format(vc_path))
            response = {"status": "invalid", "message": "Empty pool name is not allowed.",
                        "credential_validation": credential_validation,
                        "last_connectivity_checked": last_connectivity_checked}
            return response

        dbg_info = 'pool={0}'.format(pool_name)
    
    if not vc_path == [None]:
        # vc_path could be list or string based on from where is comming
        if isinstance(vc_path, list):
            vc_path = vc_path[0]
        validate_vc_domain = re.search("^[A-Za-z0-9\.\-_]+$", vc_path)
        if validate_vc_domain is None:
            logger.error("[{0}]vCenter domain  passed is not valid.".format(dbg_info))
            response = {"status": "invalid", "message": "vCenter domain  passed is not valid.",
                        "credential_validation": credential_validation,
                        "last_connectivity_checked": last_connectivity_checked}
            return response
    else:
        logger.error("[{0}]No vCenter domain passed".format(dbg_info))
        response = {"status": "invalid", "message": "No vCenter domain passed , cannot validate nothing!",
                    "credential_validation": credential_validation,
                    "last_connectivity_checked": last_connectivity_checked}
        return response

    if not username == [None] and not username == None:
        if isinstance(username, list):
            username = username[0]
        username = username.strip()
        validated_username = re.search("^[^\r\n\t\/\s]+$", username)
        if validated_username is None:
            logger.error("[{0}] for vCenter:{1} username passed is not valid.".format(dbg_info, vc_path))
            response = {"status": "invalid", "message": "vCenter username passed is not valid.",
                        "credential_validation": credential_validation,
                        "last_connectivity_checked": last_connectivity_checked}
            return response
    else:
        logger.error("[{0}] for vCenter:{1} Username is not passed".format(dbg_info, vc_path))
        response = {"status": "invalid", "message": "Username is not passed to validate_vcenter, cannot validate!",
                    "credential_validation": credential_validation,
                    "last_connectivity_checked": last_connectivity_checked}
        return response

    if not password == [None] and not password == None:
        if isinstance(password, list):
            password = password[0]
        password = password.strip()
        if len(password) == 0:
            logger.error("[{0}] for vCenter:{1} Password is not valid.".format(dbg_info, vc_path))
            response = {"status": "invalid", "message": "Password is not valid.",
                        "credential_validation": credential_validation,
                        "last_connectivity_checked": last_connectivity_checked}
            return response

    else:
        logger.error("[{0}] for vCenter:{1} Password is not passed".format(dbg_info, vc_path))
        response = {"status": "invalid", "message": "Password is not passed, cannot validate!",
                    "credential_validation": credential_validation,
                    "last_connectivity_checked": last_connectivity_checked}
        return response

    logger.info("Checking vc={0} with username={1}".format(vc_path, username))
    last_connectivity_checked = datetime.datetime.utcnow()
    try:
        vs = vsu.vSphereService(vc_path, username, password)
        credential_validation = True
        response = {"status": "valid", "message": "Everyting is valid.",
                    "credential_validation": credential_validation,
                    "last_connectivity_checked": last_connectivity_checked}
        if vs.logout():
            logger.debug("User={0} successfully logout from {1}.".format(username, vc_path))
        else:
            logger.warn("User={0} failed to logout from {1}.".format(username, vc_path))
    except vsu.ConnectionFailure:
        credential_validation = False
        logger.error("[{1}]Could not reach the vc:{0} to test credentials".format(vc_path, dbg_info))
        response = {"status": "unreachable", "message": "Could not reach the vc to test credentials",
                    "credential_validation": credential_validation,
                    "last_connectivity_checked": last_connectivity_checked}
    except vsu.LoginFailure:
        credential_validation = False
        logger.error("[{1}]Could reach vc:{0}, but login failed.".format(vc_path, dbg_info))
        response = {"status": "loginFailed", "message": "Could reach vc, but login failed",
                    "credential_validation": credential_validation,
                    "last_connectivity_checked": last_connectivity_checked}
    except Exception:
        logger.exception("[{1}]Exception occurred while validating vc:{0}".format(vc_path, dbg_info))
        credential_validation = False
        response = {"status": "unreachable", "message": "Could not reach the vc to test creds",
                    "credential_validation": credential_validation,
                    "last_connectivity_checked": last_connectivity_checked}

    return response


def validate_vcenter_forwarder(vc_path, username, password, local_session_key, logger):
    """

    :param vc_path: path of vCenter
    :param username: username of Splunk forwarder on vCenter
    :param password: password of Splunk forwarder on vCenter
    :param local_session_key: local session key of current session.
    :return: It returns the validation result as a s response
    """
    credential_validation = False
    addon_validation = False

    if not vc_path == [None]:
        # vc_path could be list or string based on from where is comming
        if isinstance(vc_path, list):
            vc_path = vc_path[0]
        validate_vc_domain = re.search("^\s*https?:\/\/[A-Za-z0-9\.\-_]+:\d+\/?\s*$", vc_path)
        if validate_vc_domain is None:
            logger.error("vCenter domain  passed is not valid.")
            response = {"status": "invalid", "message": "vc forwarder uri  passed to validate_vcenter is not valid.",
                        "credential_validation": credential_validation, "addon_validation": addon_validation}
            return response
    else:
        logger.error("No vc domain passed, cannot validate nothing!")
        response = {"status": "invalid", "message": "No vc domain passed to validate_vcenter, cannot validate nothing!",
                    "credential_validation": credential_validation, "addon_validation": addon_validation}
        return response

    if not username == [None]:
        if isinstance(username, list):
            username = username[0]
        username = username.strip()
        validated_username = re.search("^[^\r\n\t\/\s]+$", username)
        if validated_username is None:
            logger.error("vCenter forwarder username is not valid.")
            response = {"status": "invalid", "message": "vCenter forwarder username is not valid.",
                        "credential_validation": credential_validation, "addon_validation": addon_validation}
            return response
    else:
        logger.error("Username is not passed , cannot validate!")
        response = {"status": "invalid", "message": "Username is not passed to validate_vcenter, cannot validate!",
                    "credential_validation": credential_validation, "addon_validation": addon_validation}
        return response

    if not password == [None]:
        if isinstance(password, list):
            password = password[0]
        password = password.strip()
        if len(password) == 0:
            logger.error("Password is not valid.")
            response = {"status": "invalid", "message": "Password is not valid.",
                        "credential_validation": credential_validation, "addon_validation": addon_validation}
            return response
    else:
        logger.error("Password is not passed, cannot validate!")
        response = {"status": "invalid",
                    "message": "Password is not passed to validate_vcenter_forwrder,cannot validate!",
                    "credential_validation": credential_validation, "addon_validation": addon_validation}
        return response

    logger.info("Checking vc=%s  forwarder with username=%s", vc_path, username)

    # validating credentials.
    try:
        remote_session_key = get_remote_session_key(username, password, local_session_key, host_path=vc_path,
                                                    logger=logger)
        if remote_session_key is None:
            credential_validation = False
            addon_validation = False
            response = {"status": "LoginFailed", "message": "Could reach host, but login failed",
                        "credential_validation": credential_validation, "addon_validation": addon_validation}
        else:
            # Okay credentials are good, now we can check that the apps are there
            server_response, server_content = splunk_rest_request(path=vc_path + '/services/apps/local', logger=logger,
                                                                  local_session_key=local_session_key,
                                                                  session_key=remote_session_key,
                                                                  getargs={'count': '0'})
            apps = rest.format.parseFeedDocument(server_content)
            logger.info("Accessing apps")
            required_apps = ["Splunk_TA_vcenter"]
            installed_count = 0
            installed_apps = []
            for app in apps:
                contents = app.toPrimitive()
                if 'label' in contents:
                    installed_apps.append(contents['label'])
                if app.title in required_apps:
                    installed_count += 1

            credential_validation = True
            if installed_count == len(required_apps):
                addon_validation = True
                response = {"status": "valid", "message": "Everything is valid",
                            "credential_validation": credential_validation,
                            "addon_validation": addon_validation}
            else:
                logger.warning("vc forwarder did not have the required app- Splunk_TA_vcenter.")
                addon_validation = False
                response = {"status": "badapps", "message": "Username/password are good but app is not there",
                            "credential_validation": credential_validation,
                            "addon_validation": addon_validation}
    except ServerNotFoundError:
        response = {"status": "unreachable", "message": "Could not reach host",
                    "credential_validation": credential_validation,
                    "addon_validation": addon_validation}
    except splunk.SplunkdConnectionException:
        logger.error("Could not find splunkd on host=%s", vc_path)
        response = {"status": "unreachable", "message": "Could not reach host",
                    "credential_validation": credential_validation,
                    "addon_validation": addon_validation}
    except splunk.AuthenticationFailed:
        logger.error("Could not log into splunkd on host=%s, credentials are definitely bad", vc_path)
        response = {"status": "LoginFailed", "message": "Could not authenticate with remote splunkd",
                    "credential_validation": credential_validation,
                    "addon_validation": addon_validation}
    except Exception:
        logger.error("Could not log into splunkd on host=%s, due to an exception", vc_path)
        response = {"status": "LoginFailed", "message": "Could not reach host",
                    "credential_validation": credential_validation,
                    "addon_validation": addon_validation}

    return response


def toggle_vc_inputs(host_path, username, password, vc, local_session_key, pool_name, logger, disable=True):
    """
    toggle on or off all of the inputs in Splunk TA vCenter, default is disable

    RETURNS nothing
    """

    status = True
    try:
        remote_session_key = get_remote_session_key(username, password, local_session_key, host_path, logger)
    except ServerNotFoundError:
        logger.error("Could not find vc_splunk_forwarder=%s", host_path)
        remote_session_key = None
    except splunk.SplunkdConnectionException:
        logger.error("Could not find splunkd on vc_splunk_forwarder=%s", host_path)
        remote_session_key = None
    except splunk.AuthenticationFailed:
        logger.error("Could not log into splunkd on vc_splunk_forwarder=%s, credentials are definitely bad", host_path)
        remote_session_key = None
    except Exception as e:
        remote_session_key = None
        logger.error("Exception occured:{0}".format(e))

    if remote_session_key is None:
        logger.error(
            "[pool=%s]Could not log into vc_splunk_forwarder=%s with the credentials provided, cannot manage the inputs on that instance",
            pool_name, host_path)
        status = False
    else:

        uri = '/services/server/info?output_mode=json'
        info_path = host_path.rstrip("/") + uri
        server_response, server_content = splunk_rest_request(
            info_path, logger, local_session_key=local_session_key, session_key=remote_session_key)

        server_content_dict = json.loads(server_content)
        os = ""
        input_uris = [
            '/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%24ALLUSERSPROFILE%5CVMware%5CvCenterServer%5Clogs%5Cvws',
            '/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%24ALLUSERSPROFILE%5CVMware%5CvCenterServer%5Clogs%5Cvmware-vpx',
            '/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%24ALLUSERSPROFILE%5CVMware%5CvCenterServer%5Clogs%5Cperfcharts',
            '/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%2Fvar%2Flog%2Fvmware%2Fvws',
            '/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%2Fvar%2Flog%2Fvmware%2Fvpxd',
            '/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%2Fvar%2Flog%2Fvmware%2Fperfcharts'
        ]

        try:
            if server_content_dict:
                os = server_content_dict['entry'][0]['content']['os_name']
                logger.info("Splunk Machine OS:{0}".format(os))
        except Exception as e:
            logger.exception(
                "[pool={1}]Exception occurred while determining OS of the machine: {0}".format(e, pool_name))

        if os == "Windows":
            input_uris = [
                '/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%24ALLUSERSPROFILE%5CVMware%5CvCenterServer%5Clogs%5Cvws',
                '/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%24ALLUSERSPROFILE%5CVMware%5CvCenterServer%5Clogs%5Cvmware-vpx',
                '/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%24ALLUSERSPROFILE%5CVMware%5CvCenterServer%5Clogs%5Cperfcharts']
        elif os == "Linux":
            input_uris = ['/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%2Fvar%2Flog%2Fvmware%2Fvws',
                          '/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%2Fvar%2Flog%2Fvmware%2Fvpxd',
                          '/servicesNS/nobody/Splunk_TA_vcenter/data/inputs/monitor/%2Fvar%2Flog%2Fvmware%2Fperfcharts']

        for uri in input_uris:
            path = host_path.rstrip("/") + uri
            if disable:
                postargs = {"host": vc}
                action = "disable"
            else:
                postargs = {"host": vc}
                action = "enable"
            try:
                logger.info(
                    "Adjusting input with rest request on path={0} with postargs={1} and secondary action={2}".format(
                        path, postargs, action))
                splunk_rest_request(path, logger, local_session_key=local_session_key, session_key=remote_session_key,
                                    method="POST", postargs=postargs,
                                    raise_all_errors=True)
                path = path + "/" + action
                splunk_rest_request(path, logger, local_session_key=local_session_key, session_key=remote_session_key,
                                    method="POST", raise_all_errors=True)
            except ServerNotFoundError:
                logger.exception("[pool={1}]Could not reach vc_splunk_forwarder={0}", host_path, pool_name)
                status = False
            except Exception as e:
                message = "[pool={1}]Problem editing inputs on the remote vc_splunk_forwarder={0}: ".format(host_path,
                                                                                                            pool_name) + str(e)
                logger.exception(message)
                status = False
    return status


def delete_vcenter_stanza(vc_path, local_session_key, logger):
    """This function deletes vCenter stanza for given @vc_path.
    @vc_path - url of vCenter to delete."""
    # getting stanza to delete
    vc_stanza = TAVMwareCollectionStanza.from_name(vc_path, "Splunk_TA_vmware_inframon", host_path=local_host_path,
                                                   session_key=local_session_key)

    if not vc_stanza:
        logger.error("Could not find stanza for stanza_name={0} cannot delete".format(vc_path))
        raise RestError(404, "Could not find stanza for stanza_name={0} cannot delete".format(vc_path))
    else:
        vc = vc_stanza.target[0]
        username = vc_stanza.username
        logger.info("Deleting vc stanza_name=%s credentials for username=%s", vc_path, vc_stanza.username)

        pool_name = vc_stanza.pool_name

        if not vc_stanza.passive_delete():
            logger.exception("[pool={1}]Failed to delete vc collection stanza={0}".format(vc_path, pool_name))
            raise RestError(500, "Failed to delete vc collection stanza={0}".format(vc_path))

        stored_cred = SplunkStoredCredential.from_name(SplunkStoredCredential.build_name(vc, username),
                                                       app="Splunk_TA_vmware_inframon", owner="nobody",
                                                       host_path=local_host_path, session_key=local_session_key)
        if not stored_cred or not stored_cred.passive_delete():
            logger.error("Could not delete obsolete credential it may linger")

    return True


def delete_vcenter_forwarder_stanza(vc_path, local_session_key, pool_name, logger):
    """This function deletes forwarder stanza of given @vc_path
    @vc_path - url of vCenter to delete."""
    vc_forwarder_stanza = TAVMwareVCenterForwarderStanza.from_name(vc_path, "Splunk_TA_vmware_inframon", "nobody",
                                                                   session_key=local_session_key,
                                                                   host_path=local_host_path)
    if vc_forwarder_stanza:
        logger.info("Deleting vCenter forwarder stanza for the vc: {0}".format(vc_path))

        vc_splunk_uri = vc_forwarder_stanza.host
        vc_splunk_username = vc_forwarder_stanza.user

        logger.info("vc_splunk_uri: {0}, vc_splunk_username: {1}".format(vc_splunk_uri, vc_splunk_username))

        spl_stored_cred = SplunkStoredCredential.from_name(
            SplunkStoredCredential.build_name(vc_splunk_uri, vc_splunk_username), app="Splunk_TA_vmware_inframon",
            owner="nobody", host_path=local_host_path, session_key=local_session_key)

        vc_splunk_password = ""
        if spl_stored_cred:
            vc_splunk_password = spl_stored_cred.clear_password

        toggle_vc_inputs(vc_splunk_uri, vc_splunk_username, vc_splunk_password, vc_path,
                         local_session_key, pool_name, logger, disable=True)

        logger.info("Deleting forwarder Credentials.")

        if not vc_forwarder_stanza.passive_delete():
            logger.error(
                "[pool={2}]Failed to delete vCenter forwarder stanza:{0} for vc: {1}".format(vc_splunk_uri, vc_path,
                                                                                             pool_name))
        else:
            logger.info("[pool={2}]Forwarder stanza:{0} deleted for vc: {1}.".format(vc_splunk_uri, vc_path, pool_name))

        if not spl_stored_cred:
            logger.error("Credential Stanza of Forwarder for vc:{0} not found".format(vc_path))
            return False

        if not spl_stored_cred.passive_delete():
            logger.error(
                "Could not delete obsolete credential for vCenter forwarder:{0} it may linger".format(vc_splunk_uri))
        else:
            logger.info("Credential deleted for vCenter forwarder:{0}".format(vc_splunk_uri))

    else:
        logger.debug("No vCenter forwarder stanza found for vc: {0}.".format(vc_path))
        return False

    return True


def delete_dcn_stanza(node_path, local_session_key, logger):
    """

    :param node_path:
    :param local_session_key:
    :param logger:
    :return: It will return true in case of successful deletion of dcn stanza
    """
    node_stanza = HydraNodeStanza.from_name(node_path, "Splunk_TA_vmware_inframon", host_path=local_host_path,
                                            session_key=local_session_key)

    if node_stanza:
        node_username = node_stanza.user
        pool_name = node_stanza.pool_name
        stored_cred = SplunkStoredCredential.from_name(
            SplunkStoredCredential.build_name(node_stanza.host, node_username), app="Splunk_TA_vmware_inframon",
            owner="nobody", host_path=local_host_path,
            session_key=local_session_key)

        if stored_cred:
            node_password = stored_cred.clear_password
            logger.info("Deleting obsolete credential for node:{0}".format(node_stanza.host))
            if not stored_cred.passive_delete():
                logger.error(
                    "Could not delete obsolete credential it may linger")
            else:
                logger.info(
                    "Credentials deleted successfully of node {0}:".format(node_stanza.host))
        else:
            node_password = None

        if not node_stanza.passive_delete():
            logger.error("[pool={1}]Failed to delete node {0}".format(node_path, pool_name))
            raise RestError(500, "Failed to delete node {0}".format(node_path))
        else:
            logger.info("[pool={1}]Successfully deleted node: {0}".format(node_path, pool_name))
        # disabling of inputs only takes place when node is successfully deleted.
        # It will change after adding exceptions.
        # for disabling all the inputs(heads), heads is given as Zero.
        logger.info("Disabling inputs of deleted stanza: {0}".format(node_path))
        enable_heads_on_dcn(node_username, node_password, 0, local_session_key, node_path, pool_name, logger)

        return True
    else:
        logger.error(
            "Failed to find node {0}, cannot delete it".format(node_path))
        raise RestError(404, "Failed to find node {0}".format(node_path))


def set_conf_modification_time(pool_name, entity_type, local_session_key, logger):
    """"""
    pool_stanza = PoolStanza.from_name(pool_name, "Splunk_TA_vmware_inframon", host_path=local_host_path,
                                       session_key=local_session_key)
    if pool_stanza:
        if entity_type == "node":
            pool_stanza.node_modification_time = datetime.datetime.utcnow()
        elif entity_type == "collection":
            pool_stanza.collection_modification_time = datetime.datetime.utcnow()
        if not pool_stanza.passive_save():
            logger.error(
                "[pool={1}]Couldn't set the conf modification time property for type:{0} of the pool:{1}".format(
                    entity_type,
                    pool_name))
        else:
            logger.info(
                "[pool={1}]Updated the conf modification time property for type: {0} and pool: {1} ".format(entity_type,
                                                                                                            pool_name))

    else:
        logger.error("Could not find pool stanza for given pool name:{0}".format(pool_name))


def enable_heads_on_dcn(username, password, heads, local_session_key, node_path, pool_name, logger):
    """
    This function enables the head on dcn using given username, password, and local session key.
    It will enable the number of heads given in @heads parameter and disable the rest of the heads.
    So, when heads is given as zero, it will disable all the heads on that dcn.
    :param username: splunk username
    :param password: splunk password
    :param heads: no. of heads to enable on dcn (if heads is given as Zero, then it will disable all the heads on dcn.)
    :param local_session_key: session key of current session.
    :param node_path: management port url of a dcn.
    :return: it returns nothing.

    """
    try:
        remote_session_key = get_remote_session_key(username, password, local_session_key, node_path, logger)
    except ServerNotFoundError:
        logger.error("Could not find node=%s", node_path)
        remote_session_key = None
    except splunk.SplunkdConnectionException:
        logger.error("Could not find splunkd on node=%s", node_path)
        remote_session_key = None
    except splunk.AuthenticationFailed:
        logger.error("Could not log into splunkd on node=%s, credentials are definitely bad", node_path)
        remote_session_key = None
    except Exception as e:
        logger.exception(
            "could not log in to the node: {0} Exception occurred: {1} ".format(node_path, str(e)))
        remote_session_key = None

    if remote_session_key is None:
        logger.error(
            "[pool=%s]Could not log into node=%s with the credentials provided, cannot manage the heads on that node",
            pool_name, node_path)
    else:
        enable_head_status = True
        for counter in range(1, max_worker_process + 1):
            input_name = "worker_process{0}".format(counter)

            if counter <= int(heads):
                action = "enable"
            else:
                action = "disable"
            path = node_path.rstrip(
                "/") + "/servicesNS/nobody/Splunk_TA_vmware_inframon/data/inputs/ta_vmware_collection_worker_inframon/" + input_name + "/" + action
            try:
                logger.info("Adjusting input with rest request on path=%s with session_key=%s", path,
                            remote_session_key)
                splunk_rest_request(path, logger, local_session_key=local_session_key,
                                    session_key=remote_session_key, method="POST",
                                    raise_all_errors=True)
            except ServerNotFoundError:
                enable_head_status = False
                logger.exception(
                    "Problem editing the number of worker inputs on the remote node={0}, Could not reach node.".format(
                    node_path))
            except Exception as e:
                enable_head_status = False
                message = "Problem editing the number of worker inputs on the remote node={0}: ".format(
                    node_path) + str(e)
                logger.exception(message)

        if enable_head_status:
            logger.info("[pool={0}]Successfully adjusted input on node:{1}".format(pool_name, node_path))
        else:
            logger.error("[pool={0}]Problem editing the number of worker inputs on the remote node={1}.".format(
                pool_name, node_path))


def delete_template_stanza(template_name, local_session_key, logger):
    """
    :param template_name: name of the template to be deleted.
    :param local_session_key: session key of the current session
    :return: returns true if everything goes fine, It will delete the template stanza from inframon_ta_vmware_template.conf, if failed then it will throw an exception.
    """
    template_stanza = TemplateStanza.from_name(template_name, "Splunk_TA_vmware_inframon", host_path=local_host_path,
                                               session_key=local_session_key)

    if template_stanza:
        if not template_stanza.passive_delete():
            logger.error("[pool={0}]Failed to delete the template stanza={0}".format(template_name))
            raise RestError(500, "Failed to delete the template stanza={0}".format(template_name))
        else:
            logger.info("[pool={0}]template stanza successfully deleted for: {0}".format(template_name))
    else:
        logger.error("[Delete Pool] Failed to find the template stanza={0}, cannot delete it", template_name)
        raise RestError(404, "Failed to find the template stanza={0}".format(template_name))

    return True


def clear_session(node_path, username, password, local_session_key, logger):
    """
    It clears all the stanzas of inframon_hydra_session.conf for given node_path(uri of Node/DCN)
    :param node_path: node uri for clearing cache and session
    :param username: username of node
    :param password: password of node
    :param local_session_key: session key of current session
    :param logger:
    :return: it returns nothing
    """
    try:
        remote_session_key = get_remote_session_key(username, password, local_session_key, node_path, logger)
    except ServerNotFoundError:
        logger.error("Could not find node=%s", node_path)
        remote_session_key = None
    except splunk.SplunkdConnectionException:
        logger.error("Could not find splunkd on node=%s", node_path)
        remote_session_key = None
    except splunk.AuthenticationFailed:
        logger.error("Could not log into splunkd on node=%s, credentials are definitely bad", node_path)
        remote_session_key = None
    except Exception as e:
        remote_session_key = None
        logger.exception(e)

    if remote_session_key is None:
        logger.error(
            "Could not log into node=%s with the credentials provided, cannot clear session on a node.",
            node_path)
    else:
        session_stanzas = HydraSessionStanza.all(host_path=node_path, sessionKey=remote_session_key)
        session_stanzas = session_stanzas.filter_by_app("Splunk_TA_vmware_inframon")
        session_stanzas._owner = "nobody"
        logger.info("No. of session:{0} on Node:{1}".format(len(session_stanzas), node_path))
        for session_stanza in session_stanzas:
            session_name = session_stanza.name
            if not session_stanza.passive_delete():
                logger.error("Could not able to delete session_stanza={0}".format(str(session_stanza)))
            else:
                logger.info("session of: {0} for node: {1} successfully deleted.".format(session_name, node_path))


def get_node_password(node_stanza, local_session_key, logger):
    """

    :param node_stanza: stanza of node
    :param local_session_key:
    :return: it returns the credentials for the node of given node stanza
    """

    stored_cred = SplunkStoredCredential.from_name(
        SplunkStoredCredential.build_name(node_stanza.host, node_stanza.user), app="Splunk_TA_vmware_inframon",
        owner="nobody", host_path=local_host_path, session_key=local_session_key)

    password = ""
    if stored_cred:
        password = stored_cred.clear_password
    else:
        logger.error("Failed to find password for: {0} ".format(node_stanza.host))

    return password


def prepare_field_list(field_dict, ui_fields, field_type, logger):
    """

    :param field_dict: dictionary of field mapping
    :param ui_fields: List of UI fields
    :param field_type: Type of field
    :return: it maps the ui field with appropriate backend inv fields, and will return the backend names of
    corresponding UI fields.
    """
    inv_fields_set = set()

    for field in ui_fields:
        field_detail = field_dict[field_type][field]
        if field_detail["type"] == "inv":
            inv_fields_set.update(field_detail["req_metrics"])

    inv_fields = sorted(inv_fields_set)

    return inv_fields
