ansiblevmwareesxivcenter

How to add disk to VMware's VM host more efficiently by using Ansible offical module?


Env

Purpose

I have already completed my code, but I wondering if there are more better way to add disk to VM host by Ansible offical module.

My Method

The return message from vmware_guest_disk_facts

TASK [get VM info] *******************************************************************************************************
ok: [TFGH0001.TFGH.COM -> localhost] => changed=false 
  guest_disk_facts:
    '0':
      backing_datastore: VCENTER01_DS01
      backing_eagerlyscrub: false
      backing_filename: '[VCENTER01_DS01] TFGH0001/TFGH0001-000001.vmdk'
      backing_thinprovisioned: false
      backing_type: FlatVer2
      backing_uuid: 6000C294-d22d-06a9-c89b-119e13fffe33
      backing_writethrough: false
      capacity_in_bytes: 32212254720
      capacity_in_kb: 31457280
      controller_key: 1000
      key: 2000
      label: Hard disk 1
      summary: 31,457,280 KB
      unit_number: 0
    '1':
      backing_datastore: VCENTER01_DS01
      backing_eagerlyscrub: false
      backing_filename: '[VCENTER01_DS01] TFGH0001/TFGH0001_3.vmdk'
      backing_thinprovisioned: false
      backing_type: FlatVer2
      backing_uuid: 6000C293-ae37-30f6-b684-d5b2416ff2f8
      backing_writethrough: false
      capacity_in_bytes: 10737418240
      capacity_in_kb: 10485760
      controller_key: 1000
      key: 2001
      label: Hard disk 2
      summary: 10,485,760 KB
      unit_number: 1

My Playbook

