# Copyright (c) 2015 Hitachi Data Systems, Inc.
# 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.

from oslo_concurrency import processutils
from oslo_log import log
from oslo_utils import strutils
from oslo_utils import units
import paramiko

import os
import time

from manila import exception
from manila.i18n import _
from manila import utils as mutils

LOG = log.getLogger(__name__)


class HNASSSHBackend(object):
    def __init__(self, hnas_ip, hnas_username, hnas_password, ssh_private_key,
                 cluster_admin_ip0, evs_id, evs_ip, fs_name, job_timeout):
        self.ip = hnas_ip
        self.port = 22
        self.user = hnas_username
        self.password = hnas_password
        self.priv_key = ssh_private_key
        self.admin_ip0 = cluster_admin_ip0
        self.evs_id = str(evs_id)
        self.fs_name = fs_name
        self.evs_ip = evs_ip
        self.sshpool = None
        self.job_timeout = job_timeout
        LOG.debug("Hitachi HNAS Driver using SSH backend.")

    def get_stats(self):
        """Get the stats from file-system.

        :returns:
        fs_capacity.size = Total size from filesystem.
        available_space = Free space currently on filesystem.
        dedupe = True if dedupe is enabled on filesystem.
        """
        command = ['df', '-a', '-f', self.fs_name]
        try:
            output, err = self._execute(command)

        except processutils.ProcessExecutionError:
            msg = _("Could not get HNAS backend stats.")
            LOG.exception(msg)
            raise exception.HNASBackendException(msg=msg)

        line = output.split('\n')
        fs = Filesystem(line[3])
        available_space = fs.size - fs.used
        return fs.size, available_space, fs.dedupe

    def nfs_export_add(self, share_id, snapshot_id=None):
        if snapshot_id is not None:
            path = os.path.join('/snapshots', share_id, snapshot_id)
            name = os.path.join('/snapshots', snapshot_id)
        else:
            path = name = os.path.join('/shares', share_id)

        command = ['nfs-export', 'add', '-S', 'disable', '-c', '127.0.0.1',
                   name, self.fs_name, path]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError:
            msg = _("Could not create NFS export %s.") % name
            LOG.exception(msg)
            raise exception.HNASBackendException(msg=msg)

    def nfs_export_del(self, share_id=None, snapshot_id=None):
        if share_id is not None:
            name = os.path.join('/shares', share_id)
        elif snapshot_id is not None:
            name = os.path.join('/snapshots', snapshot_id)
        else:
            msg = _("NFS export not specified to delete.")
            raise exception.HNASBackendException(msg=msg)

        command = ['nfs-export', 'del', name]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError as e:
            if 'does not exist' in e.stderr:
                LOG.warning("Export %s does not exist on "
                            "backend anymore.", name)
            else:
                msg = _("Could not delete NFS export %s.") % name
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)

    def cifs_share_add(self, share_id, snapshot_id=None):
        if snapshot_id is not None:
            path = r'\\snapshots\\' + share_id + r'\\' + snapshot_id
            name = snapshot_id
        else:
            path = r'\\shares\\' + share_id
            name = share_id
        command = ['cifs-share', 'add', '-S', 'disable', '--enable-abe',
                   '--nodefaultsaa', name, self.fs_name, path]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError:
            msg = _("Could not create CIFS share %s.") % name
            LOG.exception(msg)
            raise exception.HNASBackendException(msg=msg)

    def cifs_share_del(self, name):
        command = ['cifs-share', 'del', '--target-label', self.fs_name,
                   name]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError as e:
            if e.exit_code == 1:
                LOG.warning("CIFS share %s does not exist on "
                            "backend anymore.", name)
            else:
                msg = _("Could not delete CIFS share %s.") % name
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)

    def get_nfs_host_list(self, share_id):
        export = self._get_export(share_id)
        return export[0].export_configuration

    def update_nfs_access_rule(self, host_list, share_id=None,
                               snapshot_id=None):
        if share_id is not None:
            name = os.path.join('/shares', share_id)
        elif snapshot_id is not None:
            name = os.path.join('/snapshots', snapshot_id)
        else:
            msg = _("No share/snapshot provided to update NFS rules.")
            raise exception.HNASBackendException(msg=msg)

        command = ['nfs-export', 'mod', '-c']

        if len(host_list) == 0:
            command.append('127.0.0.1')
        else:
            string_command = '"' + str(host_list[0])

            for i in range(1, len(host_list)):
                string_command += ',' + (str(host_list[i]))
            string_command += '"'
            command.append(string_command)

        command.append(name)
        try:
            self._execute(command)
        except processutils.ProcessExecutionError:
            msg = _("Could not update access rules for NFS export %s.") % name
            LOG.exception(msg)
            raise exception.HNASBackendException(msg=msg)

    def cifs_allow_access(self, name, user, permission, is_snapshot=False):
        command = ['cifs-saa', 'add', '--target-label', self.fs_name,
                   name, user, permission]

        try:
            self._execute(command)
        except processutils.ProcessExecutionError as e:
            if 'already listed as a user' in e.stderr:
                if is_snapshot:
                    LOG.debug('User %(user)s already allowed to access '
                              'snapshot %(snapshot)s.', {
                                  'user': user,
                                  'snapshot': name,
                              })
                else:
                    self._update_cifs_rule(name, user, permission)
            else:
                entity_type = "share"
                if is_snapshot:
                    entity_type = "snapshot"

                msg = _("Could not add access of user %(user)s to "
                        "%(entity_type)s %(name)s.") % {
                            'user': user,
                            'name': name,
                            'entity_type': entity_type,
                    }
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)

    def _update_cifs_rule(self, name, user, permission):
        LOG.debug('User %(user)s already allowed to access '
                  'share %(share)s. Updating access level...', {
                      'user': user,
                      'share': name,
                  })

        command = ['cifs-saa', 'change', '--target-label', self.fs_name,
                   name, user, permission]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError:
            msg = _("Could not update access of user %(user)s to "
                    "share %(share)s.") % {
                        'user': user,
                        'share': name,
                }
            LOG.exception(msg)
            raise exception.HNASBackendException(msg=msg)

    def cifs_deny_access(self, name, user, is_snapshot=False):
        command = ['cifs-saa', 'delete', '--target-label', self.fs_name,
                   name, user]

        entity_type = "share"
        if is_snapshot:
            entity_type = "snapshot"

        try:
            self._execute(command)
        except processutils.ProcessExecutionError as e:
            if ('not listed as a user' in e.stderr or
                    'Could not delete user/group' in e.stderr):
                LOG.warning('User %(user)s already not allowed to access '
                            '%(entity_type)s %(name)s.', {
                                'entity_type': entity_type,
                                'user': user,
                                'name': name
                            })
            else:
                msg = _("Could not delete access of user %(user)s to "
                        "%(entity_type)s %(name)s.") % {
                    'user': user,
                    'name': name,
                    'entity_type': entity_type,
                    }
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)

    def list_cifs_permissions(self, hnas_share_id):
        command = ['cifs-saa', 'list', '--target-label', self.fs_name,
                   hnas_share_id]
        try:
            output, err = self._execute(command)
        except processutils.ProcessExecutionError as e:
            if 'No entries for this share' in e.stderr:
                LOG.debug('Share %(share)s does not have any permission '
                          'added.', {'share': hnas_share_id})
                return []
            else:
                msg = _("Could not list access of share %s.") % hnas_share_id
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)

        permissions = CIFSPermissions(output)

        return permissions.permission_list

    def tree_clone(self, src_path, dest_path):
        command = ['tree-clone-job-submit', '-e', '-f', self.fs_name,
                   src_path, dest_path]
        try:
            output, err = self._execute(command)
        except processutils.ProcessExecutionError as e:
            if ('Cannot find any clonable files in the source directory' in
                    e.stderr):
                msg = _("Source path %s is empty.") % src_path
                LOG.debug(msg)
                raise exception.HNASNothingToCloneException(msg=msg)
            else:
                msg = _("Could not submit tree clone job to clone from %(src)s"
                        " to %(dest)s.") % {'src': src_path, 'dest': dest_path}
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)

        job_submit = JobSubmit(output)
        if job_submit.request_status == 'Request submitted successfully':
            job_id = job_submit.job_id

            job_status = None
            progress = ''
            job_rechecks = 0
            starttime = time.time()
            deadline = starttime + self.job_timeout
            while (not job_status or
                   job_status.job_state != "Job was completed"):

                command = ['tree-clone-job-status', job_id]
                output, err = self._execute(command)
                job_status = JobStatus(output)

                if job_status.job_state == 'Job failed':
                    break

                old_progress = progress
                progress = job_status.data_bytes_processed

                if old_progress == progress:
                    job_rechecks += 1
                    now = time.time()
                    if now > deadline:
                        command = ['tree-clone-job-abort', job_id]
                        self._execute(command)
                        LOG.error("Timeout in snapshot creation from "
                                  "source path %s.", src_path)
                        msg = _("Share snapshot of source path %s "
                                "was not created.") % src_path
                        raise exception.HNASBackendException(msg=msg)
                    else:
                        time.sleep(job_rechecks ** 2)
                else:
                    job_rechecks = 0

            if (job_status.job_state, job_status.job_status,
                job_status.directories_missing,
                job_status.files_missing) == ("Job was completed",
                                              "Success", '0', '0'):

                LOG.debug("Snapshot of source path %(src)s to destination "
                          "path %(dest)s created successfully.",
                          {'src': src_path,
                           'dest': dest_path})
            else:
                LOG.error('Error creating snapshot of source path %s.',
                          src_path)
                msg = _('Snapshot of source path %s was not '
                        'created.') % src_path
                raise exception.HNASBackendException(msg=msg)

    def tree_delete(self, path):
        command = ['tree-delete-job-submit', '--confirm', '-f', self.fs_name,
                   path]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError as e:
            if 'Source path: Cannot access' in e.stderr:
                LOG.warning("Attempted to delete path %s "
                            "but it does not exist.", path)
            else:
                msg = _("Could not submit tree delete job to delete path "
                        "%s.") % path
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)

    @mutils.retry(retry_param=exception.HNASSSCContextChange, wait_random=True,
                  retries=5)
    def create_directory(self, dest_path):
        self._locked_selectfs('create', dest_path)
        if not self.check_directory(dest_path):
            msg = _("Command to create directory %(path)s was run in another "
                    "filesystem instead of %(fs)s.") % {
                'path': dest_path,
                'fs': self.fs_name,
            }
            LOG.warning(msg)
            raise exception.HNASSSCContextChange(msg=msg)

    @mutils.retry(retry_param=exception.HNASSSCContextChange, wait_random=True,
                  retries=5)
    def delete_directory(self, path):
        try:
            self._locked_selectfs('delete', path)
        except exception.HNASDirectoryNotEmpty:
            pass
        else:
            if self.check_directory(path):
                msg = _("Command to delete empty directory %(path)s was run in"
                        " another filesystem instead of %(fs)s.") % {
                            'path': path,
                            'fs': self.fs_name,
                    }
                LOG.debug(msg)
                raise exception.HNASSSCContextChange(msg=msg)

    @mutils.retry(retry_param=exception.HNASSSCIsBusy, wait_random=True,
                  retries=5)
    def check_directory(self, path):
        command = ['path-to-object-number', '-f', self.fs_name, path]

        try:
            self._execute(command)
        except processutils.ProcessExecutionError as e:
            if 'path-to-object-number is currently running' in e.stdout:
                msg = (_("SSC command path-to-object-number for path %s "
                         "is currently busy.") % path)
                raise exception.HNASSSCIsBusy(msg=msg)
            if 'Unable to locate component:' in e.stdout:
                LOG.debug("Cannot find %(path)s: %(out)s",
                          {'path': path, 'out': e.stdout})
                return False
            else:
                msg = _("Could not check if path %s exists.") % path
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)
        return True

    def check_fs_mounted(self):
        command = ['df', '-a', '-f', self.fs_name]
        output, err = self._execute(command)
        if "not found" in output:
            msg = _("Filesystem %s does not exist or it is not available "
                    "in the current EVS context.") % self.fs_name
            LOG.error(msg)
            raise exception.HNASItemNotFoundException(msg=msg)
        else:
            line = output.split('\n')
            fs = Filesystem(line[3])
            return fs.mounted

    def mount(self):
        command = ['mount', self.fs_name]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError as e:
            if 'file system is already mounted' not in e.stderr:
                msg = _("Failed to mount filesystem %s.") % self.fs_name
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)

    def vvol_create(self, vvol_name):
        # create a virtual-volume inside directory
        path = '/shares/' + vvol_name
        command = ['virtual-volume', 'add', '--ensure', self.fs_name,
                   vvol_name, path]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError:
            msg = _("Failed to create vvol %s.") % vvol_name
            LOG.exception(msg)
            raise exception.HNASBackendException(msg=msg)

    def vvol_delete(self, vvol_name):
        path = '/shares/' + vvol_name
        # Virtual-volume and quota are deleted together
        command = ['tree-delete-job-submit', '--confirm', '-f',
                   self.fs_name, path]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError as e:
            if 'Source path: Cannot access' in e.stderr:
                LOG.warning("Share %s does not exist.", vvol_name)
            else:
                msg = _("Failed to delete vvol %s.") % vvol_name
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)

    def quota_add(self, vvol_name, vvol_quota):
        str_quota = str(vvol_quota) + 'G'
        command = ['quota', 'add', '--usage-limit',
                   str_quota, '--usage-hard-limit',
                   'yes', self.fs_name, vvol_name]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError:
            msg = _("Failed to add %(quota)s quota to vvol "
                    "%(vvol)s.") % {'quota': str_quota, 'vvol': vvol_name}
            LOG.exception(msg)
            raise exception.HNASBackendException(msg=msg)

    def modify_quota(self, vvol_name, new_size):
        str_quota = str(new_size) + 'G'
        command = ['quota', 'mod', '--usage-limit', str_quota,
                   self.fs_name, vvol_name]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError:
            msg = _("Failed to update quota of vvol %(vvol)s to "
                    "%(quota)s.") % {'quota': str_quota, 'vvol': vvol_name}
            LOG.exception(msg)
            raise exception.HNASBackendException(msg=msg)

    def check_vvol(self, vvol_name):
        command = ['virtual-volume', 'list', '--verbose', self.fs_name,
                   vvol_name]
        try:
            self._execute(command)
        except processutils.ProcessExecutionError:
            msg = _("Virtual volume %s does not exist.") % vvol_name
            LOG.exception(msg)
            raise exception.HNASItemNotFoundException(msg=msg)

    def check_quota(self, vvol_name):
        command = ['quota', 'list', '--verbose', self.fs_name, vvol_name]
        try:
            output, err = self._execute(command)

        except processutils.ProcessExecutionError:
            msg = _("Could not check quota of vvol %s.") % vvol_name
            LOG.exception(msg)
            raise exception.HNASBackendException(msg=msg)

        if 'No quotas matching specified filter criteria' in output:
            msg = _("Virtual volume %s does not have any"
                    " quota.") % vvol_name
            LOG.error(msg)
            raise exception.HNASItemNotFoundException(msg=msg)

    def check_export(self, vvol_name, is_snapshot=False):
        export = self._get_export(vvol_name, is_snapshot=is_snapshot)
        if (vvol_name in export[0].export_name and
                self.fs_name in export[0].file_system_label):
            return
        else:
            msg = _("Export %s does not exist.") % export[0].export_name
            LOG.error(msg)
            raise exception.HNASItemNotFoundException(msg=msg)

    def check_cifs(self, vvol_name):
        output = self._cifs_list(vvol_name)

        cifs_share = CIFSShare(output)

        if self.fs_name != cifs_share.fs:
            msg = _("CIFS share %(share)s is not located in "
                    "configured filesystem "
                    "%(fs)s.") % {'share': vvol_name,
                                  'fs': self.fs_name}
            LOG.error(msg)
            raise exception.HNASItemNotFoundException(msg=msg)

    def is_cifs_in_use(self, vvol_name):
        output = self._cifs_list(vvol_name)

        cifs_share = CIFSShare(output)

        return cifs_share.is_mounted

    def _cifs_list(self, vvol_name):
        command = ['cifs-share', 'list', vvol_name]
        try:
            output, err = self._execute(command)
        except processutils.ProcessExecutionError as e:
            if 'does not exist' in e.stderr:
                msg = _("CIFS share %(share)s was not found in EVS "
                        "%(evs_id)s") % {'share': vvol_name,
                                         'evs_id': self.evs_id}
                LOG.exception(msg)
                raise exception.HNASItemNotFoundException(msg=msg)
            else:
                msg = _("Could not list CIFS shares by vvol name "
                        "%s.") % vvol_name
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)

        return output

    def get_share_quota(self, share_id):
        command = ['quota', 'list', self.fs_name, share_id]
        output, err = self._execute(command)

        quota = Quota(output)

        if quota.limit is None:
            return None

        if quota.limit_unit == 'TB':
            return quota.limit * units.Ki
        elif quota.limit_unit == 'GB':
            return quota.limit
        else:
            msg = _("Share %s does not support quota values "
                    "below 1G.") % share_id
            LOG.error(msg)
            raise exception.HNASBackendException(msg=msg)

    def get_share_usage(self, share_id):
        command = ['quota', 'list', self.fs_name, share_id]
        output, err = self._execute(command)

        quota = Quota(output)

        if quota.usage is None:
            msg = _("Virtual volume %s does not have any quota.") % share_id
            LOG.error(msg)
            raise exception.HNASItemNotFoundException(msg=msg)
        else:
            bytes_usage = strutils.string_to_bytes(str(quota.usage) +
                                                   quota.usage_unit)
            return bytes_usage / units.Gi

    def _get_export(self, name, is_snapshot=False):
        if is_snapshot:
            name = '/snapshots/' + name
        else:
            name = '/shares/' + name

        command = ['nfs-export', 'list ', name]
        export_list = []
        try:
            output, err = self._execute(command)
        except processutils.ProcessExecutionError as e:
            if 'does not exist' in e.stderr:
                msg = _("Export %(name)s was not found in EVS "
                        "%(evs_id)s.") % {
                            'name': name,
                            'evs_id': self.evs_id,
                }
                LOG.exception(msg)
                raise exception.HNASItemNotFoundException(msg=msg)
            else:
                msg = _("Could not list NFS exports by name %s.") % name
                LOG.exception(msg)
                raise exception.HNASBackendException(msg=msg)
        items = output.split('Export name')

        if items[0][0] == '\n':
            items.pop(0)

        for i in range(0, len(items)):
            export_list.append(Export(items[i]))
        return export_list

    @mutils.retry(retry_param=exception.HNASConnException, wait_random=True)
    def _execute(self, commands):
        command = ['ssc', '127.0.0.1']
        if self.admin_ip0 is not None:
            command = ['ssc', '--smuauth', self.admin_ip0]

        command += ['console-context', '--evs', self.evs_id]
        commands = command + commands

        mutils.check_ssh_injection(commands)
        commands = ' '.join(commands)

        if not self.sshpool:
            self.sshpool = mutils.SSHPool(ip=self.ip,
                                          port=self.port,
                                          conn_timeout=None,
                                          login=self.user,
                                          password=self.password,
                                          privatekey=self.priv_key)
        with self.sshpool.item() as ssh:
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            try:
                out, err = processutils.ssh_execute(ssh, commands,
                                                    check_exit_code=True)
                LOG.debug("Command %(cmd)s result: out = %(out)s - err = "
                          "%(err)s.", {
                              'cmd': commands,
                              'out': out,
                              'err': err,
                          })
                return out, err
            except processutils.ProcessExecutionError as e:
                if 'Failed to establish SSC connection' in e.stderr:
                    msg = _("Failed to establish SSC connection.")
                    LOG.debug(msg)
                    raise exception.HNASConnException(msg=msg)
                else:
                    LOG.debug("Error running SSH command. "
                              "Command %(cmd)s result: out = %(out)s - err = "
                              "%(err)s - exit = %(exit)s.", {
                                  'cmd': e.cmd,
                                  'out': e.stdout,
                                  'err': e.stderr,
                                  'exit': e.exit_code,
                              })
                    raise

    @mutils.synchronized("hitachi_hnas_select_fs", external=True)
    def _locked_selectfs(self, op, path):
        if op == 'create':
            command = ['selectfs', self.fs_name, '\n',
                       'ssc', '127.0.0.1', 'console-context', '--evs',
                       self.evs_id, 'mkdir', '-p', path]
            try:
                self._execute(command)
            except processutils.ProcessExecutionError as e:
                if "Current file system invalid: VolumeNotFound" in e.stderr:
                    msg = _("Command to create directory %s failed due to "
                            "context change.") % path
                    LOG.debug(msg)
                    raise exception.HNASSSCContextChange(msg=msg)
                else:
                    msg = _("Failed to create directory %s.") % path
                    LOG.exception(msg)
                    raise exception.HNASBackendException(msg=msg)

        if op == 'delete':
            command = ['selectfs', self.fs_name, '\n',
                       'ssc', '127.0.0.1', 'console-context', '--evs',
                       self.evs_id, 'rmdir', path]
            try:
                self._execute(command)
            except processutils.ProcessExecutionError as e:
                if 'DirectoryNotEmpty' in e.stderr:
                    msg = _("Share %s has more snapshots.") % path
                    LOG.debug(msg)
                    raise exception.HNASDirectoryNotEmpty(msg=msg)
                elif 'cannot remove' in e.stderr and 'NotFound' in e.stderr:
                    LOG.warning("Attempted to delete path %s but it does "
                                "not exist.", path)
                elif 'Current file system invalid: VolumeNotFound' in e.stderr:
                    msg = _("Command to delete empty directory %s failed due "
                            "to context change.") % path
                    LOG.debug(msg)
                    raise exception.HNASSSCContextChange(msg=msg)
                else:
                    msg = _("Failed to delete directory %s.") % path
                    LOG.exception(msg)
                    raise exception.HNASBackendException(msg=msg)


