# Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/
# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

# Copyright 2017 IBM Corp. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
import traceback
import time
import datetime
try:
    import http.client as httplib
except ImportError:
    import httplib
import json
import logging
import os
import getpass
import requests
import threading
from collections import namedtuple

from dateutil.parser import parse
from dateutil.tz import tzlocal

import ibm_botocore.configloader
import ibm_botocore.compat
from ibm_botocore.compat import total_seconds
from ibm_botocore.exceptions import UnknownCredentialError
from ibm_botocore.exceptions import PartialCredentialsError
from ibm_botocore.exceptions import ConfigNotFound
from ibm_botocore.exceptions import InvalidConfigError
from ibm_botocore.exceptions import RefreshWithMFAUnsupportedError
from ibm_botocore.exceptions import MetadataRetrievalError
from ibm_botocore.exceptions import CredentialRetrievalError
from ibm_botocore.utils import InstanceMetadataFetcher, parse_key_val_file
from ibm_botocore.utils import ContainerMetadataFetcher

logger = logging.getLogger(__name__)
ReadOnlyCredentials = namedtuple('ReadOnlyCredentials',
                                 ['access_key', 'secret_key', 'token'])

def create_credential_resolver(session):
    """Create a default credential resolver.

    This creates a pre-configured credential resolver
    that includes the default lookup chain for
    credentials.

    """
    profile_name = session.get_config_variable('profile') or 'default'
    credential_file = session.get_config_variable('credentials_file')
    cos_credentials_file = session.get_config_variable('cos_credentials_file')
    config_file = session.get_config_variable('config_file')
    metadata_timeout = session.get_config_variable('metadata_service_timeout')
    num_attempts = session.get_config_variable('metadata_service_num_attempts')

    env_provider = EnvProvider()
    cos_provider = IbmCosCredentialsProvider(ibm_credentials_filename=cos_credentials_file)

    providers = [
        env_provider,
        cos_provider,
        AssumeRoleProvider(
            load_config=lambda: session.full_config,
            client_creator=session.create_client,
            cache={},
            profile_name=profile_name,
        ),
        SharedCredentialProvider(
            creds_filename=credential_file,
            profile_name=profile_name
        ),
        # The new config file has precedence over the legacy
        # config file.
        ConfigProvider(config_filename=config_file, profile_name=profile_name),
        OriginalEC2Provider(),
        BotoProvider(),
        ContainerProvider(),
        InstanceMetadataProvider(
            iam_role_fetcher=InstanceMetadataFetcher(
                timeout=metadata_timeout,
                num_attempts=num_attempts)
        )
    ]

    explicit_profile = session.get_config_variable('profile',
                                                   methods=('instance',))
    if explicit_profile is not None:
        # An explicitly provided profile will negate an EnvProvider.
        # We will defer to providers that understand the "profile"
        # concept to retrieve credentials.
        # The one edge case if is all three values are provided via
        # env vars:
        # export AWS_ACCESS_KEY_ID=foo
        # export AWS_SECRET_ACCESS_KEY=bar
        # export AWS_PROFILE=baz
        # Then, just like our client() calls, the explicit credentials
        # will take precedence.
        #
        # This precedence is enforced by leaving the EnvProvider in the chain.
        # This means that the only way a "profile" would win is if the
        # EnvProvider does not return credentials, which is what we want
        # in this scenario.
        providers.remove(env_provider)
        providers.remove(cos_provider)

        logger.debug('Skipping environment variable credential check'
                     ' because profile name was explicitly set.')

    resolver = CredentialResolver(providers=providers)
    return resolver

def get_credentials(session):
    resolver = create_credential_resolver(session)
    return resolver.load_credentials()

def _local_now():
    return datetime.datetime.now(tzlocal())

def _parse_if_needed(value):
    if isinstance(value, datetime.datetime):
        return value
    return parse(value)

def _serialize_if_needed(value):
    if isinstance(value, datetime.datetime):
        return value.strftime('%Y-%m-%dT%H:%M:%S%Z')
    return value

def create_assume_role_refresher(client, params):
    def refresh():
        response = client.assume_role(**params)
        credentials = response['Credentials']
        # We need to normalize the credential names to
        # the values expected by the refresh creds.
        return {
            'access_key': credentials['AccessKeyId'],
            'secret_key': credentials['SecretAccessKey'],
            'token': credentials['SessionToken'],
            'expiry_time': _serialize_if_needed(credentials['Expiration']),
        }
    return refresh

def create_mfa_serial_refresher():
    def _refresher():
        # We can explore an option in the future to support
        # reprompting for MFA, but for now we just error out
        # when the temp creds expire.
        raise RefreshWithMFAUnsupportedError()
    return _refresher

class Credentials(object):
    """
    Holds the credentials needed to authenticate requests.

    :ivar access_key: The access key part of the credentials.
    :ivar secret_key: The secret key part of the credentials.
    :ivar token: The security token, valid only for session credentials.
    :ivar method: A string which identifies where the credentials
        were found.
    """

    def __init__(self, access_key, secret_key, token=None,
                 method=None):
        self.access_key = access_key
        self.secret_key = secret_key
        self.token = token
        self.token_cache = None

        if method is None:
            method = 'explicit'
        self.method = method

        self._normalize()

    def _normalize(self):
        # Keys would sometimes (accidentally) contain non-ascii characters.
        # It would cause a confusing UnicodeDecodeError in Python 2.
        # We explicitly convert them into unicode to avoid such error.
        #
        # Eventually the service will decide whether to accept the credential.
        # This also complies with the behavior in Python 3.
        if self.access_key:
            self.access_key = ibm_botocore.compat.ensure_unicode(self.access_key)
        if self.secret_key:
            self.secret_key = ibm_botocore.compat.ensure_unicode(self.secret_key)

    def get_frozen_credentials(self):
        return ReadOnlyCredentials(self.access_key,
                                   self.secret_key,
                                   self.token)

class TokenManager(object):
    """An abstract base class for token managers.

    Every token manager must derive from this base class
    and override get_token method.

    """

    def get_token(self):
        """Returns a token, possibly retrieving it first.

        When overriden in derived classes, this method always
        returns token. If token is not available or is expired,
        this method's resposibility is to retrieve or refresh it.

        :return: A string representing valid token.

        """
        return None

