Skip to content

Commit 970bb76

Browse files
authored
Merge pull request #81 from dell/91_replication_enhancements
SRDF Volume add and Remove + Secure Snap
2 parents f4837a7 + 4620548 commit 970bb76

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+5918
-5044
lines changed

ChangeLog

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
11
PyU4V Change Log
22
================
33

4+
Version 9.1.2.0 - released 19/12/19
5+
===================================
6+
Provisioning Enhancements
7+
-------------------------
8+
- add_new_volume_to_storage_group : added functionality to be able to add
9+
volumes to SRDF protected storage groups, new volumes will automatically be
10+
protected with SRDF, volumes are added to the remote storage group to fully
11+
automate the provisioning and simplify operations. Storage group must be
12+
local to the unisphere instance and only works with storage groups that
13+
contain R1 or R11 devices.
14+
- remove_volume_from_storage_group enhanced to be able to automatically remove
15+
volumes from local and remote storage groups deleting srdf pairs as part of
16+
the process, no need to suspend, the automation takes care of everything,
17+
storage group must be local to the unisphere instance and only works with
18+
storage groups that contain R1 or R11 devices.
19+
20+
Replication Enhancements
21+
-------------------------
22+
- create_storage_group_snapshot added secure option to enable creation of
23+
secure snapshot
24+
425
Version 9.1.1.0 - released 12/12/19
526
===================================
627

@@ -86,6 +107,7 @@ Provisioning Enhancements
86107
- format_director_port : Format separate director port into single string
87108
- get_any_director_port : Get a non-GuestOS port from a director
88109

110+
89111
Fixes
90112
-----
91113
- When searching for volumes by volume name, if more than one volume matches
@@ -331,6 +353,7 @@ PyU4V/replication.py
331353
- delete_storagegroup_srdf() has been refactored and marked for deprecation,
332354
please use alternative function delete_storage_group_srdf()
333355

356+
334357
PyU4V/utils/console.py
335358
----------------------
336359
- choose_from_list() has been marked for deprecation, please implement any

PyU4V/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from .univmax_conn import U4VConn # noqa: F401
2424

2525
__title__ = 'pyu4v'
26-
__version__ = '9.1.1.0'
26+
__version__ = '9.1.2.0'
2727
__author__ = 'Dell EMC or its subsidiaries'
2828
__license__ = 'Apache 2.0'
2929
__copyright__ = 'Copyright 2019 Dell EMC Inc'

