diff --git a/README.md b/README.md index 8c585ee..ef0ce39 100644 --- a/README.md +++ b/README.md @@ -538,6 +538,8 @@ When `hypervisor.type: vmware` uses the `vmware_tools` connection: `system.disks[0]` is the OS disk (no `mount.path`). Additional entries define data disks. +On VMware, `device` is resolved on the target from the SCSI address instead: entry N is the disk at target N on the first SCSI controller (N+1 from index 7 on, since unit 7 is reserved). The kernel's `sdX` order is not stable between the install environment and the installed system, so a letter-derived path can point at another disk. + | Key | Type | Description | | ------------- | ------ | ------------------------------------------------------ | | `size` | number | Disk size in GB (required for virtual) | diff --git a/roles/partitioning/tasks/_resolve_vmware_disks.yml b/roles/partitioning/tasks/_resolve_vmware_disks.yml new file mode 100644 index 0000000..5ef20c5 --- /dev/null +++ b/roles/partitioning/tasks/_resolve_vmware_disks.yml @@ -0,0 +1,61 @@ +--- +# The virtualization role attaches system.disks to SCSI controller 0 in list order, and +# vSphere never gives a disk unit 7 (the controller's own address). The kernel's sdX order +# is not that order and differs between the install environment and the installed OS, so +# a disk derived as /dev/sd can be the wrong one. Resolve by target. +- name: List disks with their SCSI address + ansible.builtin.command: lsblk -dnpo NAME,TYPE,HCTL + register: partitioning_vmware_lsblk + changed_when: false + +- name: Map SCSI targets to devices + vars: + _scsi: >- + {%- set out = [] -%} + {%- for line in partitioning_vmware_lsblk.stdout_lines -%} + {%- set p = line.split() -%} + {%- if (p | length) == 3 and p[1] == 'disk' and p[2] is match('^\d+:0:\d+:0$') -%} + {%- set _ = out.append({'name': p[0], 'host': p[2].split(':')[0], 'target': p[2].split(':')[2]}) -%} + {%- endif -%} + {%- endfor -%} + {{ out }} + ansible.builtin.set_fact: + partitioning_vmware_hosts: "{{ _scsi | map(attribute='host') | unique | list }}" + partitioning_vmware_targets: "{{ _scsi | items2dict(key_name='target', value_name='name') }}" + partitioning_vmware_target_count: "{{ _scsi | length }}" + +- name: Assert the system disks sit on one SCSI controller with unique targets + ansible.builtin.assert: + that: + - partitioning_vmware_hosts | length == 1 + - partitioning_vmware_targets | length == partitioning_vmware_target_count | int + fail_msg: >- + Expected every disk on one SCSI controller with unique targets, got + {{ partitioning_vmware_lsblk.stdout_lines | select('search', ' disk ') | list }}. + quiet: true + +- name: Assert each system disk has a device at its SCSI target + vars: + _target: "{{ (idx if idx < 7 else idx + 1) | string }}" + ansible.builtin.assert: + that: + - _target in partitioning_vmware_targets + fail_msg: "system.disks[{{ idx }}] ({{ item.size }} GB) expects a disk at SCSI target {{ _target }}, none found." + quiet: true + loop: "{{ system_cfg.disks }}" + loop_control: + index_var: idx + label: "{{ idx }}" + +- name: Point system disks and install_drive at the resolved devices + vars: + _disks: >- + {%- set out = [] -%} + {%- for d in system_cfg.disks -%} + {%- set dev = partitioning_vmware_targets[(loop.index0 if loop.index0 < 7 else loop.index0 + 1) | string] -%} + {%- set _ = out.append(d | combine({'device': dev, 'partition': dev ~ '1'})) -%} + {%- endfor -%} + {{ out }} + ansible.builtin.set_fact: + system_cfg: "{{ system_cfg | combine({'disks': _disks}) }}" + install_drive: "{{ _disks[0].device }}" diff --git a/roles/partitioning/tasks/main.yml b/roles/partitioning/tasks/main.yml index d134b8e..64224ef 100644 --- a/roles/partitioning/tasks/main.yml +++ b/roles/partitioning/tasks/main.yml @@ -1,4 +1,10 @@ --- +- name: Resolve VMware disks by SCSI target + when: + - hypervisor_type == "vmware" + - system_cfg.type == "virtual" + ansible.builtin.include_tasks: _resolve_vmware_disks.yml + - name: Detect system sizing ansible.builtin.include_tasks: _detect_sizing.yml