Friday, 3 July 2026

Automate Exadata DBA task using Ansible and Python

Question : Error Handling in Ansible


Error handling in Ansible refers to the mechanisms used to control how a playbook responds when a task encounters a failure. By default, if a task fails on a specific managed host, Ansible stops executing subsequent tasks on that host and moves on to the remaining healthy hosts. Error handling tools override this default behavior to make playbooks resilient, allowing them to recover from expected errors, define custom failure logic, or abort gracefully. [

Core Error Handling Mechanisms
Ansible provides five primary keywords to handle errors, which are explained in the table below:
KeywordDescriptionCommon Use Case
ignore_errorsContinues playbook execution even if the specific task returns a failed status.Running a tool that returns non-standard exit codes.
failed_whenDefines custom conditions that trigger a task failure based on registered output.Failing a task if a specific word appears in the output log.
block, rescue, alwaysMimics try-catch-finally logic to group tasks, catch errors, and execute cleanup routines.Restoring a backup configuration file if a deployment task fails.
any_errors_fatalAborts the entire play on all hosts if a single host encounters a failure.Stopping a rolling deployment if the first server fails updating.
force_handlersForces notified handlers to run even if a subsequent task fails later in the play.Ensuring a service restarts after its configuration file is updated.

Comprehensive Playbook Example
The following playbook demonstrates block-rescue-always structure, custom failures via failed_when, and error bypassing via ignore_errors
yaml
---
- name: Advanced Error Handling Demonstration
  hosts: localhost
  gather_facts: false
  vars:
    target_dir: "/tmp/app_deploy"

  tasks:
    - name: Non-critical operations zone
      block:
        - name: Attempt to run a diagnostic tool that might not exist
          ansible.builtin.command: /usr/local/bin/check_status
          register: tool_result
          ignore_errors: true

        - name: Execute application deployment phase
          block:
            - name: Check system configuration output
              ansible.builtin.command: echo "STATUS=CRITICAL_ERROR"
              register: app_status
              # Fail if the output contains the string 'CRITICAL_ERROR'
              failed_when: "'CRITICAL_ERROR' in app_status.stdout"

          rescue:
            - name: Log the explicit failure message
              ansible.builtin.debug:
                msg: "Deployment failed! Caught error in task: {{ ansible_failed_task.name }}"

            - name: Perform rollback by removing target directory
              ansible.builtin.file:
                path: "{{ target_dir }}"
                state: absent

          always:
            - name: Clean up local system lock files
              ansible.builtin.file:
                path: "/tmp/deploy.lock"
                state: absent
Test Cases and Verification
To validate your error handling logic without disrupting production systems, test the configurations under these specific conditions using the Ansible Playbook Documentation as a baseline reference: 
Test Case 1: Verifying ignore_errors Behavior 
  • Objective: Ensure the playbook does not halt when a designated task breaks.
  • Setup: Create a task running a false command (ansible.builtin.command: /bin/false) and append ignore_errors: true.
  • Expected Result: The task shows a red ...ignoring message in the console output, and execution moves directly to the next task. [
Test Case 2: Validating Custom failed_when Triggers 
  • Objective: Ensure standard commands trigger failures if specific negative criteria are met.
  • Setup: Write a task that returns a successful exit status code 0, but outputs an error message like "Database Connection Refused". Use failed_when: "'Refused' in result.stdout".
  • Expected Result: Ansible marks the task as failed despite the success exit code returned by the operating system shell. [
Test Case 3: Simulating rescue and always Execution
  • Objective: Validate that rollback and cleanup routines activate under failing conditions.
  • Setup: Create a file at /tmp/deploy.lock. Force a failure inside the main block using an invalid path parameter.
  • Expected Result: The main block task fails. The rescue task triggers to delete the directory. The always task executes last, successfully removing the lock file. [
Test Case 4: Testing Dry Run Safety with Check Mode
  • Objective: Prevent destructive tasks from failing or making changes during infrastructure audits.
  • Setup: Run your playbook utilizing the --check CLI flag. Match it dynamically inside your task by declaring ignore_errors: "{{ ansible_check_mode }}".
  • Expected Result: Ansible simulates task modifications safely, generating reports without executing live system calls or throwing breaking errors. 


Question : What is Role Library


Ansible roles let you group tasks, variables, and files into a clean folder structure.
 Libraries are custom code units (modules or plugins) written in Python or PowerShell. They extend Ansible to manage specific systems or tools. [
Ansible Roles
Folder Structure
  • tasks/ - Main list of tasks to run
  • vars/ - Variables for the role
  • defaults/ - Default variables with low priority
  • files/ - Static files to copy to servers
  • templates/ - Jinja2 template files
  • handlers/ - Services to restart or trigger
  • meta/ - Author and dependency data [
Role Example
This simple role installs and starts an Nginx web server. 
  • roles/nginx/tasks/main.yml:
    yaml
    - name: Install Nginx
      apt:
        name: nginx
        state: present
    - name: Start Nginx
      service:
        name: nginx
        state: started
        enabled: true
    

  • Playbook to use the role:
    yaml
    - hosts: webservers
      roles:
        - nginx
    

Ansible Custom Libraries (Modules)
Custom Module Example
A custom library sits in a library/ folder next to your playbook. 
This custom Python module checks if a file has a specific word. 
  • library/check_word.py:
    python
    from ansible.module_utils.basic import AnsibleModule
    
    def run_module():
        module = AnsibleModule(
            argument_spec=dict(
                path=dict(type='str', required=True),
                word=dict(type='str', required=True)
            )
        )
        path = module.params['path']
        word = module.params['word']
    
        try:
            with open(path, 'r') as f:
                content = f.read()
            found = word in content
            module.exit_json(changed=False, found=found)
        except Exception as e:
            module.fail_json(msg=str(e))
    
    if __name__ == '__main__':
        run_module()
    
    Test Cases
Testing Roles
  • Use Molecule to spin up test servers in Docker or Vagrant.
  • Run a test task to verify the service is active:
    yaml
    - name: Check if Nginx port 80 is open
      wait_for:
        port: 80
        timeout: 5
    

Testing Custom Libraries
  • Write unit tests in Python using pytest and mock the AnsibleModule input.
  • Test by calling the module directly in a playbook:
    yaml
    - hosts: localhost
      tasks:
        - name: Test custom word checker
          check_word:
            path: /etc/hosts
            word: localhost
          register: result
        - name: Show result
          debug:
            msg: "Found word? {{ result.found }}"

what is roles - import vs include


In Ansible, import is static and processed when playbooks are first parsed, while include is dynamic and processed at runtime when reached. Use import_role for fixed structures and include_role when you need loops, conditional logic, or variable-driven execution
Key Differences
  • Timing: import_role loads tasks at compile time; include_role loads tasks during execution.
  • Loops: include_role supports loops (loop), whereas import_role does not properly loop over the entire role structure.
  • Conditionals: A when condition on an import_role applies to every child task inside the role. On an include_role, the condition applies only to the inclusion step itself.
  • Tags: import_role applies tags to all internal tasks. include_role applies tags only to the dynamic inclusion task unless specified otherwise.
  • Error Checking: import_role catches syntax errors early before the run starts. include_role only fails when execution reaches that specific step. 


Question: what is block



An Ansible block is a logical feature used to group multiple tasks together within a playbook
Blocks serve two primary purposes: 
  1. Shared Directives: They let you apply a configuration (like when, become, or environment) to multiple tasks at once instead of repeating it for every single task.
  2. Error Handling: They mimic the try/catch/finally mechanism of standard programming using accompanying rescue and always sections. 

Key Concepts & Structure
  • block: The main group of tasks you want to execute.
  • rescue: Tasks that run only if any task inside the main block fails.
  • always: Tasks that run regardless of whether the block or rescue sections succeeded or failed (perfect for clean-ups). 

Ansible Playbook Example
The following example groups package management tasks, defines error recovery if the main step fails, and runs a final log cleanup.
yaml
---
- name: Demonstrate Ansible Blocks
  hosts: localhost
  gather_facts: false
  vars:
    target_package: "invalid-package-name"

  tasks:
    - name: Core application setup
      block:
        - name: Print installation start
          ansible.builtin.debug:
            msg: "Attempting to install {{ target_package }}..."

        - name: Install the designated package
          ansible.builtin.apt:
            name: "{{ target_package }}"
            state: present
          # This task will fail because the package name is deliberately fake

        - name: This task will be skipped
          ansible.builtin.debug:
            msg: "This will not run because the previous task fails."

      rescue:
        - name: Handle installation error
          ansible.builtin.debug:
            msg: "Installation failed! Falling back to emergency logging."

        - name: Record error instance
          ansible.builtin.lineinfile:
            path: /tmp/setup_errors.log
            line: "Failed to install {{ target_package }} on {{ ansible_date_time.date | default('today') }}"
            create: yes

      always:
        - name: Perform post-execution cleanup
          ansible.builtin.file:
            path: /tmp/temporary_installer_cache.tmp
            state: absent
Scenario Test Cases
Testing your blocks ensures your exception workflows function predictably. You can validate your setup using these three operational scenarios: 
Test Case 1: Happy Path (No Failures)
  • Setup: Set target_package to a valid, existing package (e.g., curl).
  • Expected Result:
    • All tasks inside the block section complete successfully.
    • The rescue section is entirely skipped.
    • The always section runs cleanly at the very end. 
Test Case 2: Failure and Recovery Path (Main Block Fails)
  • Setup: Set target_package to a non-existent package (e.g., invalid-package-name). 
  • Expected Result:
    • The first debug task in the block runs.
    • The apt task fails, immediately halting further tasks inside that specific block.
    • Playbook control switches over to the rescue section, executing the emergency error logging.
    • The always section executes right after the rescue tasks complete.
    • The total execution is marked as successful (not failed) because the error was successfully rescued. 
Test Case 3: Conditional Block Inheritance Test
  • Setup: Add a condition like when: ansible_os_family == "RedHat" directly to the block: level. Run it on an Ubuntu host (Debian family). 
  • Expected Result:
    • Because the block level condition is inherited by the tasks and evaluates to false, the entire block is safely skipped.
    • The rescue section is not triggered (since nothing failed).
    • The always section still runs because it ignores success/failure outcomes. [

Question : ROLLING update


A rolling update in Ansible updates a group of servers one at a time or in small batches. This keeps your application running without downtime. You use the serial keyword in a play to set the batch size. 
How Rolling Updates Work
  • Serial control: The serial keyword tells Ansible how many hosts to update at once.
  • Batch progress: Ansible finishes all tasks for the first batch before moving to the next batch.
  • Failure stop: If a host fails in a batch, Ansible stops the rest of the run.
Example Playbook
This playbook updates web servers in batches of two. It pauses to check health before moving on. [
yaml
- hosts: webservers
  serial: 2
  max_fail_percentage: 0
  tasks:
    - name: Remove server from load balancer
      command: /usr/local/bin/lb_out.sh {{ inventory_hostname }}

    - name: Update web app package
      apt:
        name: myapp
        state: latest

    - name: Test local health endpoint
      uri:
        url: http://localhost/health
        return_content: yes
      register: health_check
      until: health_check.status == 200
      retries: 3
      delay: 5

    - name: Add server back to load balancer
      command: /usr/local/bin/lb_in.sh {{ inventory_hostname }}
Test Cases for Rolling Updates
  • Zero downtime test: Send continuous HTTP requests to your app during the run. Verify that no requests fail while a batch is updating.
  • Batch size test: Run with -v (verbose). Check the output logs to confirm that only the set number of servers (e.g., serial: 2) run tasks at the same time. 
  • Failure recovery test: Simulate a failure on one server during the health check. Confirm that Ansible stops the deployment immediately and does not update the next batch. 
  • Load balancer test: Verify that the script successfully removes the server from traffic before the update and adds it back only after a successful health check. 


Question : what is SET FACT

A module that allows setting of new variable during playbook run

   - Dynamic and set on a host-by-host basis

available to subsequent playbooks in the same run

save to fact cache to allow variable to save across executions

# example setting facts so that they will be persisted in the fact cached
  -one_fact: something
   other_fact: "{{ local_var * 2}}"
   cacheable: yes  

Question : What is task in ansible


Tasks are a collection of modules run in sequential order

Question : what is roles

Groups tasks(playbooks) together into a directory structure

ultimately  roles become the logic and functionality of your automation while the master playbook contains the hosts and other higher-level instruction

Imported/exported for added flexibility and reusability

cannot be run like a regular playbook, instead they must be included via role module in a playbook.

An Ansible role is a self-contained, portable unit of automation that organizes related tasks, variables, files, templates, and handlers into a standardized directory structure. Instead of writing a massive, single-file playbook (monolithic playbook), roles allow you to break your automation down into small, modular, and reusable components. 
Think of a playbook as a script and a role as a reusable package or class in standard programming. For example, you can write a single database_setup role and reuse it across multiple completely different projects
Standard Directory Structure
When you use a role, Ansible automatically looks for specific files named main.yml inside predefined folders. You can automatically generate this skeleton using the ansible-galaxy init <role_name> command. 
A standard Ansible role contains the following components: 
  • tasks/: Contains the main list of tasks to be executed by the role.
  • handlers/: Houses handlers, which are tasks triggered by other tasks (e.g., restarting a service after a config change).
  • defaults/: Holds default variables for the role that have the lowest priority and can be easily overridden.
  • vars/: Contains strict variables specific to the role that shouldn't be easily changed.
  • files/: Stores static files that need to be copied directly onto the managed servers.
  • templates/: Holds dynamic Jinja2 templates used to generate server configuration files on the fly.
  • meta/: Defines metadata, author info, and role dependencies (other roles this role depends on). [

Key Benefits of Using Roles
  • Modularity: Breaks complex system configurations (like setting up Kubernetes or Nginx) into clean, isolated units.
  • High Reusability: Write the code once and call it across multiple different infrastructure playbooks.
  • Clarity: Standard directories make it incredibly easy for other DevOps engineers to instantly understand your codebase.
  • Easy Sharing: Roles can be bundled and shared publicly or internally using platforms like Ansible Galaxy. 

How to Use a Role in a Playbook
Once a role is created or downloaded, you call it inside a playbook file using the roles keyword: 
yaml
---
- hosts: webservers
  roles:
    - example-role
   

What is inventory pattern


An Ansible inventory pattern is a syntax used to target specific managed hosts or groups
within your inventory when running ad-hoc commands or executing playbooks.
Instead of running tasks against your entire infrastructure, patterns allow you to filter
and select exactly which servers to execute automation on using logic
like wildcards, exclusions, intersections, and ranges.

The Sample Inventory
To understand how patterns work, assume we are using this hosts.ini inventory file:
ini
[webservers]
web-prod-01
web-prod-02
web-stage-01

[dbservers]
db-prod-01
db-stage-01

[production]
web-prod-01
web-prod-02
db-prod-01

[staging]
web-stage-01
db-stage-01
Types of Inventory Patterns with Examples
You can apply patterns directly in the hosts: field of an Ansible Playbook or
pass them as the first argument in an ad-hoc command.
1. Target Everything (all or *)
Targets every single host defined in the inventory file.
  • Ad-hoc command: ansible all -m ping
  • Playbook syntax:
    yaml
    - hosts: all
      tasks: ...
    

2. Single Host or Group
Targets a specific machine name or an entire group bracket.
  • Pattern: webservers (Targets all 3 web servers)
  • Pattern: web-prod-01 (Targets only that specific host)
3. Wildcards (*)
Matches hosts or groups based on a text string string. Always wrap wildcard patterns in quotes to prevent your terminal shell from misinterpreting them. [1]
  • Pattern: '*prod*' (Targets web-prod-01, web-prod-02, and db-prod-01)
  • Ad-hoc command: ansible '*prod*' -m ping
4. Intersection / AND Logic (:&)
Targets only the hosts that exist in both specified groups.
  • Pattern: webservers:&production
  • Result: Targets web-prod-01 and web-prod-02 (They are webservers AND in production).
5. Union / OR Logic (,)
Targets hosts that belong to either group.
  • Pattern: webservers,dbservers
  • Result: Targets all hosts across both lists.
6. Exclusion / NOT Logic (:!)
Targets a group but explicitly filters out hosts belonging to another group.
  • Pattern: production:!dbservers
  • Result: Targets web-prod-01 and web-prod-02 (All production hosts EXCEPT database servers).
7. Range Selection
Targets a subset of a group based on their numerical index position (0-indexed).
  • Pattern: webservers[0] (Targets web-prod-01)
  • Pattern: webservers[0:1] (Targets web-prod-01 and web-prod-02)

Advanced Combination Example
You can string multiple patterns together to build highly specific logic.
bash
ansible 'webservers:production:!web-prod-02' -m ping
What this does: Targets hosts that are in the webservers group AND the production group, but EXCLUDES the specific host web-prod-02. The only server that receives this command is web-prod-01. [1, 2, 3, 4, 5]



Question : What is variable



An Ansible variable is a dynamic value that allows you to manage differences
between your environments, hardware, and deployment types without
rewriting your automation code. They act as placeholders
that Jinja2 templates or tasks evaluate during a playbook run.

Variable Types
Ansible structures variables into two primary categories based on complexity:
  • Simple Variables: Store a single alphanumeric value.
    • Strings: Plain text headers or parameters (e.g., app_name: "frontend").
    • Integers/Floats: Numeric identifiers or timeouts (e.g., web_port: 8080).
    • Booleans: Logic flags (e.g., enable_ssl: true).
  • Complex Variables: Store nested, structured data using Python-like data types.
    • Lists/Arrays: Flat, sequential groupings of items used for loops (e.g., packages: ['nginx', 'git']).
    • Dictionaries/Hashes: Grouped key-value configurations (e.g., db: { name: 'prod', user: 'admin' }). [1, 2, 3]

Ansible primarily uses three structural formats to handle data:
  • Simple Variables: Single values such as strings, integers, or booleans (e.g., http_port: 80).
  • Lists (Arrays): Ordered collections of values (e.g., a list of packages to install).
  • Dictionaries (Hashes): Key-value pairs used to group related properties together (e.g., user profiles with names and shells). [1, 2, 3, 4]
Additionally, Ansible injects specialized functional variables:
  • Ansible Facts: System data automatically gathered from remote hosts (like ansible_os_family or IP addresses).
  • Registered Variables: Outputs captured from the result of a running task using the register keyword.
  • Magic Variables: Built-in variables (like hostvars, groups, or inventory_hostname) providing internal data about the Ansible environment. [1, 2, 3, 4, 5]


Variable Scope Management
Ansible manages variable lifetime and availability across three main scopes:
  1. Global Scope: Values apply to all execution contexts and hosts.
These are set via the command line (-e / --extra-vars), environment variables, or ansible.cfg config files.
  1. Play Scope: Variables are visible strictly within the context of a specific play
and its child structures (such as vars, vars_files, or role variables).
  1. Host Scope: Variables are explicitly tied to a unique managed host.
Examples include settings defined in the inventory (host_vars, group_vars),
gathered facts, or variables created dynamically using set_fact.
Precedence Hierarchy
When the same variable name is defined in multiple scopes,
Ansible resolves the conflict using a strict 22-step precedence ladder.
In basic order from lowest to highest priority:
  1. Role defaults (defaults/main.yml)
  2. Inventory group variables (group_vars)
  3. Inventory host variables (host_vars)
  4. Play variables (vars: block in playbook)
  5. Task variables (vars: block inside a task)
  6. Block or Registered variables
  7. Dynamic facts (set_fact)
  8. Extra vars (-e via command line — always overrides everything).


Variable Scopes
Ansible evaluates variables based on three main tiers of visibility and persistence:
ScopeDescriptionCommon Definitions
GlobalAccessible across all plays and all hosts in the execution.Command line flag --extra-vars, environment variables, or config files.
PlayAccessible only inside the active play structure for all targeted hosts.Playbook vars: block, vars_files, and role default definitions.
HostAssigned exclusively to specific individual hosts or groups.Inventory file variables, group_vars, collected facts, and register outputs.

Practical Playbook Example
The following deploy.yml playbook demonstrates variable initialization, lists, dictionaries, task registration, and scoping. [1, 2]
yaml
---
- name: Demonstrate Ansible Variable Types and Scopes
  hosts: localhost
  gather_facts: no
  
  # Play Scope: Simple, List, and Dictionary types
  vars:
    app_version: "2.4.1"                          # Simple: String
    max_retries: 3                                # Simple: Integer
    monitored_services:                           # Complex: List
      - nginx
      - postgresql
    database_config:                              # Complex: Dictionary
      host: "127.0.0.1"
      port: 5432

  tasks:
    - name: Display simple play-scoped variables
      ansible.builtin.debug:
        msg: "Deploying version {{ app_version }} with retry limit {{ max_retries }}."

    - name: Access dictionary properties and lists
      ansible.builtin.debug:
        msg: "Connecting to {{ database_config.host }} to check {{ monitored_services[0] }}."

    - name: Host Scope - Register command output into a host variable
      ansible.builtin.command: "uptime"
      register: system_uptime                     # Host Scope: registered variable

    - name: Access the dynamically registered host variable
      ansible.builtin.debug:
        msg: "The system status is: {{ system_uptime.stdout }}"
Testing and Validation Cases
To ensure your variable paths, naming syntax, and values resolve correctly without altering server configurations, use these test methodologies:
Test Case 1: Overriding Scopes with Global Extra Vars
Validate how global scope takes precedence over internal play variables by feeding variables straight via runtime execution. [1, 2]
bash
ansible-playbook deploy.yml --extra-vars "app_version=3.0.0 max_retries=5"
  • Expected Result: The debug task prints version 3.0.0 instead of 2.4.1 because global command-line overrides rank highest in precedence. [1, 2, 3]
Test Case 2: Validation via Dry-Run Mode
Execute check mode to dry-run your playbook structure. This evaluates your variable configurations and template paths safely. [1, 2, 3]
bash
ansible-playbook deploy.yml --check
  • Expected Result: Ansible tests the configuration step-by-step without implementing changes. Note: Tasks using register variables from dynamic commands may fail in pure --check mode if dependent subsequent tasks expect explicit execution returns. [1]
Test Case 3: Template Pre-rendering Assertion
Test complex dictionary validation using the ansible.builtin.assert module inside a test-playbook file to prevent broken deployments. [1, 2]
yaml
- name: Assert Variable Validity
  hosts: localhost
  vars:
    db_port: 5432
  tasks:
    - name: Validate that port is numeric and valid
      ansible.builtin.assert:
        that:
          - db_port is number
          - db_port == 5432
        fail_msg: "Database configuration port type error!"



Question : what is JINJA2 Template


Jinja2 is a Python-based templating engine heavily utilized by Ansible Templating
to dynamically inject variables, facts, loops, and conditional logic into text files or playbooks.
It evaluates syntax on the Ansible control node before deploying the final
rendered configurations to remote target hosts.

Core Jinja2 Syntax Delimiters
  • {{ ... }} — Expressions used to print values or variables (Interpolation).
  • {% ... %} — Control structures used for logic statements, such as loops and conditionals.
  • {# ... #} — Template comments that are ignored during rendering.

Real-World Example: Dynamic Nginx Config
This example demonstrates a common infrastructure-as-code pattern: generating an Nginx configuration file dynamically based on different backend architectures and environment requirements. [1, 2, 3, 4, 5]
1. The Template File (templates/nginx.conf.j2)
Create a template using .j2 as a standard convention:
nginx
# Generated by Ansible on {{ ansible_date_time.date }}
server {
    listen {{ nginx_port | default(80) }};
    server_name {{ server_name }};

    {% if ssl_enabled %}
    listen 443 ssl;
    ssl_certificate /etc/ssl/certs/nginx.crt;
    {% endif %}

    location / {
        proxy_pass http://backend_cluster;
    }
}

upstream backend_cluster {
    {% for server in backend_servers %}
    server {{ server }};
    {% endfor %}
}
2. The Ansible Playbook (deploy.yml)
Use the Ansible Template Module to process the .j2 template and copy it to the target system. [1, 2]
yaml
---
- name: Deploy Dynamic Nginx Configuration
  hosts: webservers
  vars:
    nginx_port: 8080
    server_name: app.example.com
    ssl_enabled: true
    backend_servers:
      - 192.168.1.10:3000
      - 192.168.1.11:3000

  tasks:
    - name: Render and copy Nginx configuration
      ansible.builtin.template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/app.conf
Jinja2 "Test Cases" (Tests vs. Filters)
In Jinja2 and Ansible, "Tests" do not refer to unit testing frameworks like PyTest. Instead, they are specific expressions used with the is keyword to evaluate data structure attributes and expressions to return a boolean True or False. [1, 2, 3]
1. Common Built-in Test Cases
You can apply these test cases directly inside templates ({% if ... %}) or inside playbook conditional constructs (when:): [1, 2, 3, 4]
  • defined / undefined: Checks whether a variable has been declared.
  • failed / success: Validates whether a previous task state succeeded or hit an error.
  • string / number / mapping: Validates variable data types.
  • even / odd: Performs mathematical parity validation.
2. Practical Test Case Execution Examples
A. Inside a Playbook using the when condition:
yaml
  tasks:
    - name: Run command and capture status
      ansible.builtin.command: /usr/local/bin/check_service.sh
      register: service_result
      ignore_errors: true

    - name: Fail explicitly if the test condition is met
      ansible.builtin.fail:
        msg: "The check script encountered a system failure!"
      when: service_result is failed  # Evaluates the 'failed' test case
B. Inside a Jinja2 Template ({% if ... is ... %}):
jinja
{# Check if a list variable is a proper mapping/dictionary before accessing keys #}
{% if my_config_var is mapping %}
  api_key: {{ my_config_var.key }}
{% else %}
  api_key: DEFAULT_KEY
{% endif %}

{# Test if an optional parameter is explicitly defined #}
{% if advanced_settings is defined %}
  max_connections: {{ advanced_settings.max_connections }}
{% endif %}
C. Testing lists with select and reject:
You can combine test expressions with filters to scrub raw data lists dynamically:
yaml
- name: Extract only the active servers from a data object
  ansible.builtin.set_fact:
    active_hosts: "{{ server_list | selectattr('status', 'equalto', 'active') | list }}"
How to Test and Debug Template Rendering
To dry-run, debug, and locally verify how your templates will transform before making live remote modifications, run the playbook using Ansible Check and Diff Modes: [1, 2]
bash
ansible-playbook deploy.yml --check --diff




Question : Ansible Module Details


There are hundreds of modules to choose from, ranging in function from API calls to writing files, to installing software, to pushing to GIT

Each module comes with parameter (remember spacing ad syntax) some of which are required, and other optional
customer modules are support


1 ) ansible.builtin.stat is a built-in core module used to retrieve file or file system status like existence, permissions, size, and checksums on remote Linux or Unix hosts
Core Parameters
  • path: The absolute path of the file or directory to check (required).
  • follow: A boolean value to decide whether to follow symbolic links (no by default).
  • get_checksum: A boolean value to compute a checksum of the file (yes by default for sha1).
  • get_mime: A boolean value to return MIME type and charset info using the system file utility (yes by default). 
Common Use Cases
  • Check if a specific file or folder exists before running tasks.
  • Inspect file permissions, ownership, or modification times for audits.
  • Validate file integrity using cryptographic hashes. 


2) ansible.builtin.fail is a core Ansible module used to stop playbook execution and return a custom error message when a specific condition is met
Key Details
  • Module name: ansible.builtin.fail (short name: fail)
  • Parameter: msg (custom string explaining why it failed)
  • Common usage: Paired with a when conditional statement to exit early if requirements fail. 
Example Playbook Usage
yaml
- name: Check required variable
  ansible.builtin.fail:
    msg: "The 'app_env' variable must be set to production or staging."
  when: app_env is not defined or app_env not in ['production', 'staging']


3) The ansible.builtin.set_fact module allows you to dynamically create or modify host variables on the fly during a playbook execution. Unlike static variables defined in inventory or vars files, facts created with set_fact are evaluated at runtime based on task outputs, conditionals, or string transformations, and they persist for the remaining tasks in the play. 
1. Setting Simple Variables
You can assign static or basic Jinja2 templated values to one or more keys simultaneously.  
yaml
- name: Set static and basic dynamic facts
  ansible.builtin.set_fact:
    environment_type: "production"
    app_version: "v2.5.1"
    deploy_time: "{{ ansible_date_time.iso8601 }}"


4) In Ansible, the legacy ansible.legacy.sudo or ansible.builtin.sudo plugin is deprecated. You should manage privilege escalation using the become directives. [
By default, setting become: true automatically utilizes sudo under the hood to execute tasks with root privileges. 
Play-Level Privilege Escalation
Use this method to apply sudo to every task inside your playbook. 
yaml
---
- name: Configure web server with global sudo privileges
  hosts: webservers
  become: true  # Enables sudo for all tasks in this play
  
  tasks:
    - name: Install Nginx package
      ansible.builtin.apt:
        name: nginx
        state: present

    - name: Ensure Nginx is running
      ansible.builtin.systemd:
        name: nginx
        state: started
Task-Level Privilege Escalation
Use this method to restrict sudo only to specific tasks that explicitly require elevated permissions. 
yaml
---
- name: Managed deployment with selective privileges
  hosts: webservers
  become: false  # Runs as the standard remote login user by default
  
  tasks:
    - name: Check server uptime (No sudo required)
      ansible.builtin.command: uptime
      changed_when: false

    - name: Restart system service (Requires sudo)
      ansible.builtin.systemd:
        name: crond
        state: restarted
      become: true  # Applies sudo exclusively to this task
Becoming a Specific User
By default, become switches your permissions to the root user. If you want to use sudo -u [user] to impersonate a different non-root user, pair become with become_user
yaml
- name: Execute tasks as a specific application user
  hosts: appservers
  tasks:
    - name: Run deploy script as the deployer account
      ansible.builtin.shell: /opt/app/bin/deploy.sh
      become: true
      become_user: deployer  # Executes via: sudo -u deployer
Passing Sudo Passwords Safely
If your target systems demand a password to invoke sudo, you can pass it to the ansible-playbook CLI tool or store it inside an encrypted variable file. 
  • Interactive CLI Prompt:
    bash
    ansible-playbook playbook.yml --ask-become-pass
    
    (Shortcut: ansible-playbook playbook.yml -K)
  • Using Ansible Vault Variables:
    Create an encrypted file containing the specific Ansible variable:
    yaml
    ansible_become_password: "YourActualSudoPasswordHere"

5) The ansible.builtin.import_role module loads and executes a role statically during playbook parsing time, allowing you to place role tasks inline between other play tasks. Key usage patterns include basic task-level imports, passing custom variables, and applying conditional tags. 
Basic Import Example
  • Run a role named common directly inside the tasks list of a play: 
yaml
- hosts: webservers
  tasks:
    - name: Import the common role
      ansible.builtin.import_role:
        name: common
Passing Variables Example
  • Supply custom variables to the imported role using the vars keyword: 
yaml
- hosts: webservers
  tasks:
    - name: Import app instance role with custom variables
      ansible.builtin.import_role:
        name: foo_app_instance
      vars:
        dir: '/opt/a'
        app_port: 5000
Applying Tags and Control
  • Apply tags directly to an imported role so they propagate to its contained tasks: 
yaml
- hosts: webservers
  tasks:
    - name: Import database role with specific tags
      ansible.builtin.import_role:
        name: database
      tags:
        - setup
        - db


6) The ansible.builtin.include_tasks module dynamically loads and executes a list of tasks from an external file at runtime. Because it evaluates files dynamically, you can use loops, conditionals, and variables to determine exactly which task files to load as your playbook executes. 
Here are the most common practical examples of using include_tasks.

1. Basic Task Inclusion
This is the simplest use case, where a large playbook is broken down into smaller, readable files. 
yaml
# main.yml (Primary Playbook)
- name: Set up the web application
  hosts: webservers
  tasks:
    - name: Include standard package installation tasks
      ansible.builtin.include_tasks: tasks/install_packages.yml
yaml
# tasks/install_packages.yml (External File)
- name: Install Nginx
  ansible.builtin.apt:
    name: nginx
    state: present
2. Conditional Inclusion (OS-Specific Tasks)
Dynamic evaluation makes include_tasks ideal for choosing different task files based on target host variables or facts. 
yaml
- name: Configure firewall dynamically
  hosts: all
  tasks:
    - name: Run Debian firewall configuration
      ansible.builtin.include_tasks: tasks/ufw_setup.yml
      when: ansible_os_family == "Debian"

    - name: Run RedHat firewall configuration
      ansible.builtin.include_tasks: tasks/firewalld_setup.yml
      when: ansible_os_family == "RedHat"
3. Dynamic File Names using Variables
You can use Jinja2 variables directly inside the file name argument to reduce boilerplate code. 
yaml
- name: Execute environment-specific configuration
  hosts: all
  tasks:
    - name: Load environment setup
      ansible.builtin.include_tasks: "tasks/{{ env_name }}_config.yml"
      # If env_name is "production", it loads tasks/production_config.yml
4. Looping Over an Included File
Unlike import_tasks, include_tasks supports standard loops. This allows you to run a multi-step sequence of tasks repeatedly for a list of items. 
yaml
# main.yml
- name: Provision user accounts
  hosts: all
  vars:
    users_to_create:
      - { name: "alice", shell: "/bin/bash" }
      - { name: "bob", shell: "/bin/zsh" }
  tasks:
    - name: Run user creation process for each entry
      ansible.builtin.include_tasks: tasks/create_user.yml
      loop: "{{ users_to_create }}"
      loop_control:
        loop_var: user_item
yaml
# tasks/create_user.yml
- name: Ensure user exists
  ansible.builtin.user:
    name: "{{ user_item.name }}"
    shell: "{{ user_item.shell }}"

- name: Create home directory profile
  ansible.builtin.file:
    path: "/home/{{ user_item.name }}/.profile"
    state: touch
5. Passing Variables via vars
You can pass localized variables into the scope of the included task file using the vars keyword. 
yaml
- name: Deploy application stack
  hosts: webservers
  tasks:
    - name: Include database backup tasks
      ansible.builtin.include_tasks: tasks/backup.yml
      vars:
        backup_dir: "/var/backups/db"
        compress: true
6. Applying Attributes to Included Tasks (apply)
If you want to pass task level parameters (like tags or become) so that they cascade down to every task inside the included file, use the apply keyword. [
yaml
- name: Perform administrative system tuning
  hosts: all
  tasks:
    - name: Load optimization tasks with specific constraints
      ansible.builtin.include_tasks: tasks/tune_kernel.yml
      apply:
        become: true
        tags:
          - optimization
          - sysctl
Summary Checklist: Include vs Import
Featureansible.builtin.include_tasks (Dynamic)ansible.builtin.import_tasks (Static)
ExecutionProcessed at runtime as encountered.Pre-parsed at playbook compilation.
LoopsFully supported (loop, with_items).Not supported directly on the task.
File NamesCan use runtime variables/facts.Must be a static string literal.
Playbook TagsTags on the include do not automatically filter subtasks when running --list-tags.Fully integrated with --list-tags and --list-tasks command options.




7) The ansible.builtin.apt module manages packages on Debian and Ubuntu systems, handling installation, removal, and system upgrades. You must include become: true on your play or task because package management requires root permissions
Basic Package Operations
  • Install a single package 
yaml
- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present
  • Install multiple packages 
yaml
- name: Install software stack
  ansible.builtin.apt:
    name:
      - curl
      - git
      - jq
    state: present
  • Install a specific version 
yaml
- name: Install specific version of nginx
  ansible.builtin.apt:
    name: nginx=1.18.0-0ubuntu1
    state: present
  • Remove a package
yaml
- name: Remove apache2 package
  ansible.builtin.apt:
    name: apache2
    state: absent
Cache & System Upgrades
  • Update apt cache only 
yaml
- name: Run apt-get update
  ansible.builtin.apt:
    update_cache: true
  • Update cache with validity check (Updates only if the last run was more than 1 hour/3600 seconds ago) 
yaml
- name: Update cache if older than 1 hour
  ansible.builtin.apt:
    update_cache: true
    cache_valid_time: 3600
  • Upgrade all packages (Equivalent to apt-get upgrade) 
yaml
- name: Upgrade all system packages
  ansible.builtin.apt:
    name: "*"
    state: latest
    update_cache: true
Advanced Cleanup & Configuration
  • Purge and autoremove (Removes configuration files and unused dependencies) [1]
yaml
- name: Completely purge a package and clean up dependencies
  ansible.builtin.apt:
    name: nginx
    state: absent
    purge: true
    autoremove: true
  • Install a local .deb package 
yaml
- name: Install package from a local path
  ansible.builtin.apt:
    deb: /tmp/google-chrome-stable_current_amd64.deb
  • Force installation without recommended packages 
yaml
- name: Install package without recommended extras
  ansible.builtin.apt:
    name: software-properties-common
    state: present
    install_recommends: false

8) The ansible.builtin.lineinfile module is designed for surgical text edits, ensuring that a single line exists, is modified, or is removed from a file. [
1. Add a Line (If Missing)
By default, this task appends the text to the bottom of the file if it is not already present. [
yaml
- name: Add a new environment variable to the file
  ansible.builtin.lineinfile:
    path: /etc/environment
    line: 'API_KEY="xyz123"'
    state: present
2. Find and Replace a Line (Using Regex)
The regexp parameter searches for an existing pattern. If found, Ansible replaces that exact line. If not found, it appends the line to the end of the file. [
yaml
- name: Ensure SSH password authentication is disabled
  ansible.builtin.lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^PasswordAuthentication'
    line: 'PasswordAuthentication no'
    state: present
3. Remove a Line
Setting state: absent deletes any line that matches your regular expression. [
yaml
- name: Remove a specific user from the sudoers file
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    regexp: '^baduser '
    state: absent
4. Insert Relative to Existing Content
You can target a specific section using insertafter or insertbefore paired with a regex string. [
yaml
- name: Insert a database configuration setting after the [database] section header
  ansible.builtin.lineinfile:
    path: /etc/myapp/config.ini
    regexp: '^db_timeout ='
    line: 'db_timeout = 30'
    insertafter: '^\[database\]'
5. Create a File if It Does Not Exist
If the target file might not exist yet, use create: true. You should also specify file permissions. 
yaml
- name: Create local configuration file and add an entry
  ansible.builtin.lineinfile:
    path: /opt/app/local_config.txt
    line: 'ENV=production'
    create: true
    owner: root
    group: root
    mode: '0644'
6. Edit and Validate Configurations Safely
For critical files like sudoers or Apache/SSH configurations, use validate to test the file syntax before saving the changes. If the validation command fails, Ansible rejects the modification. 
yaml
- name: Safely update sudoers configuration with validation
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    regexp: '^%wheel'
    line: '%wheel ALL=(ALL) NOPASSWD: ALL'
    validate: '/usr/sbin/visudo -cf %s'
Quick Alternative Guide
  • Use lineinfile: When editing or ensuring one specific line. 
  • Use ansible.builtin.blockinfile: When adding or managing a multi-line block of configuration text.
  • Use ansible.builtin.template: When managing an entire configuration file from scratch. 


9) The ansible.builtin.import_role module allows you to statically run a role mid-playbook. Core usage examples include basic imports, running specific task files instead of main.yml, passing inline variables, and applying conditionals. 
Basic Role Import
Run a standard role named myrole inside the tasks list:
yaml
- hosts: all
  tasks:
    - name: Import myrole statically
      ansible.builtin.import_role:
        name: myrole
```

### Import Specific Task File
Execute a specific file from the role's `tasks/` directory rather than the default `main.yml`:
```yaml
- hosts: all
  tasks:
    - name: Run tasks/other.yaml instead of main
      ansible.builtin.import_role:
        name: myrole
        tasks_from: other
```

### Passing Variables to a Role
Inject custom variables directly into the imported role scope:
```yaml
- hosts: all
  tasks:
    - name: Pass variables to role
      ansible.builtin.import_role:
        name: myrole
      vars:
        rolevar1: "value from task"
```

### Applying Conditionals
Apply a `when` condition directly to the imported block of tasks:
```yaml
- hosts: all
  tasks:
    - name: Apply condition to each task in role
      ansible.builtin.import_role:
        name: myrole
      when: not idontwanttorun
```

* Explore comprehensive configuration options directly in the [Official Import Role Documentation](https://docs.ansible.com/projects/ansible/latest/collections/ansible/builtin/import_role_module.html).
* Review static versus dynamic strategies via [Ansible Playbook Reuse Guide](https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_reuse_roles.html).

<FollowUp>
If you'd like, let me know:
* Are you trying to pass **loops** or **dynamic conditions**?
* Do you need to compare this with **`include_role`**?

I can provide more tailored examples for your playbook structure.
</FollowUp>

10) Common ansible.builtin.dnf playbook examples include installing a single package, managing multiple packages, updating all system packages, and removing software. Refer to the official Ansible DNF Module Documentation for full parameters. 

Install a Single Package
  • Ensures nginx is installed and at the present version:
    yaml
    - name: Install Nginx
      ansible.builtin.dnf:
        name: nginx
        state: present
    

Install Multiple Packages
  • Efficiently manages a list of development tools:
    yaml
    - name: Install dev tools
      ansible.builtin.dnf:
      name:
        - git
        - gcc
        - make
      state: present
    

Upgrade All Packages (System Update)
  • Performs an equivalent operation to dnf -y update:
    yaml
    - name: Upgrade all packages
      ansible.builtin.dnf:
        name: "*"
        state: latest
    

Remove a Package
  • Ensures an unwanted package is absent from the target system:
    yaml
    - name: Remove Apache
      ansible.builtin.dnf:
        name: httpd
        state: absent
    

11) The ansible.builtin.package module acts as a generic, cross-platform package manager wrapper that automatically detects the target operating system's package manager (like apt or dnf) and executes the correct backend command. 

This unified approach allows you to write single tasks that deploy software seamlessly across mixed environments containing both Debian-based and Red Hat-based systems. 
Below are practical, production-ready examples for using this module, along with a guide on when to transition to platform-specific built-ins.

Basic Module Usage
Install a Single Package 
This task installs a utility if it is not already on the target machine.
yaml
- name: Install Git across any Linux distribution
  ansible.builtin.package:
    name: git
    state: present
Install Multiple Packages 
Pass a YAML list to install several core items in a single execution loop. 
yaml
- name: Install common administrative utilities
  ansible.builtin.package:
    name:
      - curl
      - wget
      - tmux
      - jq
    state: present
Ensure a Package is at the Latest Version 
This checks your system's package manager cache and updates the targeted software to the newest release. 
yaml
- name: Force the latest version of OpenSSL
  ansible.builtin.package:
    name: openssl
    state: latest
Remove/Uninstall a Package 
Setting the state parameter to absent cleanly uninstalls software from the remote node. 
yaml
- name: Remove an unneeded package
  ansible.builtin.package:
    name: tcpdump
    state: absent
Advanced: Handling Different OS Package Names
Because software packages are occasionally named differently across distributions (e.g., apache2 on Ubuntu vs. httpd on RHEL), you can pair ansible.builtin.package with Ansible's OS-distribution variables. [
yaml
- name: Install web server based on OS family
  ansible.builtin.package:
    name: "{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}"
    state: present
When to Use Specific Built-in Modules
While ansible.builtin.package is perfect for OS-agnostic provisioning, it lacks advanced system-specific configuration parameters. If your workflow demands deeper functionality, you should fall back to dedicated modules: 
  • ansible.builtin.apt (Ubuntu/Debian): Use this when you need to run an update_cache (equivalent to apt-get update), specify a cache_valid_time, or target specific APT repositories. 
  • ansible.builtin.dnf / ansible.builtin.dnf5 (RHEL/Fedora/CentOS): Use these modules when managing complex package groups (@Development Tools), managing dnf modules/streams, or defining custom installroot parameters. 

For auditing what software is already present on a target system before running your playbook, look into the ansible.builtin.package_facts module, which registers an inventory of installed software directly into your system facts.   


12 ) In Ansible, combined logical conditions (if and and) are written using the when conditional statement. 
When evaluating lists with conditionals, you can either format multiple conditions as a YAML list (which implicitly acts as an and operator) or use the in operator to check if an item exists within a list. 

1. Combining if and and with a List of Conditions
The cleanest way to combine multiple and conditions in Ansible is to pass them as a YAML list under the when clause. The task will only execute if all conditions are true
yaml
- name: Execute only if the environment is production AND the OS is Ubuntu
  ansible.builtin.debug:
    msg: "This runs if both conditions are met."
  when:
    - environment_type == "production"
    - ansible_facts['distribution'] == "Ubuntu"
Alternatively, you can write it on a single line using the explicit and keyword: 
yaml
  when: environment_type == "production" and ansible_facts['distribution'] == "Ubuntu"
2. Checking if an Item Exists in a List
To check if a specific value exists inside a list variable, use the in operator. 
yaml
vars:
  supported_versions:
    - "20.04"
    - "22.04"
    - "24.04"

tasks:
  - name: Execute if the OS version is in the supported list
    ansible.builtin.debug:
      msg: "Supported OS version detected."
    when: ansible_facts['distribution_version'] in supported_versions
3. Combining and with a List Check
You can combine standard variable checks and list existence checks using the list-based when syntax. 
yaml
vars:
  admin_users:
    - "alice"
    - "bob"

tasks:
  - name: Run task if current user is an admin AND system is Ubuntu
    ansible.builtin.debug:
      msg: "Privileged action allowed."
    when:
      - current_user in admin_users
      - ansible_facts['distribution'] == "Ubuntu"
4. Advanced: Checking if All or Any Conditions in a List Are True
Ansible provides built-in Jinja2 tests to evaluate lists of boolean values directly: 
  • ansible.builtin.all: Returns true if every item in the list evaluates to true.
  • ansible.builtin.any: Returns true if at least one item in the list evaluates to true. 
yaml
vars:
  checks:
    - "{{ health_status == 'green' }}"
    - "{{ disk_space_free_gb > 20 }}"

tasks:
  - name: Run if ALL health check flags are true
    ansible.builtin.debug:
      msg: "System is perfectly healthy." 
when: checks is ansible.builtin.all 


13) The ansible.builtin.fetch module copies files from remote target nodes back to the Ansible control machine. It is the exact opposite of the ansible.builtin.copy module. [
By default, fetch appends the remote hostname, device name, and full path of the source file to the local destination directory. This prevents files from overwriting each other when fetching from multiple servers. [
1. Basic File Fetch (Organized by Hostname)
This example retrieves a single application log. If you have hosts named web1 and web2, it creates separate directories for each host under /tmp/collected-logs/. [
yaml
- name: Download application log
  ansible.builtin.fetch:
    src: /var/log/myapp/app.log
    dest: /tmp/collected-logs/
2. Flat Mode (Direct Path Overwrite)
If you want to save the file exactly to a specified path without creating host-named directories, set flat: true. Note that if you run this against multiple servers, the last host to execute will overwrite the file. Use a distinct variable like {{ inventory_hostname }} to differentiate them. 
yaml
- name: Fetch configuration directly into a specific folder
  ansible.builtin.fetch:
    src: /etc/nginx/nginx.conf
    dest: /tmp/configs/{{ inventory_hostname }}-nginx.conf
    flat: true
3. Fetching Multiple Files Using a Loop
The fetch module does not natively support transferring whole directories recursively. To fetch multiple specific files, combine the module with a loop. [
yaml
- name: Fetch Nginx access and error logs
  ansible.builtin.fetch:
    src: /var/log/nginx/{{ item }}.log
    dest: /tmp/nginx-logs/
  loop:
    - access
    - error
4. Fetching from Windows Nodes
The ansible.builtin.fetch module works seamlessly with Windows targets. You must specify standard Windows paths using backslashes. 
yaml
- name: Fetch Windows IIS logs
  ansible.builtin.fetch:
    src: C:\inetpub\logs\LogFiles\W3SVC1\u_ex260804.log
    dest: /tmp/windows-logs/
    flat: true
5. Conditional Fetching (Only If File Exists)
By default, fetch will fail the playbook if the source file is missing (fail_on_missing: true). If you want to check for file existence first and fetch it conditionally, use ansible.builtin.stat. [
yaml
- name: Check if the custom config file exists
  ansible.builtin.stat:
    path: /etc/custom-app.conf
  register: file_status

- name: Fetch custom config only if it exists
  ansible.builtin.fetch:
    src: /etc/custom-app.conf
    dest: /tmp/backup/
  when: file_status.stat.exists
Key Reference Parameters
Check the official Ansible fetch module documentation for a complete breakdown of parameters. [
  • src: The absolute path to the file on the remote machine (Must be a file, not a directory).
  • dest: The directory or file path on the local control machine.
  • flat: Set to true to skip appending hostnames/paths to the destination.
  • fail_on_missing: Defaults to true; crashes the task if the remote file cannot be read. [


14) To use group_vars directly inside your playbook directory, you must place a folder named group_vars in the exact same directory as your main playbook YAML file. Ansible automatically detects and loads these variables when you execute ansible-playbook. [
Note that playbook-adjacent group_vars automatically override matching variables defined in your inventory-adjacent group_vars. [
1. Directory Structure
Your project directory should look like this:
text
.
├── inventory.ini
├── playbook.yml
└── group_vars/
    ├── all.yml           # Variables applied to EVERY group and host
    ├── webservers.yml    # Variables applied ONLY to the 'webservers' group
    └── databases.yml     # Variables applied ONLY to the 'databases' group
2. Define the Group Variables
Create your group file (e.g., group_vars/webservers.yml) and define your variables using standard YAML syntax: [
yaml
---
# group_vars/webservers.yml
http_port: 80
app_version: "2.4.1"
server_admin: "admin@example.com"
3. Create the Playbook
Your playbook does not need any special import statements or keywords. As long as the hosts targeted in your play match the file name inside group_vars, Ansible loads them implicitly: [
yaml
---
# playbook.yml
- name: Configure Web Servers
  hosts: webservers  # This matches group_vars/webservers.yml
  become: yes
  tasks:
    - name: Display the group variable
      ansible.builtin.debug:
        msg: "The target HTTP port is {{ http_port }}"
 Important Precedence Rules
  • File Names: The filename inside the group_vars directory must exactly match the group name defined in your inventory file. 
  • The all.yml file: A file named all.yml (or all.yaml) is a special global file. Variables written inside all.yml apply to every single host but carry the lowest precedence and will be overridden by specific group files. 
  • Playbook vs Inventory: Playbook-adjacent group_vars take precedence over inventory-adjacent group_vars. However, variables explicitly defined inline inside the playbook under the vars: block or via set_fact will still override your group_vars. 


 

Question : How to automate patching activity


Automating Exadata patching involves orchestrating rolling updates across the compute nodes (e.g., updating node 1 while others host databases, then rotating). Use Ansible to orchestrate the workflow (node rotation, service management) and Python (via subprocess or Oracle's patchmgr/dbaascli) to execute pre-checks, apply patches, and verify post-patch states. 
Pre-requisites & Best Practices
  • Rolling Strategy: A Half Rack Exadata typically has 4 compute nodes. You must patch one node at a time while migrating databases and Grid Infrastructure (GI) services to surviving nodes. 
  • Software: Download the latest Exadata Database Server (DB Node) update and the Quarterly Full Stack Patch (QFSDP) from My Oracle Support
  • Cell & Storage Check: Verify cellcli confirms no degraded disks or flash cache.
  • Topology: Exadata X8M Half Node means 4 Database Servers and 7/8 Storage Cells (depending on your specific quarter rack expansion).
  • Backup: Take full RMAN backups of all databases and ensure Grid Infrastructure Management Repository (GIMR) is backed up.
  • Connectivity & SSH: Ensure passwordless SSH is configured from the Ansible control node to the root and oracle users on all Exadata compute nodes. 
  • Exadata Storage Server (Cell) & IB/RoCE Switches: Patching typically involves updating Compute Nodes, Storage Servers, and InfiniBand/RoCE switches.
  • The Driving System: Use a dedicated remote server (a "driving system") or the first Compute Node (e.g., dbadm01) to initiate the updates. The target nodes must be fully passwordless-SSH accessible via the oracle or grid user. 
  • Maintenance Window: Allocate ≈ 4 to 6 hours for a Grid Infrastructure (GI) and Database Release Update (RU) cycle. 
  • Pre-Checks: Ensure patchmgr runs with -toothpick (to verify without applying) before committing to any patch.
  • Run the patchmgr -precheck command to validate firmware versions, RPMs, and network configuration before applying changes.
  • Clusterware Integrity: Run crsctl check crs and crsctl check cluster -all to ensure the cluster is healthy. 
  • Space Utilization: Verify free space on /u01 and /var/tmp partitions where patch staging occurs.
  • Test Cases to Validate:
    • Pre-check Phase: Run patchmgr with -check on all nodes to ensure space and RPM dependencies without making changes.
    • Rolling Mode: Verify that CRS on Node 1 is gracefully stopped and resources relocate to Node 2 before applying patches.
    • Post-patch Validation: Run crsctl check crs and verify that all Databases are running and accessible on the patched nod
  • 2. Basic Knowledge: Native Utilities to Call
    • GI/DB Homes: opatchauto applies GI and DB patches.
    • OS / Firmware: Oracle's patchmgr utility (located in /opt/oracle.SupportTools/patchmgr) orchestrates the patching process for compute node operating systems and Exadata cells. 
Step 1: Python Script for Patch Execution
Save this Python script as exadata_patch.py. It provides functions to trigger pre-checks and run the patch applications via Oracle’s native utilities.
python
import subprocess
import logging
import sys

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def run_command(command):
    """Utility function to execute shell commands and stream output."""
    logging.info(f"Executing: {command}")
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    
    while True:
        output = process.stdout.readline()
        if output == '' and process.poll() is not None:
            break
        if output:
            logging.info(output.strip())
            
    rc = process.poll()
    if rc != 0:
        error = process.stderr.read()
        logging.error(f"Command failed with RC {rc}. Error: {error.strip()}")
        sys.exit(rc)

def run_patch_precheck(node):
    """Executes Exadata patchmgr prechecks."""
    logging.info(f"Running Patch Pre-checks on {node}")
    cmd = f"ssh root@{node} '/opt/oracle.SupportTools/patchmgr/patchmgr --hosts {node} --rolls_check'"
    run_command(cmd)

def apply_patch(node, patch_dir):
    """Executes the actual rolling patch application."""
    logging.info(f"Applying patch on {node}...")
    cmd = f"ssh root@{node} '/opt/oracle.SupportTools/patchmgr/patchmgr --hosts {node} --upgrade --patch_base {patch_dir}'"
    run_command(cmd)

if __name__ == "__main__":
    target_node = "exadata-node1.localdomain"
    patch_location = "/u01/stages/exadata/ru_patch"
    
    # 1. Run Precheck
    run_patch_precheck(target_node)
    
    # 2. Apply Patch
    # apply_patch(target_node, patch_location)
Step 2: Ansible Playbook
This playbook defines the rolling automation workflow. Save as rolling_patch.yml.
yaml
---
- name: Exadata Rolling Patch Automation
  hosts: exadata_nodes
  become: yes
  gather_facts: yes

  tasks:
    - name: 1. Stop Oracle Grid Infrastructure & Databases on Target Node
      ansible.builtin.shell: |
        su - oracle -c "srvctl stop home -s {{ oracle_home }} -n {{ inventory_hostname }}"
      delegate_to: localhost
      vars:
        oracle_home: /u01/app/19.0.0.0/grid

    - name: 2. Execute Python Patch Pre-Check Script
      ansible.builtin.command: python3 /opt/scripts/exadata_patch.py --node {{ inventory_hostname }} --action precheck
      delegate_to: localhost
      register: precheck_out

    - name: 3. Apply Patch via Python
      ansible.builtin.command: python3 /opt/scripts/exadata_patch.py --node {{ inventory_hostname }} --action apply
      when: precheck_out.rc == 0

    - name: 4. Post-Patch Verification (Reboot & Check)
      ansible.builtin.shell: |
        ssh root@{{ inventory_hostname }} 'reboot'
        sleep 300
      delegate_to: localhost

    - name: 5. Verify Cluster State (Wait for node to join cluster)
      ansible.builtin.shell: |
        ssh root@{{ inventory_hostname }} '/u01/app/19.0.0.0/grid/bin/crsctl check has'
      delegate_to: localhost
Test Cases & Validation
Integrate the following validation tests before and after running your automation:
  1. Test Case 1: Clusterware and Database Pre-checks
    • Objective: Ensure the targeted Exadata node is ready to be taken offline.
    • Action: Run crsctl check cluster -all to ensure all other nodes are stable.
    • Validation: No databases or instances belonging to the target node are reported as "OFFLINE" on surviving nodes unexpectedly. 
  2. Test Case 2: Verification of Free Space
    • Objective: Verify adequate space exists in /u01 or /var/log before extracting large patch payloads.
    • Action: Run df -h /u01 via Ansible.
    • Validation: Minimum 15 GB of free space is available. 
  3. Test Case 3: Post-Patch Status and OPatch
    • Objective: Verify the new release update has registered correctly.
    • Action: su - oracle -c "cd $ORACLE_HOME/OPatch && ./opatch lsinventory"
    • Validation: The new bundle patch ID (e.g., \(19.24.0.0.0\)) appears in the installed patch list. [
Useful Oracle References
Refer to official deployment methodologies depending on whether you are patching using patchmgr (for Grid/OS) or dbaascli (if managing Cloud-at-Customer instances)



Question : How to automate on-premises Half Rack RAC Exadata patching uses a hybrid approach using ansible and python

Automating on-premises Half Rack RAC Exadata patching uses a hybrid approach: Python triggers and monitors Exadata's native patchmgr utility, while Ansible orchestrates the nodes, coordinates cluster states, and manages validation. Rolling patches are applied sequentially across your 4 compute nodes. 
1. Python Driving Script (exadata_patch_executor.py)
This script uses the subprocess module to execute patchmgr commands remotely or locally via dcli on the half node. 
python
import subprocess
import json
import sys

# Configuration for Half Rack (4 nodes)
NODES = ["exadb01", "exadb02", "exadb03", "exadb04"]
PATCH_LOC = "/u01/patch/19.0.0.0/RU"

def run_command(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = process.communicate()
    return {
        "return_code": process.returncode,
        "stdout": stdout.decode('utf-8'),
        "stderr": stderr.decode('utf-8')
    }

def execute_patchmgr(node, action):
    # DCLI/patchmgr integration - assuming patchmgr is configured
    cmd = f"dcli -n {node} 'cd /opt/oracle.SupportTools/patchmgr && ./patchmgr {action} -crshome /u01/app/19.0.0/grid -dbhome /u01/app/oracle/product/19.0.0/dbhome_1'"
    return run_command(cmd)

def main():
    # Pre-patch checks
    for node in NODES:
        result = execute_patchmgr(node, "-check")
        if result["return_code"] != 0:
            print(f"Pre-check failed on {node}: {result['stderr']}")
            sys.exit(1)

    # Rolling patch application
    for node in NODES:
        print(f"Applying patch to {node}...")
        result = execute_patchmgr(node, "-apply")
        if result["return_code"] != 0:
            print(f"Patching failed on {node}: {result['stderr']}")
            sys.exit(1)

    print("Exadata Rolling Patch Applied Successfully.")

if __name__ == "__main__":
    main()
2. Ansible Playbook (patch_orchestration.yml)
This playbook orchestrates the pre-requisite node evictions and the execution of the Python automation framework.
yaml
---
- name: Exadata Rolling Patch Orchestration
  hosts: exadata_db_nodes
  gather_facts: yes
  become: yes

  tasks:
    - name: Pre-check DB Services & CRSD Status
      ansible.builtin.shell: "crsctl check cluster"
      register: crs_status

    - name: Run Python Patching Automation
      ansible.builtin.script:
        cmd: /u01/scripts/exadata_patch_executor.py
      register: patch_execution
      delegate_to: localhost

    - name: Verify Clusterware Post-Patching
      ansible.builtin.shell: "crsctl check crs"
      register: post_crs_check
      retries: 3
      delay: 60
      until: post_crs_check.rc == 0
3. Verification & Test Cases
Execute these validation commands through the Ansible ad-hoc module or standalone to guarantee patch completion:
Test Case 1: Active Node Verification
  • Command: /u01/app/19.0.0/grid/bin/crsctl stat res -t -init
  • Expected Result: Cluster synchronization must show status ONLINE on the targeted Exadata compute node.
Test Case 2: GI (Grid Infrastructure) Patch Level Validation
  • Command: /u01/app/19.0.0/grid/OPatch/opatch lspatches
  • Expected Result: The newly applied Release Update (RU) must successfully populate in the inventory list.
Test Case 3: Opatch Database Verification
  • Command: /u01/app/oracle/product/19.0.0/dbhome_1/OPatch/opatch lspatches
  • Expected Result: All database homes must log as updated without reporting conflicts or rollback failures.

  • Cluster Verification: Run /u01/app/19.0.0/grid/bin/crsctl check cluster -all to verify all clusterware processes are online.
  • Database Status: Run /u01/app/19.0.0/grid/bin/srvctl status database -d <db_name> to verify that all databases are running on their preferred nodes.
  • Patch Inventory: Execute /u01/app/19.0.0/grid/OPatch/opatch lspatches to confirm that the Oracle homes successfully show the applied Release Updates (RU).
  • Log Analysis: Review the exact diagnostic outputs generated during execution under /var/log/cellos/dbnodeupdate.log and the trace files in the patchmgr working directories. 

  • Question : How to automate Storage Metrics & Partition Maintenance

    1. The Python Script (Storage Metrics & Partition Maintenance)
    Save this as dba_maintenance.py. It establishes a connection to the database, checks storage usage, and manages partitions. 
    python
    import sys
    import oracledb
    
    def connect_db():
        # Replace with your actual Exadata connection details
        username = 'system'
        password = 'your_password'
        dsn = '://example.com'
        
        try:
            connection = oracledb.connect(user=username, password=password, dsn=dsn)
            return connection
        except Exception as e:
            print(f"Database connection failed: {e}")
            sys.exit(1)
    
    def drop_old_partitions(connection):
        cursor = connection.cursor()
        # Example: Drop partitions older than 30 days for a sample table
        sql = """
        BEGIN
            EXECUTE IMMEDIATE 'ALTER TABLE sales DROP PARTITION sales_q1_2023';
            COMMIT;
        EXCEPTION
            WHEN OTHERS THEN
                IF SQLCODE != -21402 THEN -- ORA-21402: partition does not exist
                    RAISE;
                END IF;
        END;
        """
        try:
            cursor.execute(sql)
            print("Partition maintenance completed successfully.")
        except Exception as e:
            print(f"Partition drop failed: {e}")
        finally:
            cursor.close()
    
    def get_storage_metrics(connection):
        cursor = connection.cursor()
        # Query Exadata ASM allocation units to find disk group usage
        query = "SELECT name, type, total_mb, free_mb, required_mirror_free_mb FROM v$asm_diskgroup"
        cursor.execute(query)
        
        metrics = []
        for row in cursor:
            metrics.append({
                "disk_group": row[0],
                "type": row[1],
                "total_mb": row[2],
                "free_mb": row[3],
                "required_mirror_free_mb": row[4]
            })
        cursor.close()
        return metrics
    
    if __name__ == "__main__":
        conn = connect_db()
        
        # Task 1: Check Storage
        disk_groups = get_storage_metrics(conn)
        for dg in disk_groups:
            print(f"ASM DG {dg['disk_group']} - Free: {dg['free_mb']} MB out of {dg['total_mb']} MB")
            
        # Task 2: Partition Maintenance
        drop_old_partitions(conn)
        
        conn.close()
    
    2. The Ansible Playbook
    Save this as exadata_dba_tasks.yml. This playbook targets the Exadata database nodes, provisions the Python environment, runs the script, and gathers facts. [1, 2]
    yaml
    ---
    - name: Exadata On-Premise DBA Automation
      hosts: exadata_db_nodes
      become: yes
      become_user: oracle
      vars:
        python_script_path: "/home/oracle/scripts/dba_maintenance.py"
        
      tasks:
        - name: Ensure python-oracledb is installed for the oracle user
          ansible.builtin.pip:
            name: oracledb
            state: present
            executable: pip3
            
        - name: Execute the DBA Maintenance Python Script
          ansible.builtin.command:
            cmd: python3 {{ python_script_path }}
          register: script_output
          changed_when: true
          
        - name: Output Python Execution Results
          ansible.builtin.debug:
            msg: "{{ script_output.stdout_lines }}"
    
        - name: Get Exadata Node Facts (OS level)
          ansible.builtin.setup:
            filter: 'ansible_memtotal_mb'
    
    3. Test Cases (for Unit / Integration Testing)
    To ensure the automation works safely against your Exadata environment, set up the following tests:
    Test 1: Connectivity Verification
    • Objective: Ensure the Ansible controller can reach the Exadata compute nodes and verify the Python oracledb library connects to the database.
    • Command: ansible exadata_db_nodes -m ping and python3 -c "import oracledb"
    • Expected Result: Ansible returns "pong". Python command executes without throwing an ImportError.
    Test 2: Idempotency & Error Handling (Partition Drop)
    • Objective: Ensure the script does not crash if partitions have already been dropped.
    • Action: Run dba_maintenance.py twice consecutively.
    • Expected Result: The first run prints "Partition maintenance completed". The second run catches ORA-21402 (handled gracefully by the EXCEPTION block in the Python script) and prints a safe log rather than aborting.
    Test 3: Threshold Alert Simulation
    • Objective: Validate that the ASM storage query returns accurate metrics.
    • Action: Compare the script's get_storage_metrics output against the V$ASM_DISKGROUP view inside SQL*Plus manually.
    • Expected Result: The disk group names, sizes, and free space values returned by the script exactly match the output of your manual SQL query.

    Before executing automation against critical production Exadata cell 




    Question: How to automate monitoring task in exadata on-premises environment using ansible and python 



    To automate Exadata monitoring, use an Ansible Playbook to run a Python script on your Exadata database nodes. The Python script should use the oracledb module to execute SQL health checks, and psutil for hardware telemetry. The playbook then aggregates metrics and handles alerts. 
    1. Python Script: exadata_monitor.py
    This script connects to the Oracle Database, runs basic performance queries, and evaluates critical metrics (like tablespace usage and invalid objects). 
    python
    import oracledb
    import json
    import sys
    
    # Exadata connection details
    DB_USER = 'system'
    DB_PASSWORD = 'YourAdminPassword'
    DB_DSN = '10.0.0.15:1521/PROD' # Replace with SCAN listener and Service Name
    
    def check_database_health():
        try:
            # Connect to Oracle Database
            connection = oracledb.connect(user=DB_USER, password=DB_PASSWORD, dsn=DB_DSN)
            cursor = connection.cursor()
    
            metrics = {}
    
            # 1. Check Tablespace Usage
            cursor.execute("""
                SELECT tablespace_name, ROUND((used_space * block_size) / 1024 / 1024 / 1024, 2) as used_gb 
                FROM dba_tablespace_usage_metrics 
                WHERE (used_space / tablespace_size) > 0.85
            """)
            metrics['tablespace_alerts'] = cursor.fetchall()
    
            # 2. Check Invalid Objects
            cursor.execute("SELECT COUNT(*) FROM dba_objects WHERE status = 'INVALID'")
            metrics['invalid_objects'] = cursor.fetchone()[0]
    
            # 3. Check Active Sessions
            cursor.execute("SELECT COUNT(*) FROM v$session WHERE status = 'ACTIVE' AND type != 'BACKGROUND'")
            metrics['active_sessions'] = cursor.fetchone()[0]
    
            cursor.close()
            connection.close()
    
            print(json.dumps(metrics))
    
        except Exception as e:
            print(f"Error: {e}")
            sys.exit(1)
    
    if __name__ == "__main__":
        check_database_health()
    
    2. Ansible Playbook: monitor_exadata.yml
    Use Ansible to execute the Python script across all Exadata nodes and capture the JSON output. 
    yaml
    ---
    - name: Automate Exadata Health Monitoring
      hosts: exadata_nodes
      gather_facts: yes
      tasks:
        - name: Ensure required python packages are installed
          ansible.builtin.pip:
            name: 
              - oracledb
              - psutil
            state: present
    
        - name: Run Exadata Monitoring Python script
          ansible.builtin.command: python3 /opt/scripts/exadata_monitor.py
          register: script_output
    
        - name: Parse output and display
          ansible.builtin.debug:
            msg: "{{ script_output.stdout | from_json }}"
    
        - name: Trigger alert if thresholds breached
          ansible.builtin.fail:
            msg: "Alert! Exadata performance threshold exceeded. See debug logs."
          when: >
            (script_output.stdout | from_json).invalid_objects > 10 or
            (script_output.stdout | from_json).tablespace_alerts | length > 0
    
    3. Test Cases
    To validate your monitoring automation, you can run the following local unit and integration tests:
    Test 1: Python Connectivity and Output Test
    Purpose: Ensure the Python script successfully connects to the Exadata database and emits valid JSON.
    • Execution Command: python3 exadata_monitor.py
    • Expected Output: {"tablespace_alerts": [], "invalid_objects": 4, "active_sessions": 12} (Must be a valid JSON dictionary).
    Test 2: Ansible Inventory and Syntax Test
    Purpose: Verify that Ansible syntax is correct and your Exadata hosts are reachable.
    • Execution Command: ansible-playbook monitor_exadata.yml --syntax-check
    • Expected Output: playbook: monitor_exadata.yml (No syntax errors). 
    Test 3: Threshold Trigger Integration Test
    Purpose: Verify that the playbook correctly identifies issues and triggers a failure/alert when a metric crosses the critical threshold.
    • Prerequisite: Create a dummy invalid object in the database: CREATE PROCEDURE test_invalid AS BEGIN NULL; END; / ALTER PROCEDURE test_invalid COMPILE BODY; (Introduce syntax error).
    • Execution Command: ansible-playbook monitor_exadata.yml
    • Expected Output: The playbook halts execution and throws a failure (FAILED! => {"msg": "Alert! Exadata performance threshold exceeded..."}). 
    For More Details


    Question : How to automate Tablespace Monitoring in exadata on-premises environment using ansible and python with example and test cases


    Architecture Overview
    [ Ansible Control Node ] 
           │
           ├── (SSH / dcli) ──► [ Exadata Compute Nodes ] ── (Bash/CLI) ──► Grid/CellOS
           │
           └── (Python Script) ──► [ Oracle Database ] ── (SQL/PLSQL) ──► Tablespace Mod
    
    • Ansible: Manages inventory, system checks, and script execution.
    • Python: Connects to the database to check space and run ALTER TABLESPACE.

    Step 1: Python Automation Script (extend_ts.py)
    This script checks if a tablespace is above a threshold and safely extends it. Save this on your Ansible controller or deploy it to the Exadata compute node.
    python
    import sys
    import oracledb
    
    # Database connection parameters
    DB_USER = "sys"
    DB_PASS = "YourSecureSysPassword"
    DB_DSN = "exadata-scan.localdom:1521/ORCL_TAF"
    DB_MODE = oracledb.AUTH_MODE_SYSDBA
    
    def check_and_extend(tablespace_name, threshold_pct, extend_size_mb):
        try:
            # Initialize python-oracledb in thin mode
            connection = oracledb.connect(user=DB_USER, password=DB_PASS, dsn=DB_DSN, mode=DB_MODE)
            cursor = connection.cursor()
    
            # Query space utilization
            query = """
            SELECT df.tablespace_name, 
                   round(((df.bytes - fs.bytes) / df.bytes) * 100, 2) as used_pct
            FROM (SELECT tablespace_name, SUM(bytes) bytes FROM dba_data_files GROUP BY tablespace_name) df,
                 (SELECT tablespace_name, SUM(bytes) bytes FROM dba_free_space GROUP BY tablespace_name) fs
            WHERE df.tablespace_name = fs.tablespace_name AND df.tablespace_name = :ts_name
            """
            cursor.execute(query, ts_name=tablespace_name.upper())
            row = cursor.fetchone()
    
            if not row:
                print(f"Error: Tablespace {tablespace_name} not found.")
                sys.exit(1)
    
            used_pct = row[1]
            print(f"Tablespace {tablespace_name} usage is currently at {used_pct}%.")
    
            if used_pct > float(threshold_pct):
                print(f"Threshold exceeded ({threshold_pct}%). Adding a new datafile...")
                
                # Exadata best practice: Use OMF (Oracle Managed Files) inside ASM disk groups
                alter_query = f"ALTER TABLESPACE {tablespace_name} ADD DATAFILE SIZE {extend_size_mb}M"
                cursor.execute(alter_query)
                
                print(f"Successfully extended {tablespace_name} by {extend_size_mb}MB.")
            else:
                print("Space is sufficient. No action required.")
    
            cursor.close()
            connection.close()
    
        except oracledb.DatabaseError as e:
            error, = e.args
            print(f"Database error occurred: {error.message}")
            sys.exit(1)
    
    if __name__ == "__main__":
        if len(sys.argv) < 4:
            print("Usage: python extend_ts.py <TS_NAME> <THRESHOLD_PCT> <EXTEND_SIZE_MB>")
            sys.exit(1)
        check_and_extend(sys.argv[1], sys.argv[2], sys.argv[3])
    
     Step 2: Ansible Playbook (exadata_dba_tasks.yml)
    This playbook creates a backup directory, updates the local Oracle environment variables, runs pre-checks, and executes the Python script. 
    yaml
    ---
    - name: Exadata Automated DBA Tablespace Management
      hosts: exadata_compute_nodes
      become: yes
      become_user: oracle
      vars:
        oracle_home: "/u01/app/oracle/product/19.0.0/dbhome_1"
        oracle_sid: "ORCL1"
        target_tablespace: "APP_DATA"
        threshold: 85
        increment_mb: 5120 # 5GB
    
      tasks:
        - name: Pre-check | Verify Oracle Environment
          ansible.builtin.stat:
            path: "{{ oracle_home }}/bin/sqlplus"
          register: sqlplus_file
    
        - name: Fail if Oracle Home is invalid
          ansible.builtin.fail:
            msg: "Oracle Home path is incorrect on this Exadata node."
          when: not sqlplus_file.stat.exists
    
        - name: Execution | Run Python Tablespace Extension Script
          ansible.builtin.command:
            cmd: "python3 /u01/app/oracle/scripts/extend_ts.py {{ target_tablespace }} {{ threshold }} {{ increment_mb }}"
          environment:
            ORACLE_HOME: "{{ oracle_home }}"
            ORACLE_SID: "{{ oracle_sid }}"
            PATH: "{{ oracle_home }}/bin:{{ ansible_env.PATH }}"
          register: script_output
          changed_when: "'Successfully extended' in script_output.stdout"
    
        - name: Output | Log results to console
          ansible.builtin.debug:
            var: script_output.stdout_lines
    
     Step 3: Test Cases & Validation Framework
    To ensure the automation code is robust before executing against Exadata production, run these automated tests inside a lower environment (Dev/Test).
    1. Unit Test File: Mocking Database Responses (test_extend_ts.py)
    Use Python's unittest.mock framework to test your Python logic without actually connecting to an Exadata machine.
    python
    import unittest
    from unittest.mock import patch, MagicMock
    import extend_ts
    
    class TestDBAAutomation(unittest.TestCase):
    
        @patch('oracledb.connect')
        def test_no_action_needed(self, mock_connect):
            # Mocking SQL return value: Used Pct = 50%
            mock_cursor = MagicMock()
            mock_cursor.fetchone.return_value = ('APP_DATA', 50.0)
            mock_connect.return_value.cursor.return_value = mock_cursor
    
            with patch('sys.exit') as mock_exit:
                extend_ts.check_and_extend('APP_DATA', 85, 100)
                # Ensure ALTER TABLESPACE was NOT called
                mock_cursor.execute.assert_called_once() # Only the SELECT query ran
                
        @patch('oracledb.connect')
        def test_tablespace_extension_triggered(self, mock_connect):
            # Mocking SQL return value: Used Pct = 90% (Threshold is 85)
            mock_cursor = MagicMock()
            mock_cursor.fetchone.return_value = ('APP_DATA', 90.0)
            mock_connect.return_value.cursor.return_value = mock_cursor
    
            extend_ts.check_and_extend('APP_DATA', 85, 5120)
            # Verify that the ALTER statement was triggered
            mock_cursor.execute.assert_any_call("ALTER TABLESPACE APP_DATA ADD DATAFILE SIZE 5120M")
    
    if __name__ == '__main__':
        unittest.main()
    
    2. Ansible Dry-Run (Check Mode)
    Before making live changes to Exadata storage, run Ansible in check mode to validate variables, access connectivity, and task sequences: 
    bash
    ansible-playbook -i hosts exadata_dba_tasks.yml --check
    
    3. Infrastructure Negative Integration Tests
    Intentionally force failure scenarios to evaluate script resiliency:
    Test Scenario ActionExpected Behavior
    Invalid Tablespace NamePass WRONG_TS to the playbookScript outputs Tablespace WRONG_TS not found and exits cleanly with code 1. Ansible marks task as failed.
    ASM Diskgroup FullRun when DATA diskgroup is at 100%Oracle yields ORA-01114 / ORA-15041. Python catches DatabaseError, logs the exact ORA error, and exits without crashing.
    Node DownPower off one Compute NodeAnsible targeting that specific host fails gracefully at the setup/gathering facts phase; other nodes continue.

    No comments:

    Post a Comment