# Copyright 2010 OpenStack Foundation
# 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.

"""Implementation of an image service that uses Glance as the backend."""

from __future__ import absolute_import

import copy
import itertools
import random
import sys
import time

import cryptography
import glanceclient
from glanceclient.common import http
import glanceclient.exc
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_service import sslutils
from oslo_utils import excutils
from oslo_utils import netutils
from oslo_utils import timeutils
import six
from six.moves import range
import six.moves.urllib.parse as urlparse

from nova import exception
from nova.i18n import _LE, _LI, _LW
import nova.image.download as image_xfers
from nova import objects
from nova import signature_utils


glance_opts = [
    cfg.StrOpt('host',
               default='$my_ip',
               # TODO(sdague): remove in N
               deprecated_for_removal=True,
               help='Glance server hostname or IP address'),
    cfg.IntOpt('port',
               default=9292,
               min=1,
               max=65535,
               # TODO(sdague): remove in N
               deprecated_for_removal=True,
               help='Glance server port'),
    cfg.StrOpt('protocol',
                default='http',
                choices=('http', 'https'),
                # TODO(sdague): remove in N
                deprecated_for_removal=True,
                help='Protocol to use when connecting to glance. '
                     'Set to https for SSL.'),
    cfg.ListOpt('api_servers',
                help='''
A list of the glance api servers endpoints available to nova. These
should be fully qualified urls of the form
"scheme://hostname:port[/path]" (i.e. "http://10.0.1.0:9292" or
"https://my.glance.server/image")'''),
    cfg.BoolOpt('api_insecure',
                default=False,
                help='Allow to perform insecure SSL (https) requests to '
                     'glance'),
    cfg.IntOpt('num_retries',
               default=0,
               help='Number of retries when uploading / downloading an image '
                    'to / from glance.'),
    cfg.ListOpt('allowed_direct_url_schemes',
                default=[],
                help='A list of url scheme that can be downloaded directly '
                     'via the direct_url.  Currently supported schemes: '
                     '[file].'),
    cfg.BoolOpt('verify_glance_signatures',
                default=False,
                help='Require Nova to perform signature verification on '
                     'each image downloaded from Glance.'),
    ]

LOG = logging.getLogger(__name__)
CONF = cfg.CONF
CONF.register_opts(glance_opts, 'glance')
CONF.import_opt('auth_strategy', 'nova.api.auth')
CONF.import_opt('my_ip', 'nova.netconf')

supported_glance_versions = (1, 2)


def generate_glance_url():
    """Generate the URL to glance."""
    glance_host = CONF.glance.host
    if netutils.is_valid_ipv6(glance_host):
        glance_host = '[%s]' % glance_host
    return "%s://%s:%d" % (CONF.glance.protocol, glance_host,
                           CONF.glance.port)


def generate_image_url(image_ref):
    """Generate an image URL from an image_ref."""
    return "%s/images/%s" % (generate_glance_url(), image_ref)


def _endpoint_from_image_ref(image_href):
    """Return the image_ref and guessed endpoint from an image url.

    :param image_href: href of an image
    :returns: a tuple of the form (image_id, endpoint_url)
    """
    parts = image_href.split('/')
    image_id = parts[-1]
    # the endpoint is everything in the url except the last 3 bits
    # which are version, 'images', and image_id
    endpoint = '/'.join(parts[:-3])
    return (image_id, endpoint)


def generate_identity_headers(context, status='Confirmed'):
    return {
        'X-Auth-Token': getattr(context, 'auth_token', None),
        'X-User-Id': getattr(context, 'user', None),
        'X-Tenant-Id': getattr(context, 'tenant', None),
        'X-Roles': ','.join(getattr(context, 'roles', [])),
        'X-Identity-Status': status,
    }


def _glanceclient_from_endpoint(context, endpoint, version=1):
        """Instantiate a new glanceclient.Client object."""
        params = {}
        # NOTE(sdague): even if we aren't using keystone, it doesn't
        # hurt to send these headers.
        params['identity_headers'] = generate_identity_headers(context)
        if endpoint.startswith('https://'):
            # https specific params
            params['insecure'] = CONF.glance.api_insecure
            params['ssl_compression'] = False
            sslutils.is_enabled(CONF)
            if CONF.ssl.cert_file:
                params['cert_file'] = CONF.ssl.cert_file
            if CONF.ssl.key_file:
                params['key_file'] = CONF.ssl.key_file
            if CONF.ssl.ca_file:
                params['cacert'] = CONF.ssl.ca_file
        return glanceclient.Client(str(version), endpoint, **params)


def _determine_curr_major_version(endpoint):
    """Determines the current major version of the glance API in use

    :returns Integer version number or None if unable to determine version
    """
    http_client = http.HTTPClient(endpoint)
    try:
        response, content = http_client.get('/versions')
        for version in content['versions']:
            if version['status'] == 'CURRENT':
                res = version['id']
                # The 'id' value looks like "v2.2",
                # so grab the major version number which is 2 in this case
                res = int(res[1:res.find(".")])
                return res if res in supported_glance_versions else None
    except Exception:
        LOG.error(_LE("Unable to determine the glance API version"))


def get_api_servers():
    """Shuffle a list of CONF.glance.api_servers and return an iterator
    that will cycle through the list, looping around to the beginning
    if necessary.
    """
    api_servers = []

    configured_servers = ([generate_glance_url()]
                          if CONF.glance.api_servers is None
                          else CONF.glance.api_servers)
    for api_server in configured_servers:
        if '//' not in api_server:
            api_server = 'http://' + api_server
            # NOTE(sdague): remove in N.
            LOG.warn(
                _LW("No protocol specified in for api_server '%s', "
                    "please update [glance] api_servers with fully "
                    "qualified url including scheme (http / https)"),
                api_server)
        api_servers.append(api_server)
    random.shuffle(api_servers)
    return itertools.cycle(api_servers)