class DefaultTokenManager(TokenManager):
    """A default implementation of token manager.

    Retreives token on first use and holds it in memory
    for further use. Background thread tries to refresh token
    prior to its expiration, so that main thread is always
    non-blocking and performant.

    :ivar API_TOKEN_URL: Default URL for IAM authentication.
    :ivar _advisory_refresh_timeout: The time at which we'll attempt to refresh, but not
        block if someone else is refreshing.
    :ivar _mandatory_refresh_timeout: The time at which all threads will block waiting for
        refreshed credentials.

    """

    API_TOKEN_URL = 'https://iam.ng.bluemix.net/oidc/token'

    _advisory_refresh_timeout = 0
    _mandatory_refresh_timeout = 0

    def __init__(self,
                 api_key_id=None,
                 service_instance_id=None,
                 auth_endpoint=None,
                 time_fetcher=_local_now,
                 auth_function=None,
                 verify=True):

        """Creates a new DefaultTokenManager object.

        :type api_key_id: str
        :param api_key_id: IBM api key used for IAM authentication.

        :type service_instance_id: str
        :param service_instance_id: Service Instance ID used for
            PUT bucket and GET service requests.

        :type auth_endpoint: str
        :param auth_endpoint: URL used for IAM authentication. If not provided,
            API_TOKEN_URL will be used.

        :type time_fetcher: datetime
        :param time_fetcher: current date and time used for calculating
            expiration time for token.

        :type auth_function: function
        :param auth_function: function that does custom authentication
            and returns json with token, refresh token, expiry time
            and token type. If not provided, a default authentication
            function will be used.
                :type verify: boolean/string

        :param verify: Whether or not to verify IAM service SSL certificates.
            By default SSL certificates are verified.  You can provide the
            following values:

            * False - do not validate SSL certificates.  SSL will still be
              used (unless use_ssl is False), but SSL certificates
              will not be verified.
            * path/to/cert/bundle.pem - A filename of the CA cert bundle to
              uses.  You can specify this argument if you want to use a
              different CA cert bundle than the one used by botocore.
        """
        if api_key_id is None and auth_function is None:
            raise RuntimeError('api_key_id and auth_function cannot both be None')

        self.api_key_id = api_key_id
        self.service_instance_id = service_instance_id
        self.auth_endpoint = auth_endpoint
        self._time_fetcher = time_fetcher
        self.set_verify(verify)

        if auth_function:
            self.auth_function = auth_function
        else:
            self.auth_function = self._default_auth_function

        self._token = None
        self._refresh_token = None
        self._token_type = None
        self._expiry_time = None

        self._refresh_lock = threading.Lock()

        self._background_thread = None
        self._background_thread_wakeup_event = threading.Event()

    def wakeup_refresh_thread(self):
        """
        Force the background thread to wakeup and refresh
        """
        self._background_thread_wakeup_event.set()

    def _background_refresher(self, wakeup_event):
        """Refreshes token that's about to expire.

        Runs on background thread and sleeps until _advisory_refresh_timeout
        seconds before token expiration when it wakes and
        refreshes token.

        """
        try:
            # This method will run on background thread forever
            while True:
                # We just woke up and there's a token.
                # Will see if refresh is required and will then go back to sleep
                remaining = self._seconds_remaining()
                if remaining <= self._advisory_refresh_timeout:
                    self._refresh(can_retry=False)

                new_remaining = self._seconds_remaining() - self._advisory_refresh_timeout
                if new_remaining <= 0:
                    logger.error('Background thread : Time remaining is negative.. sleep 5 seconds')
                    new_remaining = 30

                logger.debug('Background refresh thread going to sleep for ' + str(new_remaining) + ' seconds')
                wakeup_event.clear()
                wakeup_event.wait(new_remaining)

        except Exception as e:
            logger.error("Background refresh thread encountered error: " + str(e))

    def get_token(self):
        """Returns a token, possibly retrieving it first.

        Always returns token. If token is not available, retrieves.
        It also spawns background thread that makes sure that token
        never expires.

        :return: A string representing valid token.

        """
        self._refresh(can_retry=True)
        self._set_refresh_timeouts()

        if self._refresh_lock.acquire(False):
            try:
                if not self._background_thread:
                    self._background_thread = threading.Thread(target=self._background_refresher, args=(self._background_thread_wakeup_event,))
                    self._background_thread.daemon = True
                    self._background_thread.start()
            finally:
                self._refresh_lock.release()

        return self._token

    def set_verify(self, verify):
        self._verify = verify

    def get_verify(self):
        return self._verify

    def _seconds_remaining(self):
        delta = self._expiry_time - self._time_fetcher()
        return total_seconds(delta)

    def _get_token_url(self):
        if self.auth_endpoint:
            return self.auth_endpoint
        else:
            return DefaultTokenManager.API_TOKEN_URL

    def _get_body(self):
        # TODO, add support for refresh_token here, when we know how to use it
        return {u'grant_type': u'urn:ibm:params:oauth:grant-type:apikey',
                u'response_type': u'cloud_iam',
                u'apikey': self.api_key_id}

    def _get_headers(self):
        return {'accept': "application/json",
                'authorization': "Basic Yng6Yng=",
                'cache-control': "no-cache",
                'Content-Type': "application/x-www-form-urlencoded"}

    def _default_auth_function(self):
        response = requests.post(
                                 url=self._get_token_url(),
                                 data=self._get_body(),
                                 headers=self._get_headers(),
                                 timeout=5,
                                 verify=self.get_verify())

        if response.status_code != httplib.OK:
            _msg = 'HttpCode({code}) - Retrieval of tokens from server failed.'.format(code=response.status_code)
            raise CredentialRetrievalError(provider=self._get_token_url(), error_msg=_msg)

        return json.loads(response.content.decode('utf-8'))

    def _refresh_needed(self, refresh_in=None):
        """Check if a refresh is needed.

        A refresh is needed if the expiry time associated
        with the temporary credentials is less than the
        provided ``refresh_in``.  If ``time_delta`` is not
        provided, ``self.advisory_refresh_needed`` will be used.

        For example, if your temporary credentials expire
        in 10 minutes and the provided ``refresh_in`` is
        ``15 * 60``, then this function will return ``True``.

        :type refresh_in: int
        :param refresh_in: The number of seconds before the
            credentials expire in which refresh attempts should
            be made.

        :return: True if refresh neeeded, False otherwise.

        """
        if self._token is None:
            return True

        if self._expiry_time is None:
            # No expiration, so assume we don't need to refresh.
            return False

        if refresh_in is None:
            refresh_in = self._advisory_refresh_timeout
        # The credentials should be refreshed if they're going to expire
        # in less than 5 minutes.
        if self._seconds_remaining() >= refresh_in:
            # There's enough time left. Don't refresh.
            return False
        logger.debug("Credentials need to be refreshed.")
        return True

    def _is_expired(self):
        # Checks if the current credentials are expired.
        return self._refresh_needed(refresh_in=0)

    def _refresh(self, can_retry=False):
        """Initiates mandatory or advisory refresh, if needed,

        This method makes sure that refresh is done in critical section,
        if refresh is needed:
        - if lock can be acquired, mandatory or advisory refresh
        is initiated.
        - if lock cannot be acquired and refresh is advisory, we cancel
        our refresh action (because somebody is already doing the refresh)
        - if lock cannot be acquired and refresh is mandatory, be block
        until lock can be acquired (although at that point somebody else
        probably did the refresh)

        """
        # In the common case where we don't need a refresh, we
        # can immediately exit and not require acquiring the
        # refresh lock.
        if not self._refresh_needed(self._advisory_refresh_timeout):
            return

        # acquire() doesn't accept kwargs, but False is indicating
        # that we should not block if we can't acquire the lock.
        # If we aren't able to acquire the lock, we'll trigger
        # the else clause.
        if self._refresh_lock.acquire(False):
            try:
                if not self._refresh_needed(self._advisory_refresh_timeout):
                    return
                is_mandatory_refresh = self._refresh_needed(
                    self._mandatory_refresh_timeout)
                self._protected_refresh(is_mandatory=is_mandatory_refresh, can_retry=can_retry)
                return
            finally:
                self._refresh_lock.release()
        elif self._refresh_needed(self._mandatory_refresh_timeout):
            # If we're within the mandatory refresh window,
            # we must block until we get refreshed credentials.
            with self._refresh_lock:
                if not self._refresh_needed(self._mandatory_refresh_timeout):
                    return
                self._protected_refresh(is_mandatory=True, can_retry=can_retry)

    def _protected_refresh(self, is_mandatory, can_retry):
        """Performs mandatory or advisory refresh.

        Precondition: this method should only be called if you've acquired
        the self._refresh_lock.
        """
        _total_attempts = 3 if can_retry else 1
        while True:
            try:
                metadata = self.auth_function()
                break
            except Exception as e:
                _total_attempts -= 1
                if _total_attempts > 0:
                    logger.debug("Retrying auth call")
                    time.sleep(1)
                else:
                    period_name = 'mandatory' if is_mandatory else 'advisory'
                    logger.warning("Refreshing temporary credentials failed "
                                   "during %s refresh period.",
                                   period_name, exc_info=True)
                    if is_mandatory:
                        # If this is a mandatory refresh, then
                        # all errors that occur when we attempt to refresh
                        # credentials are propagated back to the user.
                        raise
                    # Otherwise we'll just return.
                    # The end result will be that we'll use the current
                    # set of temporary credentials we have.
                    return

        # Now set returned value, no matter in which way they came back.
        self._set_from_data(metadata)

        if self._is_expired():
            # We successfully refreshed credentials but for whatever
            # reason, our refreshing function returned credentials
            # that are still expired.  In this scenario, the only
            # thing we can do is let the user know and raise
            # an exception.
            msg = ("Credentials were refreshed, but the "
                   "refreshed credentials are still expired.")
            logger.warning(msg)
            raise RuntimeError(msg)

    def _set_from_data(self, data):
        self._token = data['access_token']

        # Add expires_in to current system time.
        self._expiry_time = self._time_fetcher() + datetime.timedelta(seconds=data['expires_in'])

        # Aren't currently using this, but we can later
        if 'refresh_token' in data:
            self._refresh_token = data['refresh_token']

        self._token_type = data['token_type']

        logger.debug("Retrieved credentials will expire at: %s", self._expiry_time)

    def _set_refresh_timeouts(self):
        """
        Set the advisory  timeout to 25% of remaining time - usually 15 minutes on 1 hour expiry
        Set the mandatory timeout to 17% of remaining time - usually 10 minutes on 1 hour expiry
        """
        # only want to set this once
        if self._advisory_refresh_timeout != 0:
            return

        if self._expiry_time and self._token:
            _secs = self._seconds_remaining()
            self._advisory_refresh_timeout = int(_secs / (100 / 25))
            self._mandatory_refresh_timeout = int(_secs / (100 / 17))
            logger.debug('Refresh Timeouts set to Advisory(' +
                         str(self._advisory_refresh_timeout) +
                         ') Mandatory(' +
                         str(self._mandatory_refresh_timeout) + ')')

class OAuth2Credentials(Credentials):
    """
    Holds the credentials needed to IAM authenticate requests. Credentials
    are kept in token manager, either built-in or custom one.

    """

    def __init__(self,
                 api_key_id=None,
                 service_instance_id=None,
                 auth_endpoint=None,
                 token_manager=None,
                 auth_function=None,
                 method=None,
                 time_fetcher=_local_now,
                 verify=True):
        """Creates a new OAuth2Credentials object.

        :type api_key_id: str
        :param api_key_id: IBM api key used for IAM authentication.

        :type service_instance_id: str
        :param service_instance_id: Service Instance ID used for
            PUT bucket and GET service requests.

        :type auth_endpoint: str
        :param auth_endpoint: URL used for IAM authentication. If not provided,
            API_TOKEN_URL will be used.

        :type token_manager: TokenManager
        :param token_manager: custom token manager to use. If not providedm
            an instance of DefaultTokenManager will be used.

        :type auth_function: function
        :param auth_function: function that does custom authentication
            and returns json with token, refresh token, expiry time
            and token type. If not provided, a default authentication
            function will be used.

        :type method: str
        :param method: A string which identifies where the credentials
            were found..

        :type time_fetcher: datetime
        :param time_fetcher: current date and time used for calculating
            expiration time for token.

        :param verify: Whether or not to verify IAM service SSL certificates.
            By default SSL certificates are verified.  You can provide the
            following values:

            * False - do not validate SSL certificates.  SSL will still be
              used (unless use_ssl is False), but SSL certificates
              will not be verified.
            * path/to/cert/bundle.pem - A filename of the CA cert bundle to
              uses.  You can specify this argument if you want to use a
              different CA cert bundle than the one used by botocore.
        """
        self.api_key_id = api_key_id
        self.service_instance_id = service_instance_id
        self.auth_endpoint = auth_endpoint
        self.token_manager = token_manager
        self.auth_function = auth_function
        self.method = method
        self._normalize()

        # We might get three different IAM options: api key, auth function and custom token manager.
        # Precedence logic is this (for now):
        # 1. If api key is provided, it will be used with our builtin DefaultTokenManager.
        #    custom token manager i auth function are ignored.
        # 2. If auth function is provided, it is used and custom token manager is ignored.
        # 3. If custom token manager is provided, it is used
        # 4. If nothing is provided, an error is raised.
        if api_key_id:
            self.auth_function = None
            if token_manager or auth_function:
                logger.warning('api_key_id will be used, token_manager/auth_function will be ignored')
        elif auth_function:
            if token_manager:
                logger.warning('auth_function will be used, token_manager will be ignored')
        elif token_manager:
            logger.debug('token_manager will be used')
        else:
            raise ValueError("Either api_key_id, auth_function or token_manager must be provided")

        if api_key_id or auth_function:
            self.token_manager = DefaultTokenManager(self.api_key_id,
                                                     self.service_instance_id,
                                                     self.auth_endpoint,
                                                     time_fetcher,
                                                     self.auth_function,
                                                     verify)

    def _normalize(self):
        if self.api_key_id:
            self.api_key_id = ibm_botocore.compat.ensure_unicode(self.api_key_id)
        if self.service_instance_id:
            self.service_instance_id = ibm_botocore.compat.ensure_unicode(self.service_instance_id)

    def get_frozen_credentials(self):
        """Return immutable credentials.

        The ``access_key``, ``secret_key``, and ``token`` properties
        on this class will always check and refresh credentials if
        needed before returning the particular credentials.

        This has an edge case where you can get inconsistent
        credentials.  Imagine this:

            # Current creds are "t1"
            tmp.access_key  ---> expired? no, so return t1.access_key
            # ---- time is now expired, creds need refreshing to "t2" ----
            tmp.secret_key  ---> expired? yes, refresh and return t2.secret_key

        This means we're using the access key from t1 with the secret key
        from t2.  To fix this issue, you can request a frozen credential object
        which is guaranteed not to change.

        The frozen credentials returned from this method should be used
        immediately and then discarded.  The typical usage pattern would
        be::

            creds = RefreshableCredentials(...)
            some_code = SomeSignerObject()
            # I'm about to sign the request.
            # The frozen credentials are only used for the
            # duration of generate_presigned_url and will be
            # immediately thrown away.
            request = some_code.sign_some_request(
                with_credentials=creds.get_frozen_credentials())
            print("Signed request:", request)

        """
        token = self.token_manager.get_token()

        # Signer is only interested in token, and besides, we might not even have api key
        return ReadOnlyCredentials(
            None, None, token)

class RefreshableCredentials(Credentials):
    """
    Holds the credentials needed to authenticate requests. In addition, it
    knows how to refresh itself.

    :ivar refresh_timeout: How long a given set of credentials are valid for.
        Useful for credentials fetched over the network.
    :ivar access_key: The access key part of the credentials.
    :ivar secret_key: The secret key part of the credentials.
    :ivar token: The security token, valid only for session credentials.
    :ivar method: A string which identifies where the credentials
        were found.
    """
    # The time at which we'll attempt to refresh, but not
    # block if someone else is refreshing.
    _advisory_refresh_timeout = 15 * 60
    # The time at which all threads will block waiting for
    # refreshed credentials.
    _mandatory_refresh_timeout = 10 * 60

    def __init__(self, access_key, secret_key, token,
                 expiry_time, refresh_using, method,
                 time_fetcher=_local_now):
        self._refresh_using = refresh_using
        self._access_key = access_key
        self._secret_key = secret_key
        self._token = token
        self._expiry_time = expiry_time
        self._time_fetcher = time_fetcher
        self._refresh_lock = threading.Lock()
        self.method = method
        self._frozen_credentials = ReadOnlyCredentials(
            access_key, secret_key, token)
        self._normalize()

    def _normalize(self):
        self._access_key = ibm_botocore.compat.ensure_unicode(self._access_key)
        self._secret_key = ibm_botocore.compat.ensure_unicode(self._secret_key)

    @classmethod
    def create_from_metadata(cls, metadata, refresh_using, method):
        instance = cls(
            access_key=metadata['access_key'],
            secret_key=metadata['secret_key'],
            token=metadata['token'],
            expiry_time=cls._expiry_datetime(metadata['expiry_time']),
            method=method,
            refresh_using=refresh_using
        )
        return instance

    @property
    def access_key(self):
        """Warning: Using this property can lead to race conditions if you
        access another property subsequently along the refresh boundary.
        Please use get_frozen_credentials instead.
        """
        self._refresh()
        return self._access_key

    @access_key.setter
    def access_key(self, value):
        self._access_key = value

    @property
    def secret_key(self):
        """Warning: Using this property can lead to race conditions if you
        access another property subsequently along the refresh boundary.
        Please use get_frozen_credentials instead.
        """
        self._refresh()
        return self._secret_key

    @secret_key.setter
    def secret_key(self, value):
        self._secret_key = value

    @property
    def token(self):
        """Warning: Using this property can lead to race conditions if you
        access another property subsequently along the refresh boundary.
        Please use get_frozen_credentials instead.
        """
        self._refresh()
        return self._token

    @token.setter
    def token(self, value):
        self._token = value

    def _seconds_remaining(self):
        delta = self._expiry_time - self._time_fetcher()
        return total_seconds(delta)

    def refresh_needed(self, refresh_in=None):
        """Check if a refresh is needed.

        A refresh is needed if the expiry time associated
        with the temporary credentials is less than the
        provided ``refresh_in``.  If ``time_delta`` is not
        provided, ``self.advisory_refresh_needed`` will be used.

        For example, if your temporary credentials expire
        in 10 minutes and the provided ``refresh_in`` is
        ``15 * 60``, then this function will return ``True``.

        :type refresh_in: int
        :param refresh_in: The number of seconds before the
            credentials expire in which refresh attempts should
            be made.

        :return: True if refresh neeeded, False otherwise.

        """
        if self._expiry_time is None:
            # No expiration, so assume we don't need to refresh.
            return False

        if refresh_in is None:
            refresh_in = self._advisory_refresh_timeout
        # The credentials should be refreshed if they're going to expire
        # in less than 5 minutes.
        if self._seconds_remaining() >= refresh_in:
            # There's enough time left. Don't refresh.
            return False
        logger.debug("Credentials need to be refreshed.")
        return True

    def _is_expired(self):
        # Checks if the current credentials are expired.
        return self.refresh_needed(refresh_in=0)

    def _refresh(self):
        # In the common case where we don't need a refresh, we
        # can immediately exit and not require acquiring the
        # refresh lock.
        if not self.refresh_needed(self._advisory_refresh_timeout):
            return

        # acquire() doesn't accept kwargs, but False is indicating
        # that we should not block if we can't acquire the lock.
        # If we aren't able to acquire the lock, we'll trigger
        # the else clause.
        if self._refresh_lock.acquire(False):
            try:
                if not self.refresh_needed(self._advisory_refresh_timeout):
                    return
                is_mandatory_refresh = self.refresh_needed(
                    self._mandatory_refresh_timeout)
                self._protected_refresh(is_mandatory=is_mandatory_refresh)
                return
            finally:
                self._refresh_lock.release()
        elif self.refresh_needed(self._mandatory_refresh_timeout):
            # If we're within the mandatory refresh window,
            # we must block until we get refreshed credentials.
            with self._refresh_lock:
                if not self.refresh_needed(self._mandatory_refresh_timeout):
                    return
                self._protected_refresh(is_mandatory=True)

    def _protected_refresh(self, is_mandatory):
        # precondition: this method should only be called if you've acquired
        # the self._refresh_lock.
        try:
            metadata = self._refresh_using()
        except Exception as e:
            period_name = 'mandatory' if is_mandatory else 'advisory'
            logger.warning("Refreshing temporary credentials failed "
                           "during %s refresh period.",
                           period_name, exc_info=True)
            if is_mandatory:
                # If this is a mandatory refresh, then
                # all errors that occur when we attempt to refresh
                # credentials are propagated back to the user.
                raise
            # Otherwise we'll just return.
            # The end result will be that we'll use the current
            # set of temporary credentials we have.
            return
        self._set_from_data(metadata)
        if self._is_expired():
            # We successfully refreshed credentials but for whatever
            # reason, our refreshing function returned credentials
            # that are still expired.  In this scenario, the only
            # thing we can do is let the user know and raise
            # an exception.
            msg = ("Credentials were refreshed, but the "
                   "refreshed credentials are still expired.")
            logger.warning(msg)
            raise RuntimeError(msg)
        self._frozen_credentials = ReadOnlyCredentials(
            self._access_key, self._secret_key, self._token)

    @staticmethod
    def _expiry_datetime(time_str):
        return parse(time_str)

    def _set_from_data(self, data):
        self.access_key = data['access_key']
        self.secret_key = data['secret_key']
        self.token = data['token']
        self._expiry_time = parse(data['expiry_time'])
        logger.debug("Retrieved credentials will expire at: %s",
                     self._expiry_time)
        self._normalize()

    def get_frozen_credentials(self):
        """Return immutable credentials.

        The ``access_key``, ``secret_key``, and ``token`` properties
        on this class will always check and refresh credentials if
        needed before returning the particular credentials.

        This has an edge case where you can get inconsistent
        credentials.  Imagine this:

            # Current creds are "t1"
            tmp.access_key  ---> expired? no, so return t1.access_key
            # ---- time is now expired, creds need refreshing to "t2" ----
            tmp.secret_key  ---> expired? yes, refresh and return t2.secret_key

        This means we're using the access key from t1 with the secret key
        from t2.  To fix this issue, you can request a frozen credential object
        which is guaranteed not to change.

        The frozen credentials returned from this method should be used
        immediately and then discarded.  The typical usage pattern would
        be::

            creds = RefreshableCredentials(...)
            some_code = SomeSignerObject()
            # I'm about to sign the request.
            # The frozen credentials are only used for the
            # duration of generate_presigned_url and will be
            # immediately thrown away.
            request = some_code.sign_some_request(
                with_credentials=creds.get_frozen_credentials())
            print("Signed request:", request)

        """
        self._refresh()
        return self._frozen_credentials

class CredentialProvider(object):

    # Implementations must provide a method.
    METHOD = None

    def __init__(self, session=None):
        self.session = session

    def load(self):
        """
        Loads the credentials from their source & sets them on the object.

        Subclasses should implement this method (by reading from disk, the
        environment, the network or wherever), returning ``True`` if they were
        found & loaded.

        If not found, this method should return ``False``, indictating that the
        ``CredentialResolver`` should fall back to the next available method.

        The default implementation does nothing, assuming the user has set the
        ``access_key/secret_key/token`` themselves.

        :returns: Whether credentials were found & set
        :rtype: boolean
        """
        return True

    def _extract_creds_from_mapping(self, mapping, *key_names):
        found = []
        for key_name in key_names:
            # ibm_service_instance_id and ibm_auth_endpoint are optional; append None in list
            if key_name.lower() in ['ibm_service_instance_id','ibm_auth_endpoint'] and key_name not in mapping:
                found.append(None)
            else:
                try:
                    found.append(mapping[key_name])
                except KeyError:
                    raise PartialCredentialsError(provider=self.METHOD, cred_var=key_name)
        return found

class InstanceMetadataProvider(CredentialProvider):
    METHOD = 'iam-role'

    def __init__(self, iam_role_fetcher):
        self._role_fetcher = iam_role_fetcher

    def load(self):
        fetcher = self._role_fetcher
        # We do the first request, to see if we get useful data back.
        # If not, we'll pass & move on to whatever's next in the credential
        # chain.
        metadata = fetcher.retrieve_iam_role_credentials()
        if not metadata:
            return None
        logger.debug('Found credentials from IAM Role: %s', metadata['role_name'])
        # We manually set the data here, since we already made the request &
        # have it. When the expiry is hit, the credentials will auto-refresh
        # themselves.
        creds = RefreshableCredentials.create_from_metadata(
            metadata,
            method=self.METHOD,
            refresh_using=fetcher.retrieve_iam_role_credentials,
        )
        return creds

