1# All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may 4# not use this file except in compliance with the License. You may obtain 5# a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations 13# under the License. 14 15import os 16 17from oslo_concurrency import lockutils 18from oslo_log import log as logging 19 20from os_brick import exception 21from os_brick.i18n import _ 22from os_brick.initiator.connectors import base 23from os_brick import utils 24 25LOG = logging.getLogger(__name__) 26synchronized = lockutils.synchronized_with_prefix('os-brick-') 27 28 29class HuaweiStorHyperConnector(base.BaseLinuxConnector): 30 """"Connector class to attach/detach SDSHypervisor volumes.""" 31 32 attached_success_code = 0 33 has_been_attached_code = 50151401 34 attach_mnid_done_code = 50151405 35 vbs_unnormal_code = 50151209 36 not_mount_node_code = 50155007 37 iscliexist = True 38 39 def __init__(self, root_helper, driver=None, 40 *args, **kwargs): 41 self.cli_path = os.getenv('HUAWEISDSHYPERVISORCLI_PATH') 42 if not self.cli_path: 43 self.cli_path = '/usr/local/bin/sds/sds_cli' 44 LOG.debug("CLI path is not configured, using default %s.", 45 self.cli_path) 46 if not os.path.isfile(self.cli_path): 47 self.iscliexist = False 48 LOG.error('SDS CLI file not found, ' 49 'HuaweiStorHyperConnector init failed.') 50 super(HuaweiStorHyperConnector, self).__init__(root_helper, 51 driver=driver, 52 *args, **kwargs) 53 54 @staticmethod 55 def get_connector_properties(root_helper, *args, **kwargs): 56 """The HuaweiStor connector properties.""" 57 return {} 58 59 def get_search_path(self): 60 # TODO(walter-boring): Where is the location on the filesystem to 61 # look for Huawei volumes to show up? 62 return None 63 64 def get_all_available_volumes(self, connection_properties=None): 65 # TODO(walter-boring): what to return here for all Huawei volumes ? 66 return [] 67 68 def get_volume_paths(self, connection_properties): 69 volume_path = None 70 try: 71 volume_path = self._get_volume_path(connection_properties) 72 except Exception: 73 msg = _("Couldn't find a volume.") 74 LOG.warning(msg) 75 raise exception.BrickException(message=msg) 76 return [volume_path] 77 78 def _get_volume_path(self, connection_properties): 79 out = self._query_attached_volume( 80 connection_properties['volume_id']) 81 if not out or int(out['ret_code']) != 0: 82 msg = _("Couldn't find attached volume.") 83 LOG.error(msg) 84 raise exception.BrickException(message=msg) 85 return out['dev_addr'] 86 87 @utils.trace 88 @synchronized('connect_volume', external=True) 89 def connect_volume(self, connection_properties): 90 """Connect to a volume. 91 92 :param connection_properties: The dictionary that describes all 93 of the target volume attributes. 94 :type connection_properties: dict 95 :returns: dict 96 """ 97 LOG.debug("Connect_volume connection properties: %s.", 98 connection_properties) 99 out = self._attach_volume(connection_properties['volume_id']) 100 if not out or int(out['ret_code']) not in (self.attached_success_code, 101 self.has_been_attached_code, 102 self.attach_mnid_done_code): 103 msg = (_("Attach volume failed, " 104 "error code is %s") % out['ret_code']) 105 raise exception.BrickException(message=msg) 106 107 try: 108 volume_path = self._get_volume_path(connection_properties) 109 except Exception: 110 msg = _("query attached volume failed or volume not attached.") 111 LOG.error(msg) 112 raise exception.BrickException(message=msg) 113 114 device_info = {'type': 'block', 115 'path': volume_path} 116 return device_info 117 118 @utils.trace 119 @synchronized('connect_volume', external=True) 120 def disconnect_volume(self, connection_properties, device_info, 121 force=False, ignore_errors=False): 122 """Disconnect a volume from the local host. 123 124 :param connection_properties: The dictionary that describes all 125 of the target volume attributes. 126 :type connection_properties: dict 127 :param device_info: historical difference, but same as connection_props 128 :type device_info: dict 129 """ 130 LOG.debug("Disconnect_volume: %s.", connection_properties) 131 out = self._detach_volume(connection_properties['volume_id']) 132 if not out or int(out['ret_code']) not in (self.attached_success_code, 133 self.vbs_unnormal_code, 134 self.not_mount_node_code): 135 msg = (_("Disconnect_volume failed, " 136 "error code is %s") % out['ret_code']) 137 raise exception.BrickException(message=msg) 138 139 def is_volume_connected(self, volume_name): 140 """Check if volume already connected to host""" 141 LOG.debug('Check if volume %s already connected to a host.', 142 volume_name) 143 out = self._query_attached_volume(volume_name) 144 if out: 145 return int(out['ret_code']) == 0 146 return False 147 148 def _attach_volume(self, volume_name): 149 return self._cli_cmd('attach', volume_name) 150 151 def _detach_volume(self, volume_name): 152 return self._cli_cmd('detach', volume_name) 153 154 def _query_attached_volume(self, volume_name): 155 return self._cli_cmd('querydev', volume_name) 156 157 def _cli_cmd(self, method, volume_name): 158 LOG.debug("Enter into _cli_cmd.") 159 if not self.iscliexist: 160 msg = _("SDS command line doesn't exist, " 161 "can't execute SDS command.") 162 raise exception.BrickException(message=msg) 163 if not method or volume_name is None: 164 return 165 cmd = [self.cli_path, '-c', method, '-v', volume_name] 166 out, clilog = self._execute(*cmd, run_as_root=False, 167 root_helper=self._root_helper) 168 analyse_result = self._analyze_output(out) 169 LOG.debug('%(method)s volume returns %(analyse_result)s.', 170 {'method': method, 'analyse_result': analyse_result}) 171 if clilog: 172 LOG.error("SDS CLI output some log: %s.", clilog) 173 return analyse_result 174 175 def _analyze_output(self, out): 176 LOG.debug("Enter into _analyze_output.") 177 if out: 178 analyse_result = {} 179 out_temp = out.split('\n') 180 for line in out_temp: 181 LOG.debug("Line is %s.", line) 182 if line.find('=') != -1: 183 key, val = line.split('=', 1) 184 LOG.debug("%(key)s = %(val)s", {'key': key, 'val': val}) 185 if key in ['ret_code', 'ret_desc', 'dev_addr']: 186 analyse_result[key] = val 187 return analyse_result 188 else: 189 return None 190 191 def extend_volume(self, connection_properties): 192 # TODO(walter-boring): is this possible? 193 raise NotImplementedError 194