[Python] Ansible code fails when NVMe drive changes name after reboot or reinstallation

Discussão em 'Python' iniciado por Stack, Agosto 9, 2025.

  1. Stack

    Stack Membro Participativo

    I am setting up my Proxmox cluster via Ansible, but I have a big problem with my NVMe drives changing names during reboot. So far I have lived with them changing names when I reinstall Proxmox, but having them also do so after reboot doesn't work, as my machines reboots after a kernel update. The problem occurs when setting up my Ceph cluster, which fails if the NVMe drive has changed its name. As far as I can read, I need to use the drive's UUID to ensure persistent naming, but I have no idea how to do that via Ansible.

    Here is my Ansible code used to create Ceph OSDs.

    Snippet from /inventory/host_vars/pxenode1/variables.yml:

    # Configure Ceph OSDs
    pve_ceph_osd_device: /dev/nvme0n1 # Device for the Ceph OSD.


    Snippet from /inventory/group_vars/ceph/variables.yml:

    pve_ceph_osds: # List of Ceph OSDs to create.
    - device: "{{ pve_ceph_osd_device }}"
    crush.device.class: NVMe


    The following Ansible code is executed on all machines that are members of the Ceph inventory group.

    - name: Create Ceph OSDs
    block:
    - name: Query for existing Ceph volumes
    pve_ceph_volume:
    check_mode: no
    register: _ceph_volume_data

    - name: Generate a list of active OSDs
    ansible.builtin.set_fact:
    _existing_ceph_osds: "{{ _ceph_volume_data.stdout | from_json | json_query('*[].devices[]') | default([]) }}"

    - name: Generate list of unprovisioned OSDs
    ansible.builtin.set_fact:
    _ceph_osds_diff: "{{ _ceph_osds_diff | default([]) + [item] }}"
    loop: "{{ pve_ceph_osds }}"
    when: item.device not in _existing_ceph_osds

    - name: Create Ceph OSDs
    ansible.builtin.command: >-
    pveceph osd create {{ item.device }}
    {% if "crush.device.class" in item %}--crush-device-class {{ item["crush.device.class"] }}{% endif %}
    {% if "encrypted" in item and item["encrypted"] | bool %}--encrypted 1{% endif %}
    {% if "block.db" in item %}--db_dev {{ item["block.db"] }}{% endif %}
    {% if "block.wal" in item %}--wal_dev {{ item["block.wal"] }}{% endif %}
    loop: '{{ _ceph_osds_diff | default([]) }}'
    when: _ceph_osds_diff | length > 0
    register: _ceph_osd_create


    From Ansible, a Python script is called which comes here:

    #!/usr/bin/python

    from ansible.module_utils.basic import AnsibleModule
    import datetime

    ANSIBLE_METADATA = {
    'metadata_version': '1.0',
    'status': ['preview'],
    'supported_by': 'community'
    }

    DOCUMENTATION = '''
    ---
    module: ceph_volume

    short_description: Query ceph OSDs with ceph-volume

    description:
    - Using the ceph-volume utility available in Ceph this module
    can be used to query ceph OSDs that are backed by logical volumes.
    - Only available in ceph versions luminous or greater.

    options:
    cluster:
    description:
    - The ceph cluster name.
    required: false
    default: ceph
    data:
    description:
    - The logical volume name or device to use for the OSD data.
    required: true
    data_vg:
    description:
    - If data is a lv, this must be the name of the volume group it belongs to.
    required: false

    author:
    - Andrew Schoen (@andrewschoen)
    - Sebastien Han <seb@redhat.com>
    '''

    EXAMPLES = '''
    - name: query all osds
    ceph_volume:

    - name: query single osd on test cluster
    ceph_volume:
    cluster: test
    data: /dev/sdc
    '''

    def exec_command(module, cmd, stdin=None):
    '''
    Execute command(s)
    '''
    binary_data = False
    if stdin:
    binary_data = True
    rc, out, err = module.run_command(cmd, data=stdin, binary_data=binary_data)
    return rc, cmd, out, err

    def get_data(data, data_vg):
    if data_vg:
    data = '{0}/{1}'.format(data_vg, data)
    return data


    def list_osd(module):
    '''
    List will detect whether or not a device has Ceph LVM Metadata
    '''

    # get module variables
    cluster = module.params['cluster']
    data = module.params.get('data', None)
    data_vg = module.params.get('data_vg', None)
    data = get_data(data, data_vg)

    # Build the CLI
    action = ['lvm', 'list']
    cmd = ['ceph-volume', '--cluster', cluster]
    cmd.extend(action)
    if data:
    cmd.append(data)
    cmd.append('--format=json')

    return cmd


    def run_module():
    module_args = dict(
    cluster=dict(type='str', required=False, default='ceph'),
    data=dict(type='str', required=False),
    data_vg=dict(type='str', required=False),
    )

    module = AnsibleModule(
    argument_spec=module_args,
    supports_check_mode=True,
    )

    result = dict(
    changed=False,
    stdout='',
    stderr='',
    rc=0,
    start='',
    end='',
    delta='',
    )

    if module.check_mode:
    module.exit_json(**result)

    # start execution
    startd = datetime.datetime.now()

    # List Ceph LVM Metadata on a device
    rc, cmd, out, err = exec_command(module, list_osd(module))

    endd = datetime.datetime.now()
    delta = endd - startd

    result = dict(
    cmd=cmd,
    start=str(startd),
    end=str(endd),
    delta=str(delta),
    rc=rc,
    stdout=out.rstrip('\r\n'),
    stderr=err.rstrip('\r\n'),
    changed=False,
    )

    if rc != 0:
    module.fail_json(msg='non-zero return code', **result)

    module.exit_json(**result)


    def main():
    run_module()


    if __name__ == '__main__':
    main()


    I haven't programmed the Python script nor the Ansible code myself and I have no idea about Python programming. I hope someone can help me, as this is really a showstopper in my project.

    Continue reading...

Compartilhe esta Página