PyU4V/provisioning.py

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,7 +1773,7 @@ def add_new_vol_to_storagegroup(self, sg_id, num_vols, vol_size,
17731773
:param cap_unit: capacity unit (MB, GB, TB, CYL) -- str
17741774
:param _async: if call should be async -- bool
17751775
:param vol_name: name to give to the volume, optional -- str
1776-
:param create_new_volumes: new volumes only, no ro-use -- bool
1776+
:param create_new_volumes: new volumes only, no re-use -- bool
17771777
:returns: storage group details -- dict
17781778
"""
17791779
return self.add_new_volume_to_storage_group(
@@ -1782,7 +1782,9 @@ def add_new_vol_to_storagegroup(self, sg_id, num_vols, vol_size,
17821782

17831783
def add_new_volume_to_storage_group(
17841784
self, storage_group_id, num_vols, vol_size, cap_unit, _async=False,
1785-
vol_name=None, create_new_volumes=None):
1785+
vol_name=None, create_new_volumes=None, remote_array_1_id=None,
1786+
remote_array_1_sgs=None, remote_array_2_id=None,
1787+
remote_array_2_sgs=None):
17861788
"""Expand an existing storage group by adding new volumes.
17871789
17881790
:param storage_group_id: storage group id -- str
@@ -1792,6 +1794,17 @@ def add_new_volume_to_storage_group(
17921794
:param _async: if call should be async -- bool
17931795
:param vol_name: name to give to the volume, optional -- str
17941796
:param create_new_volumes: new volumes only, no ro-use -- bool
1797+
:param remote_array_1_id: 12 digit serial number of remote array,
1798+
optional -- str
1799+
:param remote_array_1_sgs: list of storage groups on remote array to
1800+
add Remote device, Unisphere instance must be local to R1
1801+
storage group otherwise volumes will only be added to the
1802+
local group -- str or list
1803+
:param remote_array2_id: optional digit serial number of remote array,
1804+
only used in multihop SRDF, e.g. R11, or R1 - R21 - R2 optional
1805+
-- str
1806+
:param remote_array2_sgs: storage groups on remote array, optional
1807+
-- str or list
17951808
:returns: storage group details -- dict
17961809
"""
17971810
add_volume_param = {'emulation': 'FBA'}
@@ -1817,7 +1830,18 @@ def add_new_volume_to_storage_group(
18171830
expand_sg_data = ({'editStorageGroupActionParam': {
18181831
'expandStorageGroupParam': {
18191832
'addVolumeParam': add_volume_param}}})
1820-
1833+
if remote_array_1_id and remote_array_1_sgs:
1834+
if not isinstance(remote_array_1_sgs, list):
1835+
remote_array_1_sgs = [remote_array_1_sgs]
1836+
add_volume_param.update({'remoteSymmSGInfoParam': {
1837+
'remote_symmetrix_1_id': remote_array_1_id,
1838+
'remote_symmetrix_1_sgs': remote_array_1_sgs}})
1839+
if remote_array_2_id and remote_array_2_sgs:
1840+
if not isinstance(remote_array_2_sgs, list):
1841+
remote_array_2_sgs = [remote_array_2_sgs]
1842+
add_volume_param['remoteSymmSGInfoParam'].update({
1843+
'remote_symmetrix_2_id': remote_array_2_id,
1844+
'remote_symmetrix_2_sgs': remote_array_2_sgs})
18211845
if _async:
18221846
expand_sg_data.update(ASYNC_UPDATE)
18231847
return self.modify_storage_group(storage_group_id, expand_sg_data)
@@ -1841,19 +1865,57 @@ def remove_vol_from_storagegroup(self, sg_id, vol_id, _async=False):
18411865
"""
18421866
return self.remove_volume_from_storage_group(sg_id, vol_id, _async)
18431867

1844-
def remove_volume_from_storage_group(self, storage_group_id, vol_id,
1845-
_async=False):
1868+
def remove_volume_from_storage_group(
1869+
self, storage_group_id, vol_id, _async=False,
1870+
remote_array_1_id=None, remote_array_1_sgs=None,
1871+
remote_array_2_id=None, remote_array_2_sgs=None):
18461872
"""Remove a volume from a given storage group.
18471873
18481874
:param storage_group_id: storage group id -- str
18491875
:param vol_id: device id -- str
18501876
:param _async: if call should be async -- bool
1877+
:param remote_array_1_id: 12 digit serial number of remote array,
1878+
optional -- str
1879+
:param remote_array_1_sgs: list of storage groups on remote array to
1880+
add Remote device, Unisphere instance must be local to R1
1881+
storage group otherwise volumes will only be added to the
1882+
local group -- str or list
1883+
:param remote_array2_id: optional digit serial number of remote array,
1884+
only used in multihop SRDF, e.g. R11, or R1 - R21 - R2 optional
1885+
-- str
1886+
:param remote_array2_sgs: storage groups on remote array, optional
1887+
-- str or list
18511888
:returns: storage group details -- dict
18521889
"""
18531890
if not isinstance(vol_id, list):
18541891
vol_id = [vol_id]
18551892
payload = ({'editStorageGroupActionParam': {
18561893
'removeVolumeParam': {'volumeId': vol_id}}})
1894+
1895+
if remote_array_1_id and remote_array_1_sgs:
1896+
if not isinstance(remote_array_1_sgs, list):
1897+
remote_array_1_sgs = [remote_array_1_sgs]
1898+
payload.update(
1899+
{'editStorageGroupActionParam': {
1900+
'removeVolumeParam': {
1901+
'volumeId': vol_id,
1902+
'remoteSymmSGInfoParam': {
1903+
'remote_symmetrix_1_id': remote_array_1_id,
1904+
'remote_symmetrix_1_sgs': remote_array_1_sgs}}}
1905+
})
1906+
if remote_array_2_id and remote_array_2_sgs:
1907+
if not isinstance(remote_array_2_sgs, list):
1908+
remote_array_2_sgs = [remote_array_2_sgs]
1909+
payload.update(
1910+
{'editStorageGroupActionParam': {
1911+
'removeVolumeParam': {
1912+
'volumeId': vol_id,
1913+
'remoteSymmSGInfoParam': {
1914+
'remote_symmetrix_1_id': remote_array_1_id,
1915+
'remote_symmetrix_1_sgs': remote_array_1_sgs,
1916+
'remote_symmetrix_2_id': remote_array_2_id,
1917+
'remote_symmetrix_2_sgs': remote_array_1_sgs
1918+
}}}})
18571919
if _async:
18581920
payload.update(ASYNC_UPDATE)
18591921
return self.modify_storage_group(storage_group_id, payload)

PyU4V/replication.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ def create_storagegroup_snap(
215215
hours)
216216

217217
def create_storage_group_snapshot(
218-
self, storage_group_id, snap_name, ttl=None, hours=False):
218+
self, storage_group_id, snap_name, ttl=None, hours=False,
219+
secure=False):
219220
"""Create a snapVx snapshot of a storage group.
220221
221222
To establish a new generation of an existing SnapVX snapshot for a
@@ -226,11 +227,16 @@ def create_storage_group_snapshot(
226227
:param snap_name: snapshot name -- str
227228
:param ttl: Time To Live -- str
228229
:param hours: if TTL is in hours instead of days -- bool
230+
:param secure: sets secure snapshot, snapshot created with secure
231+
option can not be deleted before ttl expires -- bool
229232
:returns: snapshot details -- dict
230233
"""
231234
payload = {'snapshotName': snap_name}
232235
if ttl:
233-
payload.update({'timeToLive': ttl})
236+
if secure:
237+
payload.update({'secure': ttl})
238+
else:
239+
payload.update({'timeToLive': ttl})
234240
if hours:
235241
payload.update({'timeInHours': 'True'})
236242
return self.create_resource(

PyU4V/tests/ci_tests/base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def create_rdf_sg(self):
152152
srdf_mode='Synchronous', establish=True,
153153
_async=True)
154154
self.conn.common.wait_for_job_complete(job)
155-
local_volume = self.provision.get_vols_from_storagegroup(
155+
local_volume = self.provision.get_volumes_from_storage_group(
156156
sg_name)[0]
157157
srdf_group_number = (
158158
self.replication.get_storage_group_srdf_group_list(
@@ -177,15 +177,15 @@ def cleanup_rdfg(self, sg_name, srdf_group_number):
177177
if 'Synchronized' in current_rdf_state_list:
178178
self.replication.suspend_storage_group_srdf(
179179
storage_group_id=sg_name, srdf_group_number=srdf_group_number)
180-
local_volume_list = self.provision.get_vols_from_storagegroup(
180+
local_volume_list = self.provision.get_volumes_from_storage_group(
181181
sg_name)
182182
self.replication.delete_storage_group_srdf(
183183
storage_group_id=sg_name)
184184
self.provision.delete_storage_group(storage_group_id=sg_name)
185185
for local_volume in local_volume_list:
186186
self.provision.delete_volume(device_id=local_volume)
187187
self.conn.set_array_id(array_id=self.conn.remote_array)
188-
remote_volume_list = self.provision.get_vols_from_storagegroup(
188+
remote_volume_list = self.provision.get_volumes_from_storage_group(
189189
sg_name)
190190
self.provision.delete_storage_group(storage_group_id=sg_name)
191191
for remote_volume in remote_volume_list:

PyU4V/tests/ci_tests/test_pyu4v_ci_provisioning.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,6 +1927,18 @@ def test_add_new_vol_to_storagegroup(self):
19271927
for volume in volume_list:
19281928
self.addCleanup(self.delete_volume, storage_group_name, volume)
19291929

1930+
def test_add_new_vol_to_srdf_storage_group(self):
1931+
"""Tests adding a volume to a storage group that is replicated"""
1932+
sg_name, srdf_group_number, device_id, remote_volume = (
1933+
self.create_rdf_sg())
1934+
self.provisioning.add_new_volume_to_storage_group(
1935+
storage_group_id=sg_name, vol_size=1, num_vols=1, cap_unit='GB',
1936+
remote_array_1_id=self.conn.remote_array,
1937+
remote_array_1_sgs=[sg_name])
1938+
storage_group_details = self.provisioning.get_storage_group(
1939+
storage_group_name=sg_name)
1940+
self.assertEqual(2, storage_group_details[constants.NUM_OF_VOLS])
1941+
19301942
def test_remove_volume_from_storage_group(self):
19311943
"""Test remove_volume_from_storage_group."""
19321944
volume_name = self.generate_name()
@@ -1944,6 +1956,25 @@ def test_remove_volume_from_storage_group(self):
19441956
self.provisioning.add_existing_volume_to_storage_group(
19451957
storage_group_name, device_id)
19461958

1959+
def test_remove_volume_from_srdf_storage_group(self):
1960+
"""Tests adding a volume to a storage group that is replicated"""
1961+
sg_name, srdf_group_number, device_id, remote_volume = (
1962+
self.create_rdf_sg())
1963+
self.provisioning.add_new_volume_to_storage_group(
1964+
storage_group_id=sg_name, vol_size=1, num_vols=1, cap_unit='GB',
1965+
remote_array_1_id=self.conn.remote_array,
1966+
remote_array_1_sgs=[sg_name])
1967+
storage_group_details = self.provisioning.get_storage_group(
1968+
storage_group_name=sg_name)
1969+
self.assertEqual(2, storage_group_details[constants.NUM_OF_VOLS])
1970+
self.provisioning.remove_volume_from_storage_group(
1971+
storage_group_id=sg_name, vol_id=device_id,
1972+
remote_array_1_id=self.conn.remote_array,
1973+
remote_array_1_sgs=[sg_name])
1974+
storage_group_details = self.provisioning.get_storage_group(
1975+
storage_group_name=sg_name)
1976+
self.assertEqual(1, storage_group_details[constants.NUM_OF_VOLS])
1977+
19471978
def test_remove_vol_from_storagegroup(self):
19481979
"""Test remove_vol_from_storagegroup."""
19491980
volume_name = self.generate_name()

PyU4V/tests/unit_tests/pyu4v_common_data.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class CommonData(object):
2020

2121
array = '000197800123'
2222
remote_array = '000197800124'
23+
remote_array2 = '000197800125'
2324
srp = 'SRP_1'
2425
slo = 'Diamond'
2526
workload = 'DSS'

PyU4V/tests/unit_tests/test_pyu4v_provisioning.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,39 @@ def test_add_new_vol_to_storage_group_name_async(self):
14551455
mock_mod.assert_called_once_with(
14561456
self.data.storagegroup_name, payload)
14571457

1458+
def test_add_new_vol_to_storage_group_srdf_multihop_srdf(self):
1459+
"""Test adding new volume to replicated storage group."""
1460+
remote_array = '000197800124'
1461+
remote_array2 = '000197800125'
1462+
num_of_volumes = 1
1463+
volume_size = 10
1464+
payload = {'editStorageGroupActionParam': {
1465+
'expandStorageGroupParam': {
1466+
'addVolumeParam': {
1467+
'emulation': 'FBA',
1468+
'create_new_volumes': False,
1469+
'volumeAttributes': [{
1470+
'num_of_vols': 1,
1471+
'volume_size': 10,
1472+
'capacityUnit': 'GB'}],
1473+
'remoteSymmSGInfoParam': {
1474+
'remote_symmetrix_1_id': remote_array,
1475+
'remote_symmetrix_1_sgs': ['PU-mystoragegroup-SG'],
1476+
'remote_symmetrix_2_id': remote_array2,
1477+
'remote_symmetrix_2_sgs': ['PU-mystoragegroup-SG']
1478+
}}}}}
1479+
with mock.patch.object(
1480+
self.provisioning, 'modify_storage_group') as mock_mod:
1481+
self.provisioning.add_new_volume_to_storage_group(
1482+
storage_group_id=self.data.storagegroup_name,
1483+
num_vols=num_of_volumes, vol_size=volume_size, cap_unit='GB',
1484+
remote_array_1_id=self.data.remote_array,
1485+
remote_array_1_sgs=self.data.storagegroup_name,
1486+
remote_array_2_id=self.data.remote_array2,
1487+
remote_array_2_sgs=self.data.storagegroup_name)
1488+
mock_mod.assert_called_once_with(
1489+
self.data.storagegroup_name, payload)
1490+
14581491
def test_remove_vol_from_storage_group_volume_string(self):
14591492
"""Test remove_vol_from_storage_group single volume."""
14601493
payload = {'editStorageGroupActionParam': {
@@ -1468,6 +1501,31 @@ def test_remove_vol_from_storage_group_volume_string(self):
14681501
mock_mod.assert_called_once_with(
14691502
self.data.storagegroup_name, payload)
14701503

1504+
def test_remove_vol_from_replicated_storage_group_multihop(self):
1505+
"""Test remove_vol_from_storage_group single volume."""
1506+
payload = {
1507+
'editStorageGroupActionParam': {
1508+
'removeVolumeParam': {
1509+
'volumeId': [self.data.device_id],
1510+
'remoteSymmSGInfoParam': {
1511+
'remote_symmetrix_1_id': self.data.remote_array,
1512+
'remote_symmetrix_1_sgs': [
1513+
self.data.storagegroup_name],
1514+
'remote_symmetrix_2_id': self.data.remote_array2,
1515+
'remote_symmetrix_2_sgs': [self.data.storagegroup_name]
1516+
}}}}
1517+
with mock.patch.object(
1518+
self.provisioning, 'modify_storage_group') as mock_mod:
1519+
self.provisioning.remove_volume_from_storage_group(
1520+
storage_group_id=self.data.storagegroup_name,
1521+
vol_id=self.data.device_id,
1522+
remote_array_1_id=self.data.remote_array,
1523+
remote_array_1_sgs=self.data.storagegroup_name,
1524+
remote_array_2_id=self.data.remote_array2,
1525+
remote_array_2_sgs=self.data.storagegroup_name)
1526+
mock_mod.assert_called_once_with(
1527+
self.data.storagegroup_name, payload)
1528+
14711529
def test_remove_vol_from_storage_group_volume_list(self):
14721530
"""Test remove_vol_from_storage_group multiple volumes."""
14731531
payload = {'executionOption': 'ASYNCHRONOUS',

PyU4V/tests/unit_tests/test_pyu4v_replication.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,17 @@ def test_create_storage_group_snap(self):
146146
self.data.storagegroup_name, 'snap_name', ttl=2, hours=True)
147147
self.assertEqual(2, mock_create.call_count)
148148

149+
def test_create_storage_group_snapshot_secure(self):
150+
"""Test create_storage_group_snapshot."""
151+
with mock.patch.object(
152+
self.replication, 'create_resource') as mock_create:
153+
self.replication.create_storage_group_snapshot(
154+
self.data.storagegroup_name, 'snap_name')
155+
self.replication.create_storage_group_snapshot(
156+
self.data.storagegroup_name, 'snap_name', ttl=2, hours=True,
157+
secure=True)
158+
self.assertEqual(2, mock_create.call_count)
159+
149160
def test_get_storagegroup_snapshot_generation_list(self):
150161
"""Test get_storagegroup_snapshot_generation_list."""
151162
gen_list = self.replication.get_storagegroup_snapshot_generation_list(

PyU4V/utils/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
DELETE = 'DELETE'
3131

3232
# Unisphere REST URI constants
33-
PYU4V_VERSION = '9.1.1.0'
33+
PYU4V_VERSION = '9.1.2.0'
3434
UNISPHERE_VERSION = '91'
3535
VERSION = 'version'
3636
ITERATOR = 'Iterator'

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ PyU4V Version 9.1
2626
+-----------------------+----------------------------+
2727
| **Author** | Dell EMC |
2828
+-----------------------+----------------------------+
29-
| **PyU4V Version** | 9.1.1.0 |
29+
| **PyU4V Version** | 9.1.2.0 |
3030
+-----------------------+----------------------------+
3131
| **Unisphere Version** | 9.1.0.5 |
3232
+-----------------------+----------------------------+

docs/build/doctrees/PyU4V.doctree

-652 KB
Binary file not shown.
-9.92 KB
Binary file not shown.

docs/build/doctrees/api.doctree

-245 Bytes
Binary file not shown.
-4.96 KB
Binary file not shown.
-1.44 KB
Binary file not shown.
-3.55 KB
Binary file not shown.

docs/build/doctrees/genindex.doctree

-198 Bytes
Binary file not shown.

docs/build/doctrees/index.doctree

-3.76 KB
Binary file not shown.
-2.24 KB
Binary file not shown.
-842 Bytes
Binary file not shown.
-2.17 KB
Binary file not shown.
-651 Bytes
Binary file not shown.

docs/build/doctrees/support.doctree

-495 Bytes
Binary file not shown.

docs/build/doctrees/tools.doctree

14.5 KB
Binary file not shown.

docs/build/html/.buildinfo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Sphinx build info version 1
22
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
3-
config: 5eaf754d565a9e60b6e3dcf2f18ef4bb
3+
config: b2097566cc03ccad42629cea0334c864
44
tags: 645f666f9bcd5a90fca523b33c5a78b7

0 commit comments

Comments
 (0)