def __init__(self, hostname, username, password, vserver, pool_name,
                 cluster_id, allocation_unit=None, compute_instance_id=None):
        logger.debug('Init %s', self.__class__.__name__)
        self.cluster_id = cluster_id
        self.pool_name = pool_name
        self.vserver = vserver

        self.driver = CmodeISCSIDriver(
            hostname=hostname,
            username=username,
            password=password,
            vserver=vserver,
            api_minor_version=1,   # TODO: Remove hard-coded values
            api_major_version=21,  # TODO: Remove hard-coded values
            port=80,               # TODO: Remove hard-coded values
            server_type='FILER',   # TODO: Remove hard-coded values
            transport_type='HTTP'  # TODO: Remove hard-coded values
        )

        self._allocation_unit = allocation_unit or 1
        self._compute_instance_id = compute_instance_id or socket.gethostname()
class NetAppBlockDeviceAPI(object):
    """
    Common operations provided by all block device back-ends, exposed via
    synchronous methods.
    """

    def __init__(self, hostname, username, password, vserver, pool_name,
                 cluster_id, allocation_unit=None, compute_instance_id=None):
        logger.debug('Init %s', self.__class__.__name__)
        self.cluster_id = cluster_id
        self.pool_name = pool_name
        self.vserver = vserver

        self.driver = CmodeISCSIDriver(
            hostname=hostname,
            username=username,
            password=password,
            vserver=vserver,
            api_minor_version=1,   # TODO: Remove hard-coded values
            api_major_version=21,  # TODO: Remove hard-coded values
            port=80,               # TODO: Remove hard-coded values
            server_type='FILER',   # TODO: Remove hard-coded values
            transport_type='HTTP'  # TODO: Remove hard-coded values
        )

        self._allocation_unit = allocation_unit or 1
        self._compute_instance_id = compute_instance_id or socket.gethostname()

    def _vol_f2n(self, volname):
        return str(volname).replace('-', '_')

    def _vol_n2f(self, s):
        return s.replace('_', '-')

    def _volume_exists(self, blockdevice_id):
        """
        Check if volume exists.

        :param unicode blockdevice_id: Name of the volume to check

        :return: True if volume exists, otherwise False
        :rtype: bool
        """
        logger.debug('Checking if volume %s exists', blockdevice_id)
        for volume in self.list_volumes():
            if volume.blockdevice_id == blockdevice_id:
                logger.debug('Volume exists: %s', blockdevice_id)
                return True
        logger.debug('Volume does not exist: %s', blockdevice_id)
        return False

    def _volume_attached(self, blockdevice_id):
        """
        Check if a volume is attached to an initiator group.

        :param unicode blockdevice_id: Name of the volume to check

        :return: True if volume is attached, otherwise False
        :rtype: bool
        """
        logger.debug('Checking if volume %s is attached', blockdevice_id)
        for volume in self.list_volumes():
            if volume.blockdevice_id == blockdevice_id:
                if volume.attached_to:
                    logger.debug('Volume attached: %s', blockdevice_id)
                    return True
                else:
                    logger.debug('Volume is not attached: %s', blockdevice_id)
                    return False
        logger.error('Bad path for volume: %s', blockdevice_id)

    def allocation_unit(self):
        """
        The size, in bytes up to which
        :py:class:`~flocker.node._deploy.IDeployer` will round volume
        sizes before calling :py:meth:`create_volume`.

        :return: The size in bytes to round the volume size
        :rtype: int
        """
        return self._allocation_unit

    def compute_instance_id(self):
        """
        Get an identifier for this node.

        This will be compared against
        :py:attr:`flocker.node.agents.blockdevice.BlockDeviceVolume.attached_to`
        to determine which volumes are locally attached and it will be used
        with :py:meth:`attach_volume` to locally attach volumes.

        :return: A provider-specific node identifier which identifies the node
                 where the method is run
        :rtype: unicode
        """
        return self._compute_instance_id

    def create_volume(self, dataset_id, size):
        """
        Create a new volume.

        When called by :py:class:`~flocker.node._deploy.IDeployer`, the
        supplied size will be rounded up to the nearest
        :py:meth:`allocation_unit`.

        :param str dataset_id: The Flocker dataset ID of the dataset on this
                               volume
        :param int size: The size of the new volume in bytes

        :return: The created volume
        :rtype: :py:class:`~flocker.node.agents.blockdevice.BlockDeviceVolume`
        """
        logger.debug('Creating volume %s of size %s', dataset_id, size)
        # This is to avoid needing a separate data store for mapping
        # dataset-ids to block-device-ids and back again.
        volume = _blockdevicevolume_from_dataset_id(dataset_id, size)

        self.driver.create_volume(
            self.pool_name,
            self._vol_f2n(volume.blockdevice_id),
            size)
        return volume

    def destroy_volume(self, blockdevice_id):
        """
        Destroy an existing volume.

        :param unicode blockdevice_id: The unique identifier for the volume to
                                       destroy

        :raises UnknownVolume: If the supplied blockdevice_id does not exist
        """
        blockdevice_id_str = self._vol_f2n(blockdevice_id)
        logger.debug('Destroying volume %s', blockdevice_id)
        if not self._volume_exists(blockdevice_id=blockdevice_id):
            raise UnknownVolume(blockdevice_id)
        self.driver.delete_volume(self.pool_name, blockdevice_id_str)

    def list_volumes(self):
        """
        List all the block devices available via the back end API.

        :return: The :py:class:`~flocker.node.agents.blockdevice.BlockDeviceVolume`
                 volumes
        :rtype: list
        """
        logger.debug('Getting a list of block device volumes')
        netapp_volumes = self.driver.volumes(self.vserver,
                                             pool_name=self.pool_name)

        block_device_volumes = []
        for netapp_volume in netapp_volumes:
            logger.debug('Creating a BlockDeviceVolume object for %s',
                         netapp_volume)
            try:
                # Create a BlockDeviceVolume, and append it to list.
                attached_to = None
                # TODO: Clean this up a bit
                if unicode(netapp_volume['attached_to']) != 'None':
                    attached_to = unicode(netapp_volume['attached_to'])

                volume = _blockdevicevolume_from_blockdevice_id(
                    blockdevice_id=unicode(self._vol_n2f(
                        netapp_volume['blockdevice_id'])),
                    size=int(netapp_volume['size']),
                    attached_to=attached_to)
                block_device_volumes.append(volume)
            except Exception:  # TODO: Too broad, make more specific
                logger.exception('Unexpected error while creating a '
                                 'BlockDeviceVolume for %s', netapp_volume)

        logger.debug('Found the following block device volumes: %s',
                     block_device_volumes)
        return block_device_volumes

    def attach_volume(self, blockdevice_id, attach_to):
        """
        Attach ``blockdevice_id`` to the node indicated by ``attach_to``.

        :param unicode blockdevice_id: The unique identifier for the block
                                       device being attached.
        :param unicode attach_to: An identifier like the one returned by the
                                  ``compute_instance_id`` method indicating the
                                  node to which to attach the volume.

        :raises UnknownVolume: If the supplied ``blockdevice_id`` does not
                               exist
        :raises AlreadyAttachedVolume: If the supplied ``blockdevice_id`` is
                                       already attached

        :returns: A ``BlockDeviceVolume`` with a ``attached_to`` attribute set
                  to ``attach_to``
        """
        blockdevice_id_str = self._vol_f2n(blockdevice_id)
        logger.debug('Attaching volume %s to %s', blockdevice_id, attach_to)
        if not self._volume_exists(blockdevice_id=blockdevice_id):
            raise UnknownVolume(blockdevice_id)

        if self._volume_attached(blockdevice_id=blockdevice_id):
            raise AlreadyAttachedVolume(blockdevice_id)

        self.driver.attach_volume(pool_name=self.pool_name,
                                  volume_name=blockdevice_id_str,
                                  igroup_name=attach_to)

        # The volume is attached. Now extract the other properties of the
        # volume to create a BlockDeviceVolume.
        volumes = self.list_volumes()
        for volume in volumes:
            if volume.blockdevice_id == blockdevice_id:
                return _blockdevicevolume_from_blockdevice_id(
                    blockdevice_id=volume.blockdevice_id,
                    size=int(volume.size),
                    attached_to=attach_to)
        raise UnknownVolume(blockdevice_id)

    def detach_volume(self, blockdevice_id):
        """
        Detach ``blockdevice_id`` from whatever host it is attached to.

        :param unicode blockdevice_id: The unique identifier for the block
                                        device being detached.

        :raises UnknownVolume: If the supplied ``blockdevice_id`` does not
                               exist.
        :raises UnattachedVolume: If the supplied ``blockdevice_id`` is
                                  not attached to anything.
        """
        blockdevice_id_str = self._vol_f2n(blockdevice_id)
        logger.debug('Detaching volume %s', blockdevice_id)

        if self._volume_exists(blockdevice_id=blockdevice_id) is False:
            raise UnknownVolume(blockdevice_id)
        if self._volume_attached(blockdevice_id=blockdevice_id) is False:
            raise UnattachedVolume(blockdevice_id)

        igroup_name = None
        volumes = self.list_volumes()
        for volume in volumes:
            if volume.blockdevice_id == blockdevice_id:
                igroup_name = volume.attached_to

        self.driver.detach_volume(pool_name=self.pool_name,
                                  volume_name=blockdevice_id_str,
                                  igroup_name=igroup_name)

    # TODO: This method does a lot and can be broken down
    def get_device_path(self, blockdevice_id):
        """
        Return the device path that has been allocated to the block device on
        the host to which it is currently attached.

        :param unicode blockdevice_id: The unique identifier for the block
                                        device.
        :raises UnknownVolume: If the supplied ``blockdevice_id`` does not
                               exist.
        :raises UnattachedVolume: If the supplied ``blockdevice_id`` is
                                  not attached to a host.
        :returns: A ``FilePath`` for the device.
        """
        logger.debug('Getting device path for %s', blockdevice_id)

        blockdevice_id_str = self._vol_f2n(blockdevice_id)

        if not self._volume_exists(blockdevice_id=blockdevice_id):
            raise UnknownVolume(blockdevice_id)
        if not self._volume_attached(blockdevice_id=blockdevice_id):
            raise UnattachedVolume(blockdevice_id)

        lun_id = self.driver._get_lun_id(blockdevice_id=blockdevice_id_str,
                                         pool_name=self.pool_name,
                                         vserver=self.vserver)
        logger.debug('%s has the LUN ID %s', blockdevice_id_str, lun_id)

        # Rescan the session to ensure we get the right results with "lsscsi".
        os.system("rescan-scsi-bus.sh -a -r")
        os.system("iscsiadm -m session --rescan > /dev/null")

        scsi_device = None
        output = check_output([b"/usr/bin/lsscsi"])
        # Parse the result of "lsscsi" to get the desired path.
        for row in output.split('\n'):
            if re.search(r'NETAPP', row, re.I):
                if re.search(r'\d:\d:\d:' + str(lun_id), row, re.I):
                    # TODO: There is a better way to parse this output
                    device_name = re.findall(r'/\w+', row, re.I)
                    if device_name:
                        # /dev/sdb
                        scsi_device = device_name[0] + device_name[1]

        multipath_devices = []
        mapper_device = None
        output = check_output([b"/sbin/multipath", "-ll"])
        for row in output.split('\n'):
            if re.search(r'NETAPP', row, re.I):
                device_name = re.findall(r'[/a-zA-Z0-9_-]+', row, re.I)
                if device_name:
                    multipath_devices.append(device_name[0])
                    logger.debug('Device names: %s', ', '.join(device_name))

        logger.debug('Multipath devices for %s = %s', blockdevice_id,
                     multipath_devices)

        for i in multipath_devices:
            output = check_output([b"multipath", "-ll", "/dev/mapper/" + i])
            for row in output.split('\n'):
                if re.search(r'\d:\d:\d:\d ' + scsi_device.split("/")[2],
                             row, re.I):
                    mapper_device = i
                    logger.debug("mapper_device[3] == /dev/mapper/%s" % i)
                    break

        if mapper_device:
            return FilePath("/dev/mapper/" + mapper_device)
        else:
            raise UnattachedVolume(blockdevice_id)