import os
import sys
import copy
import json
import logging
import time
import urlparse
from datetime import datetime, timedelta
from requests import ConnectionError
from requests import Response
from requests.exceptions import ChunkedEncodingError
from requests.sessions import Session
from ssl import SSLError


logger = logging.getLogger(__name__)

global sessionDict
sessionDict = {}


def avi_timedelta(td):
    """
    This is a wrapper class to workaround python 2.6 builtin datetime.timedelta
    does not have total_seconds method
    :param td timedelta object
    """
    if type(td) != timedelta:
        raise TypeError()
    if sys.version_info >= (2, 7):
        ts = td.total_seconds()
    else:
        ts = td.seconds + (24 * 3600 * td.days)
    return ts


def avi_sdk_syslog_logger(logger_name='avi.sdk'):
    # The following sets up syslog module to log underlying avi SDK messages
    # based on the environment variables:
    #   AVI_LOG_HANDLER: names the logging handler to use. Only syslog is
    #     supported.
    #   AVI_LOG_LEVEL: Logging level used for the avi SDK. Default is DEBUG
    #   AVI_SYSLOG_ADDRESS: Destination address for the syslog handler.
    #   Default is /dev/log
    from logging.handlers import SysLogHandler
    lf = '[%(asctime)s] %(levelname)s [' \
         '%(module)s.%(funcName)s:%(lineno)d] %(message)s'
    log = logging.getLogger(logger_name)
    log_level = os.environ.get('AVI_LOG_LEVEL', 'DEBUG')
    if log_level:
        log.setLevel(getattr(logging, log_level))
    formatter = logging.Formatter(lf)
    sh = SysLogHandler(address=os.environ.get('AVI_SYSLOG_ADDRESS',
                                              '/dev/log'))
    sh.setFormatter(formatter)
    log.addHandler(sh)
    return log


class ObjectNotFound(Exception):
    pass


class APIError(Exception):
    def __init__(self, arg, rsp=None):
        self.args = [arg, rsp]
        self.rsp = rsp


class AviServerError(APIError):
    def __init__(self, arg, rsp=None):
        super(AviServerError, self).__init__(arg, rsp)


class APINotImplemented(Exception):
    pass


class ApiResponse(Response):
    """
    Returns copy of the requests.Response object provides additional helper
    routines
        1. obj: returns dictionary of Avi Object
    """
    def __init__(self, rsp):
        super(ApiResponse, self).__init__()
        for k, v in list(rsp.__dict__.items()):
            setattr(self, k, v)

    def json(self):
        """
        Extends the session default json interface to handle special errors
        and raise Exceptions
        returns the Avi object as a dictionary from rsp.text
        """
        if self.status_code in (200, 201):
            if not self.text:
                # In cases like status_code == 201 the response text could be
                # empty string.
                return None
            return super(ApiResponse, self).json()
        elif self.status_code == 204:
            # No response needed; e.g., delete operation
            return None
        elif self.status_code == 404:
            raise ObjectNotFound('HTTP Error: %d Error Msg %s' % (
                self.status_code, self.text), self)
        elif self.status_code >= 500:
            raise AviServerError('HTTP Error: %d Error Msg %s' % (
                self.status_code, self.text), self)
        else:
            raise APIError('HTTP Error: %d Error Msg %s' % (
                self.status_code, self.text), self)

    def count(self):
        """
        return the number of objects in the collection response. If it is not
        a collection response then it would simply return 1.
        """
        obj = self.json()
        if 'count' in obj:
            # this was a resposne to collection
            return obj['count']
        return 1

    @staticmethod
    def to_avi_response(resp):
        if type(resp) == Response:
            return ApiResponse(resp)
        return resp


class AviCredentials(object):
    controller = ''
    username = ''
    password = ''
    api_version = '16.4.4'
    tenant = None
    tenant_uuid = None
    token = None
    port = None
    timeout = 300
    session_id = None
    csrftoken = None

    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

    def update_from_ansible_module(self, m):
        """
        :param m: ansible module
        :return:
        """
        if m.params.get('avi_credentials'):
            for k, v in m.params['avi_credentials'].items():
                if hasattr(self, k):
                    setattr(self, k, v)
        if m.params['controller']:
            self.controller = m.params['controller']
        if m.params['username']:
            self.username = m.params['username']
        if m.params['password']:
            self.password = m.params['password']
        if (m.params['api_version'] and
                (m.params['api_version'] != '16.4.4')):
            self.api_version = m.params['api_version']
        if m.params['tenant']:
            self.tenant = m.params['tenant']
        if m.params['tenant_uuid']:
            self.tenant_uuid = m.params['tenant_uuid']
        if m.params.get('session_id'):
            self.session_id = m.params['session_id']
        if m.params.get('csrftoken'):
            self.csrftoken = m.params['csrftoken']

    def __str__(self):
        return 'controller %s user %s api %s tenant %s' % (
            self.controller, self.username, self.api_version, self.tenant)