class Export(object):
    def __init__(self, data):
        if data:
            split_data = data.split('Export configuration:\n')
            items = split_data[0].split('\n')

            self.export_name = items[0].split(':')[1].strip()
            self.export_path = items[1].split(':')[1].strip()

            if '*** not available ***' in items[2]:
                self.file_system_info = items[2].split(':')[1].strip()
                index = 0

            else:
                self.file_system_label = items[2].split(':')[1].strip()
                self.file_system_size = items[3].split(':')[1].strip()
                self.file_system_free_space = items[4].split(':')[1].strip()
                self.file_system_state = items[5].split(':')[1]
                self.formatted = items[6].split('=')[1].strip()
                self.mounted = items[7].split('=')[1].strip()
                self.failed = items[8].split('=')[1].strip()
                self.thin_provisioned = items[9].split('=')[1].strip()
                index = 7

            self.access_snapshots = items[3 + index].split(':')[1].strip()
            self.display_snapshots = items[4 + index].split(':')[1].strip()
            self.read_caching = items[5 + index].split(':')[1].strip()
            self.disaster_recovery_setting = items[6 + index].split(':')[1]
            self.recovered = items[7 + index].split('=')[1].strip()
            self.transfer_setting = items[8 + index].split('=')[1].strip()

            self.export_configuration = []
            export_config = split_data[1].split('\n')
            for i in range(0, len(export_config)):
                if any(j.isdigit() or j.isalpha() for j in export_config[i]):
                    self.export_configuration.append(export_config[i])


