# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 OpenStack LLC.
# 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
#
#
# 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.
"""
Drivers for Solaris ZFS operations in local and iSCSI modes
"""
import abc
import fcntl
import os
import subprocess
import time
import paramiko
solaris_zfs_opts = [
"""Connect to a RAD instance over TLS.
Arguments:
host string, target host
port int, target port (RAD_PORT_TLS = 12302)
locale string, locale
ca_certs string, path to file containing CA certificates
Returns:
RadConnection: a connection to RAD
"""
# We don't want SSL 2.0, SSL 3.0 nor TLS 1.0 in RAD
if ca_certs is not None:
"""OpenStack Cinder ZFS volume driver for generic ZFS volumes.
Version history:
1.0.0 - Initial driver with basic functionalities in Havana
1.1.0 - Support SAN for the remote storage nodes access in Juno
1.1.1 - Add support for the volume backup
1.1.2 - Add support for the volume migration
1.2.0 - Add support for the volume management in Kilo
1.2.1 - Enable the connect_tls by importing eventlet.green.socket
1.2.2 - Introduce the ZFS RAD for volume migration enhancement
1.2.3 - Replace volume-specific targets with one shared target in
the ZFSISCSIDriver
1.3.0 - Support the option iscsi_secondary_ip_addresses and then
return target_iqns, target_portals, target_luns in Mitaka
"""
"""Execute the command locally or remotely."""
else:
"""Check the setup error."""
pass
"""Create a volume."""
# Create a ZFS volume
"""Create a cloned volume from a snapshot."""
exception_message = (_("Could not create volume '%s' because "
"its volume size of '%s' is different "
"from that of the snapshot, '%s'.")
snapshot['volume_size']))
# Create a ZFS clone
"""Create a clone of the specified volume."""
exception_message = (_("Could not clone volume '%s' because "
"its volume size of '%s' is different "
"from that of the source volume, '%s'.")
src_vref['size']))
"""Delete a volume.
Firstly, the volume should be checked if it is a cloned one. If yes,
its parent snapshot with prefix 'tmp-snapshot-' should be deleted as
well after it is removed.
"""
try:
except processutils.ProcessExecutionError:
return
# Check if it is the temporary snapshot created for the cloned volume
if tmp_cloned_vol:
"""Create a snapshot."""
"""Delete a snapshot."""
"""Synchronously recreate an export for a logical volume."""
pass
"""Export the volume."""
pass
"""Remove an export for a volume."""
pass
"""Initialize the connection and returns connection info."""
volume['id'])
properties = {}
return {
'driver_volume_type': 'local',
'volume_path': volume_path,
'data': properties
}
"""Disconnection from the connector."""
pass
"""Callback for volume attached to instance or host."""
pass
""" Callback for volume detached."""
pass
"""Get volume status."""
if refresh:
"""Get the value of property for the dataset."""
try:
except processutils.ProcessExecutionError:
return None
"""Get the snapshot path."""
"""Add the pool name to get the ZFS volume."""
"""Piped execute on a remote host."""
if exit_status != 0:
"stdout '%s' and stderr '%s'")
"""Pipe output of cmd1 into cmd2."""
try:
except:
raise
# Set the pipe to be blocking because evenlet.green.subprocess uses
# the non-blocking pipe.
if p2.returncode:
msg = (_("_piped_execute failed with the info '%s' and '%s'.") %
# Delete the temporary snapshot if it already exists
if prop_type == 'snapshot':
# Create a temporary snapshot of volume
# Due to pipe injection protection in the ssh utils method,
# cinder.utils.check_ssh_injection(), the piped commands must be passed
# through via paramiko. These commands take no user defined input
# other than the names of the zfs datasets which are already protected
# against the special characters of concern.
else:
# Delete the temporary src snapshot and dst snapshot
"""Connect the RAD server."""
if san_info is not None:
else:
return rc
"""Replicate the ZFS dataset stream."""
# Delete the temporary snapshot if it already exists
if prop_type == 'snapshot':
# Create the temporary snapshot of src volume
try:
src_pat}))
dst_pat}))
# Set 4mb buffer size
buf_size = 4194304
while True:
# Read the data from the send stream
if not buf:
break
# Write the data to the receive steam
# Delete the temporary dst snapshot
for snap in snapshot_list:
if 'tmp-send-snapshot'in snap:
break
finally:
# Delete the temporary src snapshot
"""Get the ZFS volume path."""
"""Retrieve volume status info."""
stats = {}
stats['total_capacity_gb'] = \
stats['location_info'] =\
('ZFSVolumeDriver:%(hostname)s:%(zfs_volume_base)s:local' %
"""Extend an existing volume's size."""
try:
except Exception:
msg = (_("Failed to extend volume size to %(new_size)s GB.")
% {'new_size': new_size})
"""Rename the volume from src to dst in the same zpool."""
"""Returns the volume name of an existing reference.
And Check if an existing volume reference has a source-name
"""
if 'source-name' in existing_ref:
return vol_name
else:
reason = _("Reference must contain source-name.")
"""Return size of volume to be managed by manage_existing.
existing_ref is a dictionary of the form:
{'source-name': <name of the volume>}
"""
"""Brings an existing zfs volume object under Cinder management.
:param volume: Cinder volume to manage
:param existing_ref: Driver-specific information used to identify a
volume
"""
# Check the existence of the ZFS volume
if prop_type != 'volume':
msg = (_("Failed to identify the volume '%s'.")
if volume['name']:
else:
volume_name = 'new_zvol'
# rename the volume
"""Removes the specified volume from Cinder management."""
# Rename the volume's name to cinder-unm-* format.
"""Migrate the volume from one backend to another one.
The backends should be in the same volume type.
:param context: context
:param volume: a dictionary describing the volume to migrate
:param host: a dictionary describing the host to migrate to
"""
return false_ret
if 'capabilities' not in host:
host['host'])
return false_ret
host['host'])
return false_ret
# check if the src and dst volume are under the same zpool
if dst_san_info == 'local':
else:
# delete the source volume
provider_location = {}
return (True, provider_location)
"""Abstract base class for common COMSTAR operations."""
"""Handle the possible race during the local execution."""
tries = 0
while True:
try:
return
raise
"""Verify the target and check its status."""
try:
'-v', target)
tmp_protocol = None
status = None
break
if tmp_protocol == protocol:
return status
else:
err_msg = (_("'%s' does not match the listed protocol '%s'"
" for target '%s'.")
return None
else:
err_msg = (_("Failed to list the target '%s': '%s'")
"""Online the target in the offline state."""
"""Check if the target group exists."""
try:
return True
return False
else:
err_msg = (_("Failed to list the target group '%s': '%s'")
"""Get the LU corresponding to the volume."""
luid = None
break
else:
luid = None
if luid is not None:
else:
% volume['name'])
return luid
"""Check the view entry of the LU and then get the lun and view."""
view_and_lun = {}
try:
return view_and_lun
else:
raise
break
err_msg = (_("Failed to get the view_entry or LUN of the LU '%s'.")
% lu)
else:
return view_and_lun
"""ZFS volume operations in iSCSI mode."""
"""Get volume status."""
status['location_info'] = \
('ZFSISCSIDriver:%(hostname)s:%(zfs_volume_base)s:'
'%(san_info)s' %
'san_info': san_info})
return status
"""Create the target and then add it to the target group."""
"""Get the current target IP address."""
'-v', target)
portal = None
break
return portal
"""Verify the tpg."""
try:
'-v', tpg)
return True
return False
else:
err_msg = (_("Failed to list the tpg '%s': '%s'")
"""Create the TPG for the IP address."""
try:
err_msg = (_("Failed to create the tpg '%s': '%s'") %
"""Setup targets for the IP addresses."""
for ip in target_ips:
target_name = '%s%s-%s-%s-target' % \
if target_status == 'Online':
continue
if target_status is None:
"""Setup the target and target group."""
target_name = '%s%s-%s-target' % \
return
if target_status is None:
# Create the primary target
else:
# Online the target from the 'Offline' status
"""Get target members of the target group."""
targets = []
target_portals = []
if target == primary_target:
continue
if target_portal:
return targets, target_portals
"""Export the volume."""
# If the volume is already exported there is nothing to do, as we
# simply export volumes and they are universally available.
if luid:
if view_lun['view'] is not None:
return
else:
msg = (_("Failed to create logical unit for volume '%s' due "
# Create a Logical Unit (LU)
if not luid:
msg = (_("Failed to create LU for volume '%s'")
% volume['name'])
# Add a view entry to the logical unit
"""Remove an export for a volume.
All of the related elements about the volume, including the
target, target group, view entry and lu, are deleted.
"""
# Remove the LU
if luid is not None:
# Remove the target and its target group if they were created by
# earlier versions of the volume driver
volume['name'])
"""Get iSCSI configuration
Now we use the discovery address as the default approach to add
objects into the initiator. A discovery address is an IP address:port
combination used in a SendTargets discovery session in the initiator.
:target_discovered: boolean indicating whether discovery was used
:target_iqn: the IQN of the iSCSI target
:target_portal: the portal of the iSCSI target
:target_lun: the lun of the iSCSI target
:volume_id: the id of the volume
:auth_method:, :auth_username:, :auth_password:
the authentication details. Right now, either auth_method is not
present meaning no authentication, or auth_method == `CHAP`
meaning use CHAP with the specified credentials.
If multiple IP addresses are configured, the returns will include
:target_iqns, :target_portals, :target_luns, which contain lists of
multiple values. The main portal information is also returned in
:target_iqn, :target_portal, :target_lun for backward compatibility.
"""
if not luid:
volume['name'])
target_name = '%s%s-%s-target' % \
properties = {}
# Here the san_is_local means that the cinder-volume runs in the
# iSCSI target with iscsi_ip_address.
else:
if view_lun['lun'] is not None:
# The multipathing doesn't apply to the old volume-specific target
if not old_target_name and secondary_ifs:
target_portals = []
target_iqns = []
target_luns = []
if auth:
return properties
"""Initialize the connection and returns connection info.
The iSCSI driver returns a driver_volume_type of 'iscsi'.
The format of the driver data is defined in _get_iscsi_properties.
Example return value::
{
'driver_volume_type': 'iscsi'
'data': {
'target_discovered': True,
'target_iqn':
'iqn.1986-03.com.sun:02:200720c6-9bca-cb8f-c061-d427d7ab978f',
'target_portal': '127.0.0.1:3260',
'volume_id': 1,
}
}
"""
'for volume %(volume_name)s')
% {'initiator_name': initiator_name,
'volume_name': volume_name})
return {
'driver_volume_type': 'iscsi',
'data': iscsi_properties
}
"""Disconnection from the connector."""
'for volume %(volume_name)s')
% {'initiator_name': initiator_name,
'volume_name': volume_name})
"""ZFS volume operations in FC mode."""
"""Get volume status."""
status['location_info'] = \
('ZFSFCDriver:%(hostname)s:%(zfs_volume_base)s:'
'%(san_info)s' %
'san_info': san_info})
return status
"""Check wwns and setup the target group."""
msg = (_("Could not determine fibre channel world wide "
"node names."))
"""Get the FC port WWNs of the host."""
wwns = []
return wwns
"""Get the target members in the tg."""
'-v', tg)
wwns = []
return wwns
"""Setup the target group."""
# Add free target wwns into the target group
try:
except:
% (target_wwn))
if not target_wwns:
msg = (_("No target members exist in the target group '%s'.")
% tg)
"""Check if the LU is the only one."""
linecount = 0
linecount += 1
return True
else:
return False
"""Check if the target has been added into a target group."""
if tg is not None:
'-v', tg)
else:
return True
return False
"""Force the link to reinitialize."""
for target_wwn in target_wwns:
"""Export the volume."""
# If the volume is already exported there is nothing to do, as we
# simply export volumes and they are universally available.
if luid:
if view_lun['view'] is not None:
return
else:
msg = (_("Failed to create logical unit for volume '%s' due "
# Create a Logical Unit (LU)
if not luid:
msg = (_("Failed to create logic unit for volume '%s'")
% volume['name'])
# setup the target group if it doesn't exist.
# Add a logical unit view entry
"""Remove an export for a volume."""
if luid is not None:
if view_lun['view']:
# Remove the target group when the LU to be deleted is last one
# exposed by the target group.
# Remove the LU
"""Get Fibre Channel configuration.
:target_discovered: boolean indicating whether discovery was used
:target_wwn: the world wide name of the FC port target
:target_lun: the lun assigned to the LU for the view entry
"""
if not luid:
msg = (_("Failed to get logic unit for volume '%s'")
% volume['name'])
properties = {}
if view_lun['lun'] is not None:
return properties
"""Initializes the connection and returns connection info.
The driver returns a driver_volume_type of 'fibre_channel'.
The target_wwn can be a single entry or a list of wwns that
correspond to the list of remote wwn(s) that will export the volume.
Example return values:
{
'driver_volume_type': 'fibre_channel'
'data': {
'target_discovered': True,
'target_lun': 1,
'target_wwn': '1234567890123',
}
}
or
{
'driver_volume_type': 'fibre_channel'
'data': {
'target_discovered': True,
'target_lun': 1,
'target_wwn': ['1234567890123', '0987654321321'],
}
}
"""
return {
'driver_volume_type': 'fibre_channel',
'data': fc_properties
}