# Copyright (c) 2019 FUJITSU LIMITED # 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. # """Cinder Volume driver for Fujitsu ETERNUS DX S3 series.""" import six from cinder.i18n import _ from cinder import ssh_utils class FJDXCLI(object): """ETERNUS CLI Code.""" def __init__(self, user, storage_ip, password=None, keyfile=None): """Constructor.""" self.user = user self.storage_ip = storage_ip if password and keyfile: raise Exception(_('can not specify both password and keyfile')) self.use_ipv6 = False if storage_ip.find(':') != -1: self.use_ipv6 = True if password: self.ssh_pool = ssh_utils.SSHPool(storage_ip, 22, None, user, password=password, max_size=2) if keyfile: self.ssh_pool = ssh_utils.SSHPool(storage_ip, 22, None, user, privatekey=keyfile, max_size=2) self.ce_support = False self.CMD_dic = { 'check_user_role': self._check_user_role, 'expand_volume': self._expand_volume, 'show_pool_provision': self._show_pool_provision, 'show_qos_bandwidth_limit': self._show_qos_bandwidth_limit, 'set_qos_bandwidth_limit': self._set_qos_bandwidth_limit, 'set_volume_qos': self._set_volume_qos, 'show_volume_qos': self._show_volume_qos, 'show_enclosure_status': self._show_enclosure_status, 'delete_volume': self._delete_volume } self.SMIS_dic = { '0000': '0', # Success. '0060': '32787', # The device is in busy state. '0100': '4097' } # Size not supported. def done(self, command, **option): func = self.CMD_dic.get(command, self._default_func) return func(**option) def _exec_cli(self, cmd, StrictHostKeyChecking=True, **option): exec_cmdline = cmd + self._get_option(**option) stdoutdata = self._exec_cli_with_eternus(exec_cmdline) output = [] message = [] stdoutlist = stdoutdata.split('\r\n') output_header = "" for no, outline in enumerate(stdoutlist): if len(outline) <= 0 or outline is None: continue if not output_header.endswith(exec_cmdline): output_header += outline continue if 0 <= outline.find('Error'): raise Exception(_("Output: %(outline)s: " "Command: %(cmdline)s") % {'outline': outline, 'cmdline': exec_cmdline}) if not self._is_status(outline): continue status = int(outline, 16) lineno = no + 1 break else: raise Exception(_( "Invalid CLI output: %(exec_cmdline)s, %(stdoutlist)s") % {'exec_cmdline': exec_cmdline, 'stdoutlist': stdoutlist}) if status == 0: rc = '0' for outline in stdoutlist[lineno:]: if 0 <= outline.find('CLI>'): continue if len(outline) <= 0: continue if outline is None: continue message.append(outline) else: code = stdoutlist[lineno] for outline in stdoutlist[lineno + 1:]: if 0 <= outline.find('CLI>'): continue if len(outline) <= 0: continue if outline is None: continue output.append(outline) rc, message = self._create_error_message(code, output) return {'result': 0, 'rc': rc, 'message': message} def _exec_cli_with_eternus(self, exec_cmdline): """Execute CLI command with arguments.""" ssh = None try: ssh = self.ssh_pool.get() chan = ssh.invoke_shell() chan.send(exec_cmdline + '\n') stdoutdata = '' while True: temp = chan.recv(65535) if isinstance(temp, bytes): temp = temp.decode('utf-8') else: temp = str(temp) stdoutdata += temp # CLI command end with 'CLI>'. if stdoutdata == '\r\nCLI> ': continue if (stdoutdata[len(stdoutdata) - 5: len(stdoutdata) - 1] == 'CLI>'): break except Exception as e: raise Exception(_("Execute CLI " "command error. Error: %s") % e) finally: if ssh: self.ssh_pool.put(ssh) self.ssh_pool.remove(ssh) return stdoutdata def _create_error_message(self, code, msg): """Create error code and message using arguements.""" message = None if code in self.SMIS_dic: rc = self.SMIS_dic[code] else: rc = 'E' + code # TODO(whfnst): we will have a dic to store errors. if rc == "E0001": message = "Bad value: %s" % msg elif rc == "ED184": message = "Because OPC is being executed, " "the processing was discontinued." else: message = msg return rc, message @staticmethod def _is_status(value): """Check whether input value is status value or not.""" try: if len(value) != 2: return False int(value, 16) int(value[0], 16) int(value[1], 16) return True except ValueError: return False @staticmethod def _get_option(**option): """Create option strings from dictionary.""" ret = "" for key, value in option.items(): ret += " -%(key)s %(value)s" % {'key': key, 'value': value} return ret def _default_func(self, **option): """Default function.""" raise Exception(_("Invalid function is specified")) def _check_user_role(self, **option): """Check user role.""" try: output = self._exec_cli("show users", StrictHostKeyChecking=False, **option) # Return error. rc = output['rc'] if rc != "0": return output userlist = output.get('message') role = None for userinfo in userlist: username = userinfo.split('\t')[0] if username == self.user: role = userinfo.split('\t')[1] break output['message'] = role except Exception as ex: if 'show users' in six.text_type(ex): msg = ("Specified user(%s) does not have Software role" % self.user) elif 'Error connecting' in six.text_type(ex): msg = (six.text_type(ex)[34:] + ', Please check fujitsu_private_key_path or .xml file') else: msg = six.text_type(ex) output = { 'result': 0, 'rc': '4', 'message': msg } return output def _expand_volume(self, **option): """Exec expand volume.""" return self._exec_cli("expand volume", **option) def _set_volume_qos(self, **option): """Exec set volume-qos.""" return self._exec_cli("set volume-qos", **option) def _show_pool_provision(self, **option): """Get TPP provision capacity information.""" try: output = self._exec_cli("show volumes", **option) rc = output['rc'] if rc != "0": return output clidatalist = output.get('message') data = 0 for clidataline in clidatalist[1:]: clidata = clidataline.split('\t') if clidata[0] == 'FFFF': break data += int(clidata[7], 16) provision = data / 2097152 output['message'] = provision except Exception as ex: output = { 'result': 0, 'rc': '4', 'message': "show pool provision capacity error: %s" % ex } return output def _show_qos_bandwidth_limit(self, **option): """Get qos bandwidth limit.""" clidata = None try: output = self._exec_cli("show qos-bandwidth-limit", **option) # return error rc = output['rc'] if rc != "0": return output qoslist = [] clidatalist = output.get('message') for clidataline in clidatalist[1:]: clidata = clidataline.split('\t') qoslist.append({'total_limit': int(clidata[0], 16), 'total_iops_sec': int(clidata[1], 16), 'total_bytes_sec': int(clidata[2], 16), 'read_limit': int(clidata[0], 16), 'read_iops_sec': int(clidata[3], 16), 'read_bytes_sec': int(clidata[4], 16), 'write_limit': int(clidata[0], 16), 'write_iops_sec': int(clidata[5], 16), 'write_bytes_sec': int(clidata[6], 16)}) output['message'] = qoslist except IndexError as ex: msg = ('The results returned by cli are not as expected. ' 'Exception string: %s' % clidata) output = {'result': 0, 'rc': '4', 'message': "Show qos bandwidth limit error: %s. %s" % (ex, msg)} except Exception as ex: output = {'result': 0, 'rc': '4', 'message': "Show qos bandwidth limit error: %s" % ex} return output def _set_qos_bandwidth_limit(self, **option): """Set qos bandwidth limit""" return self._exec_cli("set qos-bandwidth-limit", **option) def _show_volume_qos(self, **option): """Get volumes with qos.""" clidata = None try: output = self._exec_cli("show volume-qos", **option) # return error rc = output['rc'] if rc != "0": return output vqosdatalist = [] clidatalist = output.get('message') for clidataline in clidatalist[1:]: clidata = clidataline.split('\t') vqosdatalist.append({'total_limit': int(clidata[2], 16), 'read_limit': int(clidata[3], 16), 'write_limit': int(clidata[4], 16)}) output['message'] = vqosdatalist except IndexError as ex: msg = ('The results returned by cli are not as expected. ' 'Exception string: %s' % clidata) output = {'result': 0, 'rc': '4', 'message': "Show volume qos error: %s. %s" % (ex, msg)} except Exception as ex: output = {'result': 0, 'rc': '4', 'message': "Show volume qos error: %s" % ex} return output def _show_enclosure_status(self, **option): """Get the version of machine.""" clidata = None try: output = self._exec_cli("show enclosure-status", **option) # return error rc = output['rc'] if rc != "0": return output clidatalist = output.get('message') clidata = clidatalist[0].split('\t') versioninfo = {'version': clidata[11]} output['message'] = versioninfo except IndexError as ex: msg = ('The results returned by cli are not as expected. ' 'Exception string: %s' % clidata) output = {'result': 0, 'rc': '4', 'message': "Show enclosure status error: %s. %s" % (ex, msg)} except Exception as ex: output = {'result': 0, 'rc': '4', 'message': "Show enclosure status error: %s" % ex} return output def _delete_volume(self, **option): """Exec delete volume.""" return self._exec_cli('delete volume', **option)