class JobStatus(object):
    def __init__(self, data):
        if data:
            lines = data.split("\n")

            self.job_id = lines[0].split()[3]
            self.physical_node = lines[2].split()[3]
            self.evs = lines[3].split()[2]
            self.volume_number = lines[4].split()[3]
            self.fs_id = lines[5].split()[4]
            self.fs_name = lines[6].split()[4]
            self.source_path = lines[7].split()[3]
            self.creation_time = " ".join(lines[8].split()[3:5])
            self.destination_path = lines[9].split()[3]
            self.ensure_path_exists = lines[10].split()[5]
            self.job_state = " ".join(lines[12].split()[3:])
            self.job_started = " ".join(lines[14].split()[2:4])
            self.job_ended = " ".join(lines[15].split()[2:4])
            self.job_status = lines[16].split()[2]

            error_details_line = lines[17].split()
            if len(error_details_line) > 3:
                self.error_details = " ".join(error_details_line[3:])
            else:
                self.error_details = None

            self.directories_processed = lines[18].split()[3]
            self.files_processed = lines[19].split()[3]
            self.data_bytes_processed = lines[20].split()[4]
            self.directories_missing = lines[21].split()[4]
            self.files_missing = lines[22].split()[4]
            self.files_skipped = lines[23].split()[4]

            skipping_details_line = lines[24].split()
            if len(skipping_details_line) > 3:
                self.skipping_details = " ".join(skipping_details_line[3:])
            else:
                self.skipping_details = None