class EnvProvider(CredentialProvider):
    METHOD = 'env'
    ACCESS_KEY = 'AWS_ACCESS_KEY_ID'
    SECRET_KEY = 'AWS_SECRET_ACCESS_KEY'
    IBM_COS_API_KEY_ID = 'IBM_API_KEY_ID'
    IBM_COS_SERVICE_INSTANCE_ID = 'IBM_SERVICE_INSTANCE_ID'
    IBM_COS_AUTH_ENDPOINT = 'IBM_AUTH_ENDPOINT'

    # The token can come from either of these env var.
    # AWS_SESSION_TOKEN is what other AWS SDKs have standardized on.
    TOKENS = ['AWS_SECURITY_TOKEN', 'AWS_SESSION_TOKEN']

    def __init__(self, environ=None, mapping=None):
        """

        :param environ: The environment variables (defaults to
            ``os.environ`` if no value is provided).
        :param mapping: An optional mapping of variable names to
            environment variable names.  Use this if you want to
            change the mapping of access_key->AWS_ACCESS_KEY_ID, etc.
            The dict can have up to 3 keys: ``access_key``, ``secret_key``,
            ``session_token``.
        """
        if environ is None:
            environ = os.environ
        self.environ = environ
        self._mapping = self._build_mapping(mapping)

    def _build_mapping(self, mapping):
        # Mapping of variable name to env var name.
        var_mapping = {}
        if mapping is None:
            # Use the class var default.
            var_mapping['access_key'] = self.ACCESS_KEY
            var_mapping['secret_key'] = self.SECRET_KEY
            var_mapping['ibm_api_key_id'] = self.IBM_COS_API_KEY_ID
            var_mapping['ibm_service_instance_id'] = self.IBM_COS_SERVICE_INSTANCE_ID
            var_mapping['ibm_auth_endpoint'] = self.IBM_COS_AUTH_ENDPOINT
            var_mapping['token'] = self.TOKENS
        else:
            var_mapping['access_key'] = mapping.get(
                'access_key', self.ACCESS_KEY)
            var_mapping['secret_key'] = mapping.get(
                'secret_key', self.SECRET_KEY)
            var_mapping['ibm_api_key_id'] = mapping.get(
                            'ibm_api_key_id', self.IBM_COS_API_KEY_ID)
            var_mapping['ibm_service_instance_id'] = mapping.get(
                            'ibm_service_instance_id', self.IBM_COS_SERVICE_INSTANCE_ID)
            var_mapping['ibm_auth_endpoint'] = mapping.get(
                            'ibm_auth_endpoint', self.IBM_COS_AUTH_ENDPOINT)
            var_mapping['token'] = mapping.get(
                'token', self.TOKENS)
            if not isinstance(var_mapping['token'], list):
                var_mapping['token'] = [var_mapping['token']]
        return var_mapping

    def load(self):
        """
        Search for credentials in explicit environment variables.
        """
        if self._mapping['ibm_api_key_id'] in self.environ:
            logger.info('Found IBM credentials in environment variables.')
            ibm_api_key_id, ibm_service_instance_id, ibm_auth_endpoint = self._extract_creds_from_mapping(
                self.environ, self._mapping['ibm_api_key_id'],
                self._mapping['ibm_service_instance_id'],
                self._mapping['ibm_auth_endpoint'])
            token = self._get_session_token()
            return OAuth2Credentials(api_key_id=ibm_api_key_id,
                                     service_instance_id=ibm_service_instance_id,
                                     auth_endpoint=ibm_auth_endpoint,
                                     method=self.METHOD)
        elif self._mapping['access_key'] in self.environ:
            logger.info('Found credentials in environment variables.')
            access_key, secret_key = self._extract_creds_from_mapping(
                self.environ, self._mapping['access_key'],
                self._mapping['secret_key'])
            token = self._get_session_token()
            return Credentials(access_key, secret_key, token,
                               method=self.METHOD)
        else:
            return None

    def _get_session_token(self):
        for token_envvar in self._mapping['token']:
            if token_envvar in self.environ:
                return self.environ[token_envvar]

class OriginalEC2Provider(CredentialProvider):
    METHOD = 'ec2-credentials-file'

    CRED_FILE_ENV = 'AWS_CREDENTIAL_FILE'
    ACCESS_KEY = 'AWSAccessKeyId'
    SECRET_KEY = 'AWSSecretKey'
    IBM_COS_API_KEY_ID = 'ibm_api_key_id'
    IBM_COS_SERVICE_INSTANCE_ID = 'ibm_service_instance_id'
    IBM_COS_AUTH_ENDPOINT = 'ibm_auth_endpoint'

    def __init__(self, environ=None, parser=None):
        if environ is None:
            environ = os.environ
        if parser is None:
            parser = parse_key_val_file
        self._environ = environ
        self._parser = parser

    def load(self):
        """
        Search for a credential file used by original EC2 CLI tools.
        """
        if 'AWS_CREDENTIAL_FILE' in self._environ:
            full_path = os.path.expanduser(self._environ['AWS_CREDENTIAL_FILE'])
            creds = self._parser(full_path)
            if self.IBM_COS_API_KEY_ID in creds:
                logger.info('Found IBM credentials in AWS_CREDENTIAL_FILE.')

                ibm_api_key_id = creds[self.IBM_COS_API_KEY_ID]
                ibm_service_instance_id = creds[self.IBM_COS_SERVICE_INSTANCE_ID]
                ibm_auth_endpoint = creds[self.IBM_COS_AUTH_ENDPOINT]
                # EC2 creds file doesn't support session tokens.
                return OAuth2Credentials(api_key_id=ibm_api_key_id,
                                         service_instance_id=ibm_service_instance_id,
                                         auth_endpoint=ibm_auth_endpoint,
                                         method=self.METHOD)
            elif self.ACCESS_KEY in creds:
                logger.info('Found credentials in AWS_CREDENTIAL_FILE.')
                access_key = creds[self.ACCESS_KEY]
                secret_key = creds[self.SECRET_KEY]
                # EC2 creds file doesn't support session tokens.
                return Credentials(access_key, secret_key, method=self.METHOD)
        else:
            return None

class SharedCredentialProvider(CredentialProvider):
    METHOD = 'shared-credentials-file'

    ACCESS_KEY = 'aws_access_key_id'
    SECRET_KEY = 'aws_secret_access_key'
    IBM_COS_API_KEY_ID = 'ibm_api_key_id'
    IBM_COS_SERVICE_INSTANCE_ID = 'ibm_service_instance_id'
    IBM_COS_AUTH_ENDPOINT = 'ibm_auth_endpoint'
    # Same deal as the EnvProvider above.  Botocore originally supported
    # aws_security_token, but the SDKs are standardizing on aws_session_token
    # so we support both.
    TOKENS = ['aws_security_token', 'aws_session_token']

    def __init__(self, creds_filename, profile_name=None, ini_parser=None, hmac_takes_precedence=False):
        self._creds_filename = creds_filename
        if profile_name is None:
            profile_name = 'default'
        self._profile_name = profile_name
        if ini_parser is None:
            ini_parser = ibm_botocore.configloader.raw_config_parse
        self._ini_parser = ini_parser
        self._hmac_takes_precedence = hmac_takes_precedence

    def load_ibm_cos_credentials(self, config):
        if self._hmac_takes_precedence and self.ACCESS_KEY in config:
            logger.info('HMAC takes precedence.')
            return False

        return self.IBM_COS_API_KEY_ID in config

    def load_hmac_credentials(self, config):
        return self.ACCESS_KEY in config

    def load(self):
        try:
            available_creds = self._ini_parser(self._creds_filename)
        except ConfigNotFound:
            return None
        if self._profile_name in available_creds:
            config = available_creds[self._profile_name]
            if self.load_ibm_cos_credentials(config):
                logger.info("Found IBMCOS credentials in shared credentials file: %s",
                            self._creds_filename)
                ibm_api_key_id, ibm_service_instance_id, ibm_auth_endpoint = self._extract_creds_from_mapping(
                    config, self.IBM_COS_API_KEY_ID,
                    self.IBM_COS_SERVICE_INSTANCE_ID,
                    self.IBM_COS_AUTH_ENDPOINT)
                token = self._get_session_token(config)
                return OAuth2Credentials(api_key_id=ibm_api_key_id,
                                         service_instance_id=ibm_service_instance_id,
                                         auth_endpoint=ibm_auth_endpoint,
                                         method=self.METHOD)
            elif self.load_hmac_credentials(config):
                logger.info("Found credentials in shared credentials file: %s",
                            self._creds_filename)
                access_key, secret_key = self._extract_creds_from_mapping(
                    config, self.ACCESS_KEY, self.SECRET_KEY)
                token = self._get_session_token(config)
                return Credentials(access_key, secret_key, token,
                                   method=self.METHOD)
            else:
                return None

    def _get_session_token(self, config):
        for token_envvar in self.TOKENS:
            if token_envvar in config:
                return config[token_envvar]

class IbmCosCredentialsProvider(SharedCredentialProvider):

    def __init__(self, ibm_credentials_filename):
        self.METHOD = 'ibm-cos-credentials-file'
        SharedCredentialProvider.__init__(self,
                                          ibm_credentials_filename,
                                          self.METHOD,
                                          self.load_ibm_credentials_filename,
                                          True)

    def get_data(self, path):
        if not os.path.isfile(path):
            raise ibm_botocore.exceptions.ConfigNotFound(path=path)

        with open(path, 'r') as f:
            return json.load(f)

    def load_ibm_credentials_filename(self, ibm_credentials_filename):
        config = {}
        path = ibm_credentials_filename
        if path is not None:
            path = os.path.expanduser(path)
            _data = self.get_data(path)

            try:
                def set_dic_value(_sec, _name, _dic, _name1, _name2=None):
                    if _name1 in _dic.keys():
                        if not _name2:
                            _sec[_name] = _dic[_name1]
                        else:
                            _dic2 = _dic[_name1]
                            if _name2 in _dic2.keys():
                                _sec[_name] = _dic2[_name2]

                _sec = config[self.METHOD] = {}
                set_dic_value(_sec, 'aws_access_key_id',        _data, 'cos_hmac_keys', 'access_key_id')
                set_dic_value(_sec, 'aws_secret_access_key',    _data, 'cos_hmac_keys', 'secret_access_key')
                set_dic_value(_sec, 'ibm_service_instance_id',  _data, 'resource_instance_id')
                set_dic_value(_sec, 'ibm_api_key_id',           _data, 'apikey')
                set_dic_value(_sec, 'ibm_kp_root_key_crn',      _data, 'iam_serviceid_crn')

                # this is for testing - if the value is set in the file then use it
                # otherwise the default endpoint is used -- 'https://iam.ng.bluemix.net/oidc/token'
                set_dic_value(_sec, 'ibm_auth_endpoint',        _data, 'iam_auth_endpoint')
                if 'ibm_auth_endpoint' not in _sec.keys():
                    _sec['ibm_auth_endpoint'] = None

            except Exception as e:
                raise ibm_botocore.exceptions.ConfigParseError(path=ibm_credentials_filename)

        return config

