Automating Oracle RMAN backup configuration on Linux with Ansible

Posted in: Oracle, Technical Track

Having valid backup of the databases is one of the most important tasks for every DBAs. Having proper RMAN backup scripts on place and covering all aspects to protect your databases can be lifesaving in the event that a crisis hits.

However, when you’re a DBA covering multiple environments with multiple databases running across them, applying your RMAN configuration and backup scripts on place can be quite frustrating and time consuming. When multiple environments are managed by multiple DBAs the biggest problem often found is consistency. Every DBA has his/her own practice and scripts for implementing or defining these tasks. It can be quite challenging when you’re jumping from server to server, trying to identify what went wrong with the backups and you have no choice but to decipher the logic behind the RMAN script that you didn’t author. Having different practices in place for sensitive tasks increases the risk of potential disaster. And (much like Murphy’s law) you will inevitably lose an environment due to problems with the backup script that is works fine everywhere else other than the critical DB that has failed.

With this said, imagine you have a template for RMAN backup scripts that has been proven to work without any issues. You have a method to generate, deploy and schedule these scripts on any server you like within seconds. I think that sounds awesome.

That is why I took initiative to work on automating this task; so we can be efficient and avoid these time consuming tasks while also keeping consistency at the highest level across the entire environment that we manage.

Here is where Ansible comes into play. I used Ansible to automate Oracle RMAN backup configuration on Linux by covering the following two aspects:

  • Standardization and,
  • Efficiency

For those of you not familiar with it, Ansible is an open source agent-less automation tool that does everything through standard SSH. More on Ansible can be found here .

How do I do it?

First, let’s ask ourselves what input parameters would be needed to generate proper RMAN scripts and schedule it in cron.

My main choice was the following:

  • ORACLE_SID: we must enter ORACLE_SID so ansible would know for which database we are setting backup for and adjust the environment variables accordingly (for RAC this needs to be instance SID)
  • Backup type: we can insert backup type whether is DISK or tape backup (SBT) so ansible would know how to adjust the RMAN parameters (channel type, PARMS … etc.) – default is DISK
  • Parallelism: we can enter parallelism degree so ansible would know how to adjust the RMAN number of channels – default is 1
  • Retention policy type: we can enter retention policy type whether is redundancy or recovery window so ansible would know how to adjust the retention policy – default is redundancy
    • redundancy: If you chose redundancy for retention policy you can enter value for redundancy > 0 – default is 1
    • recovery window: If you chose recovery window for retention policy you can enter value for recovery window > 0 – default is 7
  • Backup location: If you chose DISK for backup type then you can enter backup location (mount volume) where backups would reside – default location is /home/oracle/rman_bkp
  • Archive logs retention: we can enter a value of how many days we would like to keep the archive logs available after being backed up – default is 2 days
  • Script logs/traces retention: we can enter a value of how many days we would like to keep the scripts execution logs/traces available – default is 2 days
  • Email address for notification when backup fails – this is optional and if you add e-mail address the RMAN backup script will contain module to notify when failed – however this module functionality depends on whether you have set up the outgoing mails to work properly on the server so you can send notification mails from that server (mailx command needs to work properly as well)
  • Email address for notification when backup succeeds – this is optional and if you add e-mail address the RMAN backup script will contain module to notify when succeeds – however this module functionality depends on whether you have set up the outgoing mails to work properly on the server so you can send notification mails from that server (mailx command needs to work properly as well)
  • L0 schedule: we can enter a value for a cron schedule for the L0 backup (in cron format: ex. 0 1 * * 0) – Default is every Sunday ad 1 AM
  • L1 schedule: we can enter a value for a cron schedule for the L1 backup (in cron format: ex. 0 1 * * 1-6) – Default is every day except Sunday ad 1 AM
  • Arch schedule: we can enter a value for a cron schedule for the archive logs backup (in cron format: ex. 0 * * * *) – Default is every hour

I also have two other variables in the background with default values that I considered unnecessary to prompt for user input but are necessary for generating the RMAN backup scripts and cron schedules:

  • nls_date_format: DD-MON-YYYY HH24:MI:SS – I use this to have proper output format on the timings when rman task is executed
  • backup_scr_home: /scripts/bkp/rman – I use this to have standardized script directory location across the servers – The final location of the backup scripts after ansible generates the environment variables would be /home/oracle/scripts/bkp/rman

The full list of the defaults is this:

---
oracle_sid: ""            #ORACLE_SID so ansible would know for which database we are setting backup for and adjust the environment variables accordingly (for RAC this needs to be instance SID)
backup_type: "DISK"       #Allowed values DISK or SBT
sbt_params: ""            #SBT tape params in format ENV=(xxx,xxx) so ansible would know how to adjust the RMAN parameters
parallelism: 1            #integer > 0 ; parallelism degree so ansible would know how to adjust the RMAN number of channels
retention_policy_type: 1  #1 - redundancy | 2 - recovery window
redundancy: 1             #integer > 0
recovery_window: 7        #integer > 0
backup_dest: /home/oracle/rman_bkp  #backup location (mount volume) where backups would reside
arch_log_ret: 2           #value of how many days we would like to keep the archive logs available after being backed up - float > 0
logs_ret: 30              #value of how many days we would like to keep the scripts execution logs/traces available - integer > 0
notify_mail_succ: ""      #e-mail address on which to notify when backup failed
notify_mail_fail: ""      #e-mail address on which to notify when backup succeeded
nls_date_format: DD-MON-YYYY HH24:MI:SS
backup_scr_home: /scripts/bkp/rman   #adjust this to have desired output format on the timings when rman task is executed
sched_L0: "0 1 * * 0"     #value for a cron schedule for the L0 backup (in cron format: ex. 0 1 * * 0)
sched_L1: "0 1 * * 1-6"   #value for a cron schedule for the L1 backup (in cron format: ex. 0 1 * * 1-6)
sched_arch: "0 * * * *"   #value for a cron schedule for the archove logs backup (in cron format: ex. 0 * * * *)

Of course, all these default values can be adjusted through –extra-vars inline parameter when calling the playbook run so they all can be changed.

Now that we have defined which input parameters are required, we can start building the playbook.

For my test cases, I am using a control Linux box with hostname: control where I have Ansible configured and a database Linux box with hostname: oracledb where I have an Oracle database running with ORACLE_SID=UPGR . I will use this as reference while explaining the code for this project.

Let’s begin.

I start my playbook (rmanbackup.yml) with the vars_prompt module from Ansible to prompt the required variables in combination with the pre_tasks module where I initiate validation of the input. The code for this portion of the playbook is as follows:

