'cluster_id': 6,
'cluster_vip': '10.0.1.6'}]
+ repl_targets_unmgd = [{'target_device_id': 'target',
+ 'hpelefthand_api_url': HPELEFTHAND_API_URL2,
+ 'hpelefthand_username': HPELEFTHAND_USERNAME,
+ 'hpelefthand_password': HPELEFTHAND_PASSWORD,
+ 'hpelefthand_clustername': HPELEFTHAND_CLUSTER_NAME,
+ 'hpelefthand_ssh_port': HPELEFTHAND_SSH_PORT,
+ 'ssh_conn_timeout': HPELEFTHAND_SAN_SSH_CON_TIMEOUT,
+ 'san_private_key': HPELEFTHAND_SAN_SSH_PRIVATE,
+ 'cluster_id': 6,
+ 'cluster_vip': '10.0.1.6'}]
+
list_rep_targets = [{'target_device_id': 'target'}]
serverName = 'fakehost'
snapshot_id = 3
snapshot = {
'name': snapshot_name,
- 'volume_name': volume_name}
+ 'volume_name': volume_name,
+ 'volume': volume}
cloned_volume_name = "clone_volume"
cloned_volume = {'name': cloned_volume_name}
'provider_location': prov_location},
return_model)
+ @mock.patch('hpelefthandclient.version', "2.0.1")
+ @mock.patch.object(volume_types, 'get_volume_type')
+ def test_create_volume_replicated_unmanaged(self, _mock_get_volume_type):
+ # set up driver with default config
+ conf = self.default_mock_conf()
+ conf.replication_device = self.repl_targets_unmgd
+ mock_client = self.setup_driver(config=conf)
+ mock_client.createVolume.return_value = {
+ 'iscsiIqn': self.connector['initiator']}
+ mock_client.doesRemoteSnapshotScheduleExist.return_value = False
+ mock_replicated_client = self.setup_driver(config=conf)
+
+ _mock_get_volume_type.return_value = {
+ 'name': 'replicated',
+ 'extra_specs': {
+ 'replication_enabled': '<is> True'}}
+
+ with mock.patch.object(
+ hpe_lefthand_iscsi.HPELeftHandISCSIDriver,
+ '_create_client') as mock_do_setup, \
+ mock.patch.object(
+ hpe_lefthand_iscsi.HPELeftHandISCSIDriver,
+ '_create_replication_client') as mock_replication_client:
+ mock_do_setup.return_value = mock_client
+ mock_replication_client.return_value = mock_replicated_client
+ return_model = self.driver.create_volume(self.volume_replicated)
+
+ expected = [
+ mock.call.createVolume(
+ 'fakevolume_replicated',
+ 1,
+ units.Gi,
+ {'isThinProvisioned': True,
+ 'clusterName': 'CloudCluster1'}),
+ mock.call.doesRemoteSnapshotScheduleExist(
+ 'fakevolume_replicated_SCHED_Pri'),
+ mock.call.createRemoteSnapshotSchedule(
+ 'fakevolume_replicated',
+ 'fakevolume_replicated_SCHED',
+ 1800,
+ '1970-01-01T00:00:00Z',
+ 5,
+ 'CloudCluster1',
+ 5,
+ 'fakevolume_replicated',
+ '1.1.1.1',
+ 'foo1',
+ 'bar2'),
+ mock.call.logout()]
+
+ mock_client.assert_has_calls(
+ self.driver_startup_call_stack +
+ self.driver_startup_ssh +
+ expected)
+ prov_location = '10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0'
+ rep_data = json.dumps({"location": HPELEFTHAND_API_URL})
+ self.assertEqual({'replication_status': 'enabled',
+ 'replication_driver_data': rep_data,
+ 'provider_location': prov_location},
+ return_model)
+
@mock.patch('hpelefthandclient.version', "2.0.1")
@mock.patch.object(volume_types, 'get_volume_type')
def test_delete_volume_replicated(self, _mock_get_volume_type):
'replication_driver_data': rep_data,
'host': FAKE_FAILOVER_HOST},
return_model)
+
+ @mock.patch('hpelefthandclient.version', "2.0.1")
+ @mock.patch.object(volume_types, 'get_volume_type')
+ def test_replication_failover_unmanaged(self, _mock_get_volume_type):
+ ctxt = context.get_admin_context()
+ # set up driver with default config
+ conf = self.default_mock_conf()
+ conf.replication_device = self.repl_targets_unmgd
+ mock_client = self.setup_driver(config=conf)
+ mock_replicated_client = self.setup_driver(config=conf)
+ mock_replicated_client.getVolumeByName.return_value = {
+ 'iscsiIqn': self.connector['initiator']}
+
+ _mock_get_volume_type.return_value = {
+ 'name': 'replicated',
+ 'extra_specs': {
+ 'replication_enabled': '<is> True'}}
+
+ with mock.patch.object(
+ hpe_lefthand_iscsi.HPELeftHandISCSIDriver,
+ '_create_client') as mock_do_setup, \
+ mock.patch.object(
+ hpe_lefthand_iscsi.HPELeftHandISCSIDriver,
+ '_create_replication_client') as mock_replication_client:
+ mock_do_setup.return_value = mock_client
+ mock_replication_client.return_value = mock_replicated_client
+ valid_target_device_id = (self.repl_targets[0]['target_device_id'])
+ invalid_target_device_id = 'INVALID'
+
+ # test invalid secondary target
+ self.assertRaises(
+ exception.VolumeBackendAPIException,
+ self.driver.replication_failover,
+ ctxt,
+ self.volume_replicated,
+ invalid_target_device_id)
+
+ # test a successful failover
+ return_model = self.driver.replication_failover(
+ context.get_admin_context(),
+ self.volume_replicated,
+ valid_target_device_id)
+ rep_data = json.dumps({"location": HPELEFTHAND_API_URL2})
+ prov_location = '10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0'
+ self.assertEqual({'provider_location': prov_location,
+ 'replication_driver_data': rep_data},
+ return_model)
2.0.0 - Rebranded HP to HPE
2.0.1 - Remove db access for consistency groups
2.0.2 - Adds v2 managed replication support
+ 2.0.3 - Adds v2 unmanaged replication support
"""
- VERSION = "2.0.2"
+ VERSION = "2.0.3"
device_stats = {}
# blank is the only invalid character for cluster names
# so we need to use it as a separator
self.DRIVER_LOCATION = self.__class__.__name__ + ' %(cluster)s %(vip)s'
+ self._client_conf = {}
self._replication_targets = []
self._replication_enabled = False
- def _login(self, timeout=None):
+ def _login(self, volume=None, timeout=None):
+ conf = self._get_lefthand_config(volume)
+ if conf:
+ self._client_conf['hpelefthand_username'] = (
+ conf['hpelefthand_username'])
+ self._client_conf['hpelefthand_password'] = (
+ conf['hpelefthand_password'])
+ self._client_conf['hpelefthand_clustername'] = (
+ conf['hpelefthand_clustername'])
+ self._client_conf['hpelefthand_api_url'] = (
+ conf['hpelefthand_api_url'])
+ self._client_conf['hpelefthand_ssh_port'] = (
+ conf['hpelefthand_ssh_port'])
+ self._client_conf['hpelefthand_iscsi_chap_enabled'] = (
+ conf['hpelefthand_iscsi_chap_enabled'])
+ self._client_conf['ssh_conn_timeout'] = conf['ssh_conn_timeout']
+ self._client_conf['san_private_key'] = conf['san_private_key']
+ else:
+ self._client_conf['hpelefthand_username'] = (
+ self.configuration.hpelefthand_username)
+ self._client_conf['hpelefthand_password'] = (
+ self.configuration.hpelefthand_password)
+ self._client_conf['hpelefthand_clustername'] = (
+ self.configuration.hpelefthand_clustername)
+ self._client_conf['hpelefthand_api_url'] = (
+ self.configuration.hpelefthand_api_url)
+ self._client_conf['hpelefthand_ssh_port'] = (
+ self.configuration.hpelefthand_ssh_port)
+ self._client_conf['hpelefthand_iscsi_chap_enabled'] = (
+ self.configuration.hpelefthand_iscsi_chap_enabled)
+ self._client_conf['ssh_conn_timeout'] = (
+ self.configuration.ssh_conn_timeout)
+ self._client_conf['san_private_key'] = (
+ self.configuration.san_private_key)
+
client = self._create_client(timeout=timeout)
try:
if self.configuration.hpelefthand_debug:
client.debug_rest(True)
client.login(
- self.configuration.hpelefthand_username,
- self.configuration.hpelefthand_password)
+ self._client_conf['hpelefthand_username'],
+ self._client_conf['hpelefthand_password'])
cluster_info = client.getClusterByName(
- self.configuration.hpelefthand_clustername)
+ self._client_conf['hpelefthand_clustername'])
self.cluster_id = cluster_info['id']
virtual_ips = cluster_info['virtualIPAddresses']
self.cluster_vip = virtual_ips[0]['ipV4Address']
if hpelefthandclient.version >= MIN_REP_CLIENT_VERSION:
# Extract IP address from API URL
ssh_ip = self._extract_ip_from_url(
- self.configuration.hpelefthand_api_url)
+ self._client_conf['hpelefthand_api_url'])
known_hosts_file = CONF.ssh_hosts_key_file
policy = "AutoAddPolicy"
if CONF.strict_ssh_host_key_policy:
policy = "RejectPolicy"
client.setSSHOptions(
ssh_ip,
- self.configuration.hpelefthand_username,
- self.configuration.hpelefthand_password,
- port=self.configuration.hpelefthand_ssh_port,
- conn_timeout=self.configuration.ssh_conn_timeout,
- privatekey=self.configuration.san_private_key,
+ self._client_conf['hpelefthand_username'],
+ self._client_conf['hpelefthand_password'],
+ port=self._client_conf['hpelefthand_ssh_port'],
+ conn_timeout=self._client_conf['ssh_conn_timeout'],
+ privatekey=self._client_conf['san_private_key'],
missing_key_policy=policy,
known_hosts_file=known_hosts_file)
def _create_client(self, timeout=None):
# Timeout is only supported in version 2.0.1 and greater of the
# python-lefthandclient.
+ hpelefthand_api_url = self._client_conf['hpelefthand_api_url']
if hpelefthandclient.version >= MIN_REP_CLIENT_VERSION:
client = hpe_lh_client.HPELeftHandClient(
- self.configuration.hpelefthand_api_url, timeout=timeout)
+ hpelefthand_api_url, timeout=timeout)
else:
- client = hpe_lh_client.HPELeftHandClient(
- self.configuration.hpelefthand_api_url)
+ client = hpe_lh_client.HPELeftHandClient(hpelefthand_api_url)
return client
def _create_replication_client(self, remote_array):
finally:
self._logout(client)
+ def check_replication_flags(self, options, required_flags):
+ for flag in required_flags:
+ if not options.get(flag, None):
+ msg = _('%s is not set and is required for the replicaiton '
+ 'device to be valid.') % flag
+ LOG.error(msg)
+ raise exception.InvalidInput(reason=msg)
+
def get_version_string(self):
return (_('REST %(proxy_ver)s hpelefthandclient %(rest_ver)s') % {
'proxy_ver': self.VERSION,
def create_volume(self, volume):
"""Creates a volume."""
- client = self._login()
+ client = self._login(volume)
try:
# get the extra specs of interest from this volume's volume type
volume_extra_specs = self._get_volume_extra_specs(volume)
if optional.get('isAdaptiveOptimizationEnabled'):
del optional['isAdaptiveOptimizationEnabled']
- clusterName = self.configuration.hpelefthand_clustername
+ clusterName = self._client_conf['hpelefthand_clustername']
optional['clusterName'] = clusterName
volume_info = client.createVolume(
self._do_volume_replication_setup(volume, client, optional)):
model_update['replication_status'] = 'enabled'
model_update['replication_driver_data'] = (json.dumps(
- {'location': self.configuration.hpelefthand_api_url}))
+ {'location': self._client_conf['hpelefthand_api_url']}))
return model_update
except Exception as ex:
def delete_volume(self, volume):
"""Deletes a volume."""
- client = self._login()
+ client = self._login(volume)
# v2 replication check
# If the volume type is replication enabled, we want to call our own
# method of deconstructing the volume and its dependencies
def extend_volume(self, volume, new_size):
"""Extend the size of an existing volume."""
- client = self._login()
+ client = self._login(volume)
try:
volume_info = client.getVolumeByName(volume['name'])
def create_snapshot(self, snapshot):
"""Creates a snapshot."""
- client = self._login()
+ client = self._login(snapshot['volume'])
try:
volume_info = client.getVolumeByName(snapshot['volume_name'])
def delete_snapshot(self, snapshot):
"""Deletes a snapshot."""
- client = self._login()
+ client = self._login(snapshot['volume'])
try:
snap_info = client.getSnapshotByName(snapshot['name'])
client.deleteSnapshot(snap_info['id'])
data['storage_protocol'] = 'iSCSI'
data['vendor_name'] = 'Hewlett Packard Enterprise'
data['location_info'] = (self.DRIVER_LOCATION % {
- 'cluster': self.configuration.hpelefthand_clustername,
+ 'cluster': self._client_conf['hpelefthand_clustername'],
'vip': self.cluster_vip})
data['thin_provisioning_support'] = True
data['thick_provisioning_support'] = True
total_volumes = 0
provisioned_size = 0
volumes = client.getVolumes(
- cluster=self.configuration.hpelefthand_clustername,
+ cluster=self._client_conf['hpelefthand_clustername'],
fields=['members[id]', 'members[clusterName]', 'members[size]'])
if volumes:
total_volumes = volumes['total']
used from that host. HPE VSA requires a volume to be assigned
to a server.
"""
- client = self._login()
+ client = self._login(volume)
try:
server_info = self._create_server(connector, client)
volume_info = client.getVolumeByName(volume['name'])
def terminate_connection(self, volume, connector, **kwargs):
"""Unassign the volume from the host."""
- client = self._login()
+ client = self._login(volume)
try:
volume_info = client.getVolumeByName(volume['name'])
server_info = client.getServerByName(connector['host'])
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot."""
- client = self._login()
+ client = self._login(volume)
try:
snap_info = client.getSnapshotByName(snapshot['name'])
volume_info = client.cloneSnapshot(
self._do_volume_replication_setup(volume, client)):
model_update['replication_status'] = 'enabled'
model_update['replication_driver_data'] = (json.dumps(
- {'location': self.configuration.hpelefthand_api_url}))
+ {'location': self._client_conf['hpelefthand_api_url']}))
return model_update
except Exception as ex:
self._logout(client)
def create_cloned_volume(self, volume, src_vref):
- client = self._login()
+ client = self._login(volume)
try:
volume_info = client.getVolumeByName(src_vref['name'])
clone_info = client.cloneVolume(volume['name'], volume_info['id'])
self._do_volume_replication_setup(volume, client)):
model_update['replication_status'] = 'enabled'
model_update['replication_driver_data'] = (json.dumps(
- {'location': self.configuration.hpelefthand_api_url}))
+ {'location': self._client_conf['hpelefthand_api_url']}))
return model_update
except Exception as ex:
def _create_server(self, connector, client):
server_info = None
- chap_enabled = self.configuration.hpelefthand_iscsi_chap_enabled
+ chap_enabled = self._client_conf['hpelefthand_iscsi_chap_enabled']
try:
server_info = client.getServerByName(connector['host'])
chap_secret = server_info['chapTargetSecret']
'new_type': new_type,
'diff': diff,
'host': host})
- client = self._login()
+ client = self._login(volume)
try:
volume_info = client.getVolumeByName(volume['name'])
host['host'] is its name, and host['capabilities'] is a
dictionary of its reported capabilities.
"""
- LOG.debug('enter: migrate_volume: id=%(id)s, host=%(host)s, '
- 'cluster=%(cluster)s', {
- 'id': volume['id'],
- 'host': host,
- 'cluster': self.configuration.hpelefthand_clustername})
-
false_ret = (False, None)
if 'location_info' not in host['capabilities']:
return false_ret
host_location = host['capabilities']['location_info']
(driver, cluster, vip) = host_location.split(' ')
- client = self._login()
+ client = self._login(volume)
+ LOG.debug('enter: migrate_volume: id=%(id)s, host=%(host)s, '
+ 'cluster=%(cluster)s', {
+ 'id': volume['id'],
+ 'host': host,
+ 'cluster': self._client_conf['hpelefthand_clustername']})
try:
# get the cluster info, if it exists and compare
cluster_info = client.getClusterByName(cluster)
# volume isn't attached and can be updated
original_name = CONF.volume_name_template % volume['id']
current_name = CONF.volume_name_template % new_volume['id']
- client = self._login()
+ client = self._login(volume)
try:
volume_info = client.getVolumeByName(current_name)
volumeMods = {'name': original_name}
target_vol_name = self._get_existing_volume_ref_name(existing_ref)
# Check for the existence of the virtual volume.
- client = self._login()
+ client = self._login(volume)
try:
volume_info = client.getVolumeByName(target_vol_name)
except hpeexceptions.HTTPNotFound:
reason=reason)
# Check for the existence of the virtual volume.
- client = self._login()
+ client = self._login(volume)
try:
volume_info = client.getVolumeByName(target_vol_name)
except hpeexceptions.HTTPNotFound:
# Rename the volume's name to unm-* format so that it can be
# easily found later.
- client = self._login()
+ client = self._login(volume)
try:
volume_info = client.getVolumeByName(volume['name'])
new_vol_name = 'unm-' + six.text_type(volume['id'])
LOG.error(msg)
model_update['replication_status'] = "error"
else:
- client = self._login()
+ client = self._login(volume)
try:
if self._do_volume_replication_setup(volume, client):
model_update['replication_status'] = "enabled"
model_update['replication_status'] = 'disabled'
vol_name = volume['name']
- client = self._login()
+ client = self._login(volume)
try:
name = vol_name + self.REP_SCHEDULE_SUFFIX + "_Pri"
client.stopRemoteSnapshotSchedule(name)
# as a failover can still occur, so we need out replication
# devices to exist.
for dev in replication_devices:
- remote_array = {}
- is_managed = dev.get('managed_backend_name')
- if not is_managed:
- msg = _("Unmanaged replication is not supported at this "
- "time. Please configure cinder.conf for managed "
- "replication.")
- LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
-
- remote_array['managed_backend_name'] = is_managed
- remote_array['target_device_id'] = (
- dev.get('target_device_id'))
- remote_array['hpelefthand_api_url'] = (
- dev.get('hpelefthand_api_url'))
- remote_array['hpelefthand_username'] = (
- dev.get('hpelefthand_username'))
- remote_array['hpelefthand_password'] = (
- dev.get('hpelefthand_password'))
- remote_array['hpelefthand_clustername'] = (
- dev.get('hpelefthand_clustername'))
+ remote_array = dict(dev.items())
+ # Override and set defaults for certain entries
+ remote_array['managed_backend_name'] = (
+ dev.get('managed_backend_name'))
remote_array['hpelefthand_ssh_port'] = (
dev.get('hpelefthand_ssh_port', default_san_ssh_port))
remote_array['ssh_conn_timeout'] = (
dev.get('ssh_conn_timeout', default_ssh_conn_timeout))
remote_array['san_private_key'] = (
dev.get('san_private_key', default_san_private_key))
+ # Format hpe3par_iscsi_chap_enabled as a bool
+ remote_array['hpelefthand_iscsi_chap_enabled'] = (
+ dev.get('hpelefthand_iscsi_chap_enabled') == 'True')
remote_array['cluster_id'] = None
remote_array['cluster_vip'] = None
array_name = remote_array['target_device_id']
self._replication_enabled = True
def _is_valid_replication_array(self, target):
- for k, v in target.items():
- if v is None:
- return False
- return True
+ required_flags = ['hpelefthand_api_url', 'hpelefthand_username',
+ 'hpelefthand_password', 'target_device_id',
+ 'hpelefthand_clustername']
+ try:
+ self.check_replication_flags(target, required_flags)
+ return True
+ except Exception:
+ return False
def _is_replication_configured_correct(self):
rep_flag = True
exists = False
return exists
+ def _get_lefthand_config(self, volume):
+ conf = None
+ if volume:
+ rep_location = None
+ rep_data = volume.get('replication_driver_data')
+ if rep_data:
+ rep_data = json.loads(rep_data)
+ rep_location = rep_data.get('location')
+ if rep_location:
+ for target in self._replication_targets:
+ if target['hpelefthand_api_url'] == rep_location:
+ conf = target
+ break
+
+ return conf
+
def _do_volume_replication_setup(self, volume, client, optional=None):
"""This function will do or ensure the following: