Skip to content

Commit be05d40

Browse files
authored
Merge pull request #3 from mksteele/master
Adding some example FA2.0 Python SDK scripts
2 parents 1369263 + 5345245 commit be05d40

File tree

5 files changed

+349
-0
lines changed

5 files changed

+349
-0
lines changed

FlashArray_2X/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Example FlashArray REST 2.X REST API Scripts
2+
3+
The 2.X python SDK can be found here: https://pypi.org/project/py-pure-client
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""
2+
3+
Download latest pypureclient release from:
4+
https://pypi.org/project/py-pure-client
5+
6+
Example usage:
7+
$ python basic_storage_workflow.py --target pure001 --id-token eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRjMTIzY2EwLTllNDktNDhiZS1iNWQwLTViMGVjMTUxODIwYiJ9.eyJhdWQiOiJmMDI5MGUzNS1hODFlLTQyNzQtODIyYy1mZTMwNmI2NzAxMjUiLCJpc3MiOiJjbGllbnQiLCJyb2xlIjoiYXJyYXlfYWRtaW4iLCJleHAiOjE1OTkxNjUxNjEsImlhdCI6MTU2ODA2MTE2MSwidHlwIjoiSldUIiwic3ViIjoicHVyZXVzZXIifQ.iKujyQZoVRmGLpjxfySBlHfCTq3APNw6mgkMOv35DQ1_MjR-w1r0Rx62XTimLqOKc5ksqhEfvIC62iDJmK70jNPF3XePnMPlpbTmzMKuzGs1xdbVNyqQOCmL4Fk61Wa67frF789PoE0aYtS8Z3vMAYspLnSAcAQk0VGXkaPrbSunEuIABbFgBiqQLAaudPn1Ulm9CA8anqm3X2cjaMWBI8Z3VofiBPDTOGwE9UOMp27zZpCEygBHbuCIBXRhHca_2ycBQ5WbJGF8l3OtrVOva_mFWk2GFWXxhdUZSPeEl1MxNNWiXwL8Y5vGJGiGpnAtucQKBV7LKjYSF9S38q8pjA
8+
Created volume v3 with provisioned size 1048576
9+
Created volume v2 with provisioned size 1048576
10+
Created volume v1 with provisioned size 1048576
11+
Created host h1 with iqns ['iqn.2001-04.com.ex:sn-a8675308']
12+
Created connection between host h1 and volume v3
13+
Created connection between host h1 and volume v2
14+
Created connection between host h1 and volume v1
15+
Deleted connections
16+
Deleted host h1
17+
Set volume v3 destroyed to True
18+
Set volume v2 destroyed to True
19+
Set volume v1 destroyed to True
20+
Eradicated volumes v1,v2,v3
21+
22+
If you get "PureError: Could not retrieve a new access token", then your
23+
id_token is not valid.
24+
25+
"""
26+
import argparse
27+
28+
from pypureclient import flasharray
29+
from util import print_errs
30+
31+
VOLUME_NAMES = "v1,v2,v3"
32+
HOST_NAME = "h1"
33+
HOST_IQN = "iqn.2001-04.com.ex:sn-a8675308"
34+
35+
def setup(client):
36+
# Create 3 volumes
37+
volume_body = flasharray.Volume(provisioned=1048576)
38+
resp = client.post_volumes(names=VOLUME_NAMES, volume=volume_body)
39+
assert resp.status_code == 200, print_errs(resp)
40+
for volume in list(resp.items):
41+
print("Created volume {} with provisioned size {}".format(volume.name, volume.provisioned))
42+
43+
# Create 1 host
44+
resp = client.post_hosts(names=HOST_NAME, host=flasharray.Host(iqns=[HOST_IQN]))
45+
assert resp.status_code == 200, print_errs(resp)
46+
for host in list(resp.items):
47+
print("Created host {} with iqns {}".format(host.name, host.iqns))
48+
49+
# Connect volumes to this host
50+
resp = client.post_connections(host_names=HOST_NAME, volume_names=VOLUME_NAMES)
51+
assert resp.status_code == 200
52+
for connection in list(resp.items):
53+
print("Created connection between host {} and volume {}".format(connection.host.name, connection.volume.name))
54+
55+
56+
def cleanup(client):
57+
resp = client.delete_connections(host_names=HOST_NAME, volume_names=VOLUME_NAMES)
58+
assert resp.status_code == 200, print_errs(resp)
59+
print("Deleted connections")
60+
61+
resp = client.delete_hosts(names=HOST_NAME)
62+
assert resp.status_code == 200, print_errs(resp)
63+
print("Deleted host {}".format(HOST_NAME))
64+
65+
destroy_patch = flasharray.Volume(destroyed=True)
66+
resp = client.patch_volumes(names=VOLUME_NAMES, volume=destroy_patch)
67+
assert resp.status_code == 200, print_errs(resp)
68+
for volume in list(resp.items):
69+
print("Set volume {} destroyed to {}".format(volume.name, volume.destroyed))
70+
71+
resp = client.delete_volumes(names=VOLUME_NAMES)
72+
assert resp.status_code == 200, print_errs(resp)
73+
print("Eradicated volumes {}".format(VOLUME_NAMES))
74+
75+
76+
if __name__ == "__main__":
77+
parser = argparse.ArgumentParser()
78+
parser.add_argument('--target', help='FlashArray to run script against', required=True)
79+
parser.add_argument('--id-token', help='OAuth2 identity token', required=True)
80+
81+
args = parser.parse_args()
82+
client = flasharray.Client(target=args.target, id_token=args.id_token)
83+
try:
84+
setup(client)
85+
finally:
86+
cleanup(client)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
"""
2+
Create clones on a remote array of all volumes in a pgroup.
3+
4+
Download latest pypureclient release from:
5+
https://pypi.org/project/py-pure-client
6+
7+
Example usage:
8+
$ python clone_all_snapshots_in_pgroup.py --source pure001 --target pure002 --target-id-token eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImQ2ODNkOWZjLTY0MzUtNDQwOC1iMjEzLTc0ZGUwNTY0ODEzMSJ9.eyJhdWQiOiIxYTZiNjY1NS02ZTdmLTRkMjMtYTY4NC1lYjZiYzljZWNjYmMiLCJpc3MiOiJwZ0NsaWVudCIsInJvbGUiOiJhcnJheV9hZG1pbiIsImV4cCI6MTU5OTE3Mjg3NywiaWF0IjoxNTY4MDY4ODc3LCJ0eXAiOiJKV1QiLCJzdWIiOiJwdXJldXNlciJ9.G8aNq4Jz0PGnFoZD31Y9d_ux-fdHuzJB4R3QPl-zw4V8htB1MCwvQxKTCQ6F8uuhy61yCL18l6rqKmBcPhrrPDQCLF_lJsMBUNycJVQd-DnfWqYIzAzcpMPAf_zNHekEVXfJZd_VQ3Uv4YC2mVtPe6OKR3JiBy42bJ5tDcax4UrUDEPXg-V1QOWhmtycrYCQp2hfQnqtZMQhtOzrsJhN_9DXCpreasgKimBvWhMfbKZyJGdpELGJhO6ItcmeqdiSDSe-t-os4PYonNgB_we2IlpyDHQpQj1lCF5SSqaqFOrx1lyb-Sc0UXitKrSFs8oS5Rs8UXhN6h9gSb8T-v8sJg --source-api-token dfc11fc6-0287-fe15-c4f9-c4e5a7ce5d7d
9+
Enter name of source pgroup: pg1
10+
Found volumes in pg 'pg1': [u'vol1', u'vol2']
11+
Confirmed that script target is in pgroup targets
12+
Snapshotting pgroup 'pg1' and replicating now to pgroup targets '[{u'name': u'pure002', u'allowed': True}]'...
13+
Enter clone suffix (hit enter for no suffix): clone2
14+
Checking to see that replication has completed...
15+
Snapshot 'pure001:pg1.23.vol1': started 1568071967000, progress 0.0
16+
Snapshot 'pure001:pg1.23.vol1': started 1568071967000, progress 1.0
17+
Creating 'vol1-clone2' from 'pure001:pg1.23.vol1'
18+
Checking to see that replication has completed...
19+
Snapshot 'pure001:pg1.23.vol2': started 1568071967000, progress 1.0
20+
Creating 'vol2-clone2' from 'pure001:pg1.23.vol2'
21+
22+
If you get "PureError: Could not retrieve a new access token", then your
23+
id_token is not valid.
24+
25+
"""
26+
import argparse
27+
import purestorage
28+
import requests
29+
import sys
30+
import time
31+
32+
from pypureclient import flasharray
33+
from requests.packages.urllib3.exceptions import InsecureRequestWarning
34+
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
35+
from util import print_errs
36+
37+
# Making this script work with either python2 or 3
38+
try:
39+
input = raw_input
40+
except NameError:
41+
pass
42+
43+
"""
44+
45+
REST API CALLS
46+
Separated so I can swap out 1.X and 2.X clients easily.
47+
48+
In this script, I have to make some calls in 1.X since they
49+
aren't supported in 2.X yet.
50+
51+
"""
52+
def get_volumes_in_pgroup(client_1x, pg_name):
53+
return client_1x.list_pgroups(names=pg_name)[0]["volumes"]
54+
55+
def get_pgroup_targets(client_1x, pg_name):
56+
return client_1x.list_pgroups(names=pg_name)[0]["targets"]
57+
58+
def replicate_now(client_1x, pg_name):
59+
return client_1x.create_pgroup_snapshot(pg_name, replicate_now=True)["name"]
60+
61+
def get_transfer_stats(client_2x, pg_vol_snap_name):
62+
""" Returns a tuple of (started time, progress) """
63+
resp = client_2x.get_volume_snapshots_transfer(names=pg_vol_snap_name)
64+
assert resp.status_code == 200, print_errs(resp)
65+
transfer_stats = list(resp.items)[0]
66+
return (transfer_stats.started, transfer_stats.progress)
67+
68+
def create_clone(client_2x, clone_name, pg_vol_snap_name):
69+
volume_body = flasharray.Volume(source={"name": pg_vol_snap_name})
70+
resp = client_2x.post_volumes(names=clone_name, volume=volume_body)
71+
assert resp.status_code == 200, print_errs(resp)
72+
73+
"""
74+
SCRIPT FUNCTIONS
75+
"""
76+
def clone_new_volumes(target_client, source_array_name, source_snapshot_name, source_volumes, clone_suffix=None):
77+
# For each volume in the source pg snap, create a new volume with suffix clone_suffix
78+
for volume in source_volumes:
79+
full_pg_vol_snap_name = "{}:{}.{}".format(source_array_name, source_snapshot_name, volume)
80+
print("Checking to see that replication has completed...")
81+
progress = 0.0
82+
while progress < 1.0:
83+
transfer_stats = get_transfer_stats(target_client, full_pg_vol_snap_name)
84+
print("Snapshot '{}': started {}, progress {}".format(full_pg_vol_snap_name,
85+
transfer_stats[0], transfer_stats[1]))
86+
progress = transfer_stats[1]
87+
time.sleep(1)
88+
89+
clone_name = "{}-{}".format(volume, clone_suffix) if clone_suffix else volume
90+
print("Creating '{}' from '{}'".format(clone_name, full_pg_vol_snap_name))
91+
create_clone(target_client, clone_name, full_pg_vol_snap_name)
92+
93+
def main(source, target, source_client, target_client):
94+
pg_name = input("Enter name of source pgroup: ")
95+
source_volumes = get_volumes_in_pgroup(source_client, pg_name)
96+
source_targets = get_pgroup_targets(source_client, pg_name)
97+
98+
print("Found volumes in pg '{}': {}".format(pg_name, source_volumes))
99+
target_found = False
100+
for source_target in source_targets:
101+
if source_target["name"] == target:
102+
if not source_target["allowed"]:
103+
print("Target '{}' is not allowed; exiting...".format(
104+
target))
105+
sys.exit(1)
106+
target_found = True
107+
108+
if target_found:
109+
print("Confirmed that script target is in pgroup targets")
110+
else:
111+
print("Target '{}' is not in pgroup targets '{}'; exiting...".format(
112+
target, source_targets))
113+
sys.exit(1)
114+
115+
print("Snapshotting pgroup '{}' and replicating now to pgroup targets '{}'...".format(
116+
pg_name, source_targets))
117+
source_snapshot_name = replicate_now(source_client, pg_name)
118+
119+
clone_suffix = input("Enter clone suffix (hit enter for no suffix): ")
120+
clone_new_volumes(target_client, source, source_snapshot_name, source_volumes, clone_suffix)
121+
122+
if __name__ == "__main__":
123+
parser = argparse.ArgumentParser(description='Get all volumes in a pgroup, then snap/clone them to a remote target.')
124+
parser.add_argument('--source', help='Source array. PG Snapshot will be taken here.', required=True)
125+
parser.add_argument('--target', help='Target array. Clone volumes will be created here.', required=True)
126+
parser.add_argument('--source-api-token', help='1.X API token for source array', required=True)
127+
parser.add_argument('--target-id-token', help='OAuth2 identity token for target array', required=True)
128+
129+
args = parser.parse_args()
130+
source_1x_client = purestorage.FlashArray(args.source, api_token=args.source_api_token)
131+
target_2x_client = flasharray.Client(target=args.target, id_token=args.target_id_token)
132+
133+
main(args.source, args.target, source_1x_client, target_2x_client)

FlashArray_2X/util.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""
2+
Useful utilities for the FA2.X python client.
3+
"""
4+
5+
def print_errs(resp):
6+
for error in resp.errors:
7+
if error.context:
8+
return "error on {}: {}".format(error.context, error.message)
9+
return "error: {}".format(error.message)
10+

clone_all_snapshots_in_pgroup.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""
2+
Create clones on a remote array of all volumes in a pgroup.
3+
4+
Example usage:
5+
$ python clone_all_snapshots_in_pgroup.py --source pure001 --target pure002 --source-api-token 148b2cca-264d-16ea-a1c9-6ff5fa03c3b4 --target-api-token f5f64bc2-2502-b0f0-30d6-a7408d1ed039
6+
Enter name of source pgroup: pg1
7+
Found volumes in pg 'pg1': [u'v1', u'v2', u'v3']
8+
Confirmed that script target is in pgroup targets
9+
Snapshotting pgroup 'pg1' and replicating now to pgroup targets '[{u'name': u'pure002', u'allowed': True}]'...
10+
Enter clone suffix (hit enter for no suffix): clone
11+
Checking to see that replication has completed...
12+
Snapshot 'pure001:pg1.5.v1': started 2019-09-10T00:03:27Z, progress 1.0
13+
Creating 'v1-clone' from 'pure001:pg1.5.v1'
14+
Checking to see that replication has completed...
15+
Snapshot 'pure001:pg1.5.v2': started 2019-09-10T00:03:27Z, progress 1.0
16+
Creating 'v2-clone' from 'pure001:pg1.5.v2'
17+
Checking to see that replication has completed...
18+
Snapshot 'pure001:pg1.5.v3': started 2019-09-10T00:03:27Z, progress 1.0
19+
Creating 'v3-clone' from 'pure001:pg1.5.v3'
20+
"""
21+
import argparse
22+
import purestorage
23+
import requests
24+
import sys
25+
import time
26+
27+
from requests.packages.urllib3.exceptions import InsecureRequestWarning
28+
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
29+
30+
# Making this script work with either python2 or 3
31+
try:
32+
input = raw_input
33+
except NameError:
34+
pass
35+
36+
37+
"""
38+
REST API CALLS
39+
"""
40+
def get_volumes_in_pgroup(client, pg_name):
41+
return client.list_pgroups(names=pg_name)[0]["volumes"]
42+
43+
def get_pgroup_targets(client, pg_name):
44+
return client.list_pgroups(names=pg_name)[0]["targets"]
45+
46+
def replicate_now(client, pg_name):
47+
return client.create_pgroup_snapshot(pg_name, replicate_now=True)["name"]
48+
49+
def get_transfer_stats(client, pg_vol_snap_name):
50+
""" Returns a tuple of (started time, progress) """
51+
transfer_stats = client.list_volumes(names=pg_vol_snap_name, snap=True, transfer=True)[0]
52+
return (transfer_stats["started"], transfer_stats["progress"])
53+
54+
def create_clone(client, clone_name, pg_vol_snap_name):
55+
client.copy_volume(pg_vol_snap_name, clone_name)
56+
57+
"""
58+
SCRIPT FUNCTIONS
59+
"""
60+
def clone_new_volumes(target_client, source_array_name, source_snapshot_name, source_volumes, clone_suffix=None):
61+
# For each volume in the source pg snap, create a new volume with suffix clone_suffix
62+
for volume in source_volumes:
63+
full_pg_vol_snap_name = "{}:{}.{}".format(source_array_name, source_snapshot_name, volume)
64+
print("Checking to see that replication has completed...")
65+
progress = 0.0
66+
while progress < 1.0:
67+
transfer_stats = get_transfer_stats(target_client, full_pg_vol_snap_name)
68+
print("Snapshot '{}': started {}, progress {}".format(full_pg_vol_snap_name,
69+
transfer_stats[0], transfer_stats[1]))
70+
progress = transfer_stats[1]
71+
time.sleep(1)
72+
73+
clone_name = "{}-{}".format(volume, clone_suffix) if clone_suffix else volume
74+
print("Creating '{}' from '{}'".format(clone_name, full_pg_vol_snap_name))
75+
create_clone(target_client, clone_name, full_pg_vol_snap_name)
76+
77+
def main(source, target, source_client, target_client):
78+
pg_name = input("Enter name of source pgroup: ")
79+
source_volumes = get_volumes_in_pgroup(source_client, pg_name)
80+
source_targets = get_pgroup_targets(source_client, pg_name)
81+
82+
print("Found volumes in pg '{}': {}".format(pg_name, source_volumes))
83+
target_found = False
84+
for source_target in source_targets:
85+
if source_target["name"] == target:
86+
if not source_target["allowed"]:
87+
print("Target '{}' is not allowed; exiting...".format(
88+
target))
89+
sys.exit(1)
90+
target_found = True
91+
92+
if target_found:
93+
print("Confirmed that script target is in pgroup targets")
94+
else:
95+
print("Target '{}' is not in pgroup targets '{}'; exiting...".format(
96+
target, source_targets))
97+
sys.exit(1)
98+
99+
print("Snapshotting pgroup '{}' and replicating now to pgroup targets '{}'...".format(
100+
pg_name, source_targets))
101+
source_snapshot_name = replicate_now(source_client, pg_name)
102+
103+
clone_suffix = input("Enter clone suffix (hit enter for no suffix): ")
104+
clone_new_volumes(target_client, source, source_snapshot_name, source_volumes, clone_suffix)
105+
106+
if __name__ == "__main__":
107+
parser = argparse.ArgumentParser(description='Get all volumes in a pgroup, then snap/clone them to a remote target.')
108+
parser.add_argument('--source', help='Source array. PG Snapshot will be taken here.', required=True)
109+
parser.add_argument('--target', help='Target array. Clone volumes will be created here.', required=True)
110+
parser.add_argument('--source-api-token', help='1.X API token for source array', required=True)
111+
parser.add_argument('--target-api-token', help='1.X API token for target array', required=True)
112+
113+
args = parser.parse_args()
114+
source_client = purestorage.FlashArray(args.source, api_token=args.source_api_token)
115+
target_client = purestorage.FlashArray(args.target, api_token=args.target_api_token)
116+
117+
main(args.source, args.target, source_client, target_client)

0 commit comments

Comments
 (0)