Serverless Backup & Restore operations in EPM Cloud

 

Have you ever wanted to automate migrations from your production to your non-prod environement?  And do it without using a standalone server to run EPMAutomate?

You can write Groovy scripts to run select EPM Automate commands directly in Oracle Enterprise Performance Management Cloud, without installing EPM Automate client on a client machine. Refer to Running Commands without Installing EPM Automate and Supported Command in Working with EPM Automate for Oracle Enterprise Performance Management Cloud for information on which EPM Automate commands can be run via Groovy and example scripts.

Oracle supports two types of Groovy rules:

  • Rules that can dynamically generate calc scripts at runtime based on context other than the runtime prompts and return the calc script which is then executed against Oracle Essbase.
  • Pure Groovy rules that can, for example, perform data validations and cancel the operation if the data entered violates company policies.

Use Case

Run a buiness rule from production to clone a backup to a non prod environment.


High Level Steps
  • Set the service admin user ID for target (Test) instance within the rule. Encrypt the password using EPMAutomate explicitly and copy to password variable in the rule explicitly. You must deploy the rule post any such changes to the rule. Once deployed user can Launch the rule.
  • User needs to enter the specific date in yyyy-mm-dd format to select the required date specific snapshot to restore and hit Launch.
  • If Check is selected as true then the rule will run to list all backup snapshot names available from object storage path and exit, else by default it will continue to next step.
  • Deletes the selected file if already present from migration path.
  • Restores the selected snapshot file to migration path with the date stamp suffixed to snapshot file name and proceeds to next step.
  • Clone the target (Test) instance with the restored backup snapshot. The cloning process takes ~ 30-50 mins to complete, depending on the data and artifacts residing in the snapshot.
  • User can check the status of cloning in production under Clone Environment menu or using REST API for status check.

 

Run time prompts

 

RTP NAME TYPE DEFAULT VALUE DESCRIPTION
RTP_Date String Use Last Entered Enter date string in yyyy-mm-dd format only.
RTP_Check Boolean false If selected true, then rule will only list all the backup snapshot names available in object storage path and exit without cloning activity.

This provision is just for user reference to check if the date provided is valid for the range of 60 days.

 

Code Sample

/* RTPS: {RTP_Date} {RTP_Check} */

// Timestamp definition
String getUserName = operation.getUser().getName()
String TrgURL = "" // target instance to clone
String adm_id = "" // service admin
String adm_passwd = "" // encrypted password

DateFormat tFormat = new SimpleDateFormat("yyyy-MM-dd")
tFormat = new SimpleDateFormat("yyyy-MMM-dd,EEE hh:mm:ss a zzz")
def tstamp = tFormat.format(new Date())
String dt = rtps.RTP_Date
def archive_date = dt
int d_limit = 0
int sts_code = rtps.RTP_Check.toString() as int
String sts = ""
boolean REST_Status = false
boolean List_Check = sts_code == 0 ? false : true

println "[$tstamp] : Backup file date $archive_date selected by $getUserName."

try {
d_limit = new Date() - new Date().parse('yyyy-MM-dd', dt)
} catch (Exception e) {
sts = "Please check the entered date $dt is in yyyy-mm-dd format."
tstamp = tFormat.format(new Date())
println "[$tstamp] : Error $e - $sts"
throwVetoException("$sts")
}

if(!(d_limit>0 && d_limit<60)) {
sts = "Please select date range within last 60 days from today."
tstamp = tFormat.format(new Date())
println "[$tstamp] : Error - $sts"
throwVetoException("$sts")
}

//***************List all existing backup files****************
String ConnectionName = "REST-EPM-MIGRATION-PROD"
String api_version = '/v2'
String api_resource_path = '/backups/list'
String jRSrc = api_version + api_resource_path

HttpResponse<String> jsonGetBackupListResponse = operation.application.getConnection(ConnectionName)
.get(jRSrc)
.asString()

sts = JsonPath.parse(jsonGetBackupListResponse.body).read('$.details').toString() + "."
sts_code = JsonPath.parse(jsonGetBackupListResponse.body).read('$.status')
def fileList = JsonPath.parse(jsonGetBackupListResponse.body).read('$.items') as String[]
tstamp = tFormat.format(new Date())
println "[$tstamp] : Status $jsonGetBackupListResponse.statusText - List Files - $api_resource_path - $sts. ${fileList.size()} snapshot files available. Below files available :"

String dte = archive_date.toString().trim()
String BackupFilePath = ""
String TargetFile = "Artifact_Snapshot_" + ConnectionName.substring(ConnectionName.length()-4) + "_$archive_date"

fileList.eachWithIndex { String i, int j ->
println i
if(dte.equals(i.substring(0,10)) && !REST_Status) {
BackupFilePath = i
sts = "$sts $BackupFilePath file found."
}
}

