Total Pageviews

Translate

December 5, 2017

Extending Ansible using Python

by 4hathacker  |  in Python at  11:39 PM

Hi everyone!

This is MyAnsibleQuest!!!



In the previous post, I have discussed about Ansible playbooks for provisioning database and web servers. In this post, we will unleash the power of Ansible in Linux by writing our own module. We can write Ansible modules in many languages, but I would like to use Python here. The reasons for selecting Python are:

1. All the modules of Ansible are written in Python.
2. Easy and direct integration with Ansible is possible.
3. Python reduces the amount of code required, as we can use boiler plate code.
4. Handling JSON output is easy.

Most Importantly, I love to code in python. 

Lets start with creating a setup for module development. Its very simple. We need a directory in which we place our playbook file and inside that a 'library' folder so that our playbook automatically look for the ansible module.

[root@server hands_on_ansible]# mkdir custom_module
[root@server hands_on_ansible]# cd custom_module
[root@server custom_module]# mkdir library
[root@server custom_module]# touch custom.yml
[root@server custom_module]# touch library/custom2.py


Now we will define our playbook.
There must be a reason to define a custom module. It may be a case that you want a custom function/task to accomplish. There might not be sufficient  modules available for your task or something like that.

For this post, I would like to create a trivial module for monitoring cpu usage of my Linux servers for 'n' peak processes. There are several commands in linux for monitoring the cpu usage with ram memory e.g. top, htop, ps, free, etc. Here I will be using 'ps' command with a set of arguments to be passed to display process id, parent process id, command, etc, in a meaningful sorted manner for every server in my inventory.

As we know about ansible-playbook writing with .yml extension, we will first write it.

1. My custom.yml file is defined to operate on 'hosts' as 'dbservers'.
2. I have included an entry of 'gather_facts' as 'no' because I don't want any kind of delay in output.
3. The name of my task is 'Get top cpu consuming process'
4. In my 'custom2' named module, I have passed 7 parameters viz. pid (process id), ppid (parent process id), cmd (command), mem(memory info), cpu (cpu info), sort (to define sorting basis), num (number of peak processes to show in output).
5. At last, I have used a 'result' variable with 'register' module to save the output and displayed the result with 'debug' module.


[root@server Desktop]# cat hands_on_ansible/custom_module/custom.yml
---

- hosts: dbservers
  gather_facts: no

  tasks:
    - name: Get top cpu consuming process
      custom2: 
        pid: pid
        ppid: ppid
        cmd: cmd
        mem: mem
        cpu: cpu
        sort: mem
        num: '17'    
      register: result

    - debug:
         var: result


Secondly, we will focus on the ansible module - 'custom2.py'. Ansible module must contain some basic information like metadata, documentation, examples, return values, etc. For more enhanced details of writing ansible module follow the documentation link. I have included only the documentation, for understanding the module.

#!/usr/bin/python

DOCUMENTATION = '''
---
module: my_monitoring_module
short_description: This is my server cpu-memory monitoring module.
version_added: "2.4"
description:
    - "This is my cpu-memory monitoring module to show 'n' peak processes at the time of module call."
options:
    pid:
        description:
            - This is the value same as pid denoting process id.
        required: true
    ppid:
        description:
            - This is the value same as ppid denoting parent process id.
        required: true
    cmd:
        description:
            - This is the value same as cmd denoting the command in process.
        required: false
    mem:
        description:
            - This is the value same as mem denoting the memory in percent for a process.
        required: true
        alias: memory
    cpu:
        description:
            - This is the value same as cpu denoting the cpu usage in percent for a process.
        required: true
    sort:
        description:
            - This is the value as either cpu or mem to sort by the order of cpu usage or memory usage.
        required: true
    num:
        description:
            - This is the value to output the number of peak processes.
        required: true
author:
    - Nitin (@4hathacker)
'''

from ansible.module_utils.basic import *
import subprocess

def main():
  # defining the available arguments/parameters
  # the user must pass to module
  module = AnsibleModule(
      argument_spec = dict(
          pid  = dict(required=True, type='str'),
          ppid = dict(required=True, type='str'),
          cmd  = dict(required=False, type='str'),
          mem  = dict(aliases=['memory'], required=True, type='str'),
          cpu  = dict(required=True, type='str'),
          sort = dict(required=True, type='str'), 
          num  = dict(required=True, type='str')
      ),
  # module supports check_mode
  # but value at exit remain unchanged
  # as its for monitoring pusrpose only
      supports_check_mode=True
  )

  if module.check_mode:
    module.exit_json(changed=False)
 
  params = module.params
 
  # passing the params to a shell command
  # command = 'ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | head -n num'
  # passing the command in subprocess module


  if params['cmd'] is None:
      process = subprocess.Popen("ps -eo" + params['pid'] + "," + params['ppid'] + ",%" + params['mem'] + ",%" + params['cpu'] + " --sort=-%" + params['sort'] + " | head -n " + params['num'],shell=True, stdout=subprocess.PIPE, close_fds=True)
  else:
    process = subprocess.Popen("ps -eo" + params['pid'] + "," + params['cmd'] + "," + params['ppid'] + ",%" + params['mem'] + ",%" + params['cpu'] + " --sort=-%" + params['sort'] + " | head -n " + params['num'],shell=True, stdout=subprocess.PIPE, close_fds=True)


  exists = process.communicate()[0]
 
  # getting result if process is not None
  if exists:
        result = exists.split('\n')
        module.exit_json(changed=True, meminfo=result)
  else:
        err_info = "Error Occured: Not able to get peak cpu info"
        module.fail_json(msg=err_info)

if __name__ == '__main__':
        main()

With respect to the above mentioned module,

1. Its clear in the module, that I have used 'ps' command to accomplish the task.
2. User can use 'memory' as an alias for 'mem' in custom.yml file.
3. Python's subprocess module is used to run the ps command.
4. Result is displayed after splitting lines by '\n'.



Its confession time...

There is no need to write a module to find the top 'n' peak processes on the basis of cpu and memory usage. We can accomplish the same task by passing the ps command with same arguments in the shell module of Ansible. This check.yml file will look as given below.

[root@server Desktop]# cat check.yml
---
- hosts: dbservers
  tasks:
    - name: check memory and cpu usage in dbservers
      shell: "ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | head -n 17"
      register: result

    - debug: var=result


Just have a look at result of this file. You can observe few more things like rc, stdout, stdout_lines, etc. and explore the ansible docs to add the following in your module.


 

0 comments:

Like Our Facebook Page

Nitin Sharma's DEV Profile
Proudly Designed by 4hathacker.