cinder/cinder/volume/drivers/emc/emc_vnx_cli.py
Jay S. Bryant eafcc41e82 Explicitly import _() in Cinder code
To ensure that logs are properly translated and logged to
the Cinder log files (using the Cinder message catalogs)
we need to explicitly import _() in any python files that
use the _() function.

Closes-bug: 1306275
Related-Blueprint: i18n-enablement
Change-Id: I3cf1f1d63d5f8bd8ffd007104691f914e3fd8538
2014-07-16 23:41:05 -05:00

739 lines
28 KiB
Python

# Copyright (c) 2012 - 2014 EMC Corporation, 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.
"""
VNX CLI on iSCSI.
"""
import os
import time
from oslo.config import cfg
from cinder import exception
from cinder.openstack.common.gettextutils import _
from cinder.openstack.common import log as logging
from cinder.openstack.common import loopingcall
from cinder.openstack.common import processutils
from cinder import utils
from cinder.volume.drivers.san import san
from cinder.volume import volume_types
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
VERSION = '02.00.00'
loc_opts = [
cfg.StrOpt('naviseccli_path',
default='',
help='Naviseccli Path'),
cfg.StrOpt('storage_vnx_pool_name',
default=None,
help='ISCSI pool name'),
cfg.IntOpt('default_timeout',
default=20,
help='Default Time Out For CLI operations in minutes'),
cfg.IntOpt('max_luns_per_storage_group',
default=256,
help='Default max number of LUNs in a storage group'), ]
CONF.register_opts(loc_opts)
class EMCVnxCli(object):
"""This class defines the functions to use the native CLI functionality."""
stats = {'driver_version': VERSION,
'free_capacity_gb': 'unknown',
'reserved_percentage': 0,
'storage_protocol': None,
'total_capacity_gb': 'unknown',
'vendor_name': 'EMC',
'volume_backend_name': None}
def __init__(self, prtcl, configuration=None):
self.protocol = prtcl
self.configuration = configuration
self.configuration.append_config_values(loc_opts)
self.configuration.append_config_values(san.san_opts)
self.storage_ip = self.configuration.san_ip
self.storage_username = self.configuration.san_login
self.storage_password = self.configuration.san_password
self.pool_name = self.configuration.storage_vnx_pool_name
if not self.pool_name:
msg = (_('Pool name is not specified.'))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
self.timeout = self.configuration.default_timeout
self.max_luns = self.configuration.max_luns_per_storage_group
self.hlu_set = set(xrange(1, self.max_luns + 1))
self.navisecclipath = self.configuration.naviseccli_path
self.cli_prefix = (self.navisecclipath, '-address', self.storage_ip)
self.cli_credentials = ()
self.wait_interval = 3
# if there is a username/password provided, use those in the cmd line
if self.storage_username is not None and \
self.storage_password is not None:
self.cli_credentials += ('-user', self.storage_username,
'-password', self.storage_password,
'-scope', '0')
# Checking for existence of naviseccli tool
if not os.path.exists(self.navisecclipath):
msg = (_('Could not find NAVISECCLI tool.'))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Testing the naviseccli setup
query_list = ("storagepool", "-list",
"-name", self.pool_name, "-state")
out, rc = self._cli_execute(*query_list)
if rc != 0:
LOG.error(_("Failed to find pool %s"), self.pool_name)
raise exception.VolumeBackendAPIException(data=out)
def _cli_execute(self, *cmd, **kwargv):
if "check_exit_code" not in kwargv:
kwargv["check_exit_code"] = True
rc = 0
try:
out, _err = utils.execute(*(self.cli_prefix +
self.cli_credentials + cmd), **kwargv)
except processutils.ProcessExecutionError as pe:
rc = pe.exit_code
out = pe.stdout + pe.stderr
return out, rc
def create_volume(self, volume):
"""Creates a EMC volume."""
LOG.debug('Entering create_volume.')
volumesize = volume['size']
volumename = volume['name']
LOG.info(_('Create Volume: %(volume)s Size: %(size)s')
% {'volume': volumename,
'size': volumesize})
# defining CLI command
thinness = self._get_provisioning_by_volume(volume)
# executing CLI command to create volume
LOG.debug('Create Volume: %(volumename)s'
% {'volumename': volumename})
lun_create = ('lun', '-create',
'-type', thinness,
'-capacity', volumesize,
'-sq', 'gb',
'-poolName', self.pool_name,
'-name', volumename)
out, rc = self._cli_execute(*lun_create)
LOG.debug('Create Volume: %(volumename)s Return code: %(rc)s'
% {'volumename': volumename,
'rc': rc})
if rc == 4:
LOG.warn(_('Volume %s already exists'), volumename)
elif rc != 0:
msg = (_('Failed to create %(volumename)s: %(out)s') %
{'volumename': volumename, 'out': out})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# wait for up to a minute to verify that the LUN has progressed
# to Ready state
def _wait_for_lun_ready(volumename, start_time):
# executing cli command to check volume
command_to_verify = ('lun', '-list', '-name', volumename)
out, rc = self._cli_execute(*command_to_verify)
if rc == 0 and out.find("Ready") > -1:
raise loopingcall.LoopingCallDone()
if int(time.time()) - start_time > self.timeout * 60:
msg = (_('LUN %s failed to become Ready'), volumename)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
timer = loopingcall.FixedIntervalLoopingCall(
_wait_for_lun_ready, volumename, int(time.time()))
timer.start(interval=self.wait_interval).wait()
def delete_volume(self, volume):
"""Deletes an EMC volume."""
LOG.debug('Entering delete_volume.')
volumename = volume['name']
# defining CLI command
lun_destroy = ('lun', '-destroy',
'-name', volumename,
'-forceDetach', '-o')
# executing CLI command to delete volume
out, rc = self._cli_execute(*lun_destroy)
LOG.debug('Delete Volume: %(volumename)s Output: %(out)s'
% {'volumename': volumename, 'out': out})
if rc not in (0, 9):
msg = (_('Failed to destroy %s'), volumename)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def extend_volume(self, volume, new_size):
"""Extends an EMC volume."""
LOG.debug('Entering extend_volume.')
volumename = volume['name']
# defining CLI command
lun_expand = ('lun', '-expand',
'-name', volumename,
'-capacity', new_size,
'-sq', 'gb',
'-o', '-ignoreThresholds')
# executing CLI command to extend volume
out, rc = self._cli_execute(*lun_expand)
LOG.debug('Extend Volume: %(volumename)s Output: %(out)s'
% {'volumename': volumename,
'out': out})
if rc == 97:
msg = (_('The LUN cannot be expanded or shrunk because '
'it has snapshots. Command to extend the specified '
'volume failed.'))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if rc != 0:
msg = (_('Failed to expand %s'), volumename)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def update_volume_status(self):
"""Retrieve status info."""
LOG.debug("Updating volume status")
poolname = self.pool_name
pool_list = ('storagepool', '-list',
'-name', poolname,
'-userCap', '-availableCap')
out, rc = self._cli_execute(*pool_list)
if rc == 0:
pool_details = out.split('\n')
self.stats['total_capacity_gb'] = float(
pool_details[3].split(':')[1].strip())
self.stats['free_capacity_gb'] = float(
pool_details[5].split(':')[1].strip())
else:
msg = (_('Failed to list %s'), poolname)
LOG.error(msg)
return self.stats
def create_export(self, context, volume):
"""Driver entry point to get the export info for a new volume."""
volumename = volume['name']
device_id = self._find_lun_id(volumename)
LOG.debug('create_export: Volume: %(volume)s Device ID: '
'%(device_id)s'
% {'volume': volumename,
'device_id': device_id})
return {'provider_location': device_id}
def _find_lun_id(self, volumename):
"""Returns the LUN of a volume."""
lun_list = ('lun', '-list', '-name', volumename)
out, rc = self._cli_execute(*lun_list)
if rc == 0:
vol_details = out.split('\n')
lun = vol_details[0].split(' ')[3]
else:
msg = (_('Failed to list %s'), volumename)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return lun
def create_snapshot(self, snapshot):
"""Creates a snapshot."""
LOG.debug('Entering create_snapshot.')
snapshotname = snapshot['name']
volumename = snapshot['volume_name']
LOG.info(_('Create snapshot: %(snapshot)s: volume: %(volume)s')
% {'snapshot': snapshotname,
'volume': volumename})
volume_lun = self._find_lun_id(volumename)
# defining CLI command
snap_create = ('snap', '-create',
'-res', volume_lun,
'-name', snapshotname,
'-allowReadWrite', 'yes')
# executing CLI command to create snapshot
out, rc = self._cli_execute(*snap_create)
LOG.debug('Create Snapshot: %(snapshotname)s Unity: %(out)s'
% {'snapshotname': snapshotname,
'out': out})
if rc != 0:
msg = (_('Failed to create snap %s'), snapshotname)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def delete_snapshot(self, snapshot):
"""Deletes a snapshot."""
LOG.debug('Entering delete_snapshot.')
snapshotname = snapshot['name']
volumename = snapshot['volume_name']
LOG.info(_('Delete Snapshot: %(snapshot)s: volume: %(volume)s')
% {'snapshot': snapshotname,
'volume': volumename})
def _wait_for_snap_delete(snapshot, start_time):
# defining CLI command
snapshotname = snapshot['name']
volumename = snapshot['volume_name']
snap_destroy = ('snap', '-destroy', '-id', snapshotname, '-o')
# executing CLI command
out, rc = self._cli_execute(*snap_destroy)
LOG.debug('Delete Snapshot: Volume: %(volumename)s Snapshot: '
'%(snapshotname)s Output: %(out)s'
% {'volumename': volumename,
'snapshotname': snapshotname,
'out': out})
if rc not in [0, 9, 5]:
if rc == 13:
if int(time.time()) - start_time < \
self.timeout * 60:
LOG.info(_('Snapshot %s is in use'), snapshotname)
else:
msg = (_('Failed to destroy %s '
' because snapshot is in use.'), snapshotname)
LOG.error(msg)
raise exception.SnapshotIsBusy(data=msg)
else:
msg = (_('Failed to destroy %s'), snapshotname)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
else:
raise loopingcall.LoopingCallDone()
timer = loopingcall.FixedIntervalLoopingCall(
_wait_for_snap_delete, snapshot, int(time.time()))
timer.start(interval=self.wait_interval).wait()
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot."""
LOG.debug('Entering create_volume_from_snapshot.')
snapshotname = snapshot['name']
source_volume_name = snapshot['volume_name']
volumename = volume['name']
volumesize = snapshot['volume_size']
destvolumename = volumename + 'dest'
# Create a mount point, migrate data from source (snapshot) to
# destination volume. The destination volume is the only new volume
# to be created here.
LOG.info(_('Creating Destination Volume : %s ') % (destvolumename))
poolname = self.pool_name
thinness = self._get_provisioning_by_volume(volume)
# defining CLI command
lun_create = ('lun', '-create', '-type', thinness,
'-capacity', volumesize, '-sq', 'gb',
'-poolName', poolname,
'-name', destvolumename)
# executing CLI command
out, rc = self._cli_execute(*lun_create)
LOG.debug('Create temporary Volume: %(volumename)s '
'Output : %(out)s'
% {'volumename': destvolumename, 'out': out})
if rc != 0:
msg = (_('Command to create the destination volume failed'))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# defining CLI command
smp_create = ('lun', '-create', '-type', 'Snap',
'-primaryLunName', source_volume_name,
'-name', volumename)
# executing CLI command
out, rc = self._cli_execute(*smp_create)
LOG.debug('Create mount point : Volume: %(volumename)s '
'Source Volume: %(sourcevolumename)s Output: %(out)s'
% {'volumename': volumename,
'sourcevolumename': source_volume_name,
'out': out})
if rc != 0:
msg = (_('Failed to create SMP %s'), volumename)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# defining CLI command
lun_attach = ('lun', '-attach',
'-name', volumename,
'-snapName', snapshotname)
# executing CLI command
out, rc = self._cli_execute(*lun_attach)
LOG.debug('Attaching mount point Volume: %(volumename)s '
'with Snapshot: %(snapshotname)s Output: %(out)s'
% {'volumename': volumename,
'snapshotname': snapshotname,
'out': out})
if rc != 0:
msg = (_('Failed to attach snapshotname %s'), snapshotname)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
source_vol_lun = self._find_lun_id(volumename)
dest_vol_lun = self._find_lun_id(destvolumename)
LOG.info(_('Migrating Mount Point Volume: %s ') % (volumename))
# defining CLI command
migrate_start = ('migrate', '-start',
'-source', source_vol_lun,
'-dest', dest_vol_lun,
'-rate', 'ASAP', '-o')
# executing CLI command
out, rc = self._cli_execute(*migrate_start)
LOG.debug('Migrate Mount Point Volume: %(volumename)s '
'Output : %(out)s'
% {'volumename': volumename,
'out': out})
if rc != 0:
msg = (_('Failed to start migrating SMP %s'), volumename)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def _wait_for_sync_status(volumename, start_time):
lun_list = ('lun', '-list', '-name', volumename,
'-attachedSnapshot')
out, rc = self._cli_execute(*lun_list)
if rc == 0:
vol_details = out.split('\n')
snapshotname = vol_details[2].split(':')[1].strip()
if (snapshotname == 'N/A'):
raise loopingcall.LoopingCallDone()
else:
LOG.info(_('Waiting for the update on Sync status of %s'),
volumename)
if int(time.time()) - start_time >= self.timeout * 60:
msg = (_('Failed to really migrate %s'), volumename)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
timer = loopingcall.FixedIntervalLoopingCall(
_wait_for_sync_status, volumename, int(time.time()))
timer.start(interval=self.wait_interval).wait()
def create_cloned_volume(self, volume, src_vref):
"""Creates a clone of the specified volume."""
source_volume_name = src_vref['name']
volumesize = src_vref['size']
snapshotname = source_volume_name + '-temp-snapshot'
snapshot = {
'name': snapshotname,
'volume_name': source_volume_name,
'volume_size': volumesize,
}
# Create temp Snapshot
self.create_snapshot(snapshot)
try:
# Create volume
self.create_volume_from_snapshot(volume, snapshot)
except Exception:
msg = (_('Failed to create cloned volume %s'), volume['name'])
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
finally:
# Delete temp Snapshot
self.delete_snapshot(snapshot)
def get_storage_group(self, hostname):
"""Returns the storage group for the host node."""
storage_groupname = hostname
sg_list = ('storagegroup', '-list', '-gname', storage_groupname)
out, rc = self._cli_execute(*sg_list)
if rc != 0:
LOG.debug('creating new storage group %s', storage_groupname)
sg_create = ('storagegroup', '-create',
'-gname', storage_groupname)
out, rc = self._cli_execute(*sg_create)
LOG.debug('Create new storage group : %(storage_groupname)s, '
'Output: %(out)s'
% {'storage_groupname': storage_groupname,
'out': out})
if rc != 0:
msg = (_('Failed to create SG %s'), storage_groupname)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# connecting the new storagegroup to the host
connect_host = ('storagegroup', '-connecthost',
'-host', hostname,
'-gname', storage_groupname,
'-o')
out, rc = self._cli_execute(*connect_host)
LOG.debug('Connect storage group : %(storage_groupname)s ,'
'To Host : %(hostname)s, Output : %(out)s'
% {'storage_groupname': storage_groupname,
'hostname': hostname,
'out': out})
if rc != 0:
msg = (_('Failed to connect %s'), hostname)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
return hostname
def find_device_details(self, volume, storage_group):
"""Returns the Host Device number for the volume."""
allocated_lun_id = self._find_lun_id(volume["name"])
host_lun_id = -1
owner_sp = ""
lun_map = {}
sg_list = ('storagegroup', '-list', '-gname', storage_group)
out, rc = self._cli_execute(*sg_list)
if out.find('HLU/ALU Pairs') == -1:
LOG.info(_('NO LUNs in the storagegroup : %s ')
% (storage_group))
else:
sg_details = out.split('HLU/ALU Pairs:')[1]
sg_lun_details = sg_details.split('Shareable')[0]
lun_details = sg_lun_details.split('\n')
for data in lun_details:
if data not in ['', ' HLU Number ALU Number',
' ---------- ----------']:
data = data.strip()
items = data.split(' ')
lun_map[int(items[len(items) - 1])] = int(items[0])
for lun in lun_map.iterkeys():
if lun == int(allocated_lun_id):
host_lun_id = lun_map[lun]
LOG.debug('Host Lun Id : %s' % (host_lun_id))
break
# finding the owner SP for the LUN
lun_list = ('lun', '-list', '-l', allocated_lun_id, '-owner')
out, rc = self._cli_execute(*lun_list)
if rc == 0:
output = out.split('\n')
owner_sp = output[2].split('Current Owner: SP ')[1]
LOG.debug('Owner SP : %s' % (owner_sp))
device = {
'hostlunid': host_lun_id,
'ownersp': owner_sp,
'lunmap': lun_map,
}
return device
def _get_host_lun_id(self, host_lun_id_list):
# Returns the host lun id for the LUN to be added
# in the storage group.
used_hlu_set = set(host_lun_id_list)
for hlu in self.hlu_set - used_hlu_set:
return hlu
return None
def _add_lun_to_storagegroup(self, volume, storage_group):
storage_groupname = storage_group
volumename = volume['name']
allocated_lun_id = self._find_lun_id(volumename)
count = 0
while(count < 5):
device_info = self.find_device_details(volume, storage_group)
device_number = device_info['hostlunid']
if device_number < 0:
lun_map = device_info['lunmap']
if lun_map:
host_lun_id_list = lun_map.values()
if len(host_lun_id_list) >= self.max_luns:
msg = (_('The storage group has reached the '
'maximum capacity of LUNs. '
'Command to add LUN for volume - %s '
'in storagegroup failed') % (volumename))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
host_lun_id = self._get_host_lun_id(host_lun_id_list)
if host_lun_id is None:
msg = (_('Unable to get new host lun id. Please '
'check if the storage group can accommodate '
'new LUN. '
'Command to add LUN for volume - %s '
'in storagegroup failed') % (volumename))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
else:
host_lun_id = 1
addhlu = ('storagegroup', '-addhlu', '-o',
'-gname', storage_groupname,
'-hlu', host_lun_id,
'-alu', allocated_lun_id)
out, rc = self._cli_execute(*addhlu)
LOG.debug('Add ALU %(alu)s to SG %(sg)s as %(hlu)s. '
'Output: %(out)s'
% {'alu': allocated_lun_id,
'sg': storage_groupname,
'hlu': host_lun_id,
'out': out})
if rc == 0:
return host_lun_id
if rc == 66:
LOG.warn(_('Requested Host LUN Number already in use'))
count += 1
else:
LOG.warn(_('LUN was already added in the storage group'))
return device_number
if count == 5:
msg = (_('Failed to add %s into SG') % (volumename))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def _remove_lun_from_storagegroup(self, device_number, storage_group):
storage_groupname = storage_group
removehlu = ('storagegroup', '-removehlu',
'-gname', storage_groupname,
'-hlu', device_number,
'-o')
out, rc = self._cli_execute(*removehlu)
LOG.debug('Remove %(hlu)s from SG %(sg)s. Output: %(out)s'
% {'hlu': device_number,
'sg': storage_groupname,
'out': out})
if rc != 0:
msg = (_('Failed to remove %(hlu)s from %(sg)s')
% {'hlu': device_number, 'sg': storage_groupname})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def initialize_connection(self, volume, connector):
"""Initializes the connection and returns connection info."""
hostname = connector['host']
storage_group = self.get_storage_group(hostname)
device_number = self._add_lun_to_storagegroup(volume, storage_group)
return device_number
def terminate_connection(self, volume, connector):
"""Disallow connection from connector."""
hostname = connector['host']
storage_group = self.get_storage_group(hostname)
device_info = self.find_device_details(volume, storage_group)
device_number = device_info['hostlunid']
if device_number < 0:
LOG.error(_('Could not locate the attached volume.'))
else:
self._remove_lun_from_storagegroup(device_number, storage_group)
def _find_iscsi_protocol_endpoints(self, device_sp):
"""Returns the iSCSI initiators for a SP."""
initiator_address = []
connection_getport = ('connection', '-getport', '-sp', device_sp)
out, _rc = self._cli_execute(*connection_getport)
output = out.split('SP: ')
for port in output:
port_info = port.split('\n')
if port_info[0] == device_sp:
port_wwn = port_info[2].split('Port WWN:')[1].strip()
initiator_address.append(port_wwn)
LOG.debug('WWNs found for SP %(devicesp)s '
'are: %(initiator_address)s'
% {'devicesp': device_sp,
'initiator_address': initiator_address})
return initiator_address
def _get_volumetype_extraspecs(self, volume):
specs = {}
type_id = volume['volume_type_id']
if type_id is not None:
specs = volume_types.get_volume_type_extra_specs(type_id)
return specs
def _get_provisioning_by_volume(self, volume):
# By default, the user can not create thin LUN without thin
# provisioning enabler.
thinness = 'NonThin'
spec_id = 'storagetype:provisioning'
specs = self._get_volumetype_extraspecs(volume)
if specs and spec_id in specs:
provisioning = specs[spec_id].lower()
if 'thin' == provisioning:
thinness = 'Thin'
elif 'thick' != provisioning:
LOG.warning(_('Invalid value of extra spec '
'\'storagetype:provisioning\': %(provisioning)s')
% {'provisioning': specs[spec_id]})
else:
LOG.info(_('No extra spec \'storagetype:provisioning\' exist'))
return thinness