# Copyright (c) 2019 Dell Inc. or its subsidiaries.
# 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.
"""PowerMax backend for the Dell EMC Manila driver."""

import copy
import random
import six

from oslo_config import cfg
from oslo_log import log
from oslo_utils import excutils
from oslo_utils import units

from manila.common import constants as const
from manila import exception
from manila.i18n import _
from manila.share.drivers.dell_emc.common.enas import constants
from manila.share.drivers.dell_emc.common.enas import utils as enas_utils
from manila.share.drivers.dell_emc.plugins import base as driver
from manila.share.drivers.dell_emc.plugins.powermax import (
    object_manager as manager)
from manila.share import utils as share_utils
from manila import utils

"""Version history:
    1.0.0 - Initial version
    2.0.0 - Implement IPv6 support
    3.0.0 - Rebranding to PowerMax
    3.1.0 - Access Host details prevents a read-only share mounts
            (bug #1845147)
    3.2.0 - Wrong format of export locations (bug #1871999)
    3.3.0 - Victoria release
    3.4.0 - Wallaby release
"""
VERSION = "3.4.0"

LOG = log.getLogger(__name__)

POWERMAX_OPTS = [
    cfg.StrOpt('powermax_server_container',
               help='Data mover to host the NAS server.'),
    cfg.ListOpt('powermax_share_data_pools',
                help='Comma separated list of pools that can be used to '
                     'persist share data.'),
    cfg.ListOpt('powermax_ethernet_ports',
                help='Comma separated list of ports that can be used for '
                     'share server interfaces. Members of the list '
                     'can be Unix-style glob expressions.')
]

CONF = cfg.CONF
CONF.register_opts(POWERMAX_OPTS)


@enas_utils.decorate_all_methods(enas_utils.log_enter_exit,
                                 debug_only=True)
