diff --git a/hooks/playbooks/adoption_dcn_export.yml b/hooks/playbooks/adoption_dcn_export.yml new file mode 100644 index 0000000000..ec104b8f81 --- /dev/null +++ b/hooks/playbooks/adoption_dcn_export.yml @@ -0,0 +1,60 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# This playbook exports a TripleO stack for use by DCN stacks. +# It creates the -export.yaml file that contains +# the parameters DCN stacks need to connect to the central site. + +- name: Export TripleO stack for DCN + hosts: "{{ cifmw_target_host | default('localhost') }}" + gather_facts: false + vars: + _stack_name: "{{ stack_name | default('central') }}" + _ansible_user_dir: "{{ ansible_user_dir | default('/home/zuul') }}" + tasks: + - name: Gather ansible_user_dir from undercloud + delegate_to: "osp-undercloud-0" + ansible.builtin.setup: + gather_subset: + - user_dir + + - name: Export stack for DCN usage + delegate_to: "osp-undercloud-0" + vars: + _export_cmd: >- + source {{ ansible_user_dir }}/stackrc; + openstack overcloud export + --stack {{ _stack_name }} + --force-overwrite + --output-file {{ ansible_user_dir }}/overcloud-deploy/{{ _stack_name }}/{{ _stack_name }}-export.yaml + cifmw.general.ci_script: + chdir: "{{ ansible_user_dir }}" + output_dir: "{{ ansible_user_dir }}/ci-framework-data/artifacts" + script: "{{ _export_cmd }}" + + - name: Export Ceph configuration for DCN usage + delegate_to: "osp-undercloud-0" + vars: + _export_ceph_cmd: >- + source {{ ansible_user_dir }}/stackrc; + openstack overcloud export ceph + --stack {{ _stack_name }} + --force-overwrite + --output-file {{ ansible_user_dir }}/{{ _stack_name }}_ceph_external.yaml + cifmw.general.ci_script: + chdir: "{{ ansible_user_dir }}" + output_dir: "{{ ansible_user_dir }}/ci-framework-data/artifacts" + script: "{{ _export_ceph_cmd }}" diff --git a/hooks/playbooks/adoption_dcn_update_central.yml b/hooks/playbooks/adoption_dcn_update_central.yml new file mode 100644 index 0000000000..60fc047574 --- /dev/null +++ b/hooks/playbooks/adoption_dcn_update_central.yml @@ -0,0 +1,221 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# This playbook updates the central site after all DCN sites are deployed. +# 1. Copy Ceph keys and configuration from DCN sites to central controllers +# 2. Configure Glance with DCN Ceph stores +# 3. Restart Glance to apply the new configuration + +- name: Update central site with DCN Ceph stores + hosts: "{{ cifmw_target_host | default('localhost') }}" + gather_facts: false + vars: + _dcn_stack_names: "{{ dcn_stack_names | default('dcn1,dcn2') }}" + _glance_config_file: "/var/lib/config-data/puppet-generated/glance_api/etc/glance/glance-api.conf" + tasks: + - name: Build list of DCN sites from scenario + vars: + _dcn_name: "{{ item }}" + _dcn_stack: >- + {{ cifmw_adoption_osp_deploy_scenario.stacks | + selectattr('stackname', 'equalto', _dcn_name) | + first }} + _dcn_group: "{{ _dcn_stack.stack_nodes | first }}" + _dcn_node: "{{ _vm_groups[_dcn_group] | first }}" + ansible.builtin.set_fact: + _dcn_sites: >- + {{ _dcn_sites | default([]) + [{'name': _dcn_name, 'ceph_node': _dcn_node}] }} + loop: "{{ _dcn_stack_names.split(',') }}" + + - name: Get list of controller nodes + vars: + _central_stack: >- + {{ cifmw_adoption_osp_deploy_scenario.stacks | + selectattr('stackname', 'equalto', central_stack_name | default('central')) | + first }} + _controller_group: >- + {{ _central_stack.stack_nodes | + select('search', 'controller') | + first | default('osp-controllers') }} + ansible.builtin.set_fact: + _controller_nodes: "{{ _vm_groups[_controller_group] | default([]) }}" + + - name: Display discovered nodes + ansible.builtin.debug: + msg: + - "DCN sites: {{ _dcn_sites }}" + - "Controller nodes: {{ _controller_nodes }}" + + - name: Build Glance DCN store configuration + ansible.builtin.set_fact: + _glance_dcn_config: | + {% for site in _dcn_sites %} + [{{ site.name }}] + rbd_store_ceph_conf=/etc/ceph/{{ site.name }}.conf + rbd_store_user=openstack + rbd_store_pool=images + rbd_thin_provisioning=False + store_description={{ site.name }} rbd glance store + {% endfor %} + + - name: Copy Ceph files from DCN sites to controllers + block: + - name: Ensure ceph config directory exists on controllers + delegate_to: "{{ item }}" + become: true + ansible.builtin.file: + path: "/var/lib/tripleo-config/ceph" + state: directory + owner: root + group: root + mode: "0755" + loop: "{{ _controller_nodes }}" + + - name: Fetch Ceph config from DCN site + delegate_to: "{{ item.ceph_node }}" + become: true + ansible.builtin.fetch: + src: "/var/lib/tripleo-config/ceph/{{ item.name }}.conf" + dest: "/tmp/ceph_files/{{ item.name }}.conf" + flat: true + loop: "{{ _dcn_sites }}" + loop_control: + label: "{{ item.name }}" + + - name: Fetch Ceph keyring from DCN site + delegate_to: "{{ item.ceph_node }}" + become: true + ansible.builtin.fetch: + src: "/var/lib/tripleo-config/ceph/{{ item.name }}.client.openstack.keyring" + dest: "/tmp/ceph_files/{{ item.name }}.client.openstack.keyring" + flat: true + loop: "{{ _dcn_sites }}" + loop_control: + label: "{{ item.name }}" + + - name: Copy Ceph config to controllers + delegate_to: "{{ item.1 }}" + become: true + ansible.builtin.copy: + src: "/tmp/ceph_files/{{ item.0.name }}.conf" + dest: "/var/lib/tripleo-config/ceph/{{ item.0.name }}.conf" + owner: root + group: root + mode: "0644" + loop: "{{ _dcn_sites | product(_controller_nodes) | list }}" + loop_control: + label: "{{ item.0.name }} -> {{ item.1 }}" + + - name: Copy Ceph keyring to controllers + delegate_to: "{{ item.1 }}" + become: true + ansible.builtin.copy: + src: "/tmp/ceph_files/{{ item.0.name }}.client.openstack.keyring" + dest: "/var/lib/tripleo-config/ceph/{{ item.0.name }}.client.openstack.keyring" + owner: root + group: root + mode: "0644" + loop: "{{ _dcn_sites | product(_controller_nodes) | list }}" + loop_control: + label: "{{ item.0.name }} -> {{ item.1 }}" + + - name: Configure Glance with DCN stores + block: + - name: Check glance config status on each controller + delegate_to: "{{ controller }}" + become: true + ansible.builtin.stat: + path: "{{ _glance_config_file }}" + register: _glance_status + loop: "{{ _controller_nodes }}" + loop_control: + loop_var: controller + + - name: Build list of controllers needing Glance update + ansible.builtin.set_fact: + _controllers_to_update: >- + {{ _glance_status.results | + selectattr('stat.exists') | + map(attribute='controller') | + list }} + + - name: Display controllers to update + ansible.builtin.debug: + msg: "Controllers needing Glance update: {{ _controllers_to_update }}" + + - name: Get current enabled_backends value + delegate_to: "{{ controller }}" + become: true + ansible.builtin.command: + cmd: awk -F'\s*=\s*' '/^enabled_backends\s*=/{print $2; exit}' "{{ _glance_config_file }}" + register: _current_backends + changed_when: false + loop: "{{ _controllers_to_update }}" + loop_control: + loop_var: controller + + - name: Build DCN backends string + ansible.builtin.set_fact: + _dcn_backends_suffix: >- + {{ _dcn_sites | map(attribute='name') | map('regex_replace', '^(.*)$', ',\1:rbd') | join('') }} + + - name: Update enabled_backends to include DCN stores + delegate_to: "{{ item.controller }}" + become: true + ansible.builtin.lineinfile: + path: "{{ _glance_config_file }}" + regexp: '^enabled_backends\s*=' + line: "enabled_backends={{ item.stdout }}{{ _dcn_backends_suffix }}" + backrefs: false + loop: "{{ _current_backends.results }}" + loop_control: + label: "{{ item.controller }}" + when: _dcn_sites[0].name ~ ':rbd' not in item.stdout + + - name: Enable copy-image import method for multistore + delegate_to: "{{ controller }}" + become: true + ansible.builtin.lineinfile: + path: "{{ _glance_config_file }}" + regexp: '^enabled_import_methods\s*=' + line: 'enabled_import_methods=["web-download","copy-image"]' + insertafter: '^\[DEFAULT\]' + loop: "{{ _controllers_to_update }}" + loop_control: + loop_var: controller + + - name: Add DCN store configuration to glance-api.conf + delegate_to: "{{ controller }}" + become: true + ansible.builtin.blockinfile: + path: "{{ _glance_config_file }}" + block: "{{ _glance_dcn_config }}" + insertafter: EOF + loop: "{{ _controllers_to_update }}" + loop_control: + loop_var: controller + register: _glance_config_changed + + - name: Restart Glance API service on updated controllers + delegate_to: "{{ controller }}" + become: true + ansible.builtin.systemd: + name: tripleo_glance_api.service + state: restarted + loop: "{{ _controllers_to_update }}" + loop_control: + loop_var: controller + when: _controllers_to_update | length > 0 diff --git a/hooks/playbooks/adoption_deploy_ceph.yml b/hooks/playbooks/adoption_deploy_ceph.yml index 35dc7eb2c7..2e5b9f0870 100644 --- a/hooks/playbooks/adoption_deploy_ceph.yml +++ b/hooks/playbooks/adoption_deploy_ceph.yml @@ -72,6 +72,10 @@ }} _cloud_domain: "{{ cifmw_adoption_osp_deploy_scenario.cloud_domain }}" _source_cmd: "source {{ ansible_user_dir }}/stackrc" + # Use stack-specific filename for non-default stack names to avoid overwrites + # Default 'overcloud' stack uses 'deployed_ceph.yaml' for backward compatibility + _deployed_ceph_suffix: "{{ '' if _overcloud_name == 'overcloud' else '_' ~ _overcloud_name }}" + _deployed_ceph_file: "{{ ansible_user_dir }}/deployed_ceph{{ _deployed_ceph_suffix }}.yaml" block: - name: Copy ceph osd file delegate_to: "osp-undercloud-0" @@ -99,6 +103,7 @@ _ceph_spec_cmd: >- openstack overcloud ceph spec {{ ansible_user_dir }}/config_download_{{ _overcloud_name }}.yaml + --stack {{ _overcloud_name }} --tld {{ _cloud_domain }} --osd-spec {{ _ceph_osd_spec_file_dest }} --roles-data {{ _roles_file_dest }} @@ -111,7 +116,7 @@ - name: Ensure deployed_ceph file does not exist delegate_to: "osp-undercloud-0" ansible.builtin.file: - path: "{{ ansible_user_dir }}/deployed_ceph.yaml" + path: "{{ _deployed_ceph_file }}" state: absent - name: Gather nodes for stack {{ _overcloud_name }} @@ -159,14 +164,18 @@ - name: Deploy ceph delegate_to: "osp-undercloud-0" vars: + # Only pass --cluster for non-default stacks to set unique cluster names + # Default 'overcloud' stack uses 'ceph' cluster name for backward compatibility _ceph_deploy_cmd: >- openstack overcloud ceph deploy + --stack {{ _overcloud_name }} + {%- if _overcloud_name != 'overcloud' %} --cluster {{ _overcloud_name }}{% endif %} --tld {{ _cloud_domain }} --ntp-server {{ cifmw_adoption_osp_deploy_ntp_server }} --ceph-spec ceph_spec.yaml --network-data {{ _network_data_file_dest }} --container-image-prepare {{ ansible_user_dir }}/containers-prepare-parameters.yaml - --output {{ ansible_user_dir }}/deployed_ceph.yaml + --output {{ _deployed_ceph_file }} cifmw.general.ci_script: chdir: "{{ ansible_user_dir }}" output_dir: "{{ _cifmw_basedir_undercloud }}/artifacts" diff --git a/roles/adoption_osp_deploy/tasks/prepare_overcloud.yml b/roles/adoption_osp_deploy/tasks/prepare_overcloud.yml index 83a01bf1df..9774e3b7e3 100644 --- a/roles/adoption_osp_deploy/tasks/prepare_overcloud.yml +++ b/roles/adoption_osp_deploy/tasks/prepare_overcloud.yml @@ -254,8 +254,8 @@ delegate_to: "{{ overcloud_vm }}" vars: _node_net: "{{ cifmw_networking_env_definition.instances[overcloud_vm] }}" - _ctlplane_node_net: "{{ _node_net.networks.ctlplane | default({}) }}" - _ctlplane_net: "{{ cifmw_networking_env_definition.networks.ctlplane | default({}) }}" + _ctlplane_node_net: "{{ _node_net.networks[_ctlplane] | default({}) }}" + _ctlplane_net: "{{ cifmw_networking_env_definition.networks[_ctlplane] | default({}) }}" _dns_server: "{{ _ctlplane_net[dns_version|default('dns_v4')] | default(None) }}" _os_net_config_template: "{{ 'os_net_config_overcloud_bgp.yml.j2' if (cifmw_adoption_osp_deploy_bgp | bool) else 'os_net_config_overcloud.yml.j2' }}" # Non-BGP specific vars (may not exist in BGP mode, so use default) diff --git a/roles/adoption_osp_deploy/templates/tripleo-ansible-inventory.yaml.j2 b/roles/adoption_osp_deploy/templates/tripleo-ansible-inventory.yaml.j2 index adc5cb4fbb..66fc8315ce 100644 --- a/roles/adoption_osp_deploy/templates/tripleo-ansible-inventory.yaml.j2 +++ b/roles/adoption_osp_deploy/templates/tripleo-ansible-inventory.yaml.j2 @@ -36,7 +36,7 @@ allovercloud: computes: children: {% for group in _stack.stack_nodes %} - {% if 'computes' in group %} + {% if 'compute' in group and 'controller' not in group %} {{ cifmw_adoption_osp_deploy_scenario.roles_groups_map[group] }}: {} {% endif %} {% endfor %} diff --git a/scenarios/adoption/dcn_storage.yml b/scenarios/adoption/dcn_storage.yml new file mode 100644 index 0000000000..d3bf29f635 --- /dev/null +++ b/scenarios/adoption/dcn_storage.yml @@ -0,0 +1,168 @@ +--- +# DCN Storage Adoption Scenario +# Deploys 3 Ceph clusters: Central (az0), DCN1 (az1), DCN2 (az2) +# Each cluster runs on 3 HCI compute nodes + +_osp_img_data: &osp_base_conf + image_local_dir: "{{ cifmw_basedir }}/images/" + disk_file_name: osp-base.qcow2 + image_url: "{{ osp_base_img_url | default(cifmw_discovered_image_url) }}" + sha256_image_name: >- + {{ osp_base_img_sha256 | default(cifmw_discovered_hash) }} + +libvirt_manager_patch_layout: + vms: + osp-undercloud: + <<: *osp_base_conf + amount: 1 + memory: 16 + cpus: 8 + disksize: 80 + nets: + - ocpbm + - osp_trunk + osp-controller: + <<: *osp_base_conf + amount: 1 + disksize: 50 + memory: 8 + cpus: 4 + nets: + - ocpbm + - osp_trunk + # Let's remove the default computes, since we want to adopt the + # OSP ones + compute: + amount: 0 + # Central HCI computes with Ceph (az0) + osp-compute: + <<: *osp_base_conf + amount: "{{ [cifmw_libvirt_manager_compute_amount|int, 3] | max }}" + extra_disks_num: 3 + extra_disks_size: 30G + disksize: "{{ [cifmw_libvirt_manager_compute_disksize|int, 50] | max }}" + memory: "{{ [cifmw_libvirt_manager_compute_memory|int, 8] | max }}" + cpus: "{{ [cifmw_libvirt_manager_compute_cpus|int, 4] | max }}" + nets: + - ocpbm + - osp_trunk + dcn1-compute-az1: + amount: 0 + # DCN1 HCI computes with Ceph (az1) + osp-dcn1-compute-az1: + <<: *osp_base_conf + amount: "{{ [cifmw_libvirt_manager_compute_amount|int, 3] | max }}" + extra_disks_num: 3 + extra_disks_size: 30G + disksize: "{{ [cifmw_libvirt_manager_compute_disksize|int, 50] | max }}" + memory: "{{ [cifmw_libvirt_manager_compute_memory|int, 8] | max }}" + cpus: "{{ [cifmw_libvirt_manager_compute_cpus|int, 4] | max }}" + nets: + - dcn1_pb + - dcn1_tr + dcn2-compute-az2: + amount: 0 + # DCN2 HCI computes with Ceph (az2) + osp-dcn2-compute-az2: + <<: *osp_base_conf + amount: "{{ [cifmw_libvirt_manager_compute_amount|int, 3] | max }}" + extra_disks_num: 3 + extra_disks_size: 30G + disksize: "{{ [cifmw_libvirt_manager_compute_disksize|int, 50] | max }}" + memory: "{{ [cifmw_libvirt_manager_compute_memory|int, 8] | max }}" + cpus: "{{ [cifmw_libvirt_manager_compute_cpus|int, 4] | max }}" + nets: + - dcn2_pb + - dcn2_tr + +networking_mapper_definition_patch: + networks: + external: + network: "10.0.0.0/24" + vlan: 44 + mtu: 1496 + + group-templates: + osp-controllers: + network-template: + range: + start: 103 + length: 1 + networks: &osp_nets + ctlplane: {} + external: + trunk-parent: ctlplane + internalapi: + trunk-parent: ctlplane + tenant: + trunk-parent: ctlplane + storage: + trunk-parent: ctlplane + storagemgmt: + trunk-parent: ctlplane + osp-underclouds: + network-template: + range: + start: 100 + length: 1 + networks: *osp_nets + computes: + network-template: + range: + start: 200 + length: 1 + osp-computes: + network-template: + range: + start: 106 + length: 3 + networks: + ctlplane: {} + internalapi: + trunk-parent: ctlplane + tenant: + trunk-parent: ctlplane + storage: + trunk-parent: ctlplane + storagemgmt: + trunk-parent: ctlplane + dcn1-compute-az1s: + network-template: + range: + start: 201 + length: 1 + osp-dcn1-compute-az1s: + network-template: + range: + start: 111 + length: 10 + networks: + ctlplanedcn1: {} + internalapidcn1: + trunk-parent: ctlplanedcn1 + tenantdcn1: + trunk-parent: ctlplanedcn1 + storagedcn1: + trunk-parent: ctlplanedcn1 + storagemgmtdcn1: + trunk-parent: ctlplanedcn1 + dcn2-compute-az2s: + network-template: + range: + start: 202 + length: 1 + osp-dcn2-compute-az2s: + network-template: + range: + start: 121 + length: 10 + networks: + ctlplanedcn2: {} + internalapidcn2: + trunk-parent: ctlplanedcn2 + tenantdcn2: + trunk-parent: ctlplanedcn2 + storagedcn2: + trunk-parent: ctlplanedcn2 + storagemgmtdcn2: + trunk-parent: ctlplanedcn2