---
- hosts: oracledb
  become: true
  become_user: oracle
  vars_prompt:
    - name: "oracle_sid"
      prompt: "Please enter the ORACLE_SID"
      private: no
    - name: "backup_type"
      prompt: "Please enter the backup type: DISK or SBT - default is DISK (DISK | SBT)"
      default: "DISK"
      private: no
    - name: "sbt_params"
      prompt: "Please enter the media manager parameters for sbt backup (format ENV=(xxx,xxx)) - if you chose DISK backup skip this by hitting enter"
      private: no
    - name: "parallelism"
      prompt: "Please enter the parallelism value (number of channels) - default is 1"
      default: 1
      private: no
    - name: "retention_policy_type"
      prompt: "Please enter the retention policy type - default is redundancy (1 - redundancy | 2 - recovery window)"
      default: 1
      private: no
    - name: "redundancy"
      prompt: "Please enter the redundancy (defualt is 1) - if you chose recovery window type skip this by hitting enter"
      default: 1
      private: no
    - name: "recovery_window"
      prompt: "Please enter the recovery window in days (defualt is 7) - if you chose redundancy type skip this by hitting enter"
      default: 7
      private: no
    - name: "backup_dest"
      prompt: "Please enter the backup location where backup files will reside on disk (defualt is /home/oracle/rman_bkp) - if you chose SBT backup skip this by hitting enter"
      default: "/home/oracle/rman_bkp"
      private: no
    - name: "arch_log_ret"
      prompt: "Please enter a value in days for cleaning up archive logs that have already been backed up older then X days (defult is 2 days)"
      default: 2
      private: no
    - name: "logs_ret"
      prompt: "Please enter a value in days for cleaning up scripts execution logs/traces older then X days (defult is 30 days)"
      default: 30
      private: no
    - name: "notify_mail_succ"
      prompt: "Please enter an e-mail address for notification when scripts execution is SUCCESSFULL [optional]"
      private: no
    - name: "notify_mail_fail"
      prompt: "Please enter an e-mail address for notification when scripts execution FAILED [optional]"
      private: no
    - name: "sched_L0"
      prompt: "Please enter a value for a cron schedule for the L0 backup (in cron format: ex. 0 1 * * 0) - Default is every Sunday ad 1 AM"
      default: "0 1 * * 0"
      private: no
    - name: "sched_L1"
      prompt: "Please enter a value for a cron schedule for the L1 backup (in cron format: ex. 0 1 * * 1-6) - Default is every day except Sunday ad 1 AM"
      default: "0 1 * * 1-6"
      private: no
    - name: "sched_arch"
      prompt: "Please enter a value for a cron schedule for the archove logs backup (in cron format: ex. 0 * * * *) - Default is every hour"
      default: "0 * * * *"
      private: no

  pre_tasks:
    - assert:
        that: oracle_sid !=""
        msg: "ORACLE_SID is not defined! Please define ORACLE_SID!"
      tags: ['sid']
    - assert:
        that: backup_type|lower == "disk" or backup_type|lower == "sbt"
        msg: "Backup type is not properly defined! Please choose between DISK or SBT!"
    - assert:
        that: "'ENV=(' in sbt_params"
        msg: "sbt_params are not properly defined! Please verify input!"
      when: backup_type|lower == "sbt"
    - assert:
        that: parallelism|int > 0
        msg: "Parallelism value is not properly defined! Please insert number > 0"
    - assert:
        that: retention_policy_type == "1" or retention_policy_type == "2"
        msg: "Retention policy type is not properly defined! Please choose between option 1 or 2!"
    - assert:
        that: redundancy|int > 0
        msg: "Redundancy value is not properly defined! Please insert number > 0"
      when: retention_policy_type == "1"
    - assert:
        that: recovery_window|int > 0
        msg: "Recovery window value is not properly defined! Please insert number > 0"
      when: retention_policy_type == "2"
    - assert:
        that: arch_log_ret|float > 0
        msg: "Archive log retention value is not properly defined! Please insert number > 0"
    - assert:
        that: logs_ret|int > 0
        msg: "Scripts logs/traces retention value is not properly defined! Please insert number > 0"
    - assert:
        that: notify_mail_succ | match("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$") or notify_mail_succ ==""
        msg: "Not a valid e-mail address for SUCCESS notification! Please insert correct e-mail address!"
      tags: ['mailSvalidate']
    - assert:
        that: notify_mail_fail | match("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$") or notify_mail_fail ==""
        msg: "Not a valid e-mail address for FAILURE notification! Please insert correct e-mail address!"
      tags: ['mailFvalidate']
    - name: Get L0 cron
      command: echo "*" "{{sched_L0}}"
      register: cron_l0_validate
    - debug: var=cron_l0_validate.stdout
    - assert:
        that: cron_l0_validate.stdout | match("^\\s*($|#|\\w+\\s*=|(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[01]?\\d|2[0-3])(?:(?:-|\/|\\,)(?:[01]?\\d|2[0-3]))?(?:,(?:[01]?\\d|2[0-3])(?:(?:-|\/|\\,)(?:[01]?\\d|2[0-3]))?)*)\\s+(\\?|\\*|(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?(?:,(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?)*)\\s+(\\?|\\*|(?:[1-9]|1[012])(?:(?:-|\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?(?:,(?:[1-9]|1[012])(?:(?:-|\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?)*|\\?|\\*|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(?:,(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)*)\\s+(\\?|\\*|(?:[0-6])(?:(?:-|\/|\\,|#)(?:[0-6]))?(?:L)?(?:,(?:[0-6])(?:(?:-|\/|\\,|#)(?:[0-6]))?(?:L)?)*|\\?|\\*|(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?(?:,(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?)*)(|\\s)+(\\?|\\*|(?:|\\d{4})(?:(?:-|\/|\\,)(?:|\\d{4}))?(?:,(?:|\\d{4})(?:(?:-|\/|\\,)(?:|\\d{4}))?)*))$")
        msg: "Not a valid value for L0 cron schedule. Please insert correct value in cron format - (ex. 0 1 * * 0) !"
      tags: ['cronl0validate']
    - name: Get L1 cron
      command: echo "*" "{{sched_L1}}"
      register: cron_l1_validate
    - debug: var=cron_l1_validate.stdout
    - assert:
        that: cron_l1_validate.stdout | match("^\\s*($|#|\\w+\\s*=|(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[01]?\\d|2[0-3])(?:(?:-|\/|\\,)(?:[01]?\\d|2[0-3]))?(?:,(?:[01]?\\d|2[0-3])(?:(?:-|\/|\\,)(?:[01]?\\d|2[0-3]))?)*)\\s+(\\?|\\*|(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?(?:,(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?)*)\\s+(\\?|\\*|(?:[1-9]|1[012])(?:(?:-|\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?(?:,(?:[1-9]|1[012])(?:(?:-|\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?)*|\\?|\\*|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(?:,(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)*)\\s+(\\?|\\*|(?:[0-6])(?:(?:-|\/|\\,|#)(?:[0-6]))?(?:L)?(?:,(?:[0-6])(?:(?:-|\/|\\,|#)(?:[0-6]))?(?:L)?)*|\\?|\\*|(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?(?:,(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?)*)(|\\s)+(\\?|\\*|(?:|\\d{4})(?:(?:-|\/|\\,)(?:|\\d{4}))?(?:,(?:|\\d{4})(?:(?:-|\/|\\,)(?:|\\d{4}))?)*))$")
        msg: "Not a valid value for L1 cron schedule. Please insert correct value in cron format - (ex. 0 1 * * 1-6) !"
      tags: ['cronl1validate']
    - name: Get arch cron
      command: echo "*" "{{sched_arch}}"
      register: cron_arch_validate
    - debug: var=cron_arch_validate.stdout
    - assert:
        that: cron_arch_validate.stdout | match("^\\s*($|#|\\w+\\s*=|(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[01]?\\d|2[0-3])(?:(?:-|\/|\\,)(?:[01]?\\d|2[0-3]))?(?:,(?:[01]?\\d|2[0-3])(?:(?:-|\/|\\,)(?:[01]?\\d|2[0-3]))?)*)\\s+(\\?|\\*|(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?(?:,(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?)*)\\s+(\\?|\\*|(?:[1-9]|1[012])(?:(?:-|\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?(?:,(?:[1-9]|1[012])(?:(?:-|\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?)*|\\?|\\*|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(?:,(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)*)\\s+(\\?|\\*|(?:[0-6])(?:(?:-|\/|\\,|#)(?:[0-6]))?(?:L)?(?:,(?:[0-6])(?:(?:-|\/|\\,|#)(?:[0-6]))?(?:L)?)*|\\?|\\*|(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?(?:,(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?)*)(|\\s)+(\\?|\\*|(?:|\\d{4})(?:(?:-|\/|\\,)(?:|\\d{4}))?(?:,(?:|\\d{4})(?:(?:-|\/|\\,)(?:|\\d{4}))?)*))$")
        msg: "Not a valid value for archive logs backup cron schedule. Please insert correct value in cron format - (ex. 0 * * * *) !"
      tags: ['cronarchvalidate']

In the code noted above, most of the required input is pretty straight forward and easy to validate. However, I got stuck a bit at the cron schedule input. For that reason I needed a rather complex regular expression so I could match all possible valid cron scheduler entries. Otherwise, the user input may have caused the playbook to fail if it wasn’t validated properly when we reached the task at the end which schedules the scripts in cron. Since it wasn’t the focus of this project, I didn’t want to waste a lot of energy figuring that out so I used a regular expression that I found with a little research via Google:

This expression covers most of the cases and it works just fine but don’t be surprised if some formats are not covered. As an example, you cannot use a schedule for job run every two hours in the following format:

* */2 * * *

but instead you must explicitly define each hour you want your scheduled job to run like this:

* 0,2,4,6,8,10,12,14,16,18,20,22 * * *

In any case if this gives you a headache while you run the playbook you can always skip the cron entries validation by simply skipping the required tags: ex. –skip-tags “cronl0validate,cronl1validate,cronarchvalidate” and just make sure you put valid values, either through prompt or in the default parameters section of the role or define them with the –extra-vars inline parameter when calling the playbook. I will provide exact examples on how to run this for multiple scenarios at the end of this blog.

You can also do the same with the e-mail validate regex expression. I used the one I found here. Hopefully you won’t have problems validating that user entree. But in case you do you just run the playbook with –skip-tags “mailSvalidate,mailFvalidate”.

Ok, so now that we are all done with the input parameters and all pre-tasks, we are all set to proceed with the actual tasks that automate the Oracle RMAN backup configuration.
For that purpose I created a role called rmanbackup. I will go step by step explaining each task from this role so you can get better understanding of the logic I use for this RMAN backup automation process.

I start with a check if the provided ORACLE_SID is defined in /etc/oratab. I consider this as standard. Assuming Oracle is installed on the box by following the official documentation each configured database should have an entry in /etc/oratab. So that would be a good indication that the database we plan to take backup of actually exists. I consider this as important step since we would need the Oracle environment setup properly so the backup scripts that will be generated afterwards can run without issues. If ORACLE_SID is not found then the playbook will exit. So here is the code for that section:

- name: Check if ORACLE_SID exists
  shell: cat /etc/oratab | grep {{ oracle_sid }} | grep ^[^#] | cut -d ':' -f1
  register: oracle_sid_check
  tags: ['sid']

- name: Exit if ORACLE_SID does not exist
  fail:
    msg: "The database {{ oracle_sid }} does not exist. Please enter valid ORACLE_SID!"
  when: oracle_sid_check.stdout != oracle_sid
  tags: ['sid']

Then I follow up with checking and creating required directories structure depending on the input from the backup_dest and backup_scr_home variables. As a reminder these two variables represent the following:

  • backup_dest: I use this for backup location where backups would reside – default location is /home/oracle/rman_bkp
  • backup_scr_home: I use this to have standardized script directory location across the servers – The final location of the backup scripts after Ansible generates the environment variables would be /home/oracle/scripts/bkp/rman

So here is how I do this:

I first extract the OS user home. Since I run this playbook as Oracle user that would be the Oracle OS home directory (/home/oracle)

- name: Get user OS home directory
  command: echo ~
  register: user_home
  tags: [ 'userhome' ]

Then I check if in the Oracle OS home directory, we have the required directory structure for the scripts source home where all required scripts we’d like to be generated. If those directories do not exist I create them:

- name: Check if backup_scr_home directory exist
  stat: path={{ user_home.stdout +  backup_scr_home }}
  register: bkp_scr_dst
  tags: [ 'scrhome' ]

- name: Create the backup_scr_home directory if it does not exist
  file: path={{ user_home.stdout + backup_scr_home }} state=directory
  when: not bkp_scr_dst.stat.exists
  tags: [ 'scrhomecreate' ]

Afterwards I do the same for the backup location entered by the user. If that location does not exist, the playbook exits. If the location exists, then I check if the required directory structure exists inside that location. If it does not exist, I create the required directories:

- name: Check if backup_dest location exist
  stat: path={{ backup_dest }}
  register: bkp_dst_loc
  tags: [ 'bkpdestloc' ]

- name: Exit if backup_dest location does not exist
  fail:
    msg: "The backup destination directory does not exist. Please enter valid location!"
  when: not bkp_dst_loc.stat.exists
  tags: [ 'bkpdestloccreate' ]

- name: Check if backup_dest directory exist
  stat: path={{ backup_dest + '/' + oracle_sid + '/bkp/rman' }}
  register: bkp_dst
  tags: [ 'bkpdestdir' ]

- name: Create the backup_dest directory if it does not exist
  file: path={{ backup_dest + '/' + oracle_sid + '/bkp/rman' }} state=directory
  when: not bkp_dst.stat.exists
  tags: [ 'bkpdestdircreate' ]

Then I add one more step on creating directory for logs. In that directory, all scripts logs will be generated (backup scripts logs, environment setup logs, RMAN configuration logs … etc.)

- name: Check if log directory exist
  stat: path={{ user_home.stdout + backup_scr_home + '/logs' }}
  register: logs_dir
  tags: [ 'logdir' ]

- name: Create the log directory if it does not exist
  file: path={{ user_home.stdout + backup_scr_home + '/logs' }} state=directory
  when: not logs_dir.stat.exists
  tags: [ 'logdircreate' ]

As you can see at the end of this section, we ended up with a fully covered directory structure that I push as standard. Of course, you do not have to accept this standardization; you’re at liberty to modify the parameters as you see fit but I find it convenient enough to have the following directory structure as standard:

RMAN scripts location:      <oracle OS home>/scripts/bkp/rman/
RMAN scripts logs location: <oracle OS home>/scripts/bkp/rman/logs
RMAN backup location:       <backup volume provided by user>/<oracle_sid provided by user>/bkp/rman/

The next task is generating a script that sets up the Oracle environment properly. What does this mean?

Well, I again would like to standardize the approach for setting up all required Oracle environment variables before I run the main RMAN script. Having such a script can be very handy because you can source it out in any other script you like (not only the RMAN scripts that we will be generating in the later tasks). You simply generate a script that sets all required variables on one place and that same script can be reused wherever you like.

For that purpose I use jinja2 template called oracle_env.j2. Here is the content of it:

PATH=/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
ORACLE_SID={{ oracle_sid }}
ORAENV_ASK=NO
. oraenv
unset ORAENV_ASK
######
export NLS_DATE_FORMAT="{{ nls_date_format }}"
#####
BACKUP_DEST={{ backup_dest + '/${ORACLE_SID}/bkp/rman' }}
BACKUP_HOME={{user_home.stdout + backup_scr_home }}
BACKUP_LOGFILE=${BACKUP_HOME}/logs/rmanbackup_${ORACLE_SID}_`date +%Y%m%d%H%M%S`.log
BACKUP_ARC_LOGFILE=${BACKUP_HOME}/logs/rmanbackup_${ORACLE_SID}_arc_`date +%Y%m%d%H%M%S`.log
BACKUP_ERRORLOG=${BACKUP_HOME}/logs/RmanBakUpError_${ORACLE_SID}_`date +%Y%m%d%H%M%S`.log
RMAN_SET_ENV_LOG=${BACKUP_HOME}/logs/rmansetenv_${ORACLE_SID}_`date +%Y%m%d%H%M%S`.log
#####
ORACLE_INITFILE=$ORACLE_HOME/dbs/init${ORACLE_SID}.ora
ORACLE_PWDFILE=$ORACLE_HOME/dbs/orapw${ORACLE_SID}

Then I call the following task:

- name: Oracle ENV template
  template:
    src: oracle_env.j2
    dest: "{{ user_home.stdout + backup_scr_home + '/' + oracle_sid + '_rman.env' }}"
    mode: 0755
  tags: [ 'oraenv' ]

And the result of this task is the following script:

<oracle OS home>/scripts/bkp/rman/<oracle_sid provided by user>_rman.env  

In my test case, I used database with sid UPGR and I got the following script generated:

-rwxr-xr-x. 1 oracle dba  796 Jul 26 09:05 /home/oracle/scripts/bkp/rman/UPGR_rman.env

In my test case the content of the script is:

PATH=/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
ORACLE_SID=UPGR
ORAENV_ASK=NO
. oraenv
unset ORAENV_ASK
######
export NLS_DATE_FORMAT="DD-MON-YYYY HH24:MI:SS"
#####
BACKUP_DEST=/home/oracle/rman_bkp/${ORACLE_SID}/bkp/rman
BACKUP_HOME=/home/oracle/scripts/bkp/rman
BACKUP_LOGFILE=${BACKUP_HOME}/logs/rmanbackup_${ORACLE_SID}_`date +%Y%m%d%H%M%S`.log
BACKUP_ARC_LOGFILE=${BACKUP_HOME}/logs/rmanbackup_${ORACLE_SID}_arc_`date +%Y%m%d%H%M%S`.log
BACKUP_ERRORLOG=${BACKUP_HOME}/logs/RmanBakUpError_${ORACLE_SID}_`date +%Y%m%d%H%M%S`.log
RMAN_SET_ENV_LOG=${BACKUP_HOME}/logs/rmansetenv_${ORACLE_SID}_`date +%Y%m%d%H%M%S`.log
#####
ORACLE_INITFILE=$ORACLE_HOME/dbs/init${ORACLE_SID}.ora
ORACLE_PWDFILE=$ORACLE_HOME/dbs/orapw${ORACLE_SID}

What follows is the process to setting up the database RMAN configuration for the backup type we chose during the prompt phase. As little reminder, the following parameters were required as input:

  • Backup type: whether is DISK or tape backup (SBT) so ansible would know how to adjust the RMAN parameters (channel type, PARMS … etc.) – default is DISK
  • Parallelism: parallelism degree so ansible would know how to adjust the RMAN number of channels – default is 1
  • Retention policy type: whether is redundancy or recovery window so ansible would know how to adjust the retention policy – default is redundancy
    • redundancy: If you chose redundancy for retention policy you can enter value for redundancy > 0 – default is 1
    • recovery window: If you chose recovery window for retention policy you can enter value for recovery window > 0 – default is 7

With all the variables ready from above, I use another template called rman_env.j2. The content of this template is:

DN=`dirname $0`
BN=`basename $0`
SID=${1:?"---> USAGE: $DN/$BN <ORACLE_SID>"}
set -a
source /home/oracle/scripts/bkp/rman/${SID}_rman.env
set +a
(
/bin/env
#------------------------------------
# Setup RMAN config parameters
#------------------------------------
rman  << EOF set echo on connect target; show all; CONFIGURE CONTROLFILE AUTOBACKUP ON; {% if backup_type == "DISK" %} CONFIGURE DEFAULT DEVICE TYPE TO DISK; CONFIGURE CONTROLFILE AUTOBACKUP FORMAT FOR DEVICE TYPE DISK TO '{{ backup_dest + "/" + oracle_sid + "/bkp/rman/%d_%F.ctl"}}'; CONFIGURE DEVICE TYPE DISK PARALLELISM {{parallelism}} BACKUP TYPE TO COMPRESSED BACKUPSET; CONFIGURE CHANNEL DEVICE TYPE DISK FORMAT '{{ backup_dest + "/" + oracle_sid + "/bkp/rman/" + oracle_sid + "_%d_%I_%T_%U"}}' MAXOPENFILES 1; CONFIGURE SNAPSHOT CONTROLFILE NAME TO '{{ backup_dest + "/" + oracle_sid + "/bkp/rman/snapcf_" + oracle_sid + ".f"}}'; {% else %} CONFIGURE CHANNEL DEVICE TYPE sbt PARMS '{{sbt_params}}'; CONFIGURE DEFAULT DEVICE TYPE TO sbt; CONFIGURE DEVICE TYPE sbt PARALLELISM {{parallelism}} BACKUP TYPE TO COMPRESSED BACKUPSET; {% endif %} {% if retention_policy_type == "1" %} CONFIGURE RETENTION POLICY TO REDUNDANCY {{redundancy}}; {% else %} CONFIGURE RETENTION POLICY TO RECOVERY WINDOW OF {{recovery_window}} DAYS; {% endif %} EOF ) > ${RMAN_SET_ENV_LOG} 2>&1
exit

From this template, I generate a script called:

<oracle OS home>/scripts/bkp/rman/<oracle_sid provided by user>_rman_set_env.sh

In the next task I call this script to set the RMAN parameters defined in the template above depending on the backup type DISK or SBT.

Here are the tasks:

- name: RMAN ENV template
  template:
    src: rman_env.j2
    dest: "{{ user_home.stdout + backup_scr_home + '/' + oracle_sid + '_rman_set_env.sh' }}"
    mode: 0755
  tags: [ 'rmanenv' ]

- name: Run the rman set env script
  shell: "{{ user_home.stdout + backup_scr_home + '/' + oracle_sid + '_rman_set_env.sh' + ' ' + oracle_sid }}"
  tags: ['setrmanenv']

In my test case for the UPGR test database I ended up generating and executing the following script:

-rwxr-xr-x. 1 oracle dba  868 Jul 26 08:27 /home/oracle/scripts/bkp/rman/UPGR_rman_set_env.sh 

and its content is:

DN=`dirname $0`
BN=`basename $0`
SID=${1:?"---> USAGE: $DN/$BN <ORACLE_SID>"}
set -a
source /home/oracle/scripts/bkp/rman/${SID}_rman.env
set +a
(
/bin/env
#------------------------------------
# Setup RMAN config parameters
#------------------------------------
rman  << EOF set echo on connect target; show all; CONFIGURE CONTROLFILE AUTOBACKUP ON; CONFIGURE DEFAULT DEVICE TYPE TO DISK; CONFIGURE CONTROLFILE AUTOBACKUP FORMAT FOR DEVICE TYPE DISK TO '/home/oracle/rman_bkp/UPGR/bkp/rman/%d_%F.ctl'; CONFIGURE DEVICE TYPE DISK PARALLELISM 1 BACKUP TYPE TO COMPRESSED BACKUPSET; CONFIGURE CHANNEL DEVICE TYPE DISK FORMAT '/home/oracle/rman_bkp/UPGR/bkp/rman/UPGR_%d_%I_%T_%U' MAXOPENFILES 1; CONFIGURE SNAPSHOT CONTROLFILE NAME TO '/home/oracle/rman_bkp/UPGR/bkp/rman/snapcf_UPGR.f'; CONFIGURE RETENTION POLICY TO REDUNDANCY 1; EOF ) > ${RMAN_SET_ENV_LOG} 2>&1
exit

Now we finally get to the point where we generate the RMAN backup scripts. I have two tasks that generate two separate RMAN backup scripts. One script that can be used for db backup (L0 and L1) and one script that can be used for archive logs backup.

Again, I achieve this with templates and for this purpose I use two separate templates: rman_db_bkp_tmpl.j2 and rman_arc_bkp_tmpl.j2. The content of each template is:

rman_db_bkp_tmpl.j2
--------------------------------------------

################################################################################
# Common RMAN Backup
################################################################################
DN=`dirname $0`
BN=`basename $0`
SID=${1:?"---> USAGE: $DN/$BN <ORACLE_SID> <LEVEL>"}
LVL=${2:?"---> USAGE: $DN/$BN <ORACLE_SID> <LEVEL>"}
set -a
source {{ user_home.stdout + backup_scr_home + '/${SID}_rman.env'}}
set +a
(
/bin/env
#------------------------------------
# BackUp of Database using RMAN
#------------------------------------
rman  << EOF
set echo on
connect target;
show all;
sql 'ALTER SYSTEM ARCHIVE LOG CURRENT';
{% if backup_type == "DISK" %}
backup as compressed backupset archivelog all not backed up 1 times tag 'ARCHIVE' format '${BACKUP_DEST}/rman_al_%d_%I_%h_%e_%T_%U';
sql 'ALTER SYSTEM ARCHIVE LOG CURRENT';
backup as compressed backupset incremental level ${LVL} database tag 'LEVEL${LVL}' format '${BACKUP_DEST}/rman_l${LVL}_%U' filesperset 1;
sql 'ALTER SYSTEM ARCHIVE LOG CURRENT';
sql "alter database backup controlfile to trace as ''${BACKUP_DEST}/${ORACLE_SID}_ctl.trc'' reuse";
backup as compressed backupset archivelog all not backed up 1 times tag 'ARCHIVE' format '${BACKUP_DEST}/rman_al_%d_%I_%h_%e_%T_%U';
backup spfile format '${BACKUP_DEST}/spfile_%d_%I_%T_%U';
backup current controlfile format '${BACKUP_DEST}/ctl_%d_%I_%T_%U';
crosscheck archivelog all;
crosscheck backup of database;
crosscheck backup of archivelog all;
delete noprompt expired archivelog all;
delete noprompt obsolete;
delete noprompt archivelog until time 'sysdate - {{ arch_log_ret }}' backed up 1 times to disk;
{% else %}
backup as compressed backupset archivelog all not backed up 1 times tag 'ARCHIVE';
sql 'ALTER SYSTEM ARCHIVE LOG CURRENT';
backup as compressed backupset incremental level ${LVL} database tag 'LEVEL${LVL}' filesperset 1;
sql 'ALTER SYSTEM ARCHIVE LOG CURRENT';
sql "alter database backup controlfile to trace as ''${BACKUP_DEST}/${ORACLE_SID}_ctl.trc'' reuse";
backup as compressed backupset archivelog all not backed up 1 times tag 'ARCHIVE';
backup spfile;
backup current controlfile;
crosscheck archivelog all;
crosscheck backup of database;
crosscheck backup of archivelog all;
delete noprompt expired archivelog all;
delete noprompt obsolete;
delete noprompt archivelog until time 'sysdate - {{ arch_log_ret }}' backed up 1 times to sbt;
{% endif %}
EOF
if [ $? != 0 ]
then
{% if notify_mail_fail == "" %}
    echo "\n RMAN backup of DB $ORACLE_SID is Failed .... : \n"
    echo " Check BkUp Completed or not .....\n "
    exit 1
    {% else %}
    echo "\n RMAN backup of DB $ORACLE_SID is Failed .... : \n"
    echo " Check BkUp Completed or not .....\n "
    mailx -s "FAILED: ${ORACLE_SID} - LEVEL ${LVL} RMAN backup of ${ORACLE_SID}" "{{ notify_mail_fail }}" < ${BACKUP_LOGFILE}
    exit 1
{% endif %}
fi
#--------------------------------
# Backup of initora and password file
#---------------------------------
cp -v $ORACLE_INITFILE $BACKUP_DEST/.
cp -v $ORACLE_PWDFILE  $BACKUP_DEST/.
echo " \n BackUp of DB ${ORACLE_SID} Ended at :`date` \n"
sleep 3
{% if notify_mail_succ != "" %}
mailx -s "SUCCESS: ${ORACLE_SID} - LEVEL ${LVL} RMAN Backup of ${ORACLE_SID}." "{{ notify_mail_succ }}" < ${BACKUP_LOGFILE} {% endif %} sleep 3 [[ ! -z "$BACKUP_HOME/logs" ]] && find $BACKUP_HOME/logs -maxdepth 1 -name "*.log" -mtime +{{ logs_ret }} -type f -exec ls -lt {} \; [[ ! -z "$BACKUP_HOME/logs" ]] && find $BACKUP_HOME/logs -maxdepth 1 -name "*.log" -mtime +{{ logs_ret }} -type f -exec rm {} \; ) > ${BACKUP_LOGFILE} 2>&1
exit


rman_arc_bkp_tmpl.j2
--------------------------------------------

################################################################################
# Common RMAN Archive Logs Backup
################################################################################
DN=`dirname $0`
BN=`basename $0`
SID=${1:?"---> USAGE: $DN/$BN <ORACLE_SID> "}
set -a
source {{ user_home.stdout + backup_scr_home + '/${SID}_rman.env'}}
set +a
(
/bin/env
#------------------------------------
# BackUp of archive logs using RMAN
#------------------------------------
rman  << EOF
set echo on
connect target;
show all;
sql 'ALTER SYSTEM ARCHIVE LOG CURRENT';
{% if backup_type == "DISK" %}
backup as compressed backupset archivelog all not backed up 1 times tag 'ARCHIVE' format '${BACKUP_DEST}/rman_al_%d_%I_%h_%e_%T_%U';
sql "alter database backup controlfile to trace as ''${BACKUP_DEST}/${ORACLE_SID}_ctl.trc'' reuse";
backup spfile format '${BACKUP_DEST}/spfile_%d_%I_%T_%U';
backup current controlfile format '${BACKUP_DEST}/ctl_%d_%I_%T_%U';
crosscheck archivelog all;
crosscheck backup of archivelog all;
delete noprompt expired archivelog all;
delete noprompt archivelog until time 'sysdate - {{ arch_log_ret }}' backed up 1 times to disk;
{% else %}
backup as compressed backupset archivelog all not backed up 1 times;
sql "alter database backup controlfile to trace as ''${BACKUP_DEST}/${ORACLE_SID}_ctl.trc'' reuse";
backup spfile;
backup current controlfile;
crosscheck archivelog all;
crosscheck backup of archivelog all;
delete noprompt expired archivelog all;
delete noprompt archivelog until time 'sysdate - {{ arch_log_ret }}' backed up 1 times to sbt;
{% endif %}
EOF
if [ $? != 0 ]
then
{% if notify_mail_fail == "" %}
    echo "\n RMAN backup of archive logs for $ORACLE_SID is Failed .... : \n"
    echo " Check BkUp Completed or not .....\n "
    exit 1
    {% else %}
    echo "\n RMAN backup of archive logs for $ORACLE_SID is Failed .... : \n"
    echo " Check BkUp Completed or not .....\n "
    mailx -s "FAILED: ${ORACLE_SID}- archivelog backup of ${ORACLE_SID}" "{{ notify_mail_fail }}" < ${BACKUP_ARC_LOGFILE}
    exit 1
{% endif %}
fi
#--------------------------------
# Backup of initora and password file
#---------------------------------
cp -v $ORACLE_INITFILE $BACKUP_DEST/.
cp -v $ORACLE_PWDFILE  $BACKUP_DEST/.
echo " \n BackUp of archive log ${ORACLE_SID} Ended at :`date` \n"
sleep 3
{% if notify_mail_succ != "" %}
mailx -s "SUCCESS: ${ORACLE_SID}- Archive logs RMAN Backup of ${ORACLE_SID}." "{{ notify_mail_succ }}" < ${BACKUP_ARC_LOGFILE} {% endif %} sleep 3 [[ ! -z "$BACKUP_HOME/logs" ]] && find $BACKUP_HOME/logs -maxdepth 1 -name "*.log" -mtime +{{ logs_ret }} -type f -exec ls -lt {} \; [[ ! -z "$BACKUP_HOME/logs" ]] && find $BACKUP_HOME/logs -maxdepth 1 -name "*.log" -mtime +{{ logs_ret }} -type f -exec rm {} \; ) > ${BACKUP_ARC_LOGFILE} 2>&1
exit

In both templates you can notice at the very beginning that I source out the previously generated script for setting up the Oracle environments. Then depending on the backup type (DISK or SBT) , I generate the required commands for taking the backup.
Another item to notice is the arch_log_ret variable usage that was defined by the user as prompted in the vars_prompt section. Depending on whether or not you enter notification emails for SUCCESS or FAILURE the backup script includes or not the notifying steps as well.
And at the end there is implementation of the script logs cleanup policy depending on the logs_ret variable that is again defined by the user in the vars_prompt section.

With all the templates available, I run the following tasks that actually which generate the required RMAN scripts:

- name: RMAN DB bkp template
  template:
    src: rman_db_bkp_tmpl.j2
    dest: "{{ user_home.stdout + backup_scr_home + '/rman_db_backup.sh' }}"
    mode: 0755
  tags: [ 'dbbkptmpl' ]

- name: RMAN Archive logs bkp template
  template:
    src: rman_arc_bkp_tmpl.j2
    dest: "{{ user_home.stdout + backup_scr_home + '/rman_arc_backup.sh' }}"
    mode: 0755
  tags: [ 'archbkptmpl' ]

We end up having the following scripts available for usage, for taking:

<oracle OS home>/scripts/bkp/rman/rman_db_backup.sh
<oracle OS home>/scripts/bkp/rman/rman_arc_backup.sh

In my test case for UPGR test database I got generated the following two scripts:

-rwxr-xr-x. 1 oracle dba 2205 Jul 28 12:18 /home/oracle/scripts/bkp/rman/rman_db_backup.sh
-rwxr-xr-x. 1 oracle dba 1781 Jul 28 12:18 /home/oracle/scripts/bkp/rman/rman_arc_backup.sh

And the content of both is:

rman_db_backup.sh
------------------------------------------------------------

################################################################################
# Common RMAN Backup
################################################################################
DN=`dirname $0`
BN=`basename $0`
SID=${1:?"---> USAGE: $DN/$BN <ORACLE_SID> <LEVEL>"}
LVL=${2:?"---> USAGE: $DN/$BN <ORACLE_SID> <LEVEL>"}
set -a
source /home/oracle/scripts/bkp/rman/${SID}_rman.env
set +a
(
/bin/env
#------------------------------------
# BackUp of Database using RMAN
#------------------------------------
rman  << EOF set echo on connect target; show all; sql 'ALTER SYSTEM ARCHIVE LOG CURRENT'; backup as compressed backupset archivelog all not backed up 1 times tag 'ARCHIVE' format '${BACKUP_DEST}/rman_al_%d_%I_%h_%e_%T_%U'; sql 'ALTER SYSTEM ARCHIVE LOG CURRENT'; backup as compressed backupset incremental level ${LVL} database tag 'LEVEL${LVL}' format '${BACKUP_DEST}/rman_l${LVL}_%U' filesperset 1; sql 'ALTER SYSTEM ARCHIVE LOG CURRENT'; sql "alter database backup controlfile to trace as ''${BACKUP_DEST}/${ORACLE_SID}_ctl.trc'' reuse"; backup as compressed backupset archivelog all not backed up 1 times tag 'ARCHIVE' format '${BACKUP_DEST}/rman_al_%d_%I_%h_%e_%T_%U'; backup spfile format '${BACKUP_DEST}/spfile_%d_%I_%T_%U'; backup current controlfile format '${BACKUP_DEST}/ctl_%d_%I_%T_%U'; crosscheck archivelog all; crosscheck backup of database; crosscheck backup of archivelog all; delete noprompt expired archivelog all; delete noprompt obsolete; delete noprompt archivelog until time 'sysdate - 2' backed up 1 times to disk; EOF if [ $? != 0 ] then echo "\n RMAN backup of DB $ORACLE_SID is Failed .... : \n" echo " Check BkUp Completed or not .....\n " exit 1 fi #-------------------------------- # Backup of initora and password file #--------------------------------- cp -v $ORACLE_INITFILE $BACKUP_DEST/. cp -v $ORACLE_PWDFILE $BACKUP_DEST/. echo " \n BackUp of DB ${ORACLE_SID} Ended at :`date` \n" sleep 3 sleep 3 [[ ! -z "$BACKUP_HOME/logs" ]] && find $BACKUP_HOME/logs -maxdepth 1 -name "*.log" -mtime +30 -type f -exec ls -lt {} \; [[ ! -z "$BACKUP_HOME/logs" ]] && find $BACKUP_HOME/logs -maxdepth 1 -name "*.log" -mtime +30 -type f -exec rm {} \; ) > ${BACKUP_LOGFILE} 2>&1
exit

 
rman_arc_backup.sh
------------------------------------------------------------

################################################################################
# Common RMAN Archive Logs Backup
################################################################################
DN=`dirname $0`
BN=`basename $0`
SID=${1:?"---> USAGE: $DN/$BN <ORACLE_SID> "}
set -a
source /home/oracle/scripts/bkp/rman/${SID}_rman.env
set +a
(
/bin/env
#------------------------------------
# BackUp of archive logs using RMAN
#------------------------------------
rman  << EOF set echo on connect target; show all; sql 'ALTER SYSTEM ARCHIVE LOG CURRENT'; backup as compressed backupset archivelog all not backed up 1 times tag 'ARCHIVE' format '${BACKUP_DEST}/rman_al_%d_%I_%h_%e_%T_%U'; sql "alter database backup controlfile to trace as ''${BACKUP_DEST}/${ORACLE_SID}_ctl.trc'' reuse"; backup spfile format '${BACKUP_DEST}/spfile_%d_%I_%T_%U'; backup current controlfile format '${BACKUP_DEST}/ctl_%d_%I_%T_%U'; crosscheck archivelog all; crosscheck backup of archivelog all; delete noprompt expired archivelog all; delete noprompt archivelog until time 'sysdate - 2' backed up 1 times to disk; EOF if [ $? != 0 ] then echo "\n RMAN backup of archive logs for $ORACLE_SID is Failed .... : \n" echo " Check BkUp Completed or not .....\n " exit 1 fi #-------------------------------- # Backup of initora and password file #--------------------------------- cp -v $ORACLE_INITFILE $BACKUP_DEST/. cp -v $ORACLE_PWDFILE $BACKUP_DEST/. echo " \n BackUp of archive log ${ORACLE_SID} Ended at :`date` \n" sleep 3 sleep 3 [[ ! -z "$BACKUP_HOME/logs" ]] && find $BACKUP_HOME/logs -maxdepth 1 -name "*.log" -mtime +30 -type f -exec ls -lt {} \; [[ ! -z "$BACKUP_HOME/logs" ]] && find $BACKUP_HOME/logs -maxdepth 1 -name "*.log" -mtime +30 -type f -exec rm {} \; ) > ${BACKUP_ARC_LOGFILE} 2>&1
exit

6.	And now we have reached to the end. We have all the required scripts ready on place so all we have to do next is schedule them to run from cron. For that purpose, I use the ansible cron module the following way:

- name: Schedule RMAN L0 backup from oracle cron
  cron:
    name: "Schedule RMAN L0"
    user: "oracle"
    minute: "{{ sched_L0.split()[0] }}"
    hour: "{{ sched_L0.split()[1] }}"
    day: "{{ sched_L0.split()[2] }}"
    month: "{{ sched_L0.split()[3] }}"
    weekday: "{{ sched_L0.split()[4] }}"
    job: "{{ user_home.stdout + backup_scr_home + '/rman_db_backup.sh' + ' ' + oracle_sid + ' 0 > /tmp/rman_db_backup_L0.out 2>&1'}}"
  tags: [ 'cronsch','L0' ]

- name: Schedule RMAN L1 backup from oracle cron
  cron:
    name: "Schedule RMAN L1"
    user: "oracle"
    minute: "{{ sched_L1.split()[0] }}"
    hour: "{{ sched_L1.split()[1] }}"
    day: "{{ sched_L1.split()[2] }}"
    month: "{{ sched_L1.split()[3] }}"
    weekday: "{{ sched_L1.split()[4] }}"
    job: "{{ user_home.stdout + backup_scr_home + '/rman_db_backup.sh' + ' ' + oracle_sid + ' 1 > /tmp/rman_db_backup_L1.out 2>&1'}}"
  tags: [ 'cronsch','L1' ]

- name: Schedule RMAN archive log backup from oracle cron
  cron:
    name: "Schedule RMAN arch"
    user: "oracle"
    minute: "{{ sched_arch.split()[0] }}"
    hour: "{{ sched_arch.split()[1] }}"
    day: "{{ sched_arch.split()[2] }}"
    month: "{{ sched_arch.split()[3] }}"
    weekday: "{{ sched_arch.split()[4] }}"
    job: "{{ user_home.stdout + backup_scr_home + '/rman_arc_backup.sh' + ' ' + oracle_sid + ' > /tmp/rman_arc_backup.out 2>&1'}}"
  tags: [ 'cronsch','archive' ]

As you can see, you can play with the schedules as you like.  You can either go with default configuration for scheduling L0 to run once a week, L1 to run every day except the day when L0 runs and Archive logs backup to run every hour, or you can make combination according your needs with excluding tasks by simply skipping the required tags: ex. –skip-tags “L1”. I will provide some examples in the next section so the actual usage is clearer.

Having all the above implemented marks the end of this process, so next you simply need to call this role from the playbook we built at the very beginning of this blog (where I explained the vars_prompt and pre_tasks module) by adding the following two lines at the end of the playbook:

  roles:
    - rmanbackup

Voila, your playbook is ready for run.

But before I conclude this blog, I’d like to provide you with some examples of this playbook usage so you get better understanding of a few scenarios you might encounter:

1. If you run this playbook as is with all the variables default settings by calling:

ansible-playbook rmanbackup.yml

you will end up getting the following oracle cron entries on the database box:

#Ansible: Schedule RMAN L0
0 1 * * 0 /home/oracle/scripts/bkp/rman/rman_db_backup.sh <oracle_sid you provided> 0 > /tmp/rman_db_backup_L0.out 2>&1
#Ansible: Schedule RMAN L1
0 1 * * 1-6 /home/oracle/scripts/bkp/rman/rman_db_backup.sh <oracle_sid you provided> 1 > /tmp/rman_db_backup_L1.out 2>&1
#Ansible: Schedule RMAN arch
0 * * * * /home/oracle/scripts/bkp/rman/rman_arc_backup.sh <oracle_sid you provided> > /tmp/rman_arc_backup.out 2>&1

2. If you end up stuck with some cron schedule entire which format could not be validated by the regex I use, you can always skip the cron entries validation by simply skipping the required tags: ex. –skip-tags “cronl0validate,cronl1validate,cronarchvalidate” and just make sure you put valid values, either through prompt or in the default parameters section of the role or define them with the –extra-vars inline parameter when calling the playbook:

Ex. ansible-playbook rmanbackup.yml --skip-tags "cronl0validate,cronl1validate,cronarchvalidate" --extra-vars "sched_arch='0 */2 * * *'"

you will end up getting the following oracle cron entries on the database box:

#Ansible: Schedule RMAN L0
0 1 * * 0 /home/oracle/scripts/bkp/rman/rman_db_backup.sh <oracle_sid you provided> 0 > /tmp/rman_db_backup_L0.out 2>&1
#Ansible: Schedule RMAN L1
0 1 * * 1-6 /home/oracle/scripts/bkp/rman/rman_db_backup.sh <oracle_sid you provided> 1 > /tmp/rman_db_backup_L1.out 2>&1
#Ansible: Schedule RMAN arch
0 */2 * * * /home/oracle/scripts/bkp/rman/rman_arc_backup.sh <oracle_sid you provided> > /tmp/rman_arc_backup.out 2>&1

3. In the event that you don’t need L1 backup for any reason, and you want to schedule only L0 and Archive logs backup, you can simply skip the L1 backup schedule the following way:

Ex. ansible-playbook rmanbackup.yml --skip-tags "L1"

you will end up getting the following oracle cron entries on the database box:

#Ansible: Schedule RMAN L0
0 1 * * 0 /home/oracle/scripts/bkp/rman/rman_db_backup.sh <oracle_sid you provided> 0 > /tmp/rman_db_backup_L0.out 2>&1
#Ansible: Schedule RMAN arch
0 */2 * * * /home/oracle/scripts/bkp/rman/rman_arc_backup.sh <oracle_sid you provided> > /tmp/rman_arc_backup.out 2>&1

4. If you don’t have your database configured yet (but you would like to have all scripts generated and available for review with possible adjustments ,simply just to see what this playbook generates) you can do it the following way:

Ex. ansible-playbook rmanbackup.yml --skip-tags "sid,cronsch,setrmanenv"

You will only have all scripts generated under /home/oracle/scripts/bkp/rman

Well, that all’s from me on this topic, at least for now. I hope you’ve found this blog useful and interesting enough to get your hands dirty and try out some of the scenarios described in here. Enjoy!

email

Interested in working with Vladimir? Schedule a tech call.

3 Comments. Leave new

Mikael Sandström
August 2, 2017 5:16 pm

Good stuff!

One (minor) thing though, when creating a directory you don’t need to check for the existence of a directory (the stat tasks) before.
Since you tell Ansible the state you want the directory to be in, Ansible will just create it if it doesn’t exist, and if the directory(ies) already exist Ansible will do nothing (because of idempotency)

regards
/M

Reply

Thank you Mikael for the great feedback!

That makes perfect sense. I am still new to ansible and I must have missed that part from the ansible documentation so it looks like I made duplicate check :)

Thank you for catching this.

Reply

Thank you for sharing this post.

Reply

Leave a Reply

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