class PowerMaxStorageConnection(driver.StorageConnection):
    """Implements powermax specific functionality for Dell EMC Manila driver.

    """
    @enas_utils.log_enter_exit
    def __init__(self, *args, **kwargs):
        super(PowerMaxStorageConnection, self).__init__(*args, **kwargs)
        if 'configuration' in kwargs:
            kwargs['configuration'].append_config_values(POWERMAX_OPTS)

        self.mover_name = None
        self.pools = None
        self.manager = None
        self.pool_conf = None
        self.reserved_percentage = None
        self.reserved_snapshot_percentage = None
        self.driver_handles_share_servers = True
        self.port_conf = None
        self.ipv6_implemented = True
        self.dhss_mandatory_security_service_association = {
            'nfs': None,
            'cifs': ['active_directory', ]
        }

    def create_share(self, context, share, share_server=None):
        """Create a share and export it based on protocol used."""
        share_name = share['id']
        size = share['size'] * units.Ki

        share_proto = share['share_proto'].upper()

        # Validate the share protocol
        if share_proto not in ('NFS', 'CIFS'):
            raise exception.InvalidShare(
                reason=(_('Invalid NAS protocol supplied: %s.')
                        % share_proto))

        # Get the pool name from share host field
        pool_name = share_utils.extract_host(share['host'], level='pool')
        if not pool_name:
            message = (_("Pool is not available in the share host %s.") %
                       share['host'])
            raise exception.InvalidHost(reason=message)

        # Validate share server
        self._share_server_validation(share_server)

        if share_proto == 'CIFS':
            vdm_name = self._get_share_server_name(share_server)
            server_name = vdm_name

            # Check if CIFS server exists.
            status, server = self._get_context('CIFSServer').get(server_name,
                                                                 vdm_name)
            if status != constants.STATUS_OK:
                message = (_("CIFS server %s not found.") % server_name)
                LOG.error(message)
                raise exception.EMCPowerMaxXMLAPIError(err=message)

        self._allocate_container(share_name, size, share_server, pool_name)

        if share_proto == 'NFS':
            location = self._create_nfs_share(share_name, share_server)
        elif share_proto == 'CIFS':
            location = self._create_cifs_share(share_name, share_server)

        return [
            {'path': location}
        ]

    def _share_server_validation(self, share_server):
        """Validate the share server."""
        if not share_server:
            msg = _('Share server not provided')
            raise exception.InvalidInput(reason=msg)

        backend_details = share_server.get('backend_details')
        vdm = backend_details.get(
            'share_server_name') if backend_details else None

        if vdm is None:
            message = _("No share server found.")
            LOG.error(message)
            raise exception.EMCPowerMaxXMLAPIError(err=message)

    def _allocate_container(self, share_name, size, share_server, pool_name):
        """Allocate file system for share."""
        vdm_name = self._get_share_server_name(share_server)

        self._get_context('FileSystem').create(
            share_name, size, pool_name, vdm_name)

    def _allocate_container_from_snapshot(self, share, snapshot, share_server,
                                          pool_name):
        """Allocate file system from snapshot."""
        vdm_name = self._get_share_server_name(share_server)

        interconn_id = self._get_context('Mover').get_interconnect_id(
            self.mover_name, self.mover_name)

        self._get_context('FileSystem').create_from_snapshot(
            share['id'], snapshot['id'], snapshot['share_id'],
            pool_name, vdm_name, interconn_id)

        nwe_size = share['size'] * units.Ki
        self._get_context('FileSystem').extend(share['id'], pool_name,
                                               nwe_size)

    @enas_utils.log_enter_exit
    def _create_cifs_share(self, share_name, share_server):
        """Create CIFS share."""
        vdm_name = self._get_share_server_name(share_server)
        server_name = vdm_name

        # Get available CIFS Server and interface (one CIFS server per VDM)
        status, server = self._get_context('CIFSServer').get(server_name,
                                                             vdm_name)

        if 'interfaces' not in server or len(server['interfaces']) == 0:
            message = (_("CIFS server %s doesn't have interface, "
                         "so the share is inaccessible.")
                       % server['compName'])
            LOG.error(message)
            raise exception.EMCPowerMaxXMLAPIError(err=message)

        interface = enas_utils.export_unc_path(server['interfaces'][0])

        self._get_context('CIFSShare').create(share_name, server['name'],
                                              vdm_name)

        self._get_context('CIFSShare').disable_share_access(share_name,
                                                            vdm_name)
        location = (r'\\%(interface)s\%(name)s' %
                    {'interface': interface, 'name': share_name})
        return location

    @enas_utils.log_enter_exit
    def _create_nfs_share(self, share_name, share_server):
        """Create NFS share."""
        vdm_name = self._get_share_server_name(share_server)

        self._get_context('NFSShare').create(share_name, vdm_name)

        nfs_if = enas_utils.convert_ipv6_format_if_needed(
            share_server['backend_details']['nfs_if'])

        return ('%(nfs_if)s:/%(share_name)s'
                % {'nfs_if': nfs_if,
                   'share_name': share_name})

    def create_share_from_snapshot(self, context, share, snapshot,
                                   share_server=None, parent_share=None):
        """Create a share from a snapshot - clone a snapshot."""
        share_name = share['id']

        share_proto = share['share_proto'].upper()

        # Validate the share protocol
        if share_proto not in ('NFS', 'CIFS'):
            raise exception.InvalidShare(
                reason=(_('Invalid NAS protocol supplied: %s.')
                        % share_proto))

        # Get the pool name from share host field
        pool_name = share_utils.extract_host(share['host'], level='pool')
        if not pool_name:
            message = (_("Pool is not available in the share host %s.") %
                       share['host'])
            raise exception.InvalidHost(reason=message)

        self._share_server_validation(share_server)

        self._allocate_container_from_snapshot(
            share, snapshot, share_server, pool_name)

        nfs_if = enas_utils.convert_ipv6_format_if_needed(
            share_server['backend_details']['nfs_if'])

        if share_proto == 'NFS':
            self._create_nfs_share(share_name, share_server)
            location = ('%(nfs_if)s:/%(share_name)s'
                        % {'nfs_if': nfs_if,
                           'share_name': share_name})
        elif share_proto == 'CIFS':
            location = self._create_cifs_share(share_name, share_server)

        return [
            {'path': location}
        ]

    def create_snapshot(self, context, snapshot, share_server=None):
        """Create snapshot from share."""
        share_name = snapshot['share_id']
        status, filesystem = self._get_context('FileSystem').get(share_name)
        if status != constants.STATUS_OK:
            message = (_("File System %s not found.") % share_name)
            LOG.error(message)
            raise exception.EMCPowerMaxXMLAPIError(err=message)

        pool_id = filesystem['pools_id'][0]

        self._get_context('Snapshot').create(snapshot['id'],
                                             snapshot['share_id'],
                                             pool_id)

    def delete_share(self, context, share, share_server=None):
        """Delete a share."""
        if share_server is None:
            LOG.warning("Share network should be specified for "
                        "share deletion.")
            return

        share_proto = share['share_proto'].upper()

        if share_proto == 'NFS':
            self._delete_nfs_share(share, share_server)
        elif share_proto == 'CIFS':
            self._delete_cifs_share(share, share_server)
        else:
            raise exception.InvalidShare(
                reason=_('Unsupported share protocol'))

    @enas_utils.log_enter_exit
    def _delete_cifs_share(self, share, share_server):
        """Delete CIFS share."""
        vdm_name = self._get_share_server_name(share_server)

        name = share['id']

        self._get_context('CIFSShare').delete(name, vdm_name)

        self._deallocate_container(name, vdm_name)

    @enas_utils.log_enter_exit
    def _delete_nfs_share(self, share, share_server):
        """Delete NFS share."""
        vdm_name = self._get_share_server_name(share_server)

        name = share['id']

        self._get_context('NFSShare').delete(name, vdm_name)

        self._deallocate_container(name, vdm_name)

    @enas_utils.log_enter_exit
    def _deallocate_container(self, share_name, vdm_name):
        """Delete underneath objects of the share."""
        path = '/' + share_name

        try:
            # Delete mount point
            self._get_context('MountPoint').delete(path, vdm_name)
        except exception.EMCPowerMaxXMLAPIError as e:
            LOG.exception("CIFS server %(name)s on mover %(mover_name)s "
                          "not found due to error %(err)s. Skip the "
                          "deletion.",
                          {'name': path, 'mover_name': vdm_name,
                           'err': e.message})

        try:
            # Delete file system
            self._get_context('FileSystem').delete(share_name)
        except exception.EMCPowerMaxXMLAPIError as e:
            LOG.exception("File system  %(share_name)s not found due to "
                          "error %(err)s. Skip the deletion.",
                          {'share_name': share_name,
                           'err': e.message})

    def delete_snapshot(self, context, snapshot, share_server=None):
        """Delete a snapshot."""
        self._get_context('Snapshot').delete(snapshot['id'])

    def ensure_share(self, context, share, share_server=None):
        """Ensure that the share is exported."""

    def extend_share(self, share, new_size, share_server=None):
        # Get the pool name from share host field
        pool_name = share_utils.extract_host(share['host'], level='pool')
        if not pool_name:
            message = (_("Pool is not available in the share host %s.") %
                       share['host'])
            raise exception.InvalidHost(reason=message)

        share_name = share['id']

        self._get_context('FileSystem').extend(
            share_name, pool_name, new_size * units.Ki)

    def allow_access(self, context, share, access, share_server=None):
        """Allow access to a share."""
        access_level = access['access_level']
        if access_level not in const.ACCESS_LEVELS:
            raise exception.InvalidShareAccessLevel(level=access_level)

        share_proto = share['share_proto']

        if share_proto == 'NFS':
            self._nfs_allow_access(context, share, access, share_server)
        elif share_proto == 'CIFS':
            self._cifs_allow_access(context, share, access, share_server)
        else:
            raise exception.InvalidShare(
                reason=(_('Invalid NAS protocol supplied: %s.')
                        % share_proto))

    @enas_utils.log_enter_exit
    def _cifs_allow_access(self, context, share, access, share_server):
        """Allow access to CIFS share."""
        vdm_name = self._get_share_server_name(share_server)
        share_name = share['id']

        if access['access_type'] != 'user':
            reason = _('Only user access type allowed for CIFS share')
            raise exception.InvalidShareAccess(reason=reason)

        user_name = access['access_to']

        access_level = access['access_level']
        if access_level == const.ACCESS_LEVEL_RW:
            cifs_access = constants.CIFS_ACL_FULLCONTROL
        else:
            cifs_access = constants.CIFS_ACL_READ

        # Check if CIFS server exists.
        server_name = vdm_name
        status, server = self._get_context('CIFSServer').get(server_name,
                                                             vdm_name)
        if status != constants.STATUS_OK:
            message = (_("CIFS server %s not found.") % server_name)
            LOG.error(message)
            raise exception.EMCPowerMaxXMLAPIError(err=message)

        self._get_context('CIFSShare').allow_share_access(
            vdm_name,
            share_name,
            user_name,
            server['domain'],
            access=cifs_access)

    @enas_utils.log_enter_exit
    def _nfs_allow_access(self, context, share, access, share_server):
        """Allow access to NFS share."""
        vdm_name = self._get_share_server_name(share_server)

        access_type = access['access_type']
        if access_type != 'ip':
            reason = _('Only ip access type allowed.')
            raise exception.InvalidShareAccess(reason=reason)

        host_ip = access['access_to']
        access_level = access['access_level']

        self._get_context('NFSShare').allow_share_access(
            share['id'], host_ip, vdm_name, access_level)

    def update_access(self, context, share, access_rules, add_rules,
                      delete_rules, share_server=None):
        # deleting rules
        for rule in delete_rules:
            self.deny_access(context, share, rule, share_server)

        # adding rules
        for rule in add_rules:
            self.allow_access(context, share, rule, share_server)

        # recovery mode
        if not (add_rules or delete_rules):
            white_list = []
            for rule in access_rules:
                self.allow_access(context, share, rule, share_server)
                white_list.append(
                    enas_utils.convert_ipv6_format_if_needed(
                        rule['access_to']))
            self.clear_access(share, share_server, white_list)

    def clear_access(self, share, share_server, white_list):
        share_proto = share['share_proto'].upper()
        share_name = share['id']
        if share_proto == 'CIFS':
            self._cifs_clear_access(share_name, share_server, white_list)
        elif share_proto == 'NFS':
            self._nfs_clear_access(share_name, share_server, white_list)

    @enas_utils.log_enter_exit
    def _cifs_clear_access(self, share_name, share_server, white_list):
        """Clear access for CIFS share except hosts in the white list."""
        vdm_name = self._get_share_server_name(share_server)

        # Check if CIFS server exists.
        server_name = vdm_name
        status, server = self._get_context('CIFSServer').get(server_name,
                                                             vdm_name)
        if status != constants.STATUS_OK:
            message = (_("CIFS server %(server_name)s has issue. "
                         "Detail: %(status)s") %
                       {'server_name': server_name, 'status': status})
            raise exception.EMCPowerMaxXMLAPIError(err=message)

        self._get_context('CIFSShare').clear_share_access(
            share_name=share_name,
            mover_name=vdm_name,
            domain=server['domain'],
            white_list_users=white_list)

    @enas_utils.log_enter_exit
    def _nfs_clear_access(self, share_name, share_server, white_list):
        """Clear access for NFS share except hosts in the white list."""
        self._get_context('NFSShare').clear_share_access(
            share_name=share_name,
            mover_name=self._get_share_server_name(share_server),
            white_list_hosts=white_list)

    def deny_access(self, context, share, access, share_server=None):
        """Deny access to a share."""
        share_proto = share['share_proto']

        if share_proto == 'NFS':
            self._nfs_deny_access(share, access, share_server)
        elif share_proto == 'CIFS':
            self._cifs_deny_access(share, access, share_server)
        else:
            raise exception.InvalidShare(
                reason=_('Unsupported share protocol'))

    @enas_utils.log_enter_exit
    def _cifs_deny_access(self, share, access, share_server):
        """Deny access to CIFS share."""
        vdm_name = self._get_share_server_name(share_server)
        share_name = share['id']

        if access['access_type'] != 'user':
            LOG.warning("Only user access type allowed for CIFS share.")
            return

        user_name = access['access_to']

        access_level = access['access_level']
        if access_level == const.ACCESS_LEVEL_RW:
            cifs_access = constants.CIFS_ACL_FULLCONTROL
        else:
            cifs_access = constants.CIFS_ACL_READ

        # Check if CIFS server exists.
        server_name = vdm_name
        status, server = self._get_context('CIFSServer').get(server_name,
                                                             vdm_name)
        if status != constants.STATUS_OK:
            message = (_("CIFS server %s not found.") % server_name)
            LOG.error(message)
            raise exception.EMCPowerMaxXMLAPIError(err=message)

        self._get_context('CIFSShare').deny_share_access(
            vdm_name,
            share_name,
            user_name,
            server['domain'],
            access=cifs_access)

    @enas_utils.log_enter_exit
    def _nfs_deny_access(self, share, access, share_server):
        """Deny access to NFS share."""
        vdm_name = self._get_share_server_name(share_server)

        access_type = access['access_type']
        if access_type != 'ip':
            LOG.warning("Only ip access type allowed.")
            return

        host_ip = enas_utils.convert_ipv6_format_if_needed(access['access_to'])

        self._get_context('NFSShare').deny_share_access(share['id'], host_ip,
                                                        vdm_name)

    def check_for_setup_error(self):
        """Check for setup error."""
        # To verify the input from Manila configuration
        status, out = self._get_context('Mover').get_ref(self.mover_name,
                                                         True)
        if constants.STATUS_ERROR == status:
            message = (_("Could not find Data Mover by name: %s.") %
                       self.mover_name)
            LOG.error(message)
            raise exception.InvalidParameterValue(err=message)

        self.pools = self._get_managed_storage_pools(self.pool_conf)

    def _get_managed_storage_pools(self, pools):
        matched_pools = set()
        if pools:
            # Get the real pools from the backend storage
            status, backend_pools = self._get_context('StoragePool').get_all()
            if status != constants.STATUS_OK:
                message = (_("Failed to get storage pool information. "
                             "Reason: %s") % backend_pools)
                LOG.error(message)
                raise exception.EMCPowerMaxXMLAPIError(err=message)

            real_pools = set([item for item in backend_pools])
            conf_pools = set([item.strip() for item in pools])
            matched_pools, unmatched_pools = enas_utils.do_match_any(
                real_pools, conf_pools)

            if not matched_pools:
                msg = (_("None of the specified storage pools to be managed "
                         "exist. Please check your configuration "
                         "emc_nas_pool_names in manila.conf. "
                         "The available pools in the backend are %s.") %
                       ",".join(real_pools))
                raise exception.InvalidParameterValue(err=msg)

            LOG.info("Storage pools: %s will be managed.",
                     ",".join(matched_pools))
        else:
            LOG.debug("No storage pool is specified, so all pools "
                      "in storage system will be managed.")
        return matched_pools

    def connect(self, emc_share_driver, context):
        """Connect to PowerMax NAS server."""
        config = emc_share_driver.configuration
        config.append_config_values(POWERMAX_OPTS)
        self.mover_name = config.safe_get('powermax_server_container')

        self.pool_conf = config.safe_get('powermax_share_data_pools')

        self.reserved_percentage = config.safe_get('reserved_share_percentage')
        if self.reserved_percentage is None:
            self.reserved_percentage = 0

        self.reserved_snapshot_percentage = config.safe_get(
            'reserved_share_from_snapshot_percentage')
        if self.reserved_snapshot_percentage is None:
            self.reserved_snapshot_percentage = self.reserved_percentage

        self.manager = manager.StorageObjectManager(config)
        self.port_conf = config.safe_get('powermax_ethernet_ports')

    def get_managed_ports(self):
        # Get the real ports(devices) list from the backend storage
        real_ports = self._get_physical_devices(self.mover_name)

        if not self.port_conf:
            LOG.debug("No ports are specified, so any of the ports on the "
                      "Data Mover can be used.")
            return real_ports

        matched_ports, unmanaged_ports = enas_utils.do_match_any(
            real_ports, self.port_conf)

        if not matched_ports:
            msg = (_("None of the specified network ports exist. "
                     "Please check your configuration powermax_ethernet_ports "
                     "in manila.conf. The available ports on the Data Mover "
                     "are %s.") %
                   ",".join(real_ports))
            raise exception.BadConfigurationException(reason=msg)

        LOG.debug("Ports: %s can be used.", ",".join(matched_ports))

        return list(matched_ports)

    def update_share_stats(self, stats_dict):
        """Communicate with EMCNASClient to get the stats."""
        stats_dict['driver_version'] = VERSION

        self._get_context('Mover').get_ref(self.mover_name, True)

        stats_dict['pools'] = []

        status, pools = self._get_context('StoragePool').get_all()
        for name, pool in pools.items():
            if not self.pools or pool['name'] in self.pools:
                total_size = float(pool['total_size'])
                used_size = float(pool['used_size'])

                pool_stat = {
                    'pool_name': pool['name'],
                    'total_capacity_gb': enas_utils.mb_to_gb(total_size),
                    'free_capacity_gb':
                        enas_utils.mb_to_gb(total_size - used_size),
                    'qos': False,
                    'reserved_percentage': self.reserved_percentage,
                    'reserved_snapshot_percentage':
                        self.reserved_snapshot_percentage,
                    'snapshot_support': True,
                    'create_share_from_snapshot_support': True,
                    'revert_to_snapshot_support': False,
                    'ipv6_support': True
                }
                stats_dict['pools'].append(pool_stat)

        if not stats_dict['pools']:
            message = _("Failed to update storage pool.")
            LOG.error(message)
            raise exception.EMCPowerMaxXMLAPIError(err=message)

    def get_pool(self, share):
        """Get the pool name of the share."""
        share_name = share['id']
        status, filesystem = self._get_context('FileSystem').get(share_name)
        if status != constants.STATUS_OK:
            message = (_("File System %(name)s not found. "
                         "Reason: %(err)s") %
                       {'name': share_name, 'err': filesystem})
            LOG.error(message)
            raise exception.EMCPowerMaxXMLAPIError(err=message)

        pool_id = filesystem['pools_id'][0]

        # Get the real pools from the backend storage
        status, backend_pools = self._get_context('StoragePool').get_all()
        if status != constants.STATUS_OK:
            message = (_("Failed to get storage pool information. "
                         "Reason: %s") % backend_pools)
            LOG.error(message)
            raise exception.EMCPowerMaxXMLAPIError(err=message)

        for name, pool_info in backend_pools.items():
            if pool_info['id'] == pool_id:
                return name

        available_pools = [item for item in backend_pools]
        message = (_("No matched pool name for share: %(share)s. "
                     "Available pools: %(pools)s") %
                   {'share': share_name, 'pools': available_pools})
        raise exception.EMCPowerMaxXMLAPIError(err=message)

    def get_network_allocations_number(self):
        """Returns number of network allocations for creating VIFs."""
        return constants.IP_ALLOCATIONS

    def setup_server(self, network_info, metadata=None):
        """Set up and configure share server.

        Sets up and configures share server with given network parameters.
        """
        # Only support single security service with type 'active_directory'
        vdm_name = network_info['server_id']
        vlan_id = network_info['segmentation_id']
        active_directory = None
        allocated_interfaces = []

        if network_info.get('security_services'):
            is_valid, active_directory = self._get_valid_security_service(
                network_info['security_services'])

            if not is_valid:
                raise exception.EMCPowerMaxXMLAPIError(err=active_directory)

        try:
            if not self._vdm_exist(vdm_name):
                LOG.debug('Share server %s not found, creating '
                          'share server...', vdm_name)
                self._get_context('VDM').create(vdm_name, self.mover_name)

            devices = self.get_managed_ports()

            for net_info in network_info['network_allocations']:
                random.shuffle(devices)
                ip_version = net_info['ip_version']
                interface = {
                    'name': net_info['id'][-12:],
                    'device_name': devices[0],
                    'ip': net_info['ip_address'],
                    'mover_name': self.mover_name,
                    'vlan_id': vlan_id if vlan_id else -1,
                }
                if ip_version == 6:
                    interface['ip_version'] = ip_version
                    interface['net_mask'] = six.text_type(
                        utils.cidr_to_prefixlen(
                            network_info['cidr']))
                else:
                    interface['net_mask'] = utils.cidr_to_netmask(
                        network_info['cidr'])

                self._get_context('MoverInterface').create(interface)

                allocated_interfaces.append(interface)

            cifs_interface = allocated_interfaces[0]
            nfs_interface = allocated_interfaces[1]
            if active_directory:
                self._configure_active_directory(
                    active_directory, vdm_name, cifs_interface)

            self._get_context('VDM').attach_nfs_interface(
                vdm_name, nfs_interface['name'])

            return {
                'share_server_name': vdm_name,
                'cifs_if': cifs_interface['ip'],
                'nfs_if': nfs_interface['ip'],
            }

        except Exception:
            with excutils.save_and_reraise_exception():
                LOG.exception('Could not setup server')
                server_details = self._construct_backend_details(
                    vdm_name, allocated_interfaces)
                self.teardown_server(
                    server_details, network_info['security_services'])

    def _construct_backend_details(self, vdm_name, interfaces):
        if_number = len(interfaces)
        cifs_if = interfaces[0]['ip'] if if_number > 0 else None
        nfs_if = interfaces[1]['ip'] if if_number > 1 else None

        return {
            'share_server_name': vdm_name,
            'cifs_if': cifs_if,
            'nfs_if': nfs_if,
        }

    @enas_utils.log_enter_exit
    def _vdm_exist(self, name):
        status, out = self._get_context('VDM').get(name)
        if constants.STATUS_OK != status:
            return False

        return True

    def _get_physical_devices(self, mover_name):
        """Get a proper network device to create interface."""
        devices = self._get_context('Mover').get_physical_devices(mover_name)
        if not devices:
            message = (_("Could not get physical device port on mover %s.") %
                       self.mover_name)
            LOG.error(message)
            raise exception.EMCPowerMaxXMLAPIError(err=message)

        return devices

    def _configure_active_directory(
            self, security_service, vdm_name, interface):

        domain = security_service['domain']
        server = security_service['dns_ip']

        self._get_context('DNSDomain').create(self.mover_name, domain, server)

        cifs_server_args = {
            'name': vdm_name,
            'interface_ip': interface['ip'],
            'domain_name': security_service['domain'],
            'user_name': security_service['user'],
            'password': security_service['password'],
            'mover_name': vdm_name,
            'is_vdm': True,
        }

        self._get_context('CIFSServer').create(cifs_server_args)

    def teardown_server(self, server_details, security_services=None):
        """Teardown share server."""
        if not server_details:
            LOG.debug('Server details are empty.')
            return

        vdm_name = server_details.get('share_server_name')
        if not vdm_name:
            LOG.debug('No share server found in server details.')
            return

        cifs_if = server_details.get('cifs_if')
        nfs_if = server_details.get('nfs_if')

        status, vdm = self._get_context('VDM').get(vdm_name)
        if constants.STATUS_OK != status:
            LOG.debug('Share server %s not found.', vdm_name)
            return

        interfaces = self._get_context('VDM').get_interfaces(vdm_name)

        for if_name in interfaces['nfs']:
            self._get_context('VDM').detach_nfs_interface(vdm_name, if_name)

        if security_services:
            # Only support single security service with type 'active_directory'
            is_valid, active_directory = self._get_valid_security_service(
                security_services)
            if is_valid:
                status, servers = self._get_context('CIFSServer').get_all(
                    vdm_name)
                if constants.STATUS_OK != status:
                    LOG.error('Could not find CIFS server by name: %s.',
                              vdm_name)
                else:
                    cifs_servers = copy.deepcopy(servers)
                    for name, server in cifs_servers.items():
                        # Unjoin CIFS Server from domain
                        cifs_server_args = {
                            'name': server['name'],
                            'join_domain': False,
                            'user_name': active_directory['user'],
                            'password': active_directory['password'],
                            'mover_name': vdm_name,
                            'is_vdm': True,
                        }

                        try:
                            self._get_context('CIFSServer').modify(
                                cifs_server_args)
                        except exception.EMCPowerMaxXMLAPIError as expt:
                            LOG.debug("Failed to modify CIFS server "
                                      "%(server)s. Reason: %(err)s.",
                                      {'server': server, 'err': expt})

                        self._get_context('CIFSServer').delete(name, vdm_name)

        # Delete interface from Data Mover
        if cifs_if:
            self._get_context('MoverInterface').delete(cifs_if,
                                                       self.mover_name)

        if nfs_if:
            self._get_context('MoverInterface').delete(nfs_if,
                                                       self.mover_name)

        # Delete Virtual Data Mover
        self._get_context('VDM').delete(vdm_name)

    def _get_valid_security_service(self, security_services):
        """Validate security services and return a supported security service.

        :param security_services:
        :returns: (<is_valid>, <data>) -- <is_valid> is true to indicate
            security_services includes zero or single security service for
            active directory. Otherwise, it would return false. <data> return
            error message when <is_valid> is false. Otherwise, it will
            return zero or single security service for active directory.
        """

        # Only support single security service with type 'active_directory'
        if (len(security_services) > 1 or
                (security_services and
                    security_services[0]['type'] != 'active_directory')):
            return False, _("Unsupported security services. "
                            "Only support single security service and "
                            "only support type 'active_directory'")

        return True, security_services[0]

    def _get_share_server_name(self, share_server):
        try:
            return share_server['backend_details']['share_server_name']
        except Exception:
            LOG.debug("Didn't get share server name from share_server %s.",
                      share_server)
        return share_server['id']

    def _get_context(self, context_type):
        return self.manager.getStorageContext(context_type)
