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