---
  - hosts: all
    remote_user: kai
    become: yes
    become_user: root
    become_method: sudo
    gather_facts: true

    vars:
      vcenter_host: "VCENTER01"
      vcenter_username: "TFGH\\kaikudou"
      vcenter_password: xxxxxxxxx
      target_host: "TFGH0001"

    tasks:

    - name: get VM info
      vmware_guest_disk_facts:
        hostname: "{{ vcenter_host }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: False
        datacenter: "{{ vcenter_host }}"
        name: "{{ target_host }}"
      delegate_to: localhost
      register: vm_disk_info

    # - name: show vm_disk_info
    #   debug:
    #     msg: "{{ vm_disk_info.guest_disk_facts['0'].backing_datastore }}"

    - name: Set empty list to store varialbe
      set_fact:
        all_scsi_number_list: []  # A list to store all scsi device number
        scsi_0: []  # A list to store scsi 0's device for counting the quantity
        scsi_1: []  # A list to store scsi 1's device for counting the quantity
        scsi_2: []  # A list to store scsi 2's device for counting the quantity
        scsi_3: []  # A list to store scsi 3's device for counting the quantity
        all_unit_number_list: []  # A list to store the device number from scsi controller   

    - name: Set variable of datastore
      set_fact:
        host_datastore: "{{ vm_disk_info.guest_disk_facts['0'].backing_datastore }}"

    - name: Store scsi_number into all_scsi_number_list
      set_fact:
        all_scsi_number_list: "{{ all_scsi_number_list + [vm_disk_info.guest_disk_facts[item].controller_key] }}"
      loop: "{{ vm_disk_info.guest_disk_facts.keys() }}"

    - name: Find out scsi_controller 0 and store into scsi_0
      set_fact:
        scsi_0 : "{{ scsi_0 + [item] }}"
      loop: "{{ all_scsi_number_list }}"
      when: item == 1000

    - name: Find out the scsi_controller 1 and store into scsi_1
      set_fact:
        scsi_1 : "{{ scsi_1 + [item] }}"
      loop: "{{ all_scsi_number_list }}"
      when: item == 1001

    - name: Find out the scsi_controller 2 and store into scsi_2
      set_fact:
        scsi_2 : "{{ scsi_2 + [item] }}"
      loop: "{{ all_scsi_number_list }}"
      when: item == 1002

    - name: Find out the scsi_controller 3 and store into scsi_3
      set_fact:
        scsi_3 : "{{ scsi_3 + [item] }}"
      loop: "{{ all_scsi_number_list }}"
      when: item == 1003

    - name: Calcualte the quantity of scsi_*
      set_fact:
        scsi_0_number: "{{ scsi_0 | length }}"
        scsi_1_number: "{{ scsi_1 | length }}"
        scsi_2_number: "{{ scsi_2 | length }}"
        scsi_3_number: "{{ scsi_3 | length }}"

    - name: Verify the scsi controller's number because snapshot will also cost the device so less than 7 to prevent
      set_fact:
        scsi_number: "{{ item.src }}"
      loop:
        - { src: "0", when: "{{ (scsi_0_number <= '6' and scsi_0_number != '0') or (scsi_0_number == '0') }}" }
        - { src: "1", when: "{{ (scsi_1_number <= '6' and scsi_1_number != '0') or (scsi_1_number == '0') }}" }
        - { src: "2", when: "{{ (scsi_2_number <= '6' and scsi_2_number != '0') or (scsi_2_number == '0') }}" }
        - { src: "3", when: "{{ (scsi_3_number <= '6' and scsi_3_number != '0') or (scsi_3_number == '0') }}" }
      when: item.when

    # - name: Show scsi_number which we get
    #   debug:
    #     msg: "{{ scsi_number }}"

    - name: Change the scsi_number we obtained to 4 digit number
      set_fact:
        scsi_digit_number: "{{ item.src | int }}"
      loop:
        - { src: "1000", when: "{{ scsi_number == '0' }}" }
        - { src: "1001", when: "{{ scsi_number == '1' }}" }
        - { src: "1002", when: "{{ scsi_number == '2' }}" }
        - { src: "1003", when: "{{ scsi_number == '3' }}" }
      when: item.when

    # - name: Show scsi_digit_number which we get
    #   debug:
    #     msg: "{{ scsi_digit_number }}"

    - name: Check the number of devices from the scci_number we obtained
      set_fact:
        all_unit_number_list: "{{ all_unit_number_list + [vm_disk_info.guest_disk_facts[item].unit_number] }}"
      loop: "{{ vm_disk_info.guest_disk_facts.keys() }}"
      when: vm_disk_info.guest_disk_facts[item].controller_key == scsi_digit_number | int

    # - name: Show all_unit_number_list which we get
    #   debug:
    #     msg: "{{ all_unit_number_list | length | type_debug }}"

    - name: Find the max number in all_unit_number_list then plus 1 to add new disk
      set_fact:
        disk_number: "{{ all_unit_number_list | max + 1 }}"
      ignore_errors: yes

    - name: If we have to add new scsi controller then the all_unit_number_list will be empty list, so we need to prevent it failed
      set_fact:
        disk_number: 0
      when: all_unit_number_list | length == 0

    - name: add disk
      vmware_guest_disk:
        hostname: "{{ vcenter_host }}"
        username: "{{ vcenter_username }}"
        password: "{{ vcenter_password }}"
        validate_certs: False
        datacenter: "{{ vcenter_host }}"
        name: "{{ target_host }}"
        disk:
          - size_gb: 2
            type: thin
            state: present
            datastore: "{{ host_datastore }}"
            # autoselect_datastore: True
            scsi_controller: "{{ scsi_number | int }}"
            unit_number: "{{ disk_number | int }}"
            scsi_type: 'paravirtual'

According to VMware offical document 1 VM host can only have 4 scsi controller and each of them can have 15 devices to attach. So I have to write many condition to prevent it.

The strange thing is that when adding a hard disk from VCenter, it will not trigger the problem that the number of hard disks exceeds 7. However, Ansible's module cannot increase the hard disk when unit_number exceeds 7, and must change another SCSI Controller.

Is there any other better way besides doing this? or something I can refer to and study it?

Thank you for your help and advice!


