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.
        """

        LOG.info("Call get_device_path")

        # no mulitpath
        try:
            lun_info = self.restclient.get_lun_info(blockdevice_id)
        except Exception:
            raise UnknownVolume(blockdevice_id)

        if lun_info['EXPOSEDTOINITIATOR'].lower() == 'false':
            raise UnattachedVolume(blockdevice_id)

        lun_wwn = lun_info['WWN']

        for bd in huawei_utils.get_all_block_device():
            bd_wwn = huawei_utils.get_wwn_of_deviceblock(bd)
            if bd_wwn is not None and lun_wwn in bd_wwn:
                LOG.info("device_path finded: %s" % bd)
                return FilePath("/dev/"+bd)

        return None
    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.
        :returns: ``None``
        """

        LOG.info("Call detach_volume blockdevice_id=%s" % blockdevice_id)
        device = self.get_device_path(blockdevice_id)
        if device is not None:
            huawei_utils.remove_scsi_device(device)

        lun_info = self.restclient.get_lun_info(blockdevice_id)
        if self.get_attached_to(lun_info) is not None:
            self.restclient.delete_mapping(
                blockdevice_id, self._compute_instance_id)
        else:
            LOG.error("Volume %s not attached." % blockdevice_id)
            raise UnattachedVolume(blockdevice_id)
    def delete_mapping(self, lun_id, host_name):
        if host_name and len(host_name) > constants.MAX_HOSTNAME_LENGTH:
            host_name = hash(host_name)
        host_id = self.find_host(host_name)
        if host_id:
            mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
            view_id = self.find_mapping_view(mapping_view_name)
            if view_id:
                lungroup_id = self.find_lungroup_from_map(view_id)

        # Remove lun from lungroup.
        if lun_id and self.check_lun_exist(lun_id):
            if lungroup_id:
                lungroup_ids = self.get_lungroupids_by_lunid(lun_id)
                if lungroup_id in lungroup_ids:
                    self.remove_lun_from_lungroup(lungroup_id, lun_id)
                else:
                    LOG.info(('Lun is not in lungroup. '
                              'Lun id: %(lun_id)s. '
                              'lungroup id: %(lungroup_id)s.')
                             % {"lun_id": lun_id,
                                "lungroup_id": lungroup_id})
        else:
            LOG.error("Can't find lun on the array.")
            raise VolumeBackendAPIException
 def __init__(self, cluster_id, huawei_conf_file,
              compute_instance_id=None,
              allocation_unit=None):
     """
     :param cluster_id: An ID that include in the
         names of Huawei volumes to identify cluster.
     :param huawei_conf_file: The path of huawei config file.
     :param compute_instance_id: An ID that used to create
         host on the array to identify node.
     :param allocation_unit: Allocation unit on array.
     :returns: A ``BlockDeviceVolume``.
     """
     LOG.info("Huawei block device init")
     self._host_id = None
     self._hostgroup_id = None
     self.xml_file_path = huawei_conf_file
     self.configuration = huawei_utils.get_login_info(
         self.xml_file_path)
     self.restclient = rest_client.RestClient(self.configuration)
     self.restclient.login()
     if compute_instance_id is None:
         compute_instance_id = huawei_utils.get_instance_id(
             self.xml_file_path)
     self._compute_instance_id = compute_instance_id
     self._cluster_id = cluster_id
     if allocation_unit is None:
         allocation_unit = 512
     self._allocation_unit = allocation_unit
     LOG.info("Finish huawei block device init")
    def allocation_unit(self):
        """
        The size, in bytes up to which ``IDeployer`` will round volume
        sizes before calling ``IBlockDeviceAPI.create_volume``.

        :returns: ``int``
        """
        LOG.info("Call allocation_unit")
        return self._allocation_unit
    def do_mapping(self, lun_id, hostgroup_id, host_id, tgtportgroup_id=None):
        """Add hostgroup and lungroup to mapping view."""
        lungroup_name = constants.LUNGROUP_PREFIX + host_id
        mapping_view_name = constants.MAPPING_VIEW_PREFIX + host_id
        lungroup_id = self._find_lungroup(lungroup_name)
        view_id = self.find_mapping_view(mapping_view_name)
        map_info = {}

        LOG.info((
            'do_mapping, lun_group: %(lun_group)s, '
            'view_id: %(view_id)s, lun_id: %(lun_id)s.') %
            {'lun_group': lungroup_id,
             'view_id': view_id,
             'lun_id': lun_id})

        try:
            # Create lungroup and add LUN into to lungroup.
            if lungroup_id is None:
                lungroup_id = self._create_lungroup(lungroup_name)
            is_associated = self._is_lun_associated_to_lungroup(lungroup_id,
                                                                lun_id)
            if not is_associated:
                self.associate_lun_to_lungroup(lungroup_id, lun_id)

            if view_id is None:
                view_id = self._add_mapping_view(mapping_view_name)
                self._associate_hostgroup_to_view(view_id, hostgroup_id)
                self._associate_lungroup_to_view(view_id, lungroup_id)
                if tgtportgroup_id:
                    self._associate_portgroup_to_view(view_id, tgtportgroup_id)

            else:
                if not self.hostgroup_associated(view_id, hostgroup_id):
                    self._associate_hostgroup_to_view(view_id, hostgroup_id)
                if not self.lungroup_associated(view_id, lungroup_id):
                    self._associate_lungroup_to_view(view_id, lungroup_id)
                if tgtportgroup_id:
                    if not self._portgroup_associated(view_id,
                                                      tgtportgroup_id):
                        self._associate_portgroup_to_view(view_id,
                                                          tgtportgroup_id)

            version = self.find_array_version()
            if version >= constants.ARRAY_VERSION:
                aval_luns = self.find_view_by_id(view_id)
                map_info["lun_id"] = lun_id
                map_info["view_id"] = view_id
                map_info["aval_luns"] = aval_luns

        except Exception:
            LOG.error('Error occurred when adding hostgroup and lungroup to '
                      'view. Remove lun from lungroup now.')
            self.remove_lun_from_lungroup(lungroup_id, lun_id)
            raise VolumeBackendAPIException

        return map_info
    def get_attached_to(self, item):
        """
        TODO: Find a way to save the attach_to information.
        """
        LOG.info("Call get_attached_to")
        if item['ISADD2LUNGROUP'] == 'true':
            result = self.restclient.get_host_of_lun_map(item['ID'])
            if 'data' in result:
                return result['data'][0]['NAME']

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

        When called by ``IDeployer``, the supplied size will be
        rounded up to the nearest ``IBlockDeviceAPI.allocation_unit()``

        :param UUID dataset_id: The Flocker dataset ID of the dataset on this
            volume.
        :param int size: The size of the new volume in bytes.
        :returns: A ``BlockDeviceVolume``.
        """
        LOG.info("Call create_volume, dataset_id=%s, size=%d"
                 % (dataset_id, size))
        name = huawei_utils.encode_name(dataset_id, self._cluster_id)
        parameters = huawei_utils.get_lun_conf_params(self.xml_file_path)
        if parameters is None:
            raise VolumeException
        pool_name = huawei_utils.get_pools(self.xml_file_path)
        if pool_name is None:
            raise VolumeException
        pools = self.restclient.find_all_pools()
        pool_info = self.restclient.find_pool_info(pool_name,
                                                   pools)

        lun_param = {"TYPE": '11',
                     "NAME": name,
                     "PARENTTYPE": '216',
                     "PARENTID": pool_info['ID'],
                     "ALLOCTYPE": parameters['LUNType'],
                     "CAPACITY": str((size/512)),
                     "WRITEPOLICY": parameters['WriteType'],
                     "MIRRORPOLICY": parameters['MirrorSwitch'],
                     "PREFETCHPOLICY": parameters['PrefetchType'],
                     "PREFETCHVALUE": parameters['PrefetchValue'],
                     "DATATRANSFERPOLICY": parameters['policy'],
                     "READCACHEPOLICY": parameters['readcachepolicy'],
                     "WRITECACHEPOLICY": parameters['writecachepolicy']}

        url = "/lun"
        data = json.dumps(lun_param)
        result = self.restclient.call(url, data)
        lun_info = result['data']
        volume = BlockDeviceVolume(
            size=int(lun_info['CAPACITY'])*512,
            attached_to=None,
            dataset_id=huawei_utils.decode_name(lun_info['NAME'],
                                                self._cluster_id),
            blockdevice_id=unicode(lun_info['ID'])
        )
        return volume
    def compute_instance_id(self):
        """
        Get an identifier for this node.

        This will be compared against ``BlockDeviceVolume.attached_to``
        to determine which volumes are locally attached and it will be used
        with ``attach_volume`` to locally attach volumes.

        :returns: A ``unicode`` object giving a provider-specific node
            identifier which identifies the node where the method is run.
        """

        LOG.info("Call compute_instance_id = %s" % self._compute_instance_id)
        return unicode(self._compute_instance_id)
 def _find_alua_info(self, iscsi_conf, initiator_name):
     """Find ALUA info from xml."""
     multipath_type = 0
     for ini in iscsi_conf['Initiator']:
         if ini['Name'] == initiator_name:
             if 'ALUA' in ini:
                 if ini['ALUA'] != '1' and ini['ALUA'] != '0':
                     msg = ('Invalid ALUA value.'
                            'ALUA value must be 1 or 0.')
                     LOG.error(msg)
                     raise VolumeBackendAPIException
                 else:
                     multipath_type = ini['ALUA']
                     break
     return multipath_type
    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``.
        """

        LOG.info("Call attach_volume blockdevice_id=%s, attach_to=%s"
                 % (blockdevice_id, attach_to))

        try:
            lun_info = self.restclient.get_lun_info(blockdevice_id)
        except Exception:
            raise UnknownVolume(blockdevice_id)

        if lun_info['EXPOSEDTOINITIATOR'].lower() == 'true':
            raise AlreadyAttachedVolume(blockdevice_id)

        self.initialize_connection()

        self.restclient.do_mapping(blockdevice_id, self._hostgroup_id,
                                   self._host_id)

        huawei_utils.rescan_scsi()

        lun_info = self.restclient.get_lun_info(blockdevice_id)

        attached_volume = BlockDeviceVolume(
            size=int(lun_info['CAPACITY'])*512,
            attached_to=unicode(attach_to),
            dataset_id=huawei_utils.decode_name(
                lun_info['NAME'], self._cluster_id),
            blockdevice_id=blockdevice_id)
        return attached_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.

        :return: ``None``
        """

        LOG.info("Call destroy_volume blockdevice_id=%s" % blockdevice_id)
        try:
            self.restclient.delete_lun(blockdevice_id)
        except Exception:
            raise UnknownVolume(blockdevice_id)
    def _associate_initiator_to_host(self,
                                     xml_file_path,
                                     initiator_name,
                                     host_id):
        """Associate initiator with the host."""
        iscsi_conf = huawei_utils.get_iscsi_conf(xml_file_path)

        chapinfo = self.find_chap_info(iscsi_conf,
                                       initiator_name)
        multipath_type = self._find_alua_info(iscsi_conf,
                                              initiator_name)
        if chapinfo:
            LOG.info('Use CHAP when adding initiator to host.')
            self._use_chap(chapinfo, initiator_name, host_id)
        else:
            self._add_initiator_to_host(initiator_name, host_id)

        if multipath_type:
            LOG.info('Use ALUA when adding initiator to host.')
            self._use_alua(initiator_name, multipath_type)
    def do_call(self, url=False, data=None, method=None,
                calltimeout=constants.SOCKET_TIMEOUT):
        """Send requests to server.

        Send HTTPS call, get response in JSON.
        Convert response into Python Object and return it.
        """
        if self.url:
            url = self.url + url

        opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookie))
        urllib2.install_opener(opener)
        res_json = None

        LOG.info(('\n\n\n\nRequest URL: %(url)s\n\n'
                   'Call Method: %(method)s\n\n'
                   'Request Data: %(data)s\n\n') % {'url': url,
                                                    'method': method,
                                                    'data': data})

        try:
            urllib2.socket.setdefaulttimeout(calltimeout)
            req = urllib2.Request(url, data, self.headers)
            if method:
                req.get_method = lambda: method
            res = urllib2.urlopen(req).read().decode("utf-8")

            if "xx/sessions" not in url:
                LOG.info(('\n\n\n\nRequest URL: %(url)s\n\n'
                           'Call Method: %(method)s\n\n'
                           'Request Data: %(data)s\n\n'
                           'Response Data:%(res)s\n\n')
                          % {'url': url, 'method': method,
                             'data': data, 'res': res})

        except Exception as err:
            LOG.error('Bad response from server: %(url)s.Error: %(err)s'
                      % {'url': url, 'err': err})
            json_msg = ('{"error":{"code": %s,"description": "Connect to '
                        'server error."}}') % constants.ERROR_CONNECT_TO_SERVER
            res_json = json.loads(json_msg)
            return res_json

        try:
            res_json = json.loads(res)
        except Exception as err:
            LOG.error('JSON transfer error: %s.' % err.message)
            raise

        return res_json
    def login(self):
        """Login array."""
        login_info = self.configuration
        urlstr = login_info['RestURL']
        url = urlstr + "xx/sessions"
        data = json.dumps({"username": login_info['UserName'],
                           "password": login_info['UserPassword'],
                           "scope": "0"})
        self._init_http_head()
        result = self.do_call(url, data,
                              calltimeout=constants.LOGIN_SOCKET_TIMEOUT)

        if (result['error']['code'] != 0) or ("data" not in result):
            LOG.error("Login error, reason is: %s." % result)
            return None

        LOG.info('Login success: %(url)s' % {'url': urlstr})
        self.device_id = result['data']['deviceid']
        self.url = urlstr + self.device_id
        self.headers['iBaseToken'] = result['data']['iBaseToken']

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

        :returns: A ``list`` of ``BlockDeviceVolume``s.
        """
        LOG.info("Call list_volumes")
        volumes = []
        url = "/lun?range=[0-65535]"
        result = self.restclient.call(url, None, "GET")

        if 'data' in result:
            for item in result['data']:
                if huawei_utils.is_cluster_volume(
                        item['NAME'], self._cluster_id):
                    volume = BlockDeviceVolume(
                        size=int(item['CAPACITY'])*512,
                        attached_to=self.get_attached_to(item),
                        dataset_id=huawei_utils.decode_name(
                            item['NAME'], self._cluster_id),
                        blockdevice_id=unicode(item['ID'])
                    )
                    volumes.append(volume)
        return volumes
    def initialize_connection_fc(self):
        """
        TODO: Initialize fc connection.
        """
        wwns = huawei_utils.get_fc_wwpns()
        if not wwns:
            raise VolumeException

        # Create hostgroup if not exist.
        host_id = self.restclient.add_host_with_check(
            self._compute_instance_id)

        online_wwns_in_host = (
            self.restclient.get_host_online_fc_initiators(host_id))
        online_free_wwns = self.restclient.get_online_free_wwns()
        for wwn in wwns:
            if (wwn not in online_wwns_in_host and
                    wwn not in online_free_wwns):
                wwns_in_host = (
                    self.restclient.get_host_initiators("fc", host_id))
                iqns_in_host = (
                    self.restclient.get_host_initiators("iscsi", host_id))
                if not wwns_in_host and not iqns_in_host:
                    self.restclient.remove_host(host_id)

                LOG.error('Can not add FC initiator to host.')
                raise VolumeException

        for wwn in wwns:
            if wwn in online_free_wwns:
                self.restclient.add_fc_port_to_host(host_id, wwn)

        hostgroup_id = self.restclient.add_host_into_hostgroup(host_id)

        self._host_id = host_id
        self._hostgroup_id = hostgroup_id
    def add_host_with_check(self, host_name):
        host_name_before_hash = None
        if host_name and (len(host_name) > constants.MAX_HOSTNAME_LENGTH):
            host_name_before_hash = host_name
            host_name = hash(host_name)

        host_id = self.find_host(host_name)
        if host_id:
            LOG.info((
                'add_host_with_check. '
                'host name: %(name)s, '
                'host id: %(id)s') %
                {'name': host_name,
                 'id': host_id})
            return host_id

        try:
            host_id = self._add_host(host_name, host_name_before_hash)
        except Exception:
            LOG.info((
                'Failed to create host: %(name)s. '
                'Check if it exists on the array.') %
                {'name': host_name})
            host_id = self.find_host(host_name)
            if not host_id:
                err_msg = ((
                    'Failed to create host: %(name)s. '
                    'Please check if it exists on the array.') %
                    {'name': host_name})
                LOG.error(err_msg)
                raise VolumeBackendAPIException

        LOG.info((
            'add_host_with_check. '
            'create host success. '
            'host name: %(name)s, '
            'host id: %(id)s') %
            {'name': host_name,
             'id': host_id})
        return host_id
    def create_hostgroup_with_check(self, hostgroup_name):
        """Check if host exists on the array, or create it."""
        hostgroup_id = self.find_hostgroup(hostgroup_name)
        if hostgroup_id:
            LOG.info((
                'create_hostgroup_with_check. '
                'hostgroup name: %(name)s, '
                'hostgroup id: %(id)s') %
                {'name': hostgroup_name,
                 'id': hostgroup_id})
            return hostgroup_id

        try:
            hostgroup_id = self._create_hostgroup(hostgroup_name)
        except Exception:
            LOG.info((
                'Failed to create hostgroup: %(name)s. '
                'Please check if it exists on the array.') %
                {'name': hostgroup_name})
            hostgroup_id = self.find_hostgroup(hostgroup_name)
            if hostgroup_id is None:
                err_msg = ((
                    'Failed to create hostgroup: %(name)s. '
                    'Check if it exists on the array.')
                    % {'name': hostgroup_name})
                LOG.error(err_msg)
                raise VolumeBackendAPIException

        LOG.info((
            'create_hostgroup_with_check. '
            'Create hostgroup success. '
            'hostgroup name: %(name)s, '
            'hostgroup id: %(id)s') %
            {'name': hostgroup_name,
             'id': hostgroup_id})
        return hostgroup_id
 def _assert_rest_result(self, result, err_str):
     if result['error']['code'] != 0:
         msg = ('%(err)s\nresult: %(res)s.'
                % {'err': err_str, 'res': result})
         LOG.error(msg)
         raise VolumeBackendAPIException
 def _assert_data_in_result(self, result, msg):
     if 'data' not in result:
         err_msg = ('%s "data" was not in result.' % msg)
         LOG.error(err_msg)
         raise VolumeBackendAPIException