cinder/cinder/volume/drivers/hitachi/hnas_backend.py

670 lines
26 KiB
Python

# Copyright (c) 2014 Hitachi Data Systems, Inc.
# 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.
#
"""
Hitachi Unified Storage (HUS-HNAS) platform. Backend operations.
"""
import re
from oslo_concurrency import processutils
from oslo_log import log as logging
from oslo_utils import units
import six
from cinder.i18n import _LE, _LW, _LI
from cinder import ssh_utils
from cinder import utils
LOG = logging.getLogger("cinder.volume.driver")
class HnasBackend(object):
"""Back end. Talks to HUS-HNAS."""
def __init__(self, drv_configs):
self.drv_configs = drv_configs
self.sshpool = None
def run_cmd(self, cmd, ip0, user, pw, *args, **kwargs):
"""Run a command on SMU or using SSH
:param cmd: the command that will be run on SMU
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:returns: formated string with version information
"""
LOG.debug('Enable ssh: %s',
six.text_type(self.drv_configs['ssh_enabled']))
if self.drv_configs['ssh_enabled'] != 'True':
# Direct connection via ssc
args = (cmd, '-u', user, '-p', pw, ip0) + args
out, err = utils.execute(*args, **kwargs)
LOG.debug("command %(cmd)s result: out = %(out)s - err = "
"%(err)s", {'cmd': cmd, 'out': out, 'err': err})
return out, err
else:
if self.drv_configs['cluster_admin_ip0'] is None:
# Connect to SMU through SSH and run ssc locally
args = (cmd, 'localhost') + args
else:
args = (cmd, '--smuauth',
self.drv_configs['cluster_admin_ip0']) + args
utils.check_ssh_injection(args)
command = ' '.join(args)
command = command.replace('"', '\\"')
if not self.sshpool:
server = self.drv_configs['mgmt_ip0']
port = int(self.drv_configs['ssh_port'])
username = self.drv_configs['username']
# We only accept private/public key auth
password = ""
privatekey = self.drv_configs['ssh_private_key']
self.sshpool = ssh_utils.SSHPool(server,
port,
None,
username,
password=password,
privatekey=privatekey)
with self.sshpool.item() as ssh:
try:
out, err = processutils.ssh_execute(ssh, command,
check_exit_code=True)
LOG.debug("command %(cmd)s result: out = %(out)s - err = "
"%(err)s", {'cmd': cmd, 'out': out, 'err': err})
return out, err
except processutils.ProcessExecutionError:
LOG.error(_LE("Error running SSH command."))
raise
def get_version(self, cmd, ver, ip0, user, pw):
"""Gets version information from the storage unit
:param ver: string driver version
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:returns: formated string with version information
"""
if (self.drv_configs['ssh_enabled'] == 'True' and
self.drv_configs['cluster_admin_ip0'] is not None):
util = 'SMU ' + cmd
else:
out, err = utils.execute(cmd,
"-version",
check_exit_code=True)
util = out.split()[1]
out, err = self.run_cmd(cmd, ip0, user, pw, "cluster-getmac",
check_exit_code=True)
hardware = out.split()[2]
out, err = self.run_cmd(cmd, ip0, user, pw, "ver",
check_exit_code=True)
lines = out.split('\n')
model = ""
for line in lines:
if 'Model:' in line:
model = line.split()[1]
if 'Software:' in line:
ver = line.split()[1]
out = "Array_ID: %s (%s) version: %s LU: 256 RG: 0 RG_LU: 0 \
Utility_version: %s" % (hardware, model, ver, util)
LOG.debug('get_version: %(out)s -- %(err)s', {'out': out, 'err': err})
return out
def get_iscsi_info(self, cmd, ip0, user, pw):
"""Gets IP addresses for EVSs, use EVSID as controller.
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:returns: formated string with iSCSI information
"""
out, err = self.run_cmd(cmd, ip0, user, pw,
'evsipaddr', '-l',
check_exit_code=True)
lines = out.split('\n')
newout = ""
for line in lines:
if 'evs' in line and 'admin' not in line:
inf = line.split()
(evsnum, ip) = (inf[1], inf[3])
newout += "CTL: %s Port: 0 IP: %s Port: 3260 Link: Up\n" \
% (evsnum, ip)
LOG.debug('get_iscsi_info: %(out)s -- %(err)s',
{'out': out, 'err': err})
return newout
def get_hdp_info(self, cmd, ip0, user, pw, fslabel=None):
"""Gets the list of filesystems and fsids.
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param fslabel: filesystem label we want to get info
:returns: formated string with filesystems and fsids
"""
if fslabel is None:
out, err = self.run_cmd(cmd, ip0, user, pw, 'df', '-a',
check_exit_code=True)
else:
out, err = self.run_cmd(cmd, ip0, user, pw, 'df', '-f', fslabel,
check_exit_code=True)
lines = out.split('\n')
single_evs = True
LOG.debug("Parsing output: %s", lines)
newout = ""
for line in lines:
if 'Not mounted' in line or 'Not determined' in line:
continue
if 'not' not in line and 'EVS' in line:
single_evs = False
if 'GB' in line or 'TB' in line:
LOG.debug("Parsing output: %s", line)
inf = line.split()
if not single_evs:
(fsid, fslabel, capacity) = (inf[0], inf[1], inf[3])
(used, perstr) = (inf[5], inf[7])
(availunit, usedunit) = (inf[4], inf[6])
else:
(fsid, fslabel, capacity) = (inf[0], inf[1], inf[2])
(used, perstr) = (inf[4], inf[6])
(availunit, usedunit) = (inf[3], inf[5])
if usedunit == 'GB':
usedmultiplier = units.Ki
else:
usedmultiplier = units.Mi
if availunit == 'GB':
availmultiplier = units.Ki
else:
availmultiplier = units.Mi
m = re.match("\((\d+)\%\)", perstr)
if m:
percent = m.group(1)
else:
percent = 0
newout += "HDP: %s %d MB %d MB %d %% LUs: 256 Normal %s\n" \
% (fsid, int(float(capacity) * availmultiplier),
int(float(used) * usedmultiplier),
int(percent), fslabel)
LOG.debug('get_hdp_info: %(out)s -- %(err)s',
{'out': newout, 'err': err})
return newout
def _get_evs(self, cmd, ip0, user, pw, fsid):
"""Gets the EVSID for the named filesystem."""
out, err = self.run_cmd(cmd, ip0, user, pw, "evsfs", "list",
check_exit_code=True)
LOG.debug('get_evs: out %s', out)
lines = out.split('\n')
for line in lines:
inf = line.split()
if fsid in line and (fsid == inf[0] or fsid == inf[1]):
return inf[3]
LOG.warning(_LW('get_evs: %(out)s -- No find for %(fsid)s'),
{'out': out, 'fsid': fsid})
return 0
def _get_evsips(self, cmd, ip0, user, pw, evsid):
"""Gets the EVS IPs for the named filesystem."""
out, err = self.run_cmd(cmd, ip0, user, pw,
'evsipaddr', '-e', evsid,
check_exit_code=True)
iplist = ""
lines = out.split('\n')
for line in lines:
inf = line.split()
if 'evs' in line:
iplist += inf[3] + ' '
LOG.debug('get_evsips: %s', iplist)
return iplist
def _get_fsid(self, cmd, ip0, user, pw, fslabel):
"""Gets the FSID for the named filesystem."""
out, err = self.run_cmd(cmd, ip0, user, pw, 'evsfs', 'list',
check_exit_code=True)
LOG.debug('get_fsid: out %s', out)
lines = out.split('\n')
for line in lines:
inf = line.split()
if fslabel in line and fslabel == inf[1]:
LOG.debug('get_fsid: %s', line)
return inf[0]
LOG.warning(_LW('get_fsid: %(out)s -- No info for %(fslabel)s'),
{'out': out, 'fslabel': fslabel})
return 0
def get_nfs_info(self, cmd, ip0, user, pw):
"""Gets information on each NFS export.
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:returns: formated string
"""
out, err = self.run_cmd(cmd, ip0, user, pw,
'for-each-evs', '-q',
'nfs-export', 'list',
check_exit_code=True)
lines = out.split('\n')
newout = ""
export = ""
path = ""
for line in lines:
inf = line.split()
if 'Export name' in line:
export = inf[2]
if 'Export path' in line:
path = inf[2]
if 'File system info' in line:
fs = ""
if 'File system label' in line:
fs = inf[3]
if 'Transfer setting' in line and fs != "":
fsid = self._get_fsid(cmd, ip0, user, pw, fs)
evsid = self._get_evs(cmd, ip0, user, pw, fsid)
ips = self._get_evsips(cmd, ip0, user, pw, evsid)
newout += "Export: %s Path: %s HDP: %s FSID: %s \
EVS: %s IPS: %s\n" \
% (export, path, fs, fsid, evsid, ips)
fs = ""
LOG.debug('get_nfs_info: %(out)s -- %(err)s',
{'out': newout, 'err': err})
return newout
def create_lu(self, cmd, ip0, user, pw, hdp, size, name):
"""Creates a new Logical Unit.
If the operation can not be performed for some reason, utils.execute()
throws an error and aborts the operation. Used for iSCSI only
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param hdp: data Pool the logical unit will be created
:param size: Size (Mb) of the new logical unit
:param name: name of the logical unit
:returns: formated string with 'LUN %d HDP: %d size: %s MB, is
successfully created'
"""
_evsid = self._get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-lu', 'add', "-e",
name, hdp,
'/.cinder/' + name + '.iscsi',
size + 'M',
check_exit_code=True)
out = "LUN %s HDP: %s size: %s MB, is successfully created" \
% (name, hdp, size)
LOG.debug('create_lu: %s', out)
return out
def delete_lu(self, cmd, ip0, user, pw, hdp, lun):
"""Delete an logical unit. Used for iSCSI only
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param hdp: data Pool of the logical unit
:param lun: id of the logical unit being deleted
:returns: formated string 'Logical unit deleted successfully.'
"""
_evsid = self._get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-lu', 'del', '-d',
'-f', lun,
check_exit_code=True)
LOG.debug('delete_lu: %(out)s -- %(err)s', {'out': out, 'err': err})
return out
def create_dup(self, cmd, ip0, user, pw, src_lun, hdp, size, name):
"""Clones a volume
Clone primitive used to support all iSCSI snapshot/cloning functions.
Used for iSCSI only.
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param hdp: data Pool of the logical unit
:param src_lun: id of the logical unit being deleted
:param size: size of the LU being cloned. Only for logging purposes
:returns: formated string
"""
_evsid = self._get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-lu', 'clone', '-e',
src_lun, name,
'/.cinder/' + name + '.iscsi',
check_exit_code=True)
out = "LUN %s HDP: %s size: %s MB, is successfully created" \
% (name, hdp, size)
LOG.debug('create_dup: %(out)s -- %(err)s', {'out': out, 'err': err})
return out
def file_clone(self, cmd, ip0, user, pw, fslabel, src, name):
"""Clones NFS files to a new one named 'name'
Clone primitive used to support all NFS snapshot/cloning functions.
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param fslabel: file system label of the new file
:param src: source file
:param name: target path of the new created file
:returns: formated string
"""
_fsid = self._get_fsid(cmd, ip0, user, pw, fslabel)
_evsid = self._get_evs(cmd, ip0, user, pw, _fsid)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'file-clone-create', '-f', fslabel,
src, name,
check_exit_code=True)
out = "LUN %s HDP: %s Clone: %s -> %s" % (name, _fsid, src, name)
LOG.debug('file_clone: %(out)s -- %(err)s', {'out': out, 'err': err})
return out
def extend_vol(self, cmd, ip0, user, pw, hdp, lun, new_size, name):
"""Extend a iSCSI volume.
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param hdp: data Pool of the logical unit
:param lun: id of the logical unit being extended
:param new_size: new size of the LU
:param name: formated string
"""
_evsid = self._get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-lu', 'expand',
name, new_size + 'M',
check_exit_code=True)
out = ("LUN: %s successfully extended to %s MB" % (name, new_size))
LOG.debug('extend_vol: %s', out)
return out
def add_iscsi_conn(self, cmd, ip0, user, pw, lun, hdp,
port, iqn, initiator):
"""Setup the lun on on the specified target port
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param lun: id of the logical unit being extended
:param hdp: data pool of the logical unit
:param port: iSCSI port
:param iqn: iSCSI qualified name
:param initiator: initiator address
"""
_evsid = self._get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'list', iqn,
check_exit_code=True)
# even though ssc uses the target alias, need to return the full iqn
fulliqn = ""
lines = out.split('\n')
for line in lines:
if 'Globally unique name' in line:
fulliqn = line.split()[3]
# find first free hlun
hlun = 0
for line in lines:
if line.startswith(' '):
lunline = line.split()[0]
vol = line.split()[1]
if lunline[0].isdigit():
# see if already mounted
if vol[:29] == lun[:29]:
LOG.info(_LI('lun: %(lun)s already mounted %(lline)s'),
{'lun': lun, 'lline': lunline})
conn = (int(lunline), lun, initiator, hlun, fulliqn,
hlun, hdp, port)
out = "H-LUN: %d alreadymapped LUN: %s, iSCSI \
Initiator: %s @ index: %d, and Target: %s \
@ index %d is successfully paired @ CTL: \
%s, Port: %s" % conn
LOG.debug('add_iscsi_conn: returns %s', out)
return out
if int(lunline) == hlun:
hlun += 1
if int(lunline) > hlun:
# found a hole
break
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'addlu',
iqn, lun, six.text_type(hlun),
check_exit_code=True)
conn = (int(hlun), lun, initiator, int(hlun), fulliqn, int(hlun),
hdp, port)
out = "H-LUN: %d mapped LUN: %s, iSCSI Initiator: %s \
@ index: %d, and Target: %s @ index %d is \
successfully paired @ CTL: %s, Port: %s" % conn
LOG.debug('add_iscsi_conn: returns %s', out)
return out
def del_iscsi_conn(self, cmd, ip0, user, pw, evsid, iqn, hlun):
"""Remove the lun on on the specified target port
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param evsid: EVSID for the file system
:param iqn: iSCSI qualified name
:param hlun: logical unit id
:return: formated string
"""
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", evsid,
'iscsi-target', 'list', iqn,
check_exit_code=True)
lines = out.split('\n')
out = ("H-LUN: %d already deleted from target %s" % (int(hlun), iqn))
# see if lun is already detached
for line in lines:
if line.startswith(' '):
lunline = line.split()[0]
if lunline[0].isdigit() and lunline == hlun:
out = ""
break
if out != "":
# hlun wasn't found
LOG.info(_LI('del_iscsi_conn: hlun not found %s'), out)
return out
# remove the LU from the target
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", evsid,
'iscsi-target', 'dellu',
'-f', iqn, hlun,
check_exit_code=True)
out = "H-LUN: %d successfully deleted from target %s" \
% (int(hlun), iqn)
LOG.debug('del_iscsi_conn: %s', out)
return out
def get_targetiqn(self, cmd, ip0, user, pw, targetalias, hdp, secret):
"""Obtain the targets full iqn
Return the target's full iqn rather than its alias.
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param targetalias: alias of the target
:param hdp: data pool of the logical unit
:param secret: CHAP secret of the target
:return: string with full IQN
"""
_evsid = self._get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'list', targetalias,
check_exit_code=True)
if "does not exist" in out:
if secret == "":
secret = '""'
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'add',
targetalias, secret,
check_exit_code=True)
else:
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'add',
targetalias, secret,
check_exit_code=True)
if "success" in out:
return targetalias
lines = out.split('\n')
# returns the first iqn
for line in lines:
if 'Alias' in line:
fulliqn = line.split()[2]
return fulliqn
def set_targetsecret(self, cmd, ip0, user, pw, targetalias, hdp, secret):
"""Sets the chap secret for the specified target.
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param targetalias: alias of the target
:param hdp: data pool of the logical unit
:param secret: CHAP secret of the target
"""
_evsid = self._get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'list',
targetalias,
check_exit_code=False)
if "does not exist" in out:
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'add',
targetalias, secret,
check_exit_code=True)
else:
LOG.info(_LI('targetlist: %s'), targetalias)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'mod',
'-s', secret, '-a', 'enable',
targetalias,
check_exit_code=True)
def get_targetsecret(self, cmd, ip0, user, pw, targetalias, hdp):
"""Returns the chap secret for the specified target.
:param ip0: string IP address of controller
:param user: string user authentication for array
:param pw: string password authentication for array
:param targetalias: alias of the target
:param hdp: data pool of the logical unit
:return secret: CHAP secret of the target
"""
_evsid = self._get_evs(cmd, ip0, user, pw, hdp)
out, err = self.run_cmd(cmd, ip0, user, pw, "console-context",
"--evs", _evsid,
'iscsi-target', 'list', targetalias,
check_exit_code=True)
enabled = ""
secret = ""
lines = out.split('\n')
for line in lines:
if 'Secret' in line:
if len(line.split()) > 2:
secret = line.split()[2]
if 'Authentication' in line:
enabled = line.split()[2]
if enabled == 'Enabled':
return secret
else:
return ""