class ApiSession(Session):
    """
    Extends the Request library's session object to provide helper
    utilities to work with Avi Controller like authentication, api massaging
    etc.
    """

    # This keeps track of the process which created the cache.
    # At anytime the pid of the process changes then it would create
    # a new cache for that process.
    AVI_SLUG = 'Slug'
    SESSION_CACHE_EXPIRY = 20*60
    SHARED_USER_HDRS = ['X-CSRFToken', 'Session-Id', 'Referer', 'Content-Type']
    MAX_API_RETRIES = 3

    def __init__(self, controller_ip=None, username=None, password=None,
                 token=None, tenant=None, tenant_uuid=None, verify=False,
                 port=None, timeout=60, api_version=None,
                 retry_conxn_errors=True, data_log=False,
                 avi_credentials=None, session_id=None, csrftoken=None,
                 lazy_authentication=False, max_api_retries=None):
        """
         ApiSession takes ownership of avi_credentials and may update the
         information inside it.

        Initialize new session object with authenticated token from login api.
        It also keeps a cache of user sessions that are cleaned up if inactive
        for more than 20 mins.

        Notes:
        01. If mode is https and port is none or 443, we don't embed the
            port in the prefix. The prefix would be 'https://ip'. If port
            is a non-default value then we concatenate https://ip:port
            in the prefix.
        02. If mode is http and the port is none or 80, we don't embed the
            port in the prefix. The prefix would be 'http://ip'. If port is
            a non-default value, then we concatenate http://ip:port in
            the prefix.
        """
        super(ApiSession, self).__init__()
        logger.debug("Creating session with following values:\n "
                     "controller_ip: %s, username: %s, tenant: %s, "
                     "tenant_uuid: %s, verify: %s, port: %s, timeout: %s, "
                     "api_version: %s, retry_conxn_errors: %s, data_log: %s,"
                     "avi_credentials: %s, session_id: %s, csrftoken: %s,"
                     "lazy_authentication: %s, max_api_retries: %s"
                     % (controller_ip, username, tenant,
                        tenant_uuid, verify, port,
                        timeout, api_version, retry_conxn_errors,
                        data_log, avi_credentials, session_id,
                        csrftoken, lazy_authentication, max_api_retries))
        if not avi_credentials:
            tenant = tenant if tenant else "admin"
            self.avi_credentials = AviCredentials(
                controller=controller_ip, username=username,
                password=password, api_version=api_version,
                tenant=tenant, tenant_uuid=tenant_uuid,
                token=token, port=port, timeout=timeout,
                session_id=session_id, csrftoken=csrftoken)
        else:
            self.avi_credentials = avi_credentials
        self.headers = {}
        self.verify = verify
        self.retry_conxn_errors = retry_conxn_errors
        self.remote_api_version = {}
        self.user_hdrs = {}
        self.data_log = data_log
        self.num_session_retries = 0
        self.num_api_retries = 0
        self.retry_wait_time = 0
        self.max_session_retries = (
            self.MAX_API_RETRIES if max_api_retries is None
            else int(max_api_retries))
        # Refer Notes 01 and 02
        k_port = port if port else 443
        if self.avi_credentials.controller.startswith('http'):
            k_port = 80 if not self.avi_credentials.port else k_port
            if self.avi_credentials.port is None or \
                    self.avi_credentials.port == 80:
                self.prefix = self.avi_credentials.controller
            else:
                self.prefix = '{x}:{y}'.format(
                    x=self.avi_credentials.controller,
                    y=self.avi_credentials.port)
        else:
            if port is None or port == 443:
                self.prefix = 'https://{x}'.format(
                    x=self.avi_credentials.controller)
            else:
                self.prefix = 'https://{x}:{y}'.format(
                    x=self.avi_credentials.controller,
                    y=self.avi_credentials.port)
        self.timeout = timeout
        self.key = '%s:%s:%s' % (self.avi_credentials.controller,
                                 self.avi_credentials.username, k_port)
        # Added api token and session id to sessionDict for handle single
        # session
        if self.avi_credentials.csrftoken:
            sessionDict[self.key] = {
                'api': self,
                "csrftoken": self.avi_credentials.csrftoken,
                "session_id": self.avi_credentials.session_id,
                "last_used": datetime.utcnow()
            }
        elif lazy_authentication:
            sessionDict.get(self.key, {}).update(
                {'api': self, "last_used": datetime.utcnow()})
        else:
            self.authenticate_session()
        self.num_session_retries = 0
        self.pid = os.getpid()
        ApiSession._clean_inactive_sessions()
        return

    @property
    def controller_ip(self):
        return self.avi_credentials.controller

    @controller_ip.setter
    def controller_ip(self, controller_ip):
        self.avi_credentials.controller = controller_ip

    @property
    def username(self):
        return self.avi_credentials.username

    @property
    def connected(self):
        return sessionDict.get(self.key, {}).get('connected', False)

    @username.setter
    def username(self, username):
        self.avi_credentials.username = username

    @property
    def password(self):
        return self.avi_credentials.password

    @password.setter
    def password(self, password):
        self.avi_credentials.password = password

    @property
    def keystone_token(self):
        return sessionDict.get(self.key, {}).get('csrftoken', None)

    @keystone_token.setter
    def keystone_token(self, token):
        sessionDict[self.key]['csrftoken'] = token

    @property
    def tenant_uuid(self):
        self.avi_credentials.tenant_uuid

    @tenant_uuid.setter
    def tenant_uuid(self, tenant_uuid):
        self.avi_credentials.tenant_uuid = tenant_uuid

    @property
    def tenant(self):
        return self.avi_credentials.tenant

    @tenant.setter
    def tenant(self, tenant):
        if tenant:
            self.avi_credentials.tenant = tenant
        else:
            self.avi_credentials.tenant = 'admin'

    @property
    def port(self):
        self.avi_credentials.port

    @port.setter
    def port(self, port):
        self.avi_credentials.port = port

    @property
    def api_version(self):
        return self.avi_credentials.api_version

    @api_version.setter
    def api_version(self, api_version):
        self.avi_credentials.api_version = api_version

    @property
    def session_id(self):
        return sessionDict[self.key]['session_id']

    def get_context(self):
        return {
            'session_id': sessionDict[self.key]['session_id'],
            'csrftoken': sessionDict[self.key]['csrftoken']
        }

    @staticmethod
    def clear_cached_sessions():
        global sessionDict
        sessionDict = {}

    @staticmethod
    def get_session(
            controller_ip=None, username=None, password=None, token=None,
            tenant=None, tenant_uuid=None, verify=False, port=None, timeout=60,
            retry_conxn_errors=True, api_version=None, data_log=False,
            avi_credentials=None, session_id=None, csrftoken=None,
            lazy_authentication=False, max_api_retries=None, idp_class=None):
        """
        returns the session object for same user and tenant
        calls init if session dose not exist and adds it to session cache
        :param controller_ip: controller IP address
        :param username:
        :param password:
        :param token: Token to use; example, a valid keystone token
        :param tenant: Name of the tenant on Avi Controller
        :param tenant_uuid: Don't specify tenant when using tenant_id
        :param port: Rest-API may use a different port other than 443
        :param timeout: timeout for API calls; Default value is 60 seconds
        :param retry_conxn_errors: retry on connection errors
        :param api_version: Controller API version
        :param idp_class: IDP class. Currently supports OKtaSAMLApiSession,
        OneloginApiSession
        """

        if not idp_class:
            idp_class = ApiSession
        else:
            if not ("ApiSession" in str(idp_class.__base__)):
                raise APIError("idp_class {} not valid class. Please provide "
                               "correct idp class. Base class of idp class is "
                               "{}".format(idp_class, str(idp_class.__base__)))
        # Validate input idp_class
        if not avi_credentials:
            tenant = tenant if tenant else "admin"
            avi_credentials = AviCredentials(
                controller=controller_ip, username=username,
                password=password, api_version=api_version,
                tenant=tenant, tenant_uuid=tenant_uuid,
                token=token, port=port, timeout=timeout,
                session_id=session_id, csrftoken=csrftoken)

        k_port = avi_credentials.port if avi_credentials.port else 443
        if avi_credentials.controller.startswith('http'):
            k_port = 80 if not avi_credentials.port else k_port
        key = '%s:%s:%s' % (avi_credentials.controller,
                            avi_credentials.username, k_port)
        cached_session = sessionDict.get(key)
        if cached_session:
            user_session = cached_session['api']
            if not (user_session.avi_credentials.csrftoken or
                    lazy_authentication):
                user_session.authenticate_session()
        else:
            user_session = idp_class(
                controller_ip, username, password, token=token,
                tenant=tenant, tenant_uuid=tenant_uuid,
                verify=verify, port=port, timeout=timeout,
                retry_conxn_errors=retry_conxn_errors,
                api_version=api_version, data_log=data_log,
                avi_credentials=avi_credentials,
                lazy_authentication=lazy_authentication,
                max_api_retries=max_api_retries)
        return user_session

    def reset_session(self):
        """
        resets and re-authenticates the current session.
        """
        sessionDict[self.key]['connected'] = False
        logger.info('resetting session for %s', self.key)
        self.user_hdrs = {}
        for k, v in self.headers.items():
            if k not in self.SHARED_USER_HDRS:
                self.user_hdrs[k] = v
        self.headers = self.user_hdrs
        self.authenticate_session()

    def authenticate_session(self):
        """
        Performs session authentication with Avi controller and stores
        session cookies and sets header options like tenant.
        """
        body = {"username": self.avi_credentials.username}
        if self.avi_credentials.password:
            body["password"] = self.avi_credentials.password
        elif self.avi_credentials.token:
            body["token"] = self.avi_credentials.token
        else:
            raise APIError("Neither user password or token provided")
        logger.debug('authenticating user %s prefix %s',
                     self.avi_credentials.username, self.prefix)
        self.cookies.clear()
        err = None
        try:
            rsp = super(ApiSession, self).post(
                self.prefix+"/login", body, timeout=self.timeout,
                verify=self.verify)

            if rsp.status_code == 200:
                self.num_session_retries = 0
                self.remote_api_version = rsp.json().get('version', {})
                session_cookie_name = rsp.json().get(
                    'session_cookie_name', 'sessionid')
                self.headers.update(self.user_hdrs)
                if rsp.cookies and 'csrftoken' in rsp.cookies:
                    csrftoken = rsp.cookies['csrftoken']
                    sessionDict[self.key] = {
                        'csrftoken': csrftoken,
                        'session_id': rsp.cookies[session_cookie_name],
                        'last_used': datetime.utcnow(),
                        'api': self,
                        'connected': True
                    }
                    self.avi_credentials.csrftoken = csrftoken
                    self.avi_credentials.session_id = rsp.cookies[
                        session_cookie_name]
                logger.debug("authentication success for user %s",
                             self.avi_credentials.username)
                return
            # Check for bad request and invalid credentials response code
            elif rsp.status_code in [401, 403]:
                logger.error('Status Code %s msg %s' % (
                    rsp.status_code, rsp.text))
                err = APIError('Status Code %s msg %s' % (
                    rsp.status_code, rsp.text), rsp)
                raise err
            else:
                logger.error("Error status code %s msg %s", rsp.status_code,
                             rsp.text)
                err = APIError('Status Code %s msg %s' % (
                    rsp.status_code, rsp.text), rsp)
                raise err
        except (ConnectionError, SSLError, ChunkedEncodingError) as e:
            if not self.retry_conxn_errors:
                raise
            logger.warning('Connection error retrying %s', e)
            err = e
        # comes here only if there was either exception or login was not
        # successful
        if self.retry_wait_time:
            time.sleep(self.retry_wait_time)
        self.num_session_retries += 1
        if self.num_session_retries > self.max_session_retries:
            self.num_session_retries = 0
            logger.error("giving up after %d retries connection failure %s" % (
                self.max_session_retries, True))
            raise err
        self.authenticate_session()
        return

    def _get_api_headers(self, tenant, tenant_uuid, timeout, headers,
                         api_version):
        """
        returns the headers that are passed to the requests.Session api calls.
        """
        api_hdrs = copy.deepcopy(self.headers)
        api_hdrs.update({
            "Referer": self.prefix,
            "Content-Type": "application/json"
        })
        api_hdrs['timeout'] = str(timeout)
        if self.key in sessionDict and 'csrftoken' in \
                sessionDict.get(self.key):
            api_hdrs['X-CSRFToken'] = sessionDict.get(self.key)['csrftoken']
        else:
            self.authenticate_session()
            api_hdrs['X-CSRFToken'] = sessionDict.get(self.key)['csrftoken']
        if api_version:
            api_hdrs['X-Avi-Version'] = api_version
        elif self.avi_credentials.api_version:
            api_hdrs['X-Avi-Version'] = self.avi_credentials.api_version
        if tenant:
            tenant_uuid = None
        elif tenant_uuid:
            tenant = None
        else:
            tenant = self.avi_credentials.tenant
            tenant_uuid = self.avi_credentials.tenant_uuid
        if tenant_uuid:
            api_hdrs.update({"X-Avi-Tenant-UUID": "%s" % tenant_uuid})
            api_hdrs.pop("X-Avi-Tenant", None)
        elif tenant:
            api_hdrs.update({"X-Avi-Tenant": "%s" % tenant})
            api_hdrs.pop("X-Avi-Tenant-UUID", None)
        # Override any user headers that were passed by users. We don't know
        # when the user had updated the user_hdrs
        if self.user_hdrs:
            api_hdrs.update(self.user_hdrs)
        if headers:
            # overwrite the headers passed via the API calls.
            api_hdrs.update(headers)
        return api_hdrs

    def _api(self, api_name, path, tenant, tenant_uuid, data=None,
             headers=None, timeout=None, api_version=None, **kwargs):
        """
        It calls the requests.Session APIs and handles session expiry
        and other situations where session needs to be reset.
        returns ApiResponse object
        :param path: takes relative path to the AVI api.
        :param tenant: overrides the tenant used during session creation
        :param tenant_uuid: overrides the tenant or tenant_uuid during session
            creation
        :param timeout: timeout for API calls; Default value is 60 seconds
        :param headers: dictionary of headers that override the session
            headers.
        """
        if self.pid != os.getpid():
            logger.info('pid %d change detected new %d. Closing session',
                        self.pid, os.getpid())
            self.close()
            self.pid = os.getpid()
        if timeout is None:
            timeout = self.timeout
        fullpath = self._get_api_path(path)
        fn = getattr(super(ApiSession, self), api_name)
        api_hdrs = self._get_api_headers(tenant, tenant_uuid, timeout, headers,
                                         api_version)
        connection_error = False
        err = None
        cookies = {
            'csrftoken': api_hdrs['X-CSRFToken'],
        }
        try:
            sessionid = sessionDict[self.key]['session_id']
            cookies['sessionid'] = sessionid
            cookies['avi-sessionid'] = sessionid
        except KeyError:
            pass
        try:
            if (data is not None) and (type(data) == dict):
                resp = fn(fullpath, data=json.dumps(data), headers=api_hdrs,
                          timeout=timeout, cookies=cookies, **kwargs)
            else:
                resp = fn(fullpath, data=data, headers=api_hdrs,
                          timeout=timeout, cookies=cookies, **kwargs)
        except (ConnectionError, SSLError, ChunkedEncodingError) as e:
            logger.warning('Connection error retrying %s', e)
            if not self.retry_conxn_errors:
                raise
            connection_error = True
            err = e
        except Exception as e:
            logger.error('Error in Requests library %s', e)
            raise
        if not connection_error:
            logger.debug('path: %s http_method: %s hdrs: %s params: '
                         '%s data: %s rsp: %s', fullpath, api_name.upper(),
                         api_hdrs, kwargs, data,
                         (resp.text if self.data_log else 'None'))
        if connection_error or resp.status_code in (401, 419):
            if connection_error:
                try:
                    self.close()
                except Exception as e:
                    # ignoring exception in cleanup path
                    pass
                logger.warning('Connection failed, retrying.')
                # Adding sleep before retrying
                if self.retry_wait_time:
                    time.sleep(self.retry_wait_time)
            else:
                logger.info('received error %d %s so resetting connection',
                            resp.status_code, resp.text)
            ApiSession.reset_session(self)
            self.num_api_retries += 1
            if self.num_api_retries > self.max_session_retries:
                # Added this such that any code which re-tries can succeed
                # eventually.
                self.num_api_retries = 0
                if not connection_error:
                    err = APIError('Status Code %s msg %s' % (
                        resp.status_code, resp.text), resp)
                logger.error(
                    "giving up after %d retries conn failure %s err %s" % (
                        self.max_session_retries, connection_error, err))
                raise err
            # should restore the updated_hdrs to one passed down
            resp = self._api(api_name, path, tenant, tenant_uuid, data,
                             headers=headers, api_version=api_version,
                             timeout=timeout, **kwargs)
            self.num_api_retries = 0

        if resp.cookies and 'csrftoken' in resp.cookies:
            csrftoken = resp.cookies['csrftoken']
            self.headers.update({"X-CSRFToken": csrftoken})
        self._update_session_last_used()
        return ApiResponse.to_avi_response(resp)

    def get_controller_details(self):
        result = {
            "controller_ip": self.controller_ip,
            "controller_api_version": self.remote_api_version
        }
        return result

    def get(self, path, tenant='', tenant_uuid='', timeout=None, params=None,
            api_version=None, **kwargs):
        """
        It extends the Session Library interface to add AVI API prefixes,
        handle session exceptions related to authentication and update
        the global user session cache.
        :param path: takes relative path to the AVI api.
        :param tenant: overrides the tenant used during session creation
        :param tenant_uuid: overrides the tenant or tenant_uuid during session
            creation
        :param timeout: timeout for API calls; Default value is 60 seconds
        :param params: dictionary of key value pairs to be sent as query
            parameters
        :param api_version: overrides x-avi-header in request header during
            session creation
        get method takes relative path to service and kwargs as per Session
            class get method
        returns session's response object
        """
        return self._api('get', path, tenant, tenant_uuid, timeout=timeout,
                         params=params, api_version=api_version, **kwargs)

    def get_object_by_name(self, path, name, tenant='', tenant_uuid='',
                           timeout=None, params=None, api_version=None,
                           **kwargs):
        """
        Helper function to access Avi REST Objects using object
        type and name. It behaves like python dictionary interface where it
        returns None when the object is not present in the AviController.
        Internally, it transforms the request to api/path?name=<name>...
        :param path: relative path to service
        :param name: name of the object
        :param tenant: overrides the tenant used during session creation
        :param tenant_uuid: overrides the tenant or tenant_uuid during session
            creation
        :param timeout: timeout for API calls; Default value is 60 seconds
        :param params: dictionary of key value pairs to be sent as query
            parameters
        :param api_version: overrides x-avi-header in request header during
            session creation
        returns dictionary object if successful else None
        """
        obj = None
        if not params:
            params = {}
        params['name'] = name
        resp = self.get(path, tenant=tenant, tenant_uuid=tenant_uuid,
                        timeout=timeout,
                        params=params, api_version=api_version, **kwargs)
        if resp.status_code in (401, 419):
            ApiSession.reset_session(self)
            resp = self.get_object_by_name(
                path, name, tenant, tenant_uuid, timeout=timeout,
                params=params, **kwargs)
        if resp.status_code > 499 or 'Invalid version' in resp.text:
            logger.error('Error in get object by name for %s named %s. '
                         'Error: %s' % (path, name, resp.text))
            raise AviServerError(resp.text, rsp=resp)
        elif resp.status_code > 299:
            return obj
        try:
            if 'results' in resp.json():
                obj = resp.json()['results'][0]
            else:
                # For apis returning single object eg. api/cluster
                obj = resp.json()
        except IndexError:
            logger.warning('Warning: Object Not found for %s named %s' %
                           (path, name))
            obj = None
        self._update_session_last_used()
        return obj

    def post(self, path, data=None, tenant='', tenant_uuid='', timeout=None,
             force_uuid=None, params=None, api_version=None, **kwargs):
        """
        It extends the Session Library interface to add AVI API prefixes,
        handle session exceptions related to authentication and update
        the global user session cache.
        :param path: takes relative path to the AVI api.It is modified by
        the library to conform to AVI Controller's REST API interface
        :param data: dictionary of the data. Support for json string
            is deprecated
        :param tenant: overrides the tenant used during session creation
        :param tenant_uuid: overrides the tenant or tenant_uuid during session
            creation
        :param timeout: timeout for API calls; Default value is 60 seconds
        :param params: dictionary of key value pairs to be sent as query
            parameters
        :param api_version: overrides x-avi-header in request header during
            session creation
        returns session's response object
        """
        if force_uuid is not None:
            headers = kwargs.get('headers', {})
            headers[self.AVI_SLUG] = force_uuid
            kwargs['headers'] = headers
        return self._api('post', path, tenant, tenant_uuid, data=data,
                         timeout=timeout, params=params,
                         api_version=api_version, **kwargs)

    def put(self, path, data=None, tenant='', tenant_uuid='',
            timeout=None, params=None, api_version=None, **kwargs):
        """
        It extends the Session Library interface to add AVI API prefixes,
        handle session exceptions related to authentication and update
        the global user session cache.
        :param path: takes relative path to the AVI api.It is modified by
            the library to conform to AVI Controller's REST API interface
        :param data: dictionary of the data. Support for json string
            is deprecated
        :param tenant: overrides the tenant used during session creation
        :param tenant_uuid: overrides the tenant or tenant_uuid during session
            creation
        :param timeout: timeout for API calls; Default value is 60 seconds
        :param params: dictionary of key value pairs to be sent as query
            parameters
        :param api_version: overrides x-avi-header in request header during
            session creation
        returns session's response object
        """
        return self._api('put', path, tenant, tenant_uuid, data=data,
                         timeout=timeout, params=params,
                         api_version=api_version, **kwargs)

    def patch(self, path, data=None, tenant='', tenant_uuid='',
              timeout=None, params=None, api_version=None, **kwargs):
        """
        It extends the Session Library interface to add AVI API prefixes,
        handle session exceptions related to authentication and update
        the global user session cache.
        :param path: takes relative path to the AVI api.It is modified by
            the library to conform to AVI Controller's REST API interface
        :param data: dictionary of the data. Support for json string
            is deprecated
        :param tenant: overrides the tenant used during session creation
        :param tenant_uuid: overrides the tenant or tenant_uuid during session
            creation
        :param timeout: timeout for API calls; Default value is 60 seconds
        :param params: dictionary of key value pairs to be sent as query
            parameters
        :param api_version: overrides x-avi-header in request header during
            session creation
        returns session's response object
        """
        return self._api('patch', path, tenant, tenant_uuid, data=data,
                         timeout=timeout, params=params,
                         api_version=api_version, **kwargs)

    def put_by_name(self, path, name, data=None, tenant='',
                    tenant_uuid='', timeout=None, params=None,
                    api_version=None, **kwargs):
        """
        Helper function to perform HTTP PUT on Avi REST Objects using object
        type and name.
        Internally, it transforms the request to api/path?name=<name>...
        :param path: relative path to service
        :param name: name of the object
        :param data: dictionary of the data. Support for json string
            is deprecated
        :param tenant: overrides the tenant used during session creation
        :param tenant_uuid: overrides the tenant or tenant_uuid during session
            creation
        :param timeout: timeout for API calls; Default value is 60 seconds
        :param params: dictionary of key value pairs to be sent as query
            parameters
        :param api_version: overrides x-avi-header in request header during
            session creation
        returns session's response object
        """
        uuid = self._get_uuid_by_name(
            path, name, tenant, tenant_uuid, api_version=api_version)
        path = '%s/%s' % (path, uuid)
        return self.put(path, data, tenant, tenant_uuid, timeout=timeout,
                        params=params, api_version=api_version, **kwargs)

    def delete(self, path, tenant='', tenant_uuid='',
               timeout=None, params=None, data=None,
               api_version=None, **kwargs):
        """
        It extends the Session Library interface to add AVI API prefixes,
        handle session exceptions related to authentication and update
        the global user session cache.
        :param path: takes relative path to the AVI api.It is modified by
        the library to conform to AVI Controller's REST API interface
        :param tenant: overrides the tenant used during session creation
        :param tenant_uuid: overrides the tenant or tenant_uuid during session
            creation
        :param timeout: timeout for API calls; Default value is 60 seconds
        :param params: dictionary of key value pairs to be sent as query
            parameters
        :param data: dictionary of the data. Support for json string
            is deprecated
        :param api_version: overrides x-avi-header in request header during
            session creation
        returns session's response object
        """
        return self._api('delete', path, tenant, tenant_uuid, data=data,
                         timeout=timeout, params=params,
                         api_version=api_version, **kwargs)

    def delete_by_name(self, path, name, tenant='', tenant_uuid='',
                       timeout=None, params=None, api_version=None, **kwargs):
        """
        Helper function to perform HTTP DELETE on Avi REST Objects using object
        type and name.Internally, it transforms the request to
        api/path?name=<name>...
        :param path: relative path to service
        :param name: name of the object
        :param tenant: overrides the tenant used during session creation
        :param tenant_uuid: overrides the tenant or tenant_uuid during session
            creation
        :param timeout: timeout for API calls; Default value is 60 seconds
        :param params: dictionary of key value pairs to be sent as query
            parameters
        :param api_version: overrides x-avi-header in request header during
            session creation
        returns session's response object
        """
        uuid = self._get_uuid_by_name(path, name, tenant, tenant_uuid,
                                      api_version=api_version)
        if not uuid:
            raise ObjectNotFound("%s/?name=%s" % (path, name))
        path = '%s/%s' % (path, uuid)
        return self.delete(path, tenant, tenant_uuid, timeout=timeout,
                           params=params, api_version=api_version, **kwargs)

    def get_obj_ref(self, obj):
        """returns reference url from dict object"""
        if not obj:
            return None
        if isinstance(obj, Response):
            obj = json.loads(obj.text)
        if obj.get(0, None):
            return obj[0]['url']
        elif obj.get('url', None):
            return obj['url']
        elif obj.get('results', None):
            return obj['results'][0]['url']
        else:
            return None

    def get_obj_uuid(self, obj):
        """returns uuid from dict object"""
        if not obj:
            raise ObjectNotFound('Object %s Not found' % (obj))
        if isinstance(obj, Response):
            obj = json.loads(obj.text)
        if obj.get('uuid', None):
            return obj['uuid'].split('#')[0]
        elif obj.get('results', None):
            return obj['results'][0]['uuid'].split('#')[0]
        else:
            return None

    def _paginator(self, *args, **kwargs):
        """
        This is a private method to simulate an iterator
        on top of the pagination functionality provided by Avi Controller.

        :param *args: Accepts all args accepted by get method of class
            ApiSession.
        :params **kwargs: Accepts all kwargs accepted by get method of class
            ApiSession.

        Yields batch of objects in the range of 0 to page_size passed in kwargs.
        """
        resp = self.get(*args, **kwargs).json()
        if 'results' in resp:
            yield resp['results']
        else:
            # For apis returning single object eg. api/cluster
             yield [resp]

        page = 2 # Initialized to 2 for new page requests
        while resp.get('next', None) is not None:
            kwargs['params']['page'] = page
            resp = self.get(*args, **kwargs).json()
            page += 1
            yield resp['results']

    def get_objects_iter(self, obj_type, tenant='', tenant_uuid='',
                         timeout=None, params=None, api_version=None, **kwargs):
        """
        Iterator to fetch objects of any type from Avi Controller.
        By default, 100 objects are fetched in every batch. However, user can
        override this behaviour by passing page_size as params.

        Irrespective of page_size the method will yield only one object for
        each iteration.

        :param obj_type: Type of object that needs to be fetched from
			Avi Controller. Ex: pool, virtualservice
        :param tenant: overrides the tenant used during session creation.
        :param tenant_uuid: overrides the tenant or tenant_uuid during session
            creation.
        :param timeout: timeout for API calls; Default value is 60 seconds.
        :param params: dictionary of key value pairs to be sent as query
            parameters.
        :param api_version: overrides x-avi-header in request header during
            session creation.
        :param **kwargs: Accepts any other params accepted by Session class get
			method.

        Yields objects of type obj_type one by one. Example

        >>> # Looping over get_objects_iter
        >>> all_pools_iter = get_objects_iter('pool', params={'fields':'name'})
        >>> for pool in all_pools_iter:
        ...  #Do something with pool
        ...  print(pool)
        {u'name': u'pool-avi',
        u'server_count': 0,
        u'url': u'https://192.10.20.30/api/pool/<uuid>',
        u'uuid': u'<uuid>'}
        """
        if params is None:
            params={}

        if params.get('page_size', None) is None:
            params['page_size'] = 100

        page_size = params.get('page_size')
        if int(page_size) > 200:
            raise ValueError('page_size cannot be more than 200')
        pages_iter =  self._paginator(obj_type, tenant=tenant, params=params,
                                      tenant_uuid=tenant_uuid, timeout=timeout,
                                      api_version=api_version, **kwargs)
        for page in pages_iter:
            for obj in page:
                yield obj

    def get_slug_from_uri(self, uri):
        """return uuid/slug from URI"""
        if not uri or '/' not in uri:
            return uri
        parsed = urlparse.urlparse(uri)
        path = parsed.path
        uuid = os.path.basename(path)
        if '#' in uuid:
            uuid = uuid.split('#')[0]
        return uuid

    def _get_api_path(self, path, uuid=None):
        """
        This function returns the full url from relative path and uuid.
        """
        if path == 'logout':
            return self.prefix+'/'+path
        elif uuid:
            return self.prefix+'/api/'+path+'/'+uuid
        else:
            return self.prefix+'/api/'+path

    def _get_uuid_by_name(self, path, name, tenant='admin',
                          tenant_uuid='', api_version=None):
        """gets object by name and service path and returns uuid"""
        resp = self.get_object_by_name(
            path, name, tenant, tenant_uuid, api_version=api_version)
        if not resp:
            raise ObjectNotFound("%s/%s" % (path, name))
        return self.get_obj_uuid(resp)

    def _update_session_last_used(self):
        if self.key in sessionDict:
            sessionDict[self.key]["last_used"] = datetime.utcnow()

    @staticmethod
    def _clean_inactive_sessions():
        """Removes sessions which are inactive more than 20 min"""
        session_cache = sessionDict
        logger.debug("cleaning inactive sessions in pid %d num elem %d",
                     os.getpid(), len(session_cache))
        for key, session in list(session_cache.items()):
            tdiff = avi_timedelta(datetime.utcnow() - session["last_used"])
            if tdiff < ApiSession.SESSION_CACHE_EXPIRY:
                continue
            try:
                session["api"].post("logout")
            except Exception as e:
                logger.warning("Session not found on controller "
                               "for session ID: %s %s",
                               session, e)
                pass
            del session_cache[key]
            logger.debug("Cleaned inactive session : %s", key)

    def delete_session(self):
        """ Removes the session for cleanup"""
        logger.debug("Removed session for : %s", self.key)
        sessionDict.pop(self.key, None)
        return
# End of file
