Power Virtual Server

Power Virtual Server

Explore the cloud-based infrastructure offering from IBM that enables businesses to run IBM Power workloads in a secure, scalable virtualized environment.


#IIBManufacturing
#Power
#Power
#AppConnectEnterprise(ACE)
#Cloud
#Cloud
#IBMCloud

 View Only

Global Replication Services (GRS) for IBM i on PowerVS – Automation Runbook Using IBM Cloud API

By Ricardo Martins posted Sat March 21, 2026 12:09 PM

  

🧭Overview

Global Replication Services (GRS) on PowerVS is a powerful capability, but it comes with nuances that can easily trip you up if you’re not paying close attention. The workflow isn’t inherently difficult, yet the sequence of actions, and the consequences of getting them wrong, can be confusing. During my own testing, I managed to destroy my source partition more than once, and that experience made one thing very clear: a safe, repeatable, automation‑friendly runbook is essential.


Another important aspect to understand is the operational model that GRS enables. Unlike traditional HA setups, GRS can provide a cost-efficient and reliable DR strategy because you do not need to have a target LPAR created at all. You can keep only the replicated volumes on the DR site, and only when DR activation is required do you create the IBM i LPAR and attach the auxiliary volumes. This approach is well suited for organizations that are comfortable with an RPO in the range of ~500 seconds or greater and prioritize recovery capability, operational simplicity, and cost control over near-zero-data-loss replication.


Additionally, by using Transit Gateway prefix filtering and proper route control, it is possible to reuse the exact same subnet ranges and LPAR IP addresses on the target workspace during DR activation. This runbook includes guidance for that scenario, helping ensure a predictable failover while avoiding routing conflicts.


🎯
Why This Runbook Matters

This guide exists so you don’t have to learn those lessons the hard way. My goal is to give you a clear, reliable path for working with GRS for IBM i on PowerVS using IBM Cloud APIs, whether you’re cloning, testing, or performing a real failover. Follow the steps carefully, and you’ll avoid the pitfalls that caught me during development.

 

🌐 Context

Since the IBM Cloud CLI is not available for IBM i, this document provides an alternative that leverages the IBM Cloud APIs.

It can be executed directly on an IBM i LPAR, provided the system has network access to the IBM Cloud endpoints.

 

🧪 Tested Environments

This was tested in GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu), curl 8.5.0, wc 9.4 and jq-1.7

For IBM i this was tested in GNU bash, version 5.2.15(1)-release (powerpc-ibm-os400), curl 8.6.0 (powerpc-ibm-os400),  wc (GNU coreutils) 9.5 and jq-1.6

 

⚠️ IMPORTANT: During testing, jq version 1.7.1-1.ppc64 on IBM i consistently failded, returning jq: error: cannot allocate memory for every jq command executed. This behavior appears specific to the ppc64 build and was not observed on x86_64 platforms.

I have opened an issue with the IBM i OSS team to track this behavior:
https://github.com/IBM/ibmi-oss-issues/issues/89

This issue has been resolved in jq version 1.8.1-1, which no longer exhibits the memory allocation problem on IBM i ppc64.

Previous Workaround (No Longer Needed)
If you previously encountered this issue with version 1.7.1-1, you could downgrade to version 1.6 using:

yum remove jq libjq1 libonig5
yum install jq-1.6

Upgrade to 1.8.1-1 instead for the fix.

📋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.

 

 

📝 StepbyStep Procedures


⚙️1. To begin, create a .env file…

This file defines the variables, functions, and aliases used by the GRS runbook procedures.

  1. Create an environment file (for example, .env_grs).
  2. Paste the sample content provided below and replace the green text with your own data.
  3. 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

0 comments
35 views

Permalink