class ConfigProvider(CredentialProvider):
    """INI based config provider with profile sections."""
    METHOD = 'config-file'

    ACCESS_KEY = 'aws_access_key_id'
    SECRET_KEY = 'aws_secret_access_key'
    IBM_COS_API_KEY_ID = 'ibm_api_key_id'
    IBM_COS_SERVICE_INSTANCE_ID = 'ibm_service_instance_id'
    IBM_COS_AUTH_ENDPOINT = 'ibm_auth_endpoint'
    # Same deal as the EnvProvider above.  Botocore originally supported
    # aws_security_token, but the SDKs are standardizing on aws_session_token
    # so we support both.
    TOKENS = ['aws_security_token', 'aws_session_token']

    def __init__(self, config_filename, profile_name, config_parser=None):
        """

        :param config_filename: The session configuration scoped to the current
            profile.  This is available via ``session.config``.
        :param profile_name: The name of the current profile.
        :param config_parser: A config parser callable.

        """
        self._config_filename = config_filename
        self._profile_name = profile_name
        if config_parser is None:
            config_parser = ibm_botocore.configloader.load_config
        self._config_parser = config_parser

    def load(self):
        """
        If there is are credentials in the configuration associated with
        the session, use those.
        """
        try:
            full_config = self._config_parser(self._config_filename)
        except ConfigNotFound:
            return None
        if self._profile_name in full_config['profiles']:
            profile_config = full_config['profiles'][self._profile_name]
            if self.IBM_COS_API_KEY_ID in profile_config:
                logger.info("IBM Credentials found in AWS config file: %s",
                            self._config_filename)
                ibm_api_key_id, ibm_service_instance_id, ibm_auth_endpoint = self._extract_creds_from_mapping(
                    profile_config, self.IBM_COS_API_KEY_ID, self.IBM_COS_SERVICE_INSTANCE_ID, self.IBM_COS_AUTH_ENDPOINT)
                token = self._get_session_token(profile_config)
                return OAuth2Credentials(api_key_id=ibm_api_key_id,
                                         service_instance_id=ibm_service_instance_id,
                                         auth_endpoint=ibm_auth_endpoint,
                                         method=self.METHOD)
            elif self.ACCESS_KEY in profile_config:
                logger.info("Credentials found in AWS config file: %s",
                            self._config_filename)
                access_key, secret_key = self._extract_creds_from_mapping(
                    profile_config, self.ACCESS_KEY, self.SECRET_KEY)
                token = self._get_session_token(profile_config)
                return Credentials(access_key, secret_key, token,
                                   method=self.METHOD)
        else:
            return None

    def _get_session_token(self, profile_config):
        for token_name in self.TOKENS:
            if token_name in profile_config:
                return profile_config[token_name]

class BotoProvider(CredentialProvider):
    METHOD = 'boto-config'

    BOTO_CONFIG_ENV = 'BOTO_CONFIG'
    DEFAULT_CONFIG_FILENAMES = ['/etc/boto.cfg', '~/.boto']
    ACCESS_KEY = 'aws_access_key_id'
    SECRET_KEY = 'aws_secret_access_key'
    IBM_COS_API_KEY_ID = 'ibm_api_key_id'
    IBM_COS_SERVICE_INSTANCE_ID = 'ibm_service_instance_id'
    IBM_COS_AUTH_ENDPOINT = 'ibm_auth_endpoint'

    def __init__(self, environ=None, ini_parser=None):
        if environ is None:
            environ = os.environ
        if ini_parser is None:
            ini_parser = ibm_botocore.configloader.raw_config_parse
        self._environ = environ
        self._ini_parser = ini_parser

    def load(self):
        """
        Look for credentials in boto config file.
        """
        if self.BOTO_CONFIG_ENV in self._environ:
            potential_locations = [self._environ[self.BOTO_CONFIG_ENV]]
        else:
            potential_locations = self.DEFAULT_CONFIG_FILENAMES
        for filename in potential_locations:
            try:
                config = self._ini_parser(filename)
            except ConfigNotFound:
                # Move on to the next potential config file name.
                continue
            if 'Credentials' in config:
                credentials = config['Credentials']
                if self.IBM_COS_API_KEY_ID in credentials:
                    logger.info("Found IBM credentials in boto config file: %s", filename)
                    ibm_api_key_id, ibm_service_instance_id, ibm_auth_endpoint = self._extract_creds_from_mapping(credentials,
                                                                                self.IBM_COS_API_KEY_ID,
                                                                                self.IBM_COS_SERVICE_INSTANCE_ID,
                                                                                self.IBM_COS_AUTH_ENDPOINT)

                    return OAuth2Credentials(api_key_id=ibm_api_key_id,
                                             service_instance_id=ibm_service_instance_id,
                                             auth_endpoint=ibm_auth_endpoint,
                                             method=self.METHOD)
                elif self.ACCESS_KEY in credentials:
                    logger.info("Found credentials in boto config file: %s",
                                filename)
                    access_key, secret_key = self._extract_creds_from_mapping(
                        credentials, self.ACCESS_KEY, self.SECRET_KEY)
                    return Credentials(access_key, secret_key,
                                       method=self.METHOD)

class AssumeRoleProvider(CredentialProvider):

    METHOD = 'assume-role'
    ROLE_CONFIG_VAR = 'role_arn'
    # Credentials are considered expired (and will be refreshed) once the total
    # remaining time left until the credentials expires is less than the
    # EXPIRY_WINDOW.
    EXPIRY_WINDOW_SECONDS = 60 * 15

    def __init__(self, load_config, client_creator, cache, profile_name,
                 prompter=getpass.getpass):
        """

        :type load_config: callable
        :param load_config: A function that accepts no arguments, and
            when called, will return the full configuration dictionary
            for the session (``session.full_config``).

        :type client_creator: callable
        :param client_creator: A factory function that will create
            a client when called.  Has the same interface as
            ``ibm_botocore.session.Session.create_client``.

        :type cache: JSONFileCache
        :param cache: An object that supports ``__getitem__``,
            ``__setitem__``, and ``__contains__``.  An example
            of this is the ``JSONFileCache`` class.

        :type profile_name: str
        :param profile_name: The name of the profile.

        :type prompter: callable
        :param prompter: A callable that returns input provided
            by the user (i.e raw_input, getpass.getpass, etc.).

        """
        #: The cache used to first check for assumed credentials.
        #: This is checked before making the AssumeRole API
        #: calls and can be useful if you have short lived
        #: scripts and you'd like to avoid calling AssumeRole
        #: until the credentials are expired.
        self.cache = cache
        self._load_config = load_config
        # client_creator is a callable that creates function.
        # It's basically session.create_client
        self._client_creator = client_creator
        self._profile_name = profile_name
        self._prompter = prompter
        # The _loaded_config attribute will be populated from the
        # load_config() function once the configuration is actually
        # loaded.  The reason we go through all this instead of just
        # requiring that the loaded_config be passed to us is to that
        # we can defer configuration loaded until we actually try
        # to load credentials (as opposed to when the object is
        # instantiated).
        self._loaded_config = {}

    def load(self):
        self._loaded_config = self._load_config()
        if self._has_assume_role_config_vars():
            return self._load_creds_via_assume_role()

    def _has_assume_role_config_vars(self):
        profiles = self._loaded_config.get('profiles', {})
        return self.ROLE_CONFIG_VAR in profiles.get(self._profile_name, {})

    def _load_creds_via_assume_role(self):
        # We can get creds in one of two ways:
        # * It can either be cached on disk from an pre-existing session
        # * Cache doesn't have the creds (or is expired) so we need to make
        #   an assume role call to get temporary creds, which we then cache
        #   for subsequent requests.
        creds = self._load_creds_from_cache()
        if creds is not None:
            logger.debug("Credentials for role retrieved from cache.")
            return creds
        else:
            # We get the Credential used by ibm_botocore as well
            # as the original parsed response from the server.
            creds, response = self._retrieve_temp_credentials()
            cache_key = self._create_cache_key()
            self._write_cached_credentials(response, cache_key)
            return creds

    def _load_creds_from_cache(self):
        cache_key = self._create_cache_key()
        try:
            from_cache = self.cache[cache_key]
            if self._is_expired(from_cache):
                # Don't need to delete the cache entry,
                # when we refresh via AssumeRole, we'll
                # update the cache with the new entry.
                logger.debug(
                    "Credentials were found in cache, but they are expired.")
                return None
            else:
                return self._create_creds_from_response(from_cache)
        except KeyError:
            return None

    def _is_expired(self, credentials):
        end_time = parse(credentials['Credentials']['Expiration'])
        now = datetime.datetime.now(tzlocal())
        seconds = total_seconds(end_time - now)
        return seconds < self.EXPIRY_WINDOW_SECONDS

    def _create_cache_key(self):
        role_config = self._get_role_config_values()
        # On windows, ':' is not allowed in filenames, so we'll
        # replace them with '_' instead.
        role_arn = role_config['role_arn'].replace(':', '_')
        role_session_name = role_config.get('role_session_name')
        if role_session_name:
            cache_key = '%s--%s--%s' % (self._profile_name, role_arn,
                                        role_session_name)
        else:
            cache_key = '%s--%s' % (self._profile_name, role_arn)

        return cache_key.replace('/', '-')

    def _write_cached_credentials(self, creds, cache_key):
        self.cache[cache_key] = creds

    def _get_role_config_values(self):
        # This returns the role related configuration.
        profiles = self._loaded_config.get('profiles', {})
        try:
            source_profile = profiles[self._profile_name]['source_profile']
            role_arn = profiles[self._profile_name]['role_arn']
            mfa_serial = profiles[self._profile_name].get('mfa_serial')
        except KeyError as e:
            raise PartialCredentialsError(provider=self.METHOD,
                                          cred_var=str(e))
        external_id = profiles[self._profile_name].get('external_id')
        role_session_name = \
            profiles[self._profile_name].get('role_session_name')
        if source_profile not in profiles:
            raise InvalidConfigError(
                error_msg=(
                    'The source_profile "%s" referenced in '
                    'the profile "%s" does not exist.' % (
                        source_profile, self._profile_name)))
        source_cred_values = profiles[source_profile]
        return {
            'role_arn': role_arn,
            'external_id': external_id,
            'source_profile': source_profile,
            'mfa_serial': mfa_serial,
            'source_cred_values': source_cred_values,
            'role_session_name': role_session_name
        }

    def _create_creds_from_response(self, response):
        config = self._get_role_config_values()
        if config.get('mfa_serial') is not None:
            # MFA would require getting a new TokenCode which would require
            # prompting the user for a new token, so we use a different
            # refresh_func.
            refresh_func = create_mfa_serial_refresher()
        else:
            refresh_func = create_assume_role_refresher(
                self._create_client_from_config(config),
                self._assume_role_base_kwargs(config))
        return RefreshableCredentials(
            access_key=response['Credentials']['AccessKeyId'],
            secret_key=response['Credentials']['SecretAccessKey'],
            token=response['Credentials']['SessionToken'],
            method=self.METHOD,
            expiry_time=_parse_if_needed(
                response['Credentials']['Expiration']),
            refresh_using=refresh_func)

    def _create_client_from_config(self, config):
        source_cred_values = config['source_cred_values']
        client = self._client_creator(
            'sts', aws_access_key_id=source_cred_values['aws_access_key_id'],
            aws_secret_access_key=source_cred_values['aws_secret_access_key'],
            aws_session_token=source_cred_values.get('aws_session_token'),
        )
        return client

    def _retrieve_temp_credentials(self):
        logger.debug("Retrieving credentials via AssumeRole.")
        config = self._get_role_config_values()
        client = self._create_client_from_config(config)

        assume_role_kwargs = self._assume_role_base_kwargs(config)

        response = client.assume_role(**assume_role_kwargs)
        creds = self._create_creds_from_response(response)
        return creds, response

    def _assume_role_base_kwargs(self, config):
        assume_role_kwargs = {'RoleArn': config['role_arn']}
        if config['external_id'] is not None:
            assume_role_kwargs['ExternalId'] = config['external_id']
        if config['mfa_serial'] is not None:
            token_code = self._prompter("Enter MFA code: ")
            assume_role_kwargs['SerialNumber'] = config['mfa_serial']
            assume_role_kwargs['TokenCode'] = token_code
        if config['role_session_name'] is not None:
            assume_role_kwargs['RoleSessionName'] = config['role_session_name']
        else:
            role_session_name = 'AWS-CLI-session-%s' % (int(time.time()))
            assume_role_kwargs['RoleSessionName'] = role_session_name
        return assume_role_kwargs

