This is an automated email from the ASF dual-hosted git repository.
ssulav pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone-installer.git
The following commit(s) were added to refs/heads/master by this push:
new bba6708 HDDS-14667. Add support for multiple disks ozone installation
(#1)
bba6708 is described below
commit bba670874c23f1a773b55a23acadb709fe36d407
Author: Soumitra Sulav <[email protected]>
AuthorDate: Fri Feb 20 23:17:05 2026 +0530
HDDS-14667. Add support for multiple disks ozone installation (#1)
---
README.md | 4 ++--
ozone_installer.py | 21 ++++++++++++++++++---
playbooks/cluster.yml | 5 +++++
roles/cleanup/tasks/main.yml | 5 +++--
roles/ozone_config/defaults/main.yml | 1 +
roles/ozone_config/templates/ozone-site.xml.j2 | 16 +++++++++-------
roles/ozone_layout/tasks/main.yml | 6 +-----
roles/ozone_service/tasks/main.yml | 4 ++--
8 files changed, 41 insertions(+), 21 deletions(-)
diff --git a/README.md b/README.md
index 6a3e710..1905887 100644
--- a/README.md
+++ b/README.md
@@ -256,7 +256,7 @@ ANSIBLE_CONFIG=ansible.cfg ansible-playbook -i
inventories/dev/hosts.ini playboo
### Directories
- Install base (`install_base`, default `/opt/ozone`): where Ozone binaries
and configs live. A `current` symlink points to the active version directory.
-- Data base (`data_base`, default `/data/ozone`): where Ozone writes on‑disk
metadata and Datanode data (e.g., `ozone.metadata.dirs`, `hdds.datanode.dir`).
+- Data base (`data_base`, default `/data/ozone`): where Ozone writes on‑disk
metadata and Datanode data (e.g., `ozone.metadata.dirs`, `hdds.datanode.dir`).
Supports comma-separated multiple directories (e.g.
`/data/ozone1,/data/ozone2`) or brace expansion (e.g. `/data/ozone{1..3}` →
`/data/ozone1,/data/ozone2,/data/ozone3`); each path gets `dn`, `meta`,
`data/om`, etc. subdirs in `ozone-site.xml`.
## Components and config mapping
@@ -265,7 +265,7 @@ ANSIBLE_CONFIG=ansible.cfg ansible-playbook -i
inventories/dev/hosts.ini playboo
- HA: first three hosts serve as OM and SCM sets; all hosts are DNs; first
host is Recon.
- `ozone-site.xml` is rendered from templates based on inventory groups:
- `ozone.scm.names`, `ozone.scm.client.address`, `ozone.om.address` or HA
service IDs
- - `ozone.metadata.dirs`, `hdds.datanode.dir`, and related paths map to
`data_base`
+ - `ozone.metadata.dirs`, `hdds.datanode.dir`, and related paths map to
`data_base` (comma-separated dirs are expanded per property)
- Replication is set to ONE if DN count < 3, otherwise THREE
## Optional: S3 Gateway (S3G) and smoke
diff --git a/ozone_installer.py b/ozone_installer.py
index 93c0084..c044ac9 100755
--- a/ozone_installer.py
+++ b/ozone_installer.py
@@ -85,7 +85,7 @@ def parse_args(argv: List[str]) -> argparse.Namespace:
p.add_argument("-k", "--keyfile", help="SSH private key file (for
--auth-method=key)")
p.add_argument("-v", "--version", help="Ozone version (e.g., 2.0.0) or
'local'")
p.add_argument("-i", "--install-dir", help=f"Install root (default:
{DEFAULTS['install_base']})")
- p.add_argument("-d", "--data-dir", help=f"Data root (default:
{DEFAULTS['data_base']})")
+ p.add_argument("-d", "--data-dir", help=f"Data root(s), comma-separated or
brace expansion e.g. /data/ozone{{1..3}} (default: {DEFAULTS['data_base']})")
p.add_argument("-s", "--start", action="store_true", help="Initialize and
start after install")
p.add_argument("-M", "--cluster-mode", choices=["non-ha", "ha"],
help="Force cluster mode (default: auto by host count)")
p.add_argument("-r", "--role-file", help="Role file (YAML) for HA mapping
(optional)")
@@ -273,6 +273,20 @@ def expand_braces(expr: str) -> List[str]:
pre, a, b, post = m.group(1), int(m.group(2)), int(m.group(3)), m.group(4)
return [f"{pre}{i}{post}" for i in range(a, b + 1)]
+def parse_data_dirs(data_raw: Optional[str]) -> str:
+ """
+ Accepts comma-separated data dirs; each may contain brace expansion (e.g.
/data/ozone{1..3}).
+ Returns comma-separated string of expanded paths.
+ """
+ if not data_raw:
+ return data_raw or ""
+ out = []
+ for token in data_raw.split(","):
+ token = token.strip()
+ expanded = expand_braces(token)
+ out.extend(expanded)
+ return ",".join(out)
+
def parse_hosts(hosts_raw: Optional[str]) -> List[dict]:
"""
Accepts comma-separated hosts; each may contain brace expansion.
@@ -500,8 +514,9 @@ def main(argv: List[str]) -> int:
jdk_major = DEFAULTS["jdk_major"]
install_base = args.install_dir or (last_cfg.get("install_base") if
last_cfg else None) \
or prompt("Install directory (base directory path to store ozone
binaries, configs and logs)", default=DEFAULTS["install_base"], yes_mode=yes)
- data_base = args.data_dir or (last_cfg.get("data_base") if last_cfg else
None) \
- or prompt("Data directory (base directory path to store ozone metadata
and data)", default=DEFAULTS["data_base"], yes_mode=yes)
+ data_base_raw = args.data_dir or (last_cfg.get("data_base") if last_cfg
else None) \
+ or prompt("Data directory (base path(s), comma-separated or brace
expansion e.g. /data/ozone{1..3})", default=DEFAULTS["data_base"], yes_mode=yes)
+ data_base = parse_data_dirs(data_base_raw) if data_base_raw else
(data_base_raw or DEFAULTS["data_base"])
# Auth (before service user/group)
auth_method = args.auth_method or (last_cfg.get("auth_method") if last_cfg
else None) \
diff --git a/playbooks/cluster.yml b/playbooks/cluster.yml
index bef59f4..f67c45e 100644
--- a/playbooks/cluster.yml
+++ b/playbooks/cluster.yml
@@ -21,8 +21,13 @@
# Expect cluster_mode to be passed in (non-ha | ha). Fallback to non-ha.
cluster_mode: "{{ cluster_mode | default('non-ha') }}"
ha_enabled: "{{ cluster_mode == 'ha' }}"
+ # data_base_list: comma-separated data_base expanded to list (set in
pre_tasks)
pre_tasks:
+ - name: "Pre-install: Parse data_base into list (supports comma-separated
dirs)"
+ set_fact:
+ data_base_list: "{{ ((data_base | default('')).split(',') |
map('trim') | select | list) | default([data_base | default('/data/ozone')],
true) }}"
+
- name: "Pre-install: Gather facts"
setup:
diff --git a/roles/cleanup/tasks/main.yml b/roles/cleanup/tasks/main.yml
index 5d41c8f..14eb003 100644
--- a/roles/cleanup/tasks/main.yml
+++ b/roles/cleanup/tasks/main.yml
@@ -58,10 +58,11 @@
state: absent
become: true
- - name: "Remove data directory"
+ - name: "Remove data directories"
file:
- path: "{{ data_base }}"
+ path: "{{ item }}"
state: absent
+ loop: "{{ data_base_list | default([data_base | default('/data/ozone')])
}}"
become: true
diff --git a/roles/ozone_config/defaults/main.yml
b/roles/ozone_config/defaults/main.yml
index 7473b5b..a527b6b 100644
--- a/roles/ozone_config/defaults/main.yml
+++ b/roles/ozone_config/defaults/main.yml
@@ -15,6 +15,7 @@
---
install_base: "/opt/ozone"
+# data_base: single path or comma-separated paths (e.g. /data/ozone or
/data/ozone1,/data/ozone2)
data_base: "/data/ozone"
CONFIG_DIR: "" # if provided, can be used to feed additional properties via
vars
diff --git a/roles/ozone_config/templates/ozone-site.xml.j2
b/roles/ozone_config/templates/ozone-site.xml.j2
index f838f03..db8d458 100644
--- a/roles/ozone_config/templates/ozone-site.xml.j2
+++ b/roles/ozone_config/templates/ozone-site.xml.j2
@@ -16,6 +16,8 @@
-->
<configuration>
<!-- Minimal Ozone site config; extend via group_vars if needed -->
+{% set _data_bases = data_base_list | default([data_base |
default('/data/ozone')]) %}
+{% set _first_disk = _data_bases | first %}
{% set _om_all = groups.get('om', [])| list %}
{% set _scm_all = groups.get('scm', []) | list %}
{% set _all_dn_count = groups.get('datanodes', []) | list | length %}
@@ -101,31 +103,31 @@
{% endif %}
<property>
<name>ozone.metadata.dirs</name>
- <value>{{ data_base }}/meta</value>
+ <value>{{ _first_disk }}/meta</value>
</property>
<property>
<name>hdds.datanode.dir</name>
- <value>{{ data_base }}/dn</value>
+ <value>{% for d in _data_bases %}{{ d }}/dn{% if not loop.last %},{% endif
%}{% endfor %}</value>
</property>
<property>
<name>dfs.container.ratis.datanode.storage.dir</name>
- <value>{{ data_base }}/meta/dn</value>
+ <value>{{ _first_disk }}/meta/dn</value>
</property>
<property>
<name>ozone.om.db.dirs</name>
- <value>{{ data_base }}/data/om</value>
+ <value>{{ _first_disk }}/data/om</value>
</property>
<property>
<name>ozone.om.ratis.snapshot.dir</name>
- <value>{{ data_base }}/meta/om</value>
+ <value>{{ _first_disk }}/meta/om</value>
</property>
<property>
<name>ozone.scm.db.dirs</name>
- <value>{{ data_base }}/data/scm</value>
+ <value>{{ _first_disk }}/data/scm</value>
</property>
<property>
<name>ozone.scm.datanode.id.dir</name>
- <value>{{ data_base }}/meta/scm</value>
+ <value>{{ _first_disk }}/meta/scm</value>
</property>
<property>
<name>ozone.scm.skip.bootstrap.validation</name>
diff --git a/roles/ozone_layout/tasks/main.yml
b/roles/ozone_layout/tasks/main.yml
index 5a5f0d8..43c5c44 100644
--- a/roles/ozone_layout/tasks/main.yml
+++ b/roles/ozone_layout/tasks/main.yml
@@ -21,11 +21,7 @@
owner: "{{ service_user }}"
group: "{{ service_group }}"
mode: "0755"
- loop:
- - "{{ install_base }}"
- - "{{ data_base }}"
- - "{{ data_base }}/dn"
- - "{{ data_base }}/meta"
+ loop: "{{ [install_base] + (data_base_list | default([data_base |
default('/data/ozone')]) | product(['', '/dn', '/meta']) | map('join', '') |
list) }}"
become: true
- name: "Ensure OZONE_HOME and PATH are in profile.d/ozone.sh"
diff --git a/roles/ozone_service/tasks/main.yml
b/roles/ozone_service/tasks/main.yml
index b4ce240..8746028 100644
--- a/roles/ozone_service/tasks/main.yml
+++ b/roles/ozone_service/tasks/main.yml
@@ -27,7 +27,7 @@
- name: "Initialize first SCM"
command: "ozone scm --init"
args:
- creates: "{{ data_base }}/meta/scm"
+ creates: "{{ (data_base_list | first) | default(data_base)
}}/meta/scm"
when: (groups['scm'] | length > 0) and (inventory_hostname ==
groups['scm'][0])
register: scm_init_first
failed_when: scm_init_first.rc != 0
@@ -41,7 +41,7 @@
- name: "Initialize first OM"
command: "ozone om --init"
args:
- creates: "{{ data_base }}/meta/om"
+ creates: "{{ (data_base_list | first) | default(data_base)
}}/meta/om"
when: (groups['om'] | length > 0) and (inventory_hostname ==
groups['om'][0])
register: om_init_first
failed_when: om_init_first.rc != 0
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]