class JobSubmit(object):
    def __init__(self, data):
        if data:
            split_data = data.replace(".", "").split()

            self.request_status = " ".join(split_data[1:4])
            self.job_id = split_data[8]


class Filesystem(object):
    def __init__(self, data):
        if data:
            items = data.split()
            self.id = items[0]
            self.label = items[1]
            self.evs = items[2]
            self.size = float(items[3])
            self.size_measure = items[4]
            if self.size_measure == 'TB':
                self.size = self.size * units.Ki
            if items[5:7] == ["Not", "mounted"]:
                self.mounted = False
            else:
                self.mounted = True
                self.used = float(items[5])
                self.used_measure = items[6]
                if self.used_measure == 'TB':
                    self.used = self.used * units.Ki
            self.dedupe = 'dedupe enabled' in data


class Quota(object):
    def __init__(self, data):
        if data:
            if 'No quotas matching' in data:
                self.type = None
                self.target = None
                self.usage = None
                self.usage_unit = None
                self.limit = None
                self.limit_unit = None
            else:
                items = data.split()
                self.type = items[2]
                self.target = items[6]
                self.usage = items[9]
                self.usage_unit = items[10]
                if items[13] == 'Unset':
                    self.limit = None
                else:
                    self.limit = float(items[13])
                    self.limit_unit = items[14]


class CIFSPermissions(object):
    def __init__(self, data):
        self.permission_list = []
        hnas_cifs_permissions = [('Allow Read', 'ar'),
                                 ('Allow Change & Read', 'acr'),
                                 ('Allow Full Control', 'af'),
                                 ('Deny  Read', 'dr'),
                                 ('Deny  Change & Read', 'dcr'),
                                 ('Deny  Full Control', 'df')]

        lines = data.split('\n')

        for line in lines:
            filtered = list(filter(lambda x: x[0] in line,
                                   hnas_cifs_permissions))

            if len(filtered) == 1:
                token, permission = filtered[0]
                user = line.split(token)[1:][0].strip()
                self.permission_list.append((user, permission))


class CIFSShare(object):
    def __init__(self, data):
        lines = data.split('\n')

        for line in lines:
            if 'File system label' in line:
                self.fs = line.split(': ')[1]
            elif 'Share users' in line:
                users = line.split(': ')
                self.is_mounted = users[1] != '0'