class ContainerProvider(CredentialProvider):

    METHOD = 'container-role'
    ENV_VAR = 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI'
    ENV_VAR_FULL = 'AWS_CONTAINER_CREDENTIALS_FULL_URI'
    ENV_VAR_AUTH_TOKEN = 'AWS_CONTAINER_AUTHORIZATION_TOKEN'

    def __init__(self, environ=None, fetcher=None):
        if environ is None:
            environ = os.environ
        if fetcher is None:
            fetcher = ContainerMetadataFetcher()
        self._environ = environ
        self._fetcher = fetcher

    def load(self):
        # This cred provider is only triggered if the self.ENV_VAR is set,
        # which only happens if you opt into this feature.
        if self.ENV_VAR in self._environ or self.ENV_VAR_FULL in self._environ:
            return self._retrieve_or_fail()

    def _retrieve_or_fail(self):
        if self._provided_relative_uri():
            full_uri = self._fetcher.full_url(self._environ[self.ENV_VAR])
        else:
            full_uri = self._environ[self.ENV_VAR_FULL]
        headers = self._build_headers()
        fetcher = self._create_fetcher(full_uri, headers)
        creds = fetcher()
        return RefreshableCredentials(
            access_key=creds['access_key'],
            secret_key=creds['secret_key'],
            token=creds['token'],
            method=self.METHOD,
            expiry_time=_parse_if_needed(creds['expiry_time']),
            refresh_using=fetcher,
        )

    def _build_headers(self):
        headers = {}
        auth_token = self._environ.get(self.ENV_VAR_AUTH_TOKEN)
        if auth_token is not None:
            return {
                'Authorization': auth_token
            }

    def _create_fetcher(self, full_uri, headers):
        def fetch_creds():
            try:
                response = self._fetcher.retrieve_full_uri(
                    full_uri, headers=headers)
            except MetadataRetrievalError as e:
                logger.debug("Error retrieving container metadata: %s", e,
                             exc_info=True)
                raise CredentialRetrievalError(provider=self.METHOD,
                                               error_msg=str(e))
            return {
                'access_key': response['AccessKeyId'],
                'secret_key': response['SecretAccessKey'],
                'token': response['Token'],
                'expiry_time': response['Expiration'],
            }
        return fetch_creds

    def _provided_relative_uri(self):
        return self.ENV_VAR in self._environ

class CredentialResolver(object):

    def __init__(self, providers):
        """

        :param providers: A list of ``CredentialProvider`` instances.

        """
        self.providers = providers

    def insert_before(self, name, credential_provider):
        """
        Inserts a new instance of ``CredentialProvider`` into the chain that
        will be tried before an existing one.

        :param name: The short name of the credentials you'd like to insert the
            new credentials before. (ex. ``env`` or ``config``). Existing names
            & ordering can be discovered via ``self.available_methods``.
        :type name: string

        :param cred_instance: An instance of the new ``Credentials`` object
            you'd like to add to the chain.
        :type cred_instance: A subclass of ``Credentials``
        """
        try:
            offset = [p.METHOD for p in self.providers].index(name)
        except ValueError:
            raise UnknownCredentialError(name=name)
        self.providers.insert(offset, credential_provider)

    def insert_after(self, name, credential_provider):
        """
        Inserts a new type of ``Credentials`` instance into the chain that will
        be tried after an existing one.

        :param name: The short name of the credentials you'd like to insert the
            new credentials after. (ex. ``env`` or ``config``). Existing names
            & ordering can be discovered via ``self.available_methods``.
        :type name: string

        :param cred_instance: An instance of the new ``Credentials`` object
            you'd like to add to the chain.
        :type cred_instance: A subclass of ``Credentials``
        """
        offset = self._get_provider_offset(name)
        self.providers.insert(offset + 1, credential_provider)

    def remove(self, name):
        """
        Removes a given ``Credentials`` instance from the chain.

        :param name: The short name of the credentials instance to remove.
        :type name: string
        """
        available_methods = [p.METHOD for p in self.providers]
        if name not in available_methods:
            # It's not present. Fail silently.
            return

        offset = available_methods.index(name)
        self.providers.pop(offset)

    def get_provider(self, name):
        """Return a credential provider by name.

        :type name: str
        :param name: The name of the provider.

        :raises UnknownCredentialError: Raised if no
            credential provider by the provided name
            is found.
        """
        return self.providers[self._get_provider_offset(name)]

    def _get_provider_offset(self, name):
        try:
            return [p.METHOD for p in self.providers].index(name)
        except ValueError:
            raise UnknownCredentialError(name=name)

    def load_credentials(self):
        """
        Goes through the credentials chain, returning the first ``Credentials``
        that could be loaded.
        """
        # First provider to return a non-None response wins.
        for provider in self.providers:
            logger.debug("Looking for credentials via: %s", provider.METHOD)
            creds = provider.load()
            if creds is not None:
                return creds

        # If we got here, no credentials could be found.
        # This feels like it should be an exception, but historically, ``None``
        # is returned.
        #
        # +1
        # -js
        return None


# -*- coding: utf-8 -*-

import math

def quadratic(a,b,c):
    testNum = b * b - 4 * a * c
    if testNum < 0:
        print("wrong")
    else:
        return (-b + math.sqrt(testNum))/(2 * a), (-b - math.sqrt(testNum))/(2 * a)

print('quadratic(2, 3, 1) =', quadratic(2, 3, 1))
print('quadratic(1, 3, -4) =', quadratic(1, 3, -4))

if quadratic(2, 3, 1) != (-0.5, -1.0):
    print('测试失败')
elif quadratic(1, 3, -4) != (1.0, -4.0):
    print('测试失败')
else:
    print('测试成功')


from datetime import datetime

from airflow import DAG
from airflow.operators.python_operator import PythonOperator

from dwh import functions as DWH
from tools import cfg

default_args = {
    'owner': cfg.append_date.owner,
    'email': cfg.append_date.email,
    'email_on_failure': cfg.append_date.email_on_failure,
    'retries': cfg.append_date.retries
}

with DAG(
    'append_date_dag',
    description='Appends date to dim_date in DWH',
    schedule_interval='@daily',
    start_date=datetime(2021,8,22,0,0),
    default_args=default_args
) as dag:

    download_api_data = PythonOperator(
        task_id='append_date',
        dag=dag,
        python_callable=DWH.append_dim_date,
        provide_context=True,
    )


# coding=utf-8
# python2.7
'''
 @bloke 提供指定的卡号、点播时间<前后最大时差3分钟>，来搜索关于此卡号在此时间的点播信息。
'''

import os
import sys
import datetime
import re
import threading
import time

siteadminsvc_list = []
weiwoo_list = []
weiwoo_path = []
pho_vss = []
pho_erm = []

# 区域网段地址
area_list = {
    'A' : '172.16.72.',
    'B' : '172.16.73.',
    'C' : '172.16.74.',
    'D' : '172.16.75.',
    'E' : '172.16.76.',
    'F' : '172.16.77.',
    'G' : '172.16.78.',
    'H' : '172.16.84.',
}
area = 'A'
re_time = re.compile(r'\d{1,2}\:\d{1,2}')
#try:
#    while 1:    # 获取卡号
#        card_num = raw_input('Card Num: ').strip()
#        if card_num: break
#    while 1:    # 获取时间点
#        the_time = raw_input('Time [HH:MM]: ').strip()
#        if len(the_time) == 0: continue
#        if not re_time.match(the_time):
#            print "Please enter a valid time format !"
#            continue
#        if the_time: break
#    while 1:       #获取时间范围
#        range_time = raw_input('Range time [min]: ').strip()
#        if not range_time or not range_time.isdigit(): continue
#        else:
#            range_time *= 60
#            break
#    while 1:    #获取区域
#        area = raw_input('Area: ').strip().upper()
#        if len(area) == 0: continue
#        if len(area) > 1 or not area_list.has_key(area):
#            print "Please enter a valid area number !"
#            continue
#        else: break
#except KeyboardInterrupt, e:
#    sys.exit('\nExit ...')

# 定义不同日志的主机地址
host_list = {
    'rtspproxy' : ['21', '25', '29'],
    'ssm_tianshan' : ['21', '25', '29'],
    'mod' : ['29'],
    'weiwoo' : ['27'],
    'stream1' : ['23'],
    'stream2' : ['31'],
    'siteadminsvc' : ['21'],
}
# 定义各区域的OSTR2的主机地址
#address_list = {
#    'A' : '172.27.19.52',
#    'B' : '172.27.27.52',
#    'C' : '172.27.35.52',
#    'E' : '172.27.51.52',
#    'H' : '172.27.26.52',
#}
#for h in area_list:
#    for i,l in conf.yml.iteritems():
#        for n in l:
#            address_list[h + '_' + i] = area_list[h] + n

# 文件路径
dir_path = r'\c$\TianShan\logs'
rtspproxy_files = ['RtspProxy.log'] + ['RtspProxy.%s.log' % n for n in range(5)]
ssm_tianshan_files = ['ssm_tianshan_s1.log'] + ['ssm_tianshan_s1.%s.log' % n for n in range(10)]
mod_files = ['MODSvc.log'] + ['MODSvc.%s.log' % n for n in range(5)]
weiwoo_files = [['weiwoo.log'] + ['weiwoo.%s.log' % n for n in range(5)], ['Path.log'] + ['Path.%s.log' % n for n in range(10)], ['pho_VSS.log'] + ['pho_VSS.%s.log' % n for n in range(10)], ['pho_ERM.log'] + ['pho_ERM.%s.log' % n for n in range(5)]]
siteadminsvc_files = ['SiteAdminSvc.log'] + ['SiteAdminSvc.%s.log' % n for n in range(5)]
stream_files1 = [['NSS2.log'] + ['NSS2.%s.log' % n for n in range(5)], ['NSS.log'] + ['NSS.%s.log' % n for n in range(5)]]
stream_files2 = [['NSS3.log'] + ['NSS3.%s.log' % n for n in range(5)], ['NSS4.log'] + ['NSS4.%s.log' % n for n in range(5)]]
#ostr2_files = [['RtspProxy.log'] + ['RtspProxy.%s.log' % n for n in range(1,6)], ['ssm_ngod2.log'] + ['ssm_ngod2.%s.log' % n for n in range(1,6)], ['StreamSmith.log'] + ['StreamSmith.%s.log' % n for n in range(1,6)]]

logfile_list = {
    'rtspproxy' : rtspproxy_files,
    'ssm_tianshan' : ssm_tianshan_files,
    'mod' : mod_files,
    'weiwoo' : weiwoo_files,
    'stream1' : stream_files1,
    'stream2' : stream_files2,
    'siteadminsvc' : siteadminsvc_files,
}

# 生成主机地址对应的日志文件列表，例：{20:[file_list]}
def get_file(sign, area):
    file_abspath_dict = {}
    for l in logfile_list[sign]:
        if isinstance(l, list):
            for i in l:
                for n in host_list[sign]:
                    if n in file_abspath_dict:
                        file_abspath_dict[n].extend(['\\\\' + area_list[area] + n + dir_path + '\\' + i])
                    else:
                        file_abspath_dict[n] = ['\\\\' + area_list[area] + n + dir_path + '\\' + i]
        else:
            for n in host_list[sign]:
                if n in file_abspath_dict:
                    file_abspath_dict[n].extend(['\\\\' + area_list[area] + n + dir_path + '\\' + l])
                else:
                    file_abspath_dict[n] = ['\\\\' + area_list[area] + n + dir_path + '\\' + l]
    return file_abspath_dict

rtspproxy_abs_path = get_file('rtspproxy', area)
ssm_tianshan_abs_path = get_file('ssm_tianshan', area)
mod_abs_path = get_file('mod', area)
weiwoo_abs_path = get_file('weiwoo', area)
stream1_abs_path = get_file('stream1', area)
stream2_abs_path = get_file('stream2', area)
siteadminsvc_abs_path = get_file('siteadminsvc', area)

for key in stream2_abs_path:
    print(key)
    for l in stream2_abs_path[key]:
        print(l)
'''
# 定义正则匹配
re_number = re.compile(r'%s' % card_num)
re_time = re.compile(r'(\d{2}:\d{2}):')
re_socket = re.compile(r'(\s\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d*)')
re_session = re.compile(r'processed: session\[([\d\]]+)]')
re_stream_session = re.compile(r'STREAM\/(\S*)')
re_stream_num = re.compile(r'-h (\S*)\ -p (\S*)')
re_weiwoo_session = re.compile(r'weiwoo session\((\w+)\)')
re_mod_session = re.compile(r'ModPur\/(\S*)')

# 在rtspproxy日志中搜索指定卡号和对应时间的相关信息
def search_card_number(card_num, the_time):
    info_card_number = []
    session_num = ''
    for the_ip, files in rtspproxy_abs_path.iteritems():
        for the_file in files:
            with file(the_file, 'rb') as f:
                for line in f.xreadlines():
                    if re.findall(re_number, line):
                        log_time = re_time.findall(line)[0]
                        #将时间延长为前后三分钟
                        if abs((datetime.datetime.strptime(the_time, '%H:%M') - datetime.datetime.strptime(log_time, '%H:%M')).total_seconds()) < range_time:
                            info_card_number.append(line)
                else:
                    # 一旦在一个文件中匹配到相关信息，则退出循环
                    if len(info_card_number) > 0: break
            if len(info_card_number) > 0: break
        if len(info_card_number) > 0: break
    print info_card_number
# 返回对应卡号和时间点的信息，日志文件所在机器的IP地址的最后一位，匹配到信息的日志文件名
    return info_card_number, the_ip, the_file

# 从得到的卡号的相关信息中匹配出进程相关信息
def search_socket(info_card_number, the_ip, the_file, n=0):
    info_socket = []
    # 如果在指定卡号和时间内，匹配出多行信息，需要选择
    if len(info_card_number) > 1:
        for l in enumerate(info_card_number):
            print l
        try:
            n = int(raw_input('Number: ').strip())
            info_socket_num = re_socket.findall(info_card_number[n])[0]
        except KeyboardInterrupt, e:
            sys.exit('\nExit ...')
        except IndexError, e:
            print "Please enter the correct serial number ! [%s]" % e
            sys.exit('Error.')
        except TypeError, e:
            print "Please enter the correct serial number ! [%s]" % e

    else:
        try:
            info_socket_num = re_socket.findall(info_card_number[n])[0]
        except IndexError, e:
            sys.exit('No relevant information. [ %s ]' % e)
    with file(the_file, 'rb') as f:
        for line in f.xreadlines():
            if info_socket_num in line:
                info_socket.append(line)
    if not info_socket:
        raise NameError('No socket information.')
    else:
        for i in info_socket:
            if re_session.findall(i):
                try:
                    session_num = re_session.findall(i)[0]
                except IndexError,e:
                    pass
    return info_socket, session_num, the_ip #返回socket相关信息，session号

def search_ssm_tianshan(info_socket, session_num, the_ip):
    info_tianshan = []
    for file_n in ssm_tianshan_abs_path[the_ip]:
        with file(file_n) as f:
            for line in f.xreadlines():
                if session_num in line:
                    info_tianshan.append(line)
    return info_tianshan

def search_id(info_tianshan):
    stream_session, weiwoo_session, mod_session, stream_h, stream_p = '','','','',''
    for i in info_tianshan:
        if re_stream_session.findall(i):
            stream_session = re_stream_session.findall(i)[0]
            if re_stream_num.findall(i):
                stream_h, stream_p = re_stream_num.findall(i)[0]
        if re_weiwoo_session.findall(i):
            weiwoo_session = re_weiwoo_session.findall(i)[0]
        if re_mod_session.findall(i):
            mod_session = re_mod_session.findall(i)[0]
    return stream_session, weiwoo_session, mod_session, stream_h, stream_p

def search_mod_info(mod_session=''):
    the_mod_info = []
    if mod_session:
        for the_ip, file_l in mod_abs_path.iteritems():
            for file_n in file_l:
                with file(file_n, 'rb') as f:
                    for line in f.xreadlines():
                        if mod_session in line:
                            the_mod_info.append(line)
                if len(the_mod_info) > 0: break
            if len(the_mod_info) > 0: break
    return the_mod_info

def generate_flist(s, s_list):
    ss = []
    for i in s_list:
        if s in i:
            ss.append(i)
    return ss

def search_weiwoo_siteadminsvc_log(weiwoo_session, f):
    global siteadminsvc_list
    siteadminsvc_list.append('\n' + 'Siteadminsvc'.center(40, '*') + '\n')
    if weiwoo_session:
        siteadminsvc_dict = siteadminsvc_abs_path
        for the_ip, items in siteadminsvc_dict.iteritems():
            for item in items:
                with file(item, 'rb') as f:
                    for line in f.xreadlines():
                        if weiwoo_session in line:
                            siteadminsvc_list.append(line)
                if len(siteadminsvc_list) > 1: break

def search_weiwoo_weiwoo_log(weiwoo_session, f):
    global weiwoo_list
    weiwoo_list.append('\n' + 'Weiwoo'.center(40, '*') + '\n')
    if weiwoo_session:
        for the_ip, file_n in weiwoo_abs_path.iteritems():
            weiwoo_file_list = generate_flist('weiwoo', file_n)
        for item in weiwoo_file_list:
            with file(item, 'rb') as f:
                for line in f.xreadlines():
                    if weiwoo_session in line:
                        weiwoo_list.append(line)
            if len(weiwoo_list) > 1: break

def search_weiwoo_path_log(weiwoo_session, f):
    global weiwoo_path
    weiwoo_path.append('\n' + 'Path'.center(40, '*') + '\n')
    if weiwoo_session:
        for the_ip, file_n in weiwoo_abs_path.iteritems():
            path_file_list = generate_flist('Path', file_n)
        for item in path_file_list:
            with file(item, 'rb') as f:
                for line in f.xreadlines():
                    if weiwoo_session in line:
                        weiwoo_path.append(line)
            if len(weiwoo_path) > 1: break

def search_weiwoo_pho_vss_log(weiwoo_session, f):
    global pho_vss
    pho_vss.append('\n' + 'Pho_vss'.center(40, '*') + '\n')
    if weiwoo_session:
        for the_ip, file_n in weiwoo_abs_path.iteritems():
            pho_vss_file_list = generate_flist('pho_VSS', file_n)
        for item in pho_vss_file_list:
            with file(item, 'rb') as f:
                for line in f.xreadlines():
                    if weiwoo_session in line:
                        pho_vss.append(line)
            if len(pho_vss) > 1: break

def search_weiwoo_pho_erm_log(weiwoo_session, f):
    global pho_erm
    pho_erm.append('\n' + 'Pho_erm'.center(40, '*') + '\n')
    if weiwoo_session:
        for the_ip, file_n in weiwoo_abs_path.iteritems():
            pho_erm_file_list = generate_flist('pho_ERM', file_n)
        for item in pho_erm_file_list:
            with file(item, 'rb') as f:
                for line in f.xreadlines():
                    if weiwoo_session in line:
                        pho_erm.append(line)
            if len(pho_erm) > 1: break

def search_stream_info(stream_session, stream_h, stream_p):
    nss_log = []
    if stream_session != '':
        if stream_h == 'sss6_ss_cl':
            sign1 = 'stream2'
            if stream_p == '10800':
                sign2 = 'NSS3'
            else:
                sign2 = 'NSS4'
        else:
            sign1 = 'stream1'
            if stream_p == '20800':
                sign2 = 'NSS2'
            else: sign2 = 'NSS'
        if sign1 == 'stream2':
            for the_ip, file_l in stream2_abs_path.iteritems():
                for file_n in file_l:
                    if sign2 in file_n:
                        with file(file_n, 'rb') as f:
                            for line in f.xreadlines():
                                if stream_session in line:
                                    nss_log.append(line)
                    if len(nss_log) > 0:
                        break
                if len(nss_log) > 0:
                    break
        else:
            for the_ip, file_l in stream1_abs_path.iteritems():
                for file_n in file_l:
                    if sign2 in file_n:
                        with file(file_n, 'rb') as f:
                            for line in f.xreadlines():
                                if stream_session in line:
                                    nss_log.append(line)
                    if len(nss_log) > 0:
                        break
                if len(nss_log) > 0:
                    break
    return nss_log

if __name__ == '__main__':
    f = file('log_%s.txt' % str(card_num), 'wb')
    info_card_number, the_ip, the_file = search_card_number(card_num, the_time)
    if not info_card_number:
        f.close()
        sys.exit('No relevant information.\nExit !')
    f.write('RTSPPROXY'.center(40, '*') + '\n')
    for i in info_card_number: f.write(i)
    try:
        info_socket, session_num, the_ip = search_socket(info_card_number, the_ip, the_file)
        for i in info_socket: f.write(i)
    except NameError, e:
        f.write(e)
        f.close()
        sys.exit(e)
    print "RTSPPROXY logging searches completed ..."

    info_tianshan = search_ssm_tianshan(info_socket, session_num, the_ip)
    f.write('\n' + 'TIANSHAN'.center(40,'*') + '\n')
    for i in info_tianshan: f.write(i)
    print "TIANSHAN logging searches completed ..."

    stream_session, weiwoo_session, mod_session, stream_h, stream_p = search_id(info_tianshan)
    the_mod_info = search_mod_info(mod_session)
    f.write('\n' + 'MOD'.center(40, '*') + '\n')
    for i in the_mod_info: f.write(i)
    print "MOD logging searches completed ..."

    nss_log = search_stream_info(stream_session, stream_h, stream_p)
    f.write('\n' + 'NSS'.center(40, '*') + '\n')
    for i in nss_log: f.write(i)
    print "NSS logging searches completed ..."

    q = []
    for n in [search_weiwoo_siteadminsvc_log, search_weiwoo_weiwoo_log, search_weiwoo_path_log, search_weiwoo_pho_vss_log, search_weiwoo_pho_erm_log]:
        p = threading.Thread(target=n, name=str(n), args=(weiwoo_session, f))
        p.start()
        q.append(p)
    for i in q: i.join()
    for l in [siteadminsvc_list, weiwoo_list, weiwoo_path, pho_vss, pho_erm]:
        for i in l:
            f.write(i)
    print "WEIWOO logging searches completed ...\n"
    t=5
    ll = range(1,6)
    for t in reversed(ll):
        sys.stdout.write( "[ %s seconds after the exit. ] \r" % t )
        t -= 1
        time.sleep(1)
'''


# users/views.py

from django.shortcuts import redirect, render
from django.core.files.storage import FileSystemStorage
from django.contrib.auth.decorators import login_required
from random import randint
from datetime import date
import django_tables2 as tables

from users.forms import UserForm, DealerInfoForm
from users.models import Dealer_Info, Personal_Info
from common import create_exception

def dashboard(request):
    return render(request, "users/dashboard.html")

def register(request):
    res = create_exception(
        request, __name__, exception="unknown request method")

    if request.method == "POST":
        form = UserForm(request.POST)
        if form.is_valid():
            user_obj = form.save()
            user_obj.first_name = user_obj.first_name.capitalize()
            user_obj.last_name = user_obj.last_name.capitalize()

            username = user_obj.first_name.lower(
            )[0] + user_obj.last_name.lower() + str(randint(1, 10000))
            user_obj.username = username
            user_details_obj = Personal_Info(username=username, date_of_birth=form.cleaned_data['date_of_birth'],
                                             pin_code=form.cleaned_data['pin_code'], address=form.cleaned_data['address'],
                                             city=form.cleaned_data['city'], email_verified=False)
            try:
                user_obj.save()
                user_details_obj.save()
                print("{} : {} {} added".format(
                    username, user_obj.first_name, user_obj.last_name))
            except Exception as e:
                print(e)
                res = create_exception(request, __name__, exception=e)
            else:
                res = redirect('dashboard')
        else:
            res = create_exception(request, __name__, str(
                ValueError("form data in request not valid")))
    elif request.method == "GET":
        empty_form = UserForm()
        res = render(request, "users/register.html", {"form": empty_form, "helper": empty_form.get_helper()})

    return res

@login_required
def add_dealer(request):
    res = create_exception(
        request, __name__, exception="unknown request method")
    if request.method == "POST":
        data = request.POST.dict()
        dealer_obj = Dealer_Info(first_name=data['first_name'], last_name=data['last_name'], pin_code=data['pin_code'], address=data['address'], city=data['city'],
                                 managed_by=data['managed_by'], date_of_registration=date.today(), pan_number=data['pan_number'], aadhar_number=data['aadhar_number'], unique_code='NOT_ASSIGNED')
        try:
            dealer_obj.save()
        except Exception as e:
            print(e)
            res = create_exception(request, __name__, exception=e)
        else:
            res = redirect('dashboard')
    elif request.method == "GET":
        empty_form = DealerInfoForm(username=request.user)
        res = render(request, "users/add_dealer.html",
                     {"form": empty_form, "helper": empty_form.get_helper()})

    return res

@login_required
def upload_user_headshot(request):
    res = create_exception(
        request, __name__, exception="unknown request method")
    fs = FileSystemStorage()
    if request.method == "POST":
        if request.FILES['profile_picture']:
            file = request.FILES['profile_picture']
            file_name = './users/' + \
                str(request.user) + '.' + file.name.split('.')[-1]

            if fs.exists(file_name):
                fs.delete(file_name)

            try:
                filename = fs.save(file_name, file)
            except Exception as e:
                print(e)
                res = create_exception(request, __name__, exception=e)
            else:
                res = render(request, 'users/user_headshot_upload.html',
                            {'uploaded_file_url': fs.url(filename)})
        else:
            res = create_exception(request, __name__, exception="no file uploaded")

    elif request.method == "GET":
        if str(request.user) != "AnonymousUser":
            file_name = './users/' + str(request.user) + '.png'
            if fs.exists(file_name):
                res = render(request, 'users/user_headshot_upload.html',
                             {'uploaded_file_url': fs.url(file_name)})
        # user is AnonymousUser or doesn't have profilePic
        file_name = './users/profilePic.png'
        res = render(request, 'users/user_headshot_upload.html',
                     {'uploaded_file_url': fs.url(file_name)})

    return res

@login_required
def get_my_dealers(request):
    res = create_exception(
        request, __name__, exception="unknown request method")

    class Dealer_Info_Table(tables.Table):
        class Meta:
            model = Dealer_Info

    if request.method == "GET":
        username = str(request.user)
        dealer_objs = Dealer_Info.objects.filter(managed_by=username)
        table = Dealer_Info_Table(dealer_objs)
        res = render(request, 'users/dealers_under_user.html',
                     {'table': table})
    elif request.method == "POST":
        res = create_exception(
            request, __name__, exception="POST method not implemented")

    return res

@login_required
def upload_dealer_docs(request):
    res = create_exception(
        request, __name__, exception="unknown request method")
    if request.method == "POST":
        fs = FileSystemStorage()
        unique_code = request.POST.dict()['unique_code']
        aadhar_card = request.FILES['aadhar_card']
        pan_card = request.FILES['pan_card']

        # save both the files correctly
        file_name_aadhar_card = './dealers/' + unique_code + '/' + aadhar_card.name
        file_name_pan_card = './dealers/' + unique_code + '/' + pan_card.name

        if fs.exists(file_name_aadhar_card):
            fs.delete(file_name_aadhar_card)
        fs.save(file_name_aadhar_card, aadhar_card)

        if fs.exists(file_name_pan_card):
            fs.delete(file_name_pan_card)
        fs.save(file_name_pan_card, pan_card)

        res = render(request, 'users/dashboard.html')

    elif request.method == "GET":
        unique_code = request.GET.dict()['unique_code']
        dealer_obj = None
        try:
            dealer_obj = Dealer_Info.objects.get(unique_code=unique_code)
        except Exception as e:
            print(e)
            res = create_exception(request, __name__, exception=e)
        if dealer_obj is not None:
            res = render(request, 'users/add_dealer_docs.html',
                         {'dealer': dealer_obj})
        else:
            res = create_exception(
                request, __name__, exception="dealer with unique_code({}) not found".format(unique_code))

    return res