📋Prerequisite – Creating an IBM Cloud API key
Before running this procedure, ensure you have an IBM Cloud API key.
You can create one in the IBM Cloud console by navigating to:
IBM Cloud Console → Manage → Access (IAM) → API keys → Create
IBM documentation:
“Creating an API key” – https://cloud.ibm.com/docs/account?topic=account-userapikey
This key is required for authentication when using IBM Cloud APIs.
Download it as JSON if you want this runbook to read the key directly from apikey.json.
⚠️ Security Warning
API keys provide full programmatic access to your IBM Cloud account.
Handle them with the same care as a password:
- Never store API keys in source code repositories.
- Never share them in scripts, emails, or screenshots.
- Restrict permissions to the minimum required IAM roles.
- Rotate keys periodically and delete unused keys immediately.
- If a key is exposed, revoke it at once from the IAM console.
📝 Step‑by‑Step Procedures
⚙️1. To begin, create a .env file…
This file defines the variables, functions, and aliases used by the GRS runbook procedures.
- Create an environment file (for example, .env_grs).
- Paste the sample content provided below and replace the green text with your own data.
- Load the environment file in your shell:
source .env_grs.
# authentication
# api_key="put_your_token_here" # Optional placeholder: hardcode the API key here (not recommended)
api_key=$(jq -r '.apikey' apikey.json) ## Recommended: load the API key securely from apikey.json
header_json="Content-Type: application/json"
header_accept="Accept: application/json"
iam_token=$(curl -s -X POST "https://iam.cloud.ibm.com/identity/token" -H "Content-Type: application/x-www-form-urlencoded" -H "$header_accept" -d "grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey=${api_key}" | jq -r '.access_token')
header_auth="Authorization: Bearer $iam_token"
# Current date (YYYY-MM-DD). Required for Transit Gateway API versioning.
version=$(date +%F)
# base endpoints per region
base_syd="https://syd.power-iaas.cloud.ibm.com"
base_sao="https://sao.power-iaas.cloud.ibm.com"
base_mon="https://mon.power-iaas.cloud.ibm.com"
base_tor="https://tor.power-iaas.cloud.ibm.com"
base_eu_de="https://eu-de.power-iaas.cloud.ibm.com"
base_lon="https://lon.power-iaas.cloud.ibm.com"
base_che="https://che.power-iaas.cloud.ibm.com"
base_tok="https://tok.power-iaas.cloud.ibm.com"
base_osa="https://osa.power-iaas.cloud.ibm.com"
base_mad="https://mad.power-iaas.cloud.ibm.com"
base_us_east="https://us-east.power-iaas.cloud.ibm.com"
base_us_south="https://us-south.power-iaas.cloud.ibm.com"
# Define the API endpoints for the source and target regions.
source_url=$base_eu_de
target_url=$base_mad
# Source and Target Workspace CRNs
ws_source="INSERT_SOURCE_WORKSPACE_CRN_HERE"
ws_target="INSERT_TARGET_WORKSPACE_CRN_HERE"
# Source and Target VSI IDs
vsi_id="INSERT_SOURCE_VSI_ID_HERE"
tgvsi_id="INSERT_TARGET_VSI_ID_HERE"
# Source and Target Cloud Instance ID - Workspace ID
ci_source="INSERT_SOURCE_WORKSPACE_ID"
ci_target="INSERT_TARGET_WORKSPACE_ID"
# Transit Gateway ID
TGW_ID="INSERT_TRANSIT_GATEWAY_ID_HERE"
# Source and Target Volume Group Name
vg_name="SOURCE_VOLUME_GROUP_NAME" # Name of the Volume Group to be created.
vol_com_name="IBMiGRS" # Prefix used to identify source VSI volumes. Adjust to match your naming convention. Example: IBMiGRS-ed88e3ad-00017083-data-19
tgvol_com_name="IBMiGRSF" # Prefix used to identify target auxiliary volumes. Adjust as needed. Example: IBMiGRSF-41613-7
# Prefix Filter CIDR. Leave unchanged if prefix filtering is not used.
pfip="aaa.bbb.ccc.ddd/mm"
###################### Below this line, there's no need to change anything ######################
# Helper function: sets environment parameters for source or target workspace.
set_node() {
case "$1" in
source) base_url=$source_url;CRN=$ws_source;PVM_ID=$vsi_id;CLOUD_INSTANCE_ID=$ci_source ;;
target) base_url=$target_url;CRN=$ws_target;PVM_ID=$tgvsi_id;CLOUD_INSTANCE_ID=$ci_target ;;
*) echo "invalid region: $1" ;;
esac
}
# Workspace management aliases
alias ws_ls='curl -sX GET $base_url/v1/workspaces -H "$header_auth" -H "$header_json"'
# Instance (VSI) management aliases
alias ins_get='curl -sX GET $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/pvm-instances/$PVM_ID -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias ins_vol_ls='curl -sX GET $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/pvm-instances/$PVM_ID/volumes -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias ins_vol_bdet='curl -X DELETE $base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/pvm-instances/$PVM_ID/volumes -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
alias ins_ls='curl -sX GET $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/pvm-instances -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias ins_act='curl -X POST $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/pvm-instances/$PVM_ID/action -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
# Volume management aliases
alias vol_ls='curl -sX GET $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volumes -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vol_get='curl -sX GET $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/pvm-instances/$PVM_ID/volumes/$VOL_ID -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vol_act='curl -X POST $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volumes/$VOL_ID/action -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
alias vol_att='curl -X POST $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/pvm-instances/$PVM_ID/volumes/$VOL_ID -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vol_att_multi='curl -X POST $base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/pvm-instances/$PVM_ID/volumes -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
alias vol_del='curl -X DELETE $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volumes/$VOL_ID -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vol_bdel='curl -X DELETE $base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/volumes -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
# Volume cloning operations (attached and detached methods)
alias vol_cl_cr='curl -X POST "$base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/volumes-clone" -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
alias vol_cl_get='curl -sX GET "$base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/volumes-clone/$VOL_CLONE_ID" -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vol_cl_ls='curl -sX GET "$base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/volumes-clone" -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vol_cl_del='curl -X DELETE "$base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/volumes-clone/$VOL_CLONE_ID" -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vol_cl_st='curl -L -X POST $base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/volumes-clone/$VOL_CLONE_ID/start -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vol_cl_ex='curl -L -X POST $base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/volumes-clone/$VOL_CLONE_ID/execute -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
alias vol_cl_ca='curl -L -X POST $base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/volumes-clone/$VOL_CLONE_ID/cancel -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{\"force\": $FORCE}"'
alias vol_det_cl_cr='curl -X POST $base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/volumes/clone -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
alias vol_det_cl_ls='curl -X GET $base_url/pcloud/v2/cloud-instances/$CLOUD_INSTANCE_ID/volumes/clone-tasks/$CLONE_TASK_ID -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
# Volume Group management aliases
alias vg_cr='curl -X POST $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volume-groups -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
alias vg_ls='curl -sX GET $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volume-groups -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vg_rcr='curl -sX GET $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volume-groups/$VOLUME_GROUP_ID/remote-copy-relationships -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vg_sd='curl -sX GET $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volume-groups/$VOLUME_GROUP_ID/storage-details -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vg_get='curl -sX GET $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volume-groups/$VOLUME_GROUP_ID/details -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vg_act='curl -X POST $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volume-groups/$VOLUME_GROUP_ID/action -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
alias vg_del='curl -X DELETE $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volume-groups/$VOLUME_GROUP_ID -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias vg_upd='curl -X PUT $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volume-groups/$VOLUME_GROUP_ID -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
# Auxiliary volume onboarding aliases
alias on_ls='curl -sX GET $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volumes/onboarding -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
alias on_cr='curl -X POST $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volumes/onboarding -H "$header_auth" -H "CRN: $CRN" -H "$header_json" -d "{$ACTIONS}"'
alias on_get='curl -sX GET $base_url/pcloud/v1/cloud-instances/$CLOUD_INSTANCE_ID/volumes/onboarding/$VOLUME_ONBOARDING_ID -H "$header_auth" -H "CRN: $CRN" -H "$header_json"'
# Transit Gateway prefix filter and connection aliases
alias tg_gws='curl -sX GET -L -H "$header_auth" -H "$header_accept" "${base_url}/transit_gateways?version=$version"'
alias tg_cs='curl -sX GET -L -H "$header_auth" -H "$header_accept" "${base_url}/connections?version=$version"'
alias tg_pfs='curl -sX GET -L -H "$header_auth" -H "$header_accept" "$base_url/transit_gateways/$TGW_ID/connections/$CONNID/prefix_filters?version=$version"'
alias tg_pfu='curl -sX PATCH -L -H "$header_auth" -H "$header_accept" -H "$header_json" --data "{\"action\": \"$pfaction\"}" "$base_url/transit_gateways/$TGW_ID/connections/$CONNID/prefix_filters/$PFID?version=$version"'
alias tg_pfc='curl -sX POST -L -H "$header_auth" -H "$header_accept" -H "$header_json" --data "{\"action\": \"$pfaction\", \"prefix\": \"$pfip\"}" "${base_url}/transit_gateways/$TGW_ID/connections/$CONNID/prefix_filters?version=$version"'
alias tg_pfd='curl -sX DELETE -L -H "$header_auth" "$base_url/transit_gateways/$TGW_ID/connections/$CONNID/prefix_filters/$PFID?version=$version"'
# Ensures all volumes have replication enabled; enables it if required.
chk_vol_rep() {
echo
echo
echo
echo "Checking and enabling replication on volumes if not enabled…"
flag=0
for i in $vol_ids
do
VOL_ID=$i
rep_enabled=$(vol_get | jq -r .replicationEnabled)
if [[ $rep_enabled == "false" ]]
then
flag=1
echo
echo "Volume ID: $i = $rep_enabled. Enabling Replication on the Volume..."
ACTIONS='"replicationEnabled": true'
vol_act
fi
printf "."
done
echo
echo
echo "Checking done!"
if [[ $flag == "1" ]]
then
echo
echo "There were volumes with replication-enabled=false and changed to replication-enabled=true"
echo "Waiting 10 seconds…"
sleep 10
while true
do
echo
echo "The change can take some time. Rechecking…"
if vol_ls | jq -r '.volumes[]? | "\(.name) \(.replicationEnabled)"' | grep -w $vol_com_name | grep false > /dev/null
then
echo "There are still some volumes with replication-enabled=false..."
echo "Waiting 10 seconds…"
sleep 10
else
break
fi
done
fi
echo
echo "Great! All volumes are replication-enabled!"
echo
}
# Waits until all volumes reach consistent_copying state.
chk_vol_mirror() {
while true
do
echo
if vol_ls | jq -r '.volumes[]? | "\(.name) \(.mirroringState)"' | grep -w $vol_com_name | grep inconsistent_copying
then
echo
echo "These volumes are still in inconsistent_copying status."
echo "Waiting 60 seconds..."
sleep 60
else
echo
echo "All Volumes are now in consistent_copying status, you may now continue the next steps"
echo
break
fi
done
}
# Monitors onboarding until it leaves RUNNING state (SUCCESS or FAILURE).
chk_on_status() {
while true
do
echo
on_status=$(on_get| jq -r '.status')
if [[ $on_status == "RUNNING" ]]
then
on_progress=$(on_get| jq -r '.progress')
echo
echo "Onboarding Status at $on_progress%."
echo "Waiting 60 seconds..."
sleep 60
else
echo
echo "Onboarding $on_status."
echo
break
fi
done
}
# Helper selector for choosing a prefix filter.
prefix_sel() {
echo
tg_pfs | jq -r '.prefix_filters[] | "Prefix: \(.prefix) Action: \(.action)"'
echo
PS3="Select a prefix you want to $1: "
select choice in $(tg_pfs | jq -r '.prefix_filters[] | "\(.prefix)"')
do
echo
echo "You select prefix: $choice"
echo
break
done
}
# Waits for volume-clone request to reach 'prepared' state.
chk_cl_req() {
echo
echo "Clone Request ID $cl_req_id with Name $cl_req_name..."
while true
do
cl_req=$(vol_cl_ls | jq -r ".volumesClone[] | select(.name==\"$tgvol_com_name\") | \"\(.name) \(.status) \(.percentComplete) \(.volumesCloneID)\"")
cl_req_id=$(echo $cl_req | awk {'print $4'})
cl_req_name=$(echo $cl_req | awk {'print $1'})
cl_req_sts=$(echo $cl_req | awk {'print $2'})
cl_req_progress=$(echo $cl_req | awk {'print $3'})
if [[ $cl_req_sts == "prepared" || $cl_req_sts == "completed" ]]
then
echo "Is Prepared! You may continue now."
break
else
echo -ne "Status: $cl_req_sts -- Progress: $cl_req_progress%"
fi
done
}
# Disable replication on volumes
dis_rep_vol() {
echo
echo "Disabling replication on volumes IDs:"
echo "$volid_rem"
echo
for i in $volid_rem
do
VOL_ID=$i
printf "."
vol_act
done
}
🏁End of .env file
🏗️2. Configuring GRS
2.1 Load environment variables
source .env_YOUR_FILE
➡️Set the active workspace to the source environment
2.2 Select the Source Workspace
set_node source
2.3 Retrieve the IDs of the volumes attached to the selected instance.
vol_ids=$(ins_vol_ls | jq -r .volumes[].volumeID)
‼Confirm the Volume Count is equal to number of Volumes in the Source VSI
echo $vol_ids | wc -w
2.4 Check if the VSI Volumes are Replication Enabled and enable if not
chk_vol_rep
ins_vol_ls | jq -r '.volumes[]? | "\(.name) \(.replicationEnabled)"' | grep -w $vol_com_name
‼ Before creating the VG check if all volumes are consistent_copying ‼
chk_vol_mirror # Monitor volumes not yet in consistent_copying state
2.5 Create Volume Group
volids_api=$(echo $vol_ids | sed 's/ /","/g')
ACTIONS="\"name\": \"$vg_name\", \"volumeIDs\": [\"$volids_api\"]"
vg_cr
VOLUME_GROUP_ID=$(vg_ls | jq -r '.volumeGroups[]? | "\(.id) \(.name)"' | grep $vg_name | awk {'print$1'})
‼ Verify that the number of auxiliary volumes matches the number of primary volumes defined in the original LPAR
vg_rcr | jq -r .remoteCopyRelationships[].auxVolumeName | wc -l
2.6 Get Auxiliary Volume Names
auxvol_names=$(vg_rcr | jq -r .remoteCopyRelationships[].auxVolumeName)
auxvolnames=$(echo $auxvol_names | sed 's/ /,/g')
‼ Confirm auxiliary volume count matches the source VSI volume count.
echo $auxvol_names | wc -w
2.7 Get boot volume aux volume name
bootvol_auxname=$(ins_vol_ls | jq -r '.volumes[]? | "\(.bootable) \(.auxVolumeName)"' | grep $vol_com_name | grep true | awk {'print $2'})
echo $bootvol_auxname
2.8 Verify the status of the volume group
‼ Verify that the volume group is created successfully and wait for a state of consistent_copying ‼
# Retrieve Volume Group storage details (state, volume count). #
vg_sd | jq -r '"State: \(.state) - Number of Volumes: \(.numOfvols)"'
# Retrieve details about Volume Group #
vg_get | jq
# Display remote copy relationships, progress, and master volumes. #
vg_rcr | jq -r '.remoteCopyRelationships[]? | "Progress: \(.progress) -- RCR: \(.name) -- Master: \(.masterVolumeName)"'
cgname=$(vg_ls | jq -r --arg vg_name "$vg_name" '.volumeGroups[] | select(.name == $vg_name) | .consistencyGroupName')
echo $cgname
‼ Note: DO NOT continue until you have the Volume Group showing a state of consistent_copying ‼
➡️Target Workspace
2.9 Select the Target Workspace
set_node target
2.10 Onboarding Volumes
ondesc="onboard_aux_vols_$vol_com_name"
auxvolnames_api=$(echo $auxvolnames |sed 's/,/"},{"auxVolumeName": "/g')
ACTIONS=$(cat <<EOF
"Volumes": [
{
"auxiliaryVolumes": [
{
"auxVolumeName": "$auxvolnames_api"
}
],
"sourceCRN": "$ws_source"
}
],
"description": "$ondesc"
EOF
)
on_cr # Initiate onboarding of auxiliary volumes on target workspace.
VOLUME_ONBOARDING_ID=$(on_ls | jq -r --arg desc "$ondesc" '[.onboardings[] | select(.description == $desc)][-1].id')
2.11 Verify the status of the onboarding
chk_on_status # Monitors onboarding progress until completion or failure.
# Get Onboard Description, Status and Progress
on_get | jq -r '"ID: \(.id) - Description: \(.description) - Status: \(.status) - Progress: \(.progress)"' # Status RUNNING
# status should be SUCCESS, if not check the Failure Message in the output of the command below.
on_get | jq -r '"ID: \(.id) - Description: \(.description) - Status: \(.status) - Failure Message: \(.results.volumeOnboardingFailures[].failureMessage)"' # Status not RUNNING, this should return empty if SUCCESS
2.12 Check Volume Group
# Retrieve and store the target Volume Group ID. #
VOLUME_GROUP_ID=$(vg_ls | jq -r --arg cgname "$cgname" '.volumeGroups[] | select(.consistencyGroupName == $cgname) | .id')
🏁GRS Done! # Replication is now active: source VSI volumes mirrored to the target workspace.
💥3. Performing a failover - Activate Target
This procedure can be used for a real failover, or it can be used also to start the target VSI for testing, using either the auxiliary volumes or cloned auxiliary volumes.
➡️Target Workspace
3.1 Stop the auxiliary volume group and access the auxiliary volumes on the secondary location
set_node target
ACTIONS=$(cat <<EOF
"stop": {
"access": true
}
EOF
)
vg_act
3.2 Verify that the volume group is in the idling state
vg_sd | jq
ℹ️ ✅❎If you want to run a test with cloned auxiliary volumes, jump to section 5 – Clone GRS volumes for migrating a VSI.
ℹ️ If not yet attached, you must attach the target aux volumes to the VSI. If you are doing failover, only to clone aux volumes skip steps 3.3 and 3.4
✅❎ Attach the Aux Volumes to the Target VSI
3.3 Attach boot aux volume
VOL_ID=$bootvol_auxname
ACTIONS="\"volumeIDs\": [\"$bootvol_auxname\"]"
vol_att_multi
3.4 Attach all other aux volumes
vol_att=$(echo $auxvolnames | sed 's/'$bootvol_auxname',//g')
vol_att_api=$(echo "$vol_att" | sed 's/,/","/g')
ACTIONS="\"volumeIDs\": [\"$vol_att_api\"]"
vol_att_multi
ins_vol_ls | jq -r '.volumes[]? | "Aux Name: \(.auxVolumeName) -- ID: \(.volumeID)"'
ins_vol_ls | jq -r '.volumes[]? | "Aux Name: \(.auxVolumeName) -- ID: \(.volumeID)"' | wc -l
ℹ️ Note: Confirm all expected volumes are attached to the VSI.
‼ Note: Once the VG is in idling state, the VSI can be safely started. ‼
🏁 3.5 If you don't have a prefix filter to handle, you have finished!
⚠️Important Note:
At this stage, replication is stopped.
- If this failover was performed for compliance or testing purposes and you intend to continue working on the target VSI, it is recommended to reverse replication by completing step 4.1.
- If the failover occurred due to a disaster at the source location, then once the source becomes available again, you should follow the steps in section 4 to fail back, or at minimum perform step 4.1 to re-establish replication.
✅❎➡️Prefix filters
3.6 Deny Users Network on Source Workspace
⚠️ Run this step for each prefix filter used by the workload. Repeat until all required prefixes are set to deny on the source.
ℹ️This step prevents user traffic from reaching the source workspace during failover preparation.
base_url="https://transit.cloud.ibm.com/v1"
CONNID=$(tg_cs | jq -r ".connections[] | select(.network_id==\"$ws_source\") | .id")
pfaction="deny"
prefix_sel $pfaction
PFID=$(tg_pfs | jq -r ".prefix_filters[] | select(.prefix==\"$choice\") | .id")
tg_pfu | jq -r '"Prefix: \(.prefix) - ID: \(.id) - Action: \(.action)"'
echo ""
tg_pfs | jq -r '.prefix_filters[] | "Prefix: \(.prefix) Action: \(.action)"'
3.6.1 Create Prefix Filter (if it does not exist).
ℹ️Run this step only if the required prefix filter is not present.
# Prefix Filter CIDR for THIS action (create).
# Repeat for each required prefix.
pfip="aaa.bbb.ccc.ddd/mm"
base_url="https://transit.cloud.ibm.com/v1"
CONNID=$(tg_cs | jq -r ".connections[] | select(.network_id==\"$ws_source\") | .id")
pfaction="deny"
PFID=$(tg_pfc | jq -r '.id')
tg_pfs | jq ".prefix_filters[] | select(.id==\"$PFID\")"
3.6.2 Delete Prefix Filter (optional)
ℹ️Use this step only if you need to remove an existing prefix filter.
# Prefix Filter CIDR for THIS action (delete).
pfip="aaa.bbb.ccc.ddd/mm"
PFID=$(tg_pfs | jq -r ".prefix_filters[] | select(.prefix==\"$pfip\") | .id")
tg_pfd
tg_pfs | jq
3.7 Permit Users Network on Target Workspace
⚠️ Run this step for each prefix filter used by the workload. Repeat until all required prefixes are set to permit on the source.
ℹ️This step allows user traffic to reach the target workspace after DR activation.
base_url="https://transit.cloud.ibm.com/v1"
CONNID=$(tg_cs | jq -r ".connections[] | select(.network_id==\"$ws_target\") | .id")
pfaction="permit"
prefix_sel $pfaction
PFID=$(tg_pfs | jq -r ".prefix_filters[] | select(.prefix==\"$choice\") | .id")
tg_pfu | jq -r '"Prefix: \(.prefix) - ID: \(.id) - Action: \(.action)"'
echo ""
tg_pfs | jq -r '.prefix_filters[] | "Prefix: \(.prefix) Action: \(.action)"'
3.7.1 Create Prefix Filter (if it does not exist)
ℹ️ Run this step only if the required prefix filter is not present.
# Prefix Filter CIDR for THIS action (create).
# Repeat for each required prefix.
pfip="aaa.bbb.ccc.ddd/mm"
base_url="https://transit.cloud.ibm.com/v1"
CONNID=$(tg_cs | jq -r ".connections[] | select(.network_id==\"$ws_target\") | .id")
pfaction="permit"
PFID=$(tg_pfc | jq -r '.id')
tg_pfs | jq ".prefix_filters[] | select(.id==\"$PFID\")"
3.7.2 Delete Prefix Filter (optional)
ℹ️Use this step only if you need to remove an existing prefix filter.
# Prefix Filter CIDR for THIS action (delete).
pfip="aaa.bbb.ccc.ddd/mm"
PFID=$(tg_pfs | jq -r ".prefix_filters[] | select(.prefix==\"$pfip\") | .id")
tg_pfd
tg_pfs | jq
‼ Note: You may have to change the CMN Resource on the Ethernet Line Descriptions (WRKLIND) ‼‼
🏁3.8 At this point, you are working on the VSI in the secondary location.
🔙4. Performing a failback
‼ Both VSI must be SHUTOFF. Turn off the VSI on the secondary location, and also the VSI on the primary location if that's the case.
➡️Target Workspace
4.1 Synchronize the I/O updates from auxiliary volume to primary volume
set_node target
ACTIONS=$(cat <<EOF
"start": {
"source": "aux"
}
EOF
)
vg_act
# ‼ Verify whether the primaryRole value of the volume group is set to aux. Wait for a state of consistent_copying
vg_sd | jq -r '"State: \(.state) - Number of Volumes: \(.numOfvols)"'
# You can check progress with this command below #
vg_rcr | jq -r '.remoteCopyRelationships[]? | "Progress: \(.progress)% -- RCR: \(.name) -- Master: \(.masterVolumeName)"'
➡️Source Workspace
4.2 Stopping the primary volume group to disable replication
set_node source
ACTIONS=$(cat <<EOF
"stop": {
"access": true
}
EOF
)
vg_act
vg_get # Check primary volume group status
ℹ️ Wait for the replicationStatus parameter of the primary volume group to change to the disabled state
4.3 Reenabling replication on the primary volume group
ACTIONS=$(cat <<EOF
"start": {
"source": "master"
}
EOF
)
vg_act
vg_get | jq
vg_rcr | jq -r '.remoteCopyRelationships[]? | "Progress: \(.progress) -- RCR: \(.name) -- Master: \(.masterVolumeName)"'
ℹ️ Wait for the volume group to be replication-enabled and in a consistent_copying state. When replication is active, the I/O operations on the primary volume are replicated to the auxiliary volume on the secondary location.
🏁4.4 At this point you can start your primary VSI
✅❎➡️Prefix filters
# The steps below assume prefix filters are already created for this connection.
# Repeat these steps for each relevant prefix filter.
4.5 Deny Users Network on Target Workspace
base_url="https://transit.cloud.ibm.com/v1"
CONNID=$(tg_cs | jq -r ".connections[] | select(.network_id==\"$ws_target\") | .id")
pfaction="deny"
prefix_sel $pfaction
PFID=$(tg_pfs | jq -r ".prefix_filters[] | select(.prefix==\"$choice\") | .id")
tg_pfu | jq -r '"Prefix: \(.prefix) - ID: \(.id) - Action: \(.action)"'
echo ""
tg_pfs | jq -r '.prefix_filters[] | "Prefix: \(.prefix) Action: \(.action)"'
4.6 Permit Users Network on Source Workspace
base_url="https://transit.cloud.ibm.com/v1"
CONNID=$(tg_cs | jq -r ".connections[] | select(.network_id==\"$ws_source\") | .id")
pfaction="permit"
prefix_sel $pfaction
PFID=$(tg_pfs | jq -r ".prefix_filters[] | select(.prefix==\"$choice\") | .id")
tg_pfu | jq -r '"Prefix: \(.prefix) - ID: \(.id) - Action: \(.action)"'
echo ""
tg_pfs | jq -r '.prefix_filters[] | "Prefix: \(.prefix) Action: \(.action)"'
‼ Note: You may have to change the CMN Resource on the Ethernet Line Descriptions (WRKLIND) ‼‼
🏁4.7 At this point, you are working on the VSI in the primary location.
🏗️5. Clone GRS Volumes for migrating a VSI
# If you are using GRS to migrate a VSI from one region to another, create the GRS configuration as stated above and then do the steps below.
# These steps support VSI migration across PowerVS regions. i.e. from eu-de-1 to mad2, or from mad4 to eu-de-2
# This procedure can also be used to deploy a test LPAR in another region using the cloned volumes.
➡️Source Workspace
5.1 Define variables
set_node source
VOLUME_GROUP_ID=$(vg_ls | jq -r '.volumeGroups[]? | "\(.id) \(.name)"' | grep $vg_name | awk {'print$1'})
auxvol_names=$(vg_rcr | jq -r .remoteCopyRelationships[].auxVolumeName)
auxvolnames=$(echo $auxvol_names | sed 's/ /,/g')
echo $auxvol_names | wc -w # Confirm auxiliary volume count matches the source VSI volume count.
bootvol_auxname=$(ins_vol_ls | jq -r '.volumes[]? | "\(.bootable) \(.auxVolumeName)"' | grep $vol_com_name | grep true | awk {'print $2'})
cgname=$(vg_ls | jq -r --arg vg_name "$vg_name" '.volumeGroups[] | select(.name == $vg_name) | .consistencyGroupName')
➡️Target Workspace
✅❎5.2 Create Volume Clone Request of the Aux Volumes if attached to the VSI
ℹ️ If the aux volumes are detached from the VSI jump to step 5.3
set_node target
auxvolnames_clone=$(echo $auxvolnames |sed 's/,/","/g')
ACTIONS=$(cat <<EOF
"name": "$tgvol_com_name",
"volumeIDs": [
"$auxvolnames_clone"
]
EOF
)
vol_cl_cr
vol_cl_ls | jq
VOL_CLONE_ID=$(vol_cl_ls | jq -r ".volumesClone[] | select(.name==\"$tgvol_com_name\") | .volumesCloneID")
chk_cl_req
5.2.1 Start Volume Clone # Start action starts the consistency group to initiate the flash copy
vol_cl_st
vol_cl_get | jq -r '"Status: \(.status)\nProgress: \(.percentComplete)%"'
5.2.2 Execute Volume Clone # Execute action creates the cloned volumes using the volume snapshots
ACTIONS=$(cat <<EOF
"name": "$tgvol_com_name",
"targetReplicationEnabled": false
EOF
)
vol_cl_ex # Wait for 100 in "Percent Completed"
vol_cl_get | jq -r '"Status: \(.status)\nProgress: \(.percentComplete)%"'
5.2.3 Detach all aux volumes from target VSI ℹ️ If you don't have any volumes attached to the target VSI you can skip this step.
ACTIONS=$(cat <<EOF
"detachAllVolumes": true,
"detachPrimaryBootVolume" : true
EOF
)
ins_vol_bdet
5.2.4 Attach boot clone volume
cltgvol_com_name="clone-$tgvol_com_name"
boot_clname=$(vol_ls | jq -r --arg prefix "$cltgvol_com_name" '.volumes[] | select(.bootable == true and (.name | startswith($prefix))) | .name')
ACTIONS="\"volumeIDs\": [\"$boot_clname\"]"
vol_att_multi
5.2.5 Attach the other clone volumes
clvol_att=$(vol_ls | jq -r '.volumes[]? | "\(.name)"' | grep $cltgvol_com_name | grep -v $boot_clname)
clvol_att_api=$(echo $clvol_att | sed 's/ /","/g')
ACTIONS="\"volumeIDs\": [\"$clvol_att_api\"]"
vol_att_multi
5.2.6 Reenabling replication of the volumes group
# After clone is done you can reenable volume group replication
ACTIONS=$(cat <<EOF
"start": {
"source": "master"
}
EOF
)
vg_act
vg_get | jq
vg_rcr | jq -r '.remoteCopyRelationships[]? | "Progress: \(.progress) -- RCR: \(.name) -- Master: \(.masterVolumeName)"'
🏁At this point, you have completed cloning the auxiliary volumes that were attached to the VSI.
➡️Target Workspace
✅❎5.3 Create Volume Clone Request of the Aux Volumes if detached from the VSI
# Use this path when auxiliary volumes are not attached to the target VSI.
set_node target
cl_tier="tier3"
auxvolnames_clone=$(echo $auxvolnames |sed 's/,/","/g')
ACTIONS=$(cat <<EOF
"name": "$tgvol_com_name",
"volumeIDs": [
"$auxvolnames_clone"
],
"targetReplicationEnabled": false,
"targetStorageTier": "$cl_tier"
EOF
)
vol_det_cl_cr
ℹ️ Record the cloneTaskID from the output of command vol_det_cl_cr
CLONE_TASK_ID="cloneTaskID"
vol_det_cl_ls | jq -r '"Status: \(.status)\nProgress: \(.percentComplete)%"'
5.3.1 Attach boot clone volume
bootvol_auxid=$(vol_ls |jq -r --arg name "$bootvol_auxname" '.volumes[] | select(.name == $name) | .volumeID')
echo $bootvol_auxid
boot_clid=$(vol_det_cl_ls | jq -r --arg id "$bootvol_auxid" '.clonedVolumes[] | select(.sourceVolumeID == $id) | .clonedVolumeID')
boot_clname=$(vol_ls | jq -r --arg id "$boot_clid" '.volumes[] | select(.volumeID == $id) | .name')
ACTIONS="\"volumeIDs\": [\"$boot_clname\"]"
vol_att_multi
5.3.2 Attach the other clone volumes
cltgvol_com_name="clone-$tgvol_com_name"
clvol_att=$(vol_ls | jq -r '.volumes[]? | "\(.name)"' | grep "$cltgvol_com_name" | grep -v $boot_clname)
clvol_att_api=$(echo $clvol_att | sed 's/ /","/g')
ACTIONS="\"volumeIDs\": [\"$clvol_att_api\"]"
vol_att_multi
5.4 At this point you can start your VSI in the target location.
ℹ️ Note: Ensure all cloned volumes are attached as expected before starting the VSI.
ins_vol_ls | jq -r '.volumes[]? | "Name: \(.name) -- ID: \(.volumeID)"'
ins_vol_ls | jq -r '.volumes[]? | "Name: \(.name) -- ID: \(.volumeID)"' | wc -l
ACTIONS="\"action\":\"start\""
ins_act
ins_get | jq -r '"Name: \(.serverName)\nStatus: \(.status)\nSRC Timestamp: \(.srcs[0][0].timestamp)\nSRC: \(.srcs[0][0].src)"'
🏁At this point you finished cloning aux volumes that were detached from the VSI.
🏁Done! Your VSI has now been successfully migrated from the source location to the target location. If this was not a migration scenario, you can freely use this VSI for any testing purposes you require.
🗑️6. Delete Cloned Volumes
➡️Target Workspace
6.1 Detach all cloned volumes from VSI
set_node target
cltgvol_com_name="clone-$tgvol_com_name"
ACTIONS=$(cat <<EOF
"detachAllVolumes": true,
"detachPrimaryBootVolume" : true
EOF
)
ins_vol_bdet
ins_vol_ls | jq -r .volumes[].name # Wait until no volumes remain attached.
ins_get | jq # Wait to be without volumes
6.2 Delete cloned volumes
tgvolidrem=$(vol_ls | jq -r --arg prefix "$cltgvol_com_name" '.volumes[] | select(.name | startswith($prefix)) | .volumeID')
tgvolidrem_api=$(echo $tgvolidrem | sed 's/ /","/g')
echo $tgvolidrem | wc -w
ACTIONS=$(cat <<EOF
"volumeIDs": ["$tgvolidrem_api"]
EOF
)
vol_bdel
🏁Done! You have deleted the cloned volumes.
🗑️7. Delete GRS - without deleting the primary volumes
➡️Source Workspace
7.1 Get the volume IDs to remove from the VG, and remove them
set_node source
VOLUME_GROUP_ID=$(vg_ls | jq -r '.volumeGroups[]? | "\(.id) \(.name)"' | grep $vg_name | awk {'print$1'})
cgname=$(vg_ls | jq -r --arg vg_name "$vg_name" '.volumeGroups[] | select(.name == $vg_name) | .consistencyGroupName')
volid_rem=$(vol_ls | jq -r --arg prefix "$vol_com_name" '.volumes[] | select(.name | startswith($prefix)) | .volumeID')
echo $volid_rem | wc -w # Check the number of volumes; it should match the source volume count.
volidrem_api=$(echo $volid_rem | sed 's/ /","/g')
ACTIONS=$(cat <<EOF
"removeVolumes": ["$volidrem_api"]
EOF
)
vg_upd
vg_sd | jq -r '"State: \(.state)"' # Wait for "State: empty"
7.2 Delete Source VG
vg_del
vg_ls | jq
7.3 Disable replication on the volumes
# Check before
vol_ls | jq -r --arg prefix "$vol_com_name" '.volumes[] | select(.name | startswith($prefix)) | "Name: \(.name) | ReplicationEnabled: \(.replicationEnabled)"'
ACTIONS='"replicationEnabled": false'
dis_rep_vol
vol_ls | jq -r --arg prefix "$vol_com_name" '.volumes[] | select(.name | startswith($prefix)) | "Name: \(.name) | ReplicationEnabled: \(.replicationEnabled)"'
➡️Target Workspace
7.4 Get Auxiliary Volume IDs to remove from the VG, and remove them
set_node target
VOLUME_GROUP_ID=$(vg_ls | jq -r --arg target "$cgname" '.volumeGroups[] | select(.name | startswith($target)) | .id')
tgvolid_rem=$(vol_ls | jq -r --arg prefix "$vol_com_name" '.volumes[] | select(.name | contains($prefix)) | .volumeID')
echo $tgvolid_rem
echo $tgvolid_rem | wc -w # Validate auxiliary volumes count and naming. It should match the source volume count.
tgvolidrem_api=$(echo $tgvolid_rem | sed 's/ /","/g')
ACTIONS=$(cat <<EOF
"removeVolumes": ["$tgvolidrem_api"]
EOF
)
vg_upd
7.5 Delete Target VG
vg_del
vg_ls
✅❎7.6 Detach Auxiliary Volumes # If not already detached
ACTIONS=$(cat <<EOF
"detachAllVolumes": true,
"detachPrimaryBootVolume" : true
EOF
)
ins_vol_bdet
ins_get | jq # Wait to be without volumes
ins_vol_ls | jq -r .volumes[].name # Wait until no volumes remain attached.
⚠️Before performing step 7.7, ensure that replication is disabled on all primary volumes. If you delete the auxiliary volumes while replication is still enabled, the primary volumes will also be deleted, resulting in the loss of your source VSI.
7.7 Delete Auxiliary Volumes
vol_ls | jq -r --arg prefix "$vol_com_name" '.volumes[] | select(.name | startswith($prefix)) | "Name: \(.name) | ReplicationEnabled: \(.replicationEnabled)"' # Make sure all are ReplicationEnabled: false
ACTIONS=$(cat <<EOF
"volumeIDs": ["$tgvolidrem_api"]
EOF
)
vol_bdel
vol_ls | jq -r '.volumes[]? | "Name: \(.name) -- ID: \(.volumeID)"'
ℹ️ Primary VSI remains unaffected; auxiliary volumes removed from target.
🏁GRS deleted!
↩️8. Cancel Failover
# This procedure resumes replication from the Source to the Target and is typically used when a failover was performed only to create a clone or to conduct a failover test. No work or changes were done in target VSI.
➡️Source Workspace
8.1 Resume replication from Source to Target
set_node source
ACTIONS='"start":{"source":"master"}'
vg_act
8.2 Verify that the volume group is in consistent_copying
vg_sd | jq
📚References
Global Replication Services (GRS)
IBM Power Virtual Server (Power Cloud) API Reference
IBM Cloud Transit Gateway
(Used for prefix filter manipulation during failover/failback)
Authentication & API Keys