Solution

  • I tried to rephrase the problem a bit. This is far from perfect as it will not take into a account "holes" in numbering (i.e. disk or controllers removed from the sequence). But I don't think your current implementation does either. Mine should work with far less variable assignment and will gracefully fail if there are no more slots available.

    Note: I used json_query so you will need to pip(3) install jmespath on your ansible controller machine to run the example.

    The playbook:

    ---
    - name: My take to your vmware disk management question
      hosts: localhost
      gather_facts: false
    
      vars:
        # Info copied from your example.
        vcenter_host: "VCENTER01"
        vcenter_username: "TFGH\\kaikudou"
        vcenter_password: xxxxxxxxx
        target_host: "TFGH0001"
    
        # Max number of devices per scsi controller
        scsi_max_devices: 15
        # Max id for scsi controllers
        scsi_max_controller_id: 3
    
        # Calling the two following vars before getting facts will fail
        # but since we don't need to loop to get them they can be statically
        # declared in playbook vars
        scsi_controller_unique_keys: >-
          {{
            vm_disk_info.guest_disk_facts
            | dict2items
            | map(attribute='value.controller_key')
            | list
            | unique
            | sort
          }}
        host_datastore: "{{ vm_disk_info.guest_disk_facts['0'].backing_datastore }}"
    
        # Your example data to play with (minified, single line)
        # To take from module call return IRL.
        vm_disk_info: {"guest_disk_facts":{"0":{"backing_datastore":"VCENTER01_DS01","backing_eagerlyscrub":false,"backing_filename":"[VCENTER01_DS01] TCB0945/TFGH0001-000001.vmdk","backing_thinprovisioned":false,"backing_type":"FlatVer2","backing_uuid":"6000C294-d22d-06a9-c89b-119e13fffe33","backing_writethrough":false,"capacity_in_bytes":32212254720,"capacity_in_kb":31457280,"controller_key":1000,"key":2000,"label":"Hard disk 1","summary":"31,457,280 KB","unit_number":0},"1":{"backing_datastore":"VCENTER01_DS01","backing_eagerlyscrub":false,"backing_filename":"[VCENTER01_DS01] TFGH0001/TFGH0001_3.vmdk","backing_thinprovisioned":false,"backing_type":"FlatVer2","backing_uuid":"6000C293-ae37-30f6-b684-d5b2416ff2f8","backing_writethrough":false,"capacity_in_bytes":10737418240,"capacity_in_kb":10485760,"controller_key":1000,"key":2001,"label":"Hard disk 2","summary":"10,485,760 KB","unit_number":1}}}
    
      tasks:
    
        - name: Create a list holding all the info we need for each existing controller
          vars:
            scsi_controller_devices_query: >-
              [?to_string(value.controller_key)=='{{ controller_key }}'].value.unit_number[]
            scsi_controller_devices: >-
              {{
                vm_disk_info.guest_disk_facts |
                dict2items |
                json_query(scsi_controller_devices_query)
              }}
            # Construct object directly as json so that we retain int type for further comparison usage.
            current_controller: >-
              {
                "controller_number": {{ controller_number | int }},
                "controller_key": {{ controller_key | int }},
                "number_of_devices": {{ scsi_controller_devices | length | int }},
                "max_unit_number": {{ scsi_controller_devices | max | int }},
              }
          set_fact:
            scsi_controllers_info: "{{ scsi_controllers_info | default([]) + [current_controller] }}"
          loop: "{{ scsi_controller_unique_keys }}"
          loop_control:
            loop_var: controller_key
            index_var: controller_number
    
        - block:
    
            # Note: This was already sorted when we got controllers list in our first loop
            - name: "Extract first controller having less than {{ scsi_max_devices }} disks"
              set_fact:
                scsi_controller: >-
                  {{
                    (
                      scsi_controllers_info |
                      selectattr('number_of_devices', '<', scsi_max_devices) |
                      list
                    ).0
                  }}
    
          rescue:
    
            - name: Fail if we cannot add an other controller id
              # i.e.controllernumber of our last element in list is equal (or greater for tests) that scsi_max_controller_id
              fail:
                msg: All scsi controllers are full, disk cannot be added.
              when: scsi_controllers_info[-1].controller_number >= scsi_max_controller_id
    
            - name: Return an empty controller with incremented id
              set_fact:
                scsi_controller: >-
                  {
                    "controller_number": {{ scsi_controllers_info[-1].controller_number + 1 | int }},
                    "controller_key": {{ scsi_controllers_info[-1].controller_key + 1 | int }},
                    "number_of_devices": 0,
                    "max_unit_number": -1,
                  }
    
    
        - name: Show what the call to vmware_guest_disk would look like
          vars:
            vmware_guest_disk_params:
              hostname: "{{ vcenter_host }}"
              username: "{{ vcenter_username }}"
              password: "{{ vcenter_password }}"
              validate_certs: False
              datacenter: "{{ vcenter_host }}"
              name: "{{ target_host }}"
              disk:
                - size_gb: 2
                  type: thin
                  state: present
                  datastore: "{{ host_datastore }}"
                  # autoselect_datastore: True
                  scsi_controller: "{{ scsi_controller.controller_number }}"
                  unit_number: "{{ scsi_controller.max_unit_number + 1 }}"
                  scsi_type: 'paravirtual'
          debug:
            msg: "{{ vmware_guest_disk_params }}"