if(BackupFilePath == "") {
sts = "No file selected for date $archive_date. Please select date range within last 60 days from today."
tstamp = tFormat.format(new Date())
println "[$tstamp] : Status $jsonGetBackupListResponse.statusText - $sts"
throwVetoException("$sts")
} else {
tstamp = tFormat.format(new Date())
println "[$tstamp] : Status $jsonGetBackupListResponse.statusText - $sts"
}

if(List_Check) {
sts = "Exiting program as just file check option selected. ${fileList.size()} backup snapshots available in object storage."
tstamp = tFormat.format(new Date())
println "[$tstamp] : Status OK - $sts"
throwVetoException("$sts")
}

//***************Delete existing backup file from Prod migration path if any****************
api_version = '/11.1.2.3.600'
api_resource_path = '/applicationsnapshots/' + TargetFile
jRSrc = api_version + api_resource_path
delFile(ConnectionName, jRSrc, tFormat)

//***************Restore existing backup files****************
api_version = '/v2'
api_resource_path = '/backups/restore'
jRSrc = api_version + api_resource_path

HttpResponse<String> jsonRestoreBackupResponse = operation.application.getConnection(ConnectionName)
.post(jRSrc)
.header("Content-Type", "application/json")
.body(json(["backupName":"$BackupFilePath", "parameters":["targetName":"$TargetFile"]]))
.asString()

String op = "Restore Backup"
sts = JsonPath.parse(jsonRestoreBackupResponse.body).read('$.details').toString() + "."
sts_code = JsonPath.parse(jsonRestoreBackupResponse.body).read('$.status')
tstamp = tFormat.format(new Date())
println "[$tstamp] : Status $jsonRestoreBackupResponse.statusText - $op - $api_resource_path - $sts"

//***************Restore backup files status check****************
statusCheck(jsonRestoreBackupResponse, ConnectionName, api_version, op, tFormat)

//***************Clone snapshot to source****************
api_version = '/v1'
api_resource_path = '/services/clone'
jRSrc = api_version + api_resource_path

HttpResponse<String> jsonCloneBackupResponse = operation.application.getConnection(ConnectionName)
.post(jRSrc)
.header("Content-Type", "application/json")
.body(json(["targetURL":"$TrgURL", "targetUserName":"$adm_id", "targetEncryptPassword":"$adm_passwd", "parameters":["snapshotName":"$TargetFile", "migrateUsers":"false", "maintenanceStartTime":"true", "dataManagement":"true", "jobConsole":"false", "applicationAudit":"false", "storedSnapshotsAndFiles":"false"]]))
.asString()

op = "Clone Backup"
sts = JsonPath.parse(jsonCloneBackupResponse.body).read('$.details').toString() + "."
sts_code = JsonPath.parse(jsonCloneBackupResponse.body).read('$.status')
tstamp = tFormat.format(new Date())
println "[$tstamp] : Status $jsonCloneBackupResponse.statusText - $op - $api_resource_path - $sts"

//***************Clone backup status check****************
//statusCheck(jsonCloneBackupResponse, ConnectionName, api_version, op, tFormat)
op = "Clone Status Check"
api_version = '/v1'
api_resource_path = '/services/clone/status'
jRSrc = api_version + api_resource_path

HttpResponse<String> jsonCloneStatusResponse = operation.application.getConnection(ConnectionName)
.get(jRSrc)
.asString()

sts = JsonPath.parse(jsonCloneStatusResponse.body).read('$.details').toString() + "."
sts_code = JsonPath.parse(jsonCloneStatusResponse.body).read('$.status')
tstamp = tFormat.format(new Date())
println "[$tstamp] : Status $jsonCloneStatusResponse.statusText - $op - $api_resource_path - $sts"
//sts = getImportJobStatus(ConnectionName, jRSrc)
REST_Status = awaitCompletion(jsonCloneStatusResponse, "$ConnectionName", "$op", "$jRSrc")
sts = getImportJobStatus(ConnectionName, jRSrc)
tstamp = tFormat.format(new Date())
println "[$tstamp] : Status $jsonCloneStatusResponse.statusText - $op \n$sts"

//************EPM Helper Functions****************
// Delete existing file
def delFile(String ConnectionName, String jRSrc, DateFormat tFormat) {
HttpResponse<String> jsonFileDeleteResponse = operation.application.getConnection(ConnectionName)
.delete(jRSrc)
.header("Content-Type", "application/json")
.asString()

int sts_code = JsonPath.parse(jsonFileDeleteResponse.body).read('$.status')
String sts = JsonPath.parse(jsonFileDeleteResponse.body).read('$.details').toString() + "."
//sts = sts + JsonPath.parse(jsonFileDeleteResponse.body).read('$.links[0].href').toString()
def tstamp = tFormat.format(new Date())
println "[$tstamp] : Status $jsonFileDeleteResponse.statusText - Delete Snapshot - $jRSrc - $ConnectionName $sts"
}
// v2 REST status check
def statusCheck(HttpResponse<String> jsonResponse, String ConnectionName, String api_version, String opr, DateFormat tFormat) {
String StatusURL = opr=="Clone Backup" ? JsonPath.parse(jsonResponse.body).read('$.links[0].href').toString() : JsonPath.parse(jsonResponse.body).read('$.links[1].href').toString()
String api_resource_path = StatusURL.substring(StatusURL.indexOf(api_version)+3, StatusURL.length())
String jRSrc = api_version + api_resource_path

HttpResponse<String> jsonCheckResponse = operation.application.getConnection(ConnectionName)
.get(jRSrc)
.asString()

int sts_code = JsonPath.parse(jsonCheckResponse.body).read('$.status')
String sts = JsonPath.parse(jsonCheckResponse.body).read('$.details').toString() + "."
def tstamp = tFormat.format(new Date())
println "[$tstamp] : Status $jsonCheckResponse.statusText - $opr - $api_resource_path - $ConnectionName $sts"
boolean REST_Status = awaitCompletion(jsonCheckResponse, "$ConnectionName", "$opr", "$jRSrc")
}
// Await till REST completes
def awaitCompletion(HttpResponse<String> jsonResponse, String connectionName, String opr, String jrSrc) {
DateFormat tFormat = new SimpleDateFormat('yyyy-MMM-dd,EEE hh:mm:ss a zzz')
final int IN_PROGRESS = -1
int status = JsonPath.parse(jsonResponse.body).read('$.status')
def tstamp = tFormat.format(new Date())
if (!(200..299).contains(jsonResponse.status))
throwVetoException("Error : $status occured to execute $opr. $jsonResponse.statusText.")

// Parse the JSON response to get the status of the operation. Keep polling the REST call until the operation completes.
String j_Id = jrSrc.substring(jrSrc.lastIndexOf('/') + 1, jrSrc.length())
for (long delay = 500; status == IN_PROGRESS; delay = Math.min(2000, delay * 2)) {
sleep(delay)
status = getJobStatus(connectionName, jrSrc, status)
}
String Resp_Details = JsonPath.parse(jsonResponse.body).read('$.details').toString()
def itms = (List)JsonPath.parse(jsonResponse.body).read('$.items')
/*if (opr == 'Clone Backup') {
if (itms.size() > 0) {
itms.eachWithIndex { r, i ->
String StatusURL = r['links']['href'].toString().replace(']', '').replace('[', '')
String api_resource_path = StatusURL.substring(StatusURL.indexOf('/v2'))
StatusURL = getImportJobStatus(connectionName, api_resource_path)
if (StatusURL.length() > 0) {
String info = r['destination'].toString() + ' - ' + StatusURL //getImportJobStatus(connectionName, jRSrc)
tstamp = tFormat.format(new Date())
println("[$tstamp] : $i - $info")
}
}
}
def info = (List)JsonPath.parse(jsonResponse.body).read('$.intermittentStatus')
tstamp = tFormat.format(new Date())
println("[$tstamp] : Status $jsonResponse.statusText - $opr - $info")
}*/
tstamp = tFormat.format(new Date())
println("[$tstamp] : Status $jsonResponse.statusText - $opr - $jrSrc - ${status == 0 ? "successful for Job Id $j_Id" : "failed for Job Id $j_Id"}. \n$Resp_Details")
return status
}
// Poll the REST call to get the job status
int getJobStatus(String connectionName, String jobId, int get_sts) {
HttpResponse<String> pingResponse = operation.application.getConnection(connectionName)
.get(jobId)
.asString()
get_sts = JsonPath.parse(pingResponse.body).read('$.status')
if (get_sts == -1) {
return getJobStatus(connectionName, jobId, get_sts)
} else {
return get_sts
}
}
// Poll the REST call to get the import job details
String getImportJobStatus(String connectionName, String jobId) {
String get_sts = ''
HttpResponse<String> pingResponse = operation.application.getConnection(connectionName)
.get(jobId)
.asString()
int sts_code = JsonPath.parse(pingResponse.body).read('$.status')
if(sts_code == -1) {
sleep(1000)
get_sts = getImportJobStatus(connectionName, jobId)
} else {
//get_sts = JsonPath.parse(pingResponse.body).read('$.items[*].msgText').toString()
get_sts = JsonPath.parse(pingResponse.body).read('$.intermittentStatus').toString()
return get_sts
}
}

 


Anand Gaonkar

 


Where Can I Learn More About Groovy Rules?

 

Your Goal Learn More
Watch videos and tutorials that teach best practices when implementing and using Groovy rules
Create Groovy business rules using Calculation Manager See Designing with Calculation Manager for Oracle Enterprise Performance Management Cloud
Connect to the Java APIs used for creating Groovy rules See Java API Reference for Groovy Rules
Edit the script for a Groovy business rule or template using Calculation Manager. See Designing with Calculation Manager for Oracle Enterprise Performance Management Cloud
0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.