# Copyright 2018 Red Hat, Inc.
# Copyright 2022 OVHCloud
# 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 abc

import glance_store as store_api
from glance_store import backend
from oslo_config import cfg
from oslo_log import log as logging
import six
from taskflow import task

from glance.common import exception
from glance.i18n import _, _LE

LOG = logging.getLogger(__name__)

CONF = cfg.CONF


@six.add_metaclass(abc.ABCMeta)
class BaseDownload(task.Task):

    default_provides = 'file_uri'

    def __init__(self, task_id, task_type, action_wrapper, stores,
                 plugin_name):
        self.task_id = task_id
        self.task_type = task_type
        self.image_id = action_wrapper.image_id
        self.action_wrapper = action_wrapper
        self.stores = stores
        self._path = None
        self.plugin_name = plugin_name or 'Download'
        super(BaseDownload, self).__init__(
            name='%s-%s-%s' % (task_type, self.plugin_name, task_id))

        # NOTE(abhishekk): Use reserved 'os_glance_staging_store' for
        # staging the data, the else part will be removed once old way
        # of configuring store is deprecated.
        if CONF.enabled_backends:
            self.store = store_api.get_store_from_store_identifier(
                'os_glance_staging_store')
        else:
            if CONF.node_staging_uri is None:
                msg = (_("%(task_id)s of %(task_type)s not configured "
                         "properly. Missing node_staging_uri: %(work_dir)s") %
                       {'task_id': self.task_id,
                        'task_type': self.task_type,
                        'work_dir': CONF.node_staging_uri})
                raise exception.BadTaskConfiguration(msg)

            self.store = self._build_store()

    def _build_store(self):
        # NOTE(flaper87): Due to the nice glance_store api (#sarcasm), we're
        # forced to build our own config object, register the required options
        # (and by required I mean *ALL* of them, even the ones we don't want),
        # and create our own store instance by calling a private function.
        # This is certainly unfortunate but it's the best we can do until the
        # glance_store refactor is done. A good thing is that glance_store is
        # under our team's management and it gates on Glance so changes to
        # this API will (should?) break task's tests.
        # TODO(abhishekk): After removal of backend module from glance_store
        # need to change this to use multi_backend module.
        conf = cfg.ConfigOpts()
        try:
            backend.register_opts(conf)
        except cfg.DuplicateOptError:
            pass

        conf.set_override('filesystem_store_datadir',
                          CONF.node_staging_uri[7:],
                          group='glance_store')

        # NOTE(flaper87): Do not even try to judge me for this... :(
        # With the glance_store refactor, this code will change, until
        # that happens, we don't have a better option and this is the
        # least worst one, IMHO.
        store = store_api.backend._load_store(conf, 'file')

        if store is None:
            msg = (_("%(task_id)s of %(task_type)s not configured "
                     "properly. Could not load the filesystem store") %
                   {'task_id': self.task_id, 'task_type': self.task_type})
            raise exception.BadTaskConfiguration(msg)

        store.configure()
        return store

    def revert(self, result, **kwargs):
        LOG.error(_LE('Task: %(task_id)s failed to import image '
                      '%(image_id)s to the filesystem.'),
                  {'task_id': self.task_id,
                   'image_id': self.image_id})
        # NOTE(abhishekk): Revert image state back to 'queued' as
        # something went wrong.
        # NOTE(danms): If we failed to stage the image, then none
        # of the _ImportToStore() tasks could have run, so we need
        # to move all stores out of "importing" and into "failed".
        with self.action_wrapper as action:
            action.set_image_attribute(status='queued')
            action.remove_importing_stores(self.stores)
            action.add_failed_stores(self.stores)

        # NOTE(abhishekk): Deleting partial image data from staging area
        if self._path is not None:
            LOG.debug(('Deleting image %(image_id)s from staging '
                       'area.'), {'image_id': self.image_id})
            try:
                if CONF.enabled_backends:
                    store_api.delete(self._path, None)
                else:
                    store_api.delete_from_backend(self._path)
            except Exception:
                LOG.exception(_LE("Error reverting web/glance download "
                                  "task: %(task_id)s"), {
                    'task_id': self.task_id})
