# Copyright (c) 2015 Alex Meade
# Copyright (c) 2015 Rushil Chugh
# Copyright (c) 2015 Navneet Singh
# Copyright (c) 2015 Yogesh Kshirsagar
#  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 copy
import math
import socket
import time
import uuid

from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import units
import six

from cinder import exception
from cinder.i18n import _, _LE, _LI, _LW
from cinder.openstack.common import loopingcall
from cinder import utils as cinder_utils
from cinder.volume.drivers.netapp.eseries import client
from cinder.volume.drivers.netapp.eseries import utils
from cinder.volume.drivers.netapp import options as na_opts
from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume import utils as volume_utils
from cinder.zonemanager import utils as fczm_utils


LOG = logging.getLogger(__name__)

CONF = cfg.CONF
CONF.register_opts(na_opts.netapp_basicauth_opts)
CONF.register_opts(na_opts.netapp_connection_opts)
CONF.register_opts(na_opts.netapp_eseries_opts)
CONF.register_opts(na_opts.netapp_transport_opts)


class NetAppESeriesLibrary(object):
    """Executes commands relating to Volumes."""

    VERSION = "1.0.0"
    REQUIRED_FLAGS = ['netapp_server_hostname', 'netapp_controller_ips',
                      'netapp_login', 'netapp_password',
                      'netapp_storage_pools']
    SLEEP_SECS = 5
    MAX_LUNS_PER_HOST = 255
    HOST_TYPES = {'aix': 'AIX MPIO',
                  'avt': 'AVT_4M',
                  'factoryDefault': 'FactoryDefault',
                  'hpux': 'HP-UX TPGS',
                  'linux_atto': 'LnxTPGSALUA',
                  'linux_dm_mp': 'LnxALUA',
                  'linux_mpp_rdac': 'Linux',
                  'linux_pathmanager': 'LnxTPGSALUA_PM',
                  'macos': 'MacTPGSALUA',
                  'ontap': 'ONTAP',
                  'svc': 'SVC',
                  'solaris_v11': 'SolTPGSALUA',
                  'solaris_v10': 'Solaris',
                  'vmware': 'VmwTPGSALUA',
                  'windows':
                  'Windows 2000/Server 2003/Server 2008 Non-Clustered',
                  'windows_atto': 'WinTPGSALUA',
                  'windows_clustered':
                  'Windows 2000/Server 2003/Server 2008 Clustered'
                  }
    # NOTE(ameade): This maps what is reported by the e-series api to a
    # consistent set of values that are reported by all NetApp drivers
    # to the cinder scheduler.
    SSC_DISK_TYPE_MAPPING = {
        'scsi': 'SCSI',
        'fibre': 'FCAL',
        'sas': 'SAS',
        'sata': 'SATA',
    }
    SSC_UPDATE_INTERVAL = 60  # seconds
    WORLDWIDENAME = 'worldWideName'

    def __init__(self, driver_name, driver_protocol="iSCSI",
                 configuration=None, **kwargs):
        self.configuration = configuration
        self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
        self.configuration.append_config_values(
            na_opts.netapp_connection_opts)
        self.configuration.append_config_values(na_opts.netapp_transport_opts)
        self.configuration.append_config_values(na_opts.netapp_eseries_opts)
        self.lookup_service = fczm_utils.create_lookup_service()
        self._backend_name = self.configuration.safe_get(
            "volume_backend_name") or "NetApp_ESeries"
        self.driver_name = driver_name
        self.driver_protocol = driver_protocol
        self._stats = {}
        self._ssc_stats = {}

    def do_setup(self, context):
        """Any initialization the volume driver does while starting."""
        self.context = context
        na_utils.check_flags(self.REQUIRED_FLAGS, self.configuration)

        port = self.configuration.netapp_server_port
        scheme = self.configuration.netapp_transport_type.lower()
        if port is None:
            if scheme == 'http':
                port = 8080
            elif scheme == 'https':
                port = 8443

        self._client = client.RestClient(
            scheme=scheme,
            host=self.configuration.netapp_server_hostname,
            port=port,
            service_path=self.configuration.netapp_webservice_path,
            username=self.configuration.netapp_login,
            password
=self.configuration.netapp_password)
        self._check_mode_get_or_register_storage_system()

    def _start_periodic_tasks(self):
        ssc_periodic_task = loopingcall.FixedIntervalLoopingCall(
            self._update_ssc_info)
        ssc_periodic_task.start(
interval=self.SSC_UPDATE_INTERVAL)

    def check_for_setup_error(self):
        self._check_host_type()
        self._check_multipath()
        self._check_storage_system()
        self._start_periodic_tasks()

    def _check_host_type(self):
        self.host_type =\
            self.HOST_TYPES.get(self.configuration.netapp_eseries_host_type,
                                None)
        if not self.host_type:
            raise exception.NetAppDriverException(
                _('Configured host type is not supported.'))

    def _check_multipath(self):
        if not self.configuration.use_multipath_for_image_xfer:
            msg = _LW('Production use of "%(backend)s" backend requires the '
                      'Cinder controller to have multipathing properly set up '
                      'and the configuration option "%(mpflag)s" to be set to '
                      '"True".') % {'backend': self._backend_name,
                                    'mpflag': 'use_multipath_for_image_xfer'}
            LOG.warning(msg)

    def _check_mode_get_or_register_storage_system(self):
        """Does validity checks for storage system registry and health."""
        def _resolve_host(host):
            try:
                ip = na_utils.resolve_hostname(host)
                return ip
            except socket.gaierror as e:
                LOG.error(_LE('Error resolving host %(host)s. Error - %(e)s.')
                          % {'host': host, 'e': e})
                raise exception.NoValidHost(
                    _("Controller IP '%(host)s' could not be resolved: %(e)s.")
                    % {'host': host, 'e': e})

        ips = self.configuration.netapp_controller_ips
        ips = [i.strip() for i in ips.split(",")]
        ips = [x for x in ips if _resolve_host(x)]
        host = na_utils.resolve_hostname(
            self.configuration.netapp_server_hostname)
        if host in ips:
            LOG.info(_LI('Embedded mode detected.'))
            system = self._client.list_storage_systems()[0]
        else:
            LOG.info(_LI('Proxy mode detected.'))
            system = self._client.register_storage_system(
                ips, password=self.configuration.netapp_sa_password)
        self._client.set_system_id(system.get('id'))

    def _check_storage_system(self):
        """Checks whether system is registered and has good status."""
        try:
            system = self._client.list_storage_system()
        except exception.NetAppDriverException:
            with excutils.save_and_reraise_exception():
                msg = _LI("System with controller addresses [%s] is not"
                          " registered with web service.")
                LOG.info(msg % self.configuration.netapp_controller_ips)
        password_not_in_sync = False
        if system.get('status', '').lower() == 'passwordoutofsync':
            password_not_in_sync = True
            new_pwd = self.configuration.netapp_sa_password
            self._client.update_stored_system_password(new_pwd)
            time.sleep(self.SLEEP_S