Add OpenStack iSCSI Multipath Playbooks and Jobs

Change-Id: Id29b17c9ef799bd5ae0560eaedfada6dd55a3b3d
diff --git a/playbooks/setup-openstack-client/post.yaml b/playbooks/setup-openstack-client/post.yaml
new file mode 100644
index 0000000..d2525ac
--- /dev/null
+++ b/playbooks/setup-openstack-client/post.yaml
@@ -0,0 +1,8 @@
+- hosts: undercloud-client
+  vars_files:
+    - vars.yaml
+  tasks:
+  - name: Deprovision OpenStack Credentials
+    ansible.builtin.file:
+      path: "{{ os_creds_dir }}"
+      state: absent
diff --git a/playbooks/setup-openstack-client/pre.yaml b/playbooks/setup-openstack-client/pre.yaml
new file mode 100644
index 0000000..2534c6e
--- /dev/null
+++ b/playbooks/setup-openstack-client/pre.yaml
@@ -0,0 +1,48 @@
+- hosts: undercloud-client
+  vars_files:
+    - vars.yaml
+  tasks:
+    # TODO: To be removed once DNS starts working again.
+    - name: Provision systemd-resovled Configuration
+      no_log: true
+      become: true
+      ansible.builtin.copy:
+        content: "{{ RESOLVED_DATA['v'] }}"
+        dest: /etc/systemd/resolved.conf
+
+    - name: Restart systemd-resolved
+      become: true
+      ansible.builtin.service:
+        name: systemd-resolved
+        state: restarted
+
+    - name: Ensure OpenStack Credentials Directory Exists
+      ansible.builtin.file:
+        path: "{{ os_creds_dir }}"
+        state: directory
+        mode: '0700'
+        recurse: true
+
+    - name: Provision OpenStack Credentials
+      no_log: true
+      ansible.builtin.copy:
+        content: "{{ OPENSTACK_DATA['v'] }}"
+        dest: "{{ os_creds_path }}"
+        mode: '0600'
+
+    - name: Install python3-pip
+      become: true
+      ansible.builtin.apt:
+        name: python3-pip
+        update_cache: yes
+
+    - name: Install virtualenv
+      become: true
+      ansible.builtin.pip:
+        name: virtualenv
+        executable: pip3
+
+    - name: Create an OpenStack CLI Venv
+      ansible.builtin.pip:
+        name: python-openstackclient
+        virtualenv: "{{ os_venv }}"
diff --git a/playbooks/setup-openstack-client/vars.yaml b/playbooks/setup-openstack-client/vars.yaml
new file mode 100644
index 0000000..3b859dd
--- /dev/null
+++ b/playbooks/setup-openstack-client/vars.yaml
@@ -0,0 +1,2 @@
+os_creds_dir: "{{ ansible_env.HOME }}/.config/openstack"
+os_creds_path: "{{ os_creds_dir }}/clouds.yaml"
diff --git a/playbooks/setup-openstack-iscsi-multipath-storpool/post.yaml b/playbooks/setup-openstack-iscsi-multipath-storpool/post.yaml
new file mode 100644
index 0000000..e0164d7
--- /dev/null
+++ b/playbooks/setup-openstack-iscsi-multipath-storpool/post.yaml
@@ -0,0 +1,51 @@
+- hosts: undercloud-client
+  vars_files:
+    - vars.yaml
+  tasks:
+    - name: Remove the StorPool Ports from the StorPool Node
+      ansible.builtin.command:
+        argv:
+          - "{{ os_venv }}/bin/openstack"
+          - --os-cloud
+          - openstack-testing
+          - server
+          - remove
+          - port
+          - "{{ storpool_node }}"
+          - "{{ item }}"
+      loop:
+        - "{{ port_sp_api }}"
+        - "{{ port_sp0 }}"
+        - "{{ port_sp1 }}"
+
+    - name: Delete the StorPool Ports
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "port", "delete", "{{ item }}" ]
+      loop:
+        - "{{ port_sp_api }}"
+        - "{{ port_sp0 }}"
+        - "{{ port_sp1 }}"
+
+    - name: Delete the StorPool Networks
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "network", "delete", "{{ item }}" ]
+      loop:
+        - "{{ network_sp_api }}"
+        - "{{ network_sp0 }}"
+        - "{{ network_sp1 }}"
+
+    - name: Remove the StorPool Volume to the StorPool Node
+      ansible.builtin.command:
+        argv:
+          - "{{ os_venv }}/bin/openstack"
+          - --os-cloud
+          - openstack-testing
+          - server
+          - remove
+          - volume
+          - "{{ storpool_node }}"
+          - "{{ storpool_volume }}"
+
+    - name: Delete the StorPool Volume
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "volume", "delete", "{{ storpool_volume }}" ]
diff --git a/playbooks/setup-openstack-iscsi-multipath-storpool/pre.yaml b/playbooks/setup-openstack-iscsi-multipath-storpool/pre.yaml
new file mode 100644
index 0000000..8034c12
--- /dev/null
+++ b/playbooks/setup-openstack-iscsi-multipath-storpool/pre.yaml
@@ -0,0 +1,380 @@
+- hosts: undercloud-client
+  vars_files:
+    - vars.yaml
+  tasks:
+    - name: Create the StorPool Networks
+      ansible.builtin.command:
+        argv:
+          - "{{ os_venv }}/bin/openstack"
+          - --os-cloud
+          - openstack-testing
+          - network
+          - create
+          - "{{ item }}"
+      loop:
+        - "{{ network_sp_api }}"
+        - "{{ network_sp0 }}"
+        - "{{ network_sp1 }}"
+
+    - name: Create the StorPool Subnets
+      ansible.builtin.command:
+        argv:
+          - "{{ os_venv }}/bin/openstack"
+          - --os-cloud
+          - openstack-testing
+          - subnet
+          - create
+          - --subnet-range
+          - "{{ item.ip_range }}"
+          - --network
+          - "{{ item.network }}"
+          - "{{ item.subnet }}"
+      loop:
+        - { ip_range: "{{ network_ip_sp_api }}", network: "{{ network_sp_api }}", subnet: "{{ subnet_sp_api }}" }
+        - { ip_range: "{{ network_ip_sp0 }}", network: "{{ network_sp0 }}", subnet: "{{ subnet_sp0 }}" }
+        - { ip_range: "{{ network_ip_sp1 }}", network: "{{ network_sp1 }}", subnet: "{{ subnet_sp1 }}" }
+
+    - name: Create the StorPool Ports
+      ansible.builtin.command:
+        argv:
+          - "{{ os_venv }}/bin/openstack"
+          - --os-cloud
+          - openstack-testing
+          - port
+          - create
+          - --network
+          - "{{ item.network }}"
+          - --fixed-ip
+          - "subnet={{ item.subnet }},ip-address={{ item.ip }}"
+          - "{{ item.port }}"
+      loop:
+        - { network: "{{ network_sp_api }}", subnet: "{{ subnet_sp_api }}", ip: "{{ ip_sp_api }}", port: "{{ port_sp_api }}" }
+        - { network: "{{ network_sp0 }}", subnet: "{{ subnet_sp0 }}", ip: "{{ ip_sp0 }}", port: "{{ port_sp0 }}" }
+        - { network: "{{ network_sp1 }}", subnet: "{{ subnet_sp1 }}", ip: "{{ ip_sp1 }}", port: "{{ port_sp1 }}" }
+
+    - name: Attach the StorPool Ports to the StorPool Node
+      ansible.builtin.command:
+        argv:
+          - "{{ os_venv }}/bin/openstack"
+          - --os-cloud
+          - openstack-testing
+          - server
+          - add
+          - port
+          - "{{ storpool_node }}"
+          - "{{ item }}"
+      loop:
+        - "{{ port_sp_api }}"
+        - "{{ port_sp0 }}"
+        - "{{ port_sp1 }}"
+
+    - name: Get Information About port_sp_api
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "port", "show", "--format", "json", "{{ port_sp_api }}" ]
+      register: port_sp_api_info
+
+    - name: Get MAC Address of port_sp_api
+      ansible.builtin.set_fact:
+        port_sp_api_mac: "{{ (port_sp_api_info.stdout | from_json).mac_address }}"
+
+    - name: Get Information About port_sp0
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "port", "show", "--format", "json", "{{ port_sp0 }}" ]
+      register: port_sp0_info
+
+    - name: Get MAC Address of port_sp0
+      ansible.builtin.set_fact:
+        port_sp0_mac: "{{ (port_sp0_info.stdout | from_json).mac_address }}"
+
+    - name: Get Information About port_sp1
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "port", "show", "--format", "json", "{{ port_sp1 }}" ]
+      register: port_sp1_info
+
+    - name: Get MAC Address of port_sp1
+      ansible.builtin.set_fact:
+        port_sp1_mac: "{{ (port_sp1_info.stdout | from_json).mac_address }}"
+
+    - name: Create the StorPool Volume
+      ansible.builtin.command:
+        argv:
+          - "{{ os_venv }}/bin/openstack"
+          - --os-cloud
+          - openstack-testing
+          - volume
+          - create
+          - --size
+          - "230"
+          - "{{ storpool_volume }}"
+
+    - name: Attach the StorPool Volume to the StorPool Node
+      ansible.builtin.command:
+        argv:
+          - "{{ os_venv }}/bin/openstack"
+          - --os-cloud
+          - openstack-testing
+          - server
+          - add
+          - volume
+          - "{{ storpool_node }}"
+          - "{{ storpool_volume }}"
+
+- hosts: localhost
+  vars_files:
+    - vars.yaml
+  tasks:
+    - name: Create a Temporary Directory
+      ansible.builtin.tempfile:
+        state: directory
+        prefix: multipath-iscsi-storpool.
+      register: tmpdir
+
+    - set_fact:
+        tmpdir: "{{ tmpdir.path }}"
+
+- hosts: undercloud-client
+  vars_files:
+    - vars.yaml
+  tasks:
+    - name: Download Required Information from Previous Stage to Ansible Controller
+      ansible.builtin.fetch:
+        src: "~/ansivars.yaml"
+        dest: "{{ hostvars.localhost.tmpdir }}"
+
+    - name: Load Required Information from Previous Stage
+      ansible.builtin.include_vars:
+        file: "{{ hostvars.localhost.tmpdir }}/undercloud-client/home/ubuntu/ansivars.yaml"
+
+- hosts: lab-sp-a1
+  vars_files:
+    - vars.yaml
+  tasks:
+    - set_fact:
+        port_sp_api_mac: "{{ hostvars['undercloud-client']['port_sp_api_mac'] }}"
+        sp_sp0_mac: "{{ hostvars['undercloud-client']['port_sp0_mac'] }}"
+        sp_sp1_mac: "{{ hostvars['undercloud-client']['port_sp1_mac'] }}"
+        sp_iscsi0_mac: "{{ hostvars['undercloud-client']['port_node_1_iscsi0_mac'] }}"
+        sp_iscsi1_mac: "{{ hostvars['undercloud-client']['port_node_1_iscsi1_mac'] }}"
+        ip_node_1_iscsi0: "{{ hostvars['undercloud-client']['ip_node_1_iscsi0'] }}/24"
+        ip_node_1_iscsi1: "{{ hostvars['undercloud-client']['ip_node_1_iscsi1'] }}/24"
+        ip_sp_api: "{{ ip_sp_api }}/24"
+        ip_sp0: "{{ ip_sp0 }}/24"
+        ip_sp1: "{{ ip_sp1 }}/24"
+
+    - set_fact:
+        storpool_netplan: "{{ hostvars['localhost'].STORPOOL_NETPLAN.v }}"
+      no_log: true
+
+    - name: Provision the Netplan Template
+      no_log: true
+      become: true
+      ansible.builtin.copy:
+        content: "{{ storpool_netplan }}"
+        dest: /etc/netplan/60-storpool.yaml
+
+    - name: Generate Netplan
+      become: true
+      ansible.builtin.command:
+        argv: ["netplan", "generate"]
+
+    - name: Restart Neplan
+      become: true
+      ansible.builtin.command:
+        argv: ["netplan", "apply"]
+
+    - name: Create Service to Apply Interface Configuration on Boot
+      no_log: true
+      become: true
+      ansible.builtin.copy:
+        content: "{{ hostvars['localhost'].STORPOOL_NETPLAN_SERVICE.v }}"
+        dest: /etc/systemd/system/netplan-fix.service
+
+    - name: Enable Service to Apply Interface Configuration on Boot
+      no_log: true
+      become: true
+      ansible.builtin.command:
+        argv: [ "systemctl", "enable", "netplan-fix" ]
+
+    - name: Provision StorPool Deployment Configuration
+      no_log: true
+      ansible.builtin.lineinfile:
+        path: /home/ubuntu/.ssh/authorized_keys
+        line: "{{ STORPOOL_DEPLOY_KEY_PUB['v'] }}"
+        insertafter: EOF
+
+- hosts: undercloud-client
+  vars_files:
+    - vars.yaml
+  tasks:
+    - name: Ensure StorPool Deployment Configuration Directory Exists
+      ansible.builtin.file:
+        path: /home/ubuntu/.ssh
+        state: directory
+        mode: '0700'
+        recurse: true
+
+    - name: Provision StorPool Deployment Configuration 1
+      no_log: true
+      ansible.builtin.copy:
+        content: "{{ STORPOOL_DEPLOY_KEY['v'] }}"
+        dest: /home/ubuntu/.ssh/id_rsa
+        mode: '0400'
+
+    - name: Provision StorPool Deployment Configuration 2
+      no_log: true
+      ansible.builtin.copy:
+        content: "{{ STORPOOL_DEPLOY_KEY_PUB['v'] }}"
+        dest: /home/ubuntu/.ssh/id_rsa.pub
+
+    - name: Provision StorPool Deployment Configuration 3
+      no_log: true
+      ansible.builtin.lineinfile:
+        line: "{{ item }}"
+        path: /home/ubuntu/.ssh/config
+        insertafter: EOF
+        create: yes
+      loop:
+        - "Host {{ storpool_node_ip }}"
+        - "    StrictHostKeyChecking no"
+
+    - name: Install python3-pip
+      become: true
+      ansible.builtin.apt:
+        name: python3-pip
+        update_cache: yes
+
+    - name: Install ansible
+      become: true
+      ansible.builtin.pip:
+        name: ansible
+        executable: pip3
+
+    - name: Checkout StorPool Ansible
+      ansible.builtin.git:
+        repo: 'https://github.com/storpool/ansible'
+        dest: /home/ubuntu/ansible
+
+    - name: Download Required Ansible Roles
+      ansible.builtin.command:
+        argv: ["ansible-galaxy", "install", "-r", "roles/requirements.yml"]
+      args:
+        chdir: "/home/ubuntu/ansible"
+
+    - name: Provision StorPool Ansible Inventory
+      no_log: true
+      ansible.builtin.copy:
+        content: "{{ STORPOOL_INVENTORY['v'] }}"
+        dest: /home/ubuntu/ansible/inventory.yaml
+
+    - name: Configure the StorPool Ansible Inventory
+      ansible.builtin.replace:
+        path: /home/ubuntu/ansible/inventory.yaml
+        regexp: 'STORPOOL_NODE'
+        replace: "{{ storpool_node_ip }}"
+
+    - set_fact:
+        storpool_node_hostname: "{{ hostvars['lab-sp-a1']['ansible_hostname'] }}"
+
+    - set_fact:
+        storpool_conf: "{{ hostvars['localhost'].STORPOOL_CONF.v }}"
+      no_log: true
+
+    - name: Provision the StorPool Template
+      no_log: true
+      ansible.builtin.copy:
+        content: "{{ storpool_conf }}"
+        dest: /home/ubuntu/ansible/storpool.conf
+
+    - name: Deploy StorPool
+      ansible.builtin.command:
+        argv: ["ansible-playbook", "playbook.yml", "-i", "inventory.yaml", "--skip-tags", "perform-tests"]
+      args:
+        chdir: "/home/ubuntu/ansible"
+
+- hosts: lab-sp-a1
+  vars_files:
+    - vars.yaml
+  tasks:
+    - name: Wait for the StorPool Cluster to Come Up
+      become: true
+      ansible.builtin.command:
+        argv: ["storpool", "-j", "service", "list"]
+      register: r
+      until: r.rc == 0 and (r.stdout | from_json).data.clusterStatus == "running"
+      delay: 1
+      retries: 120
+
+    - name: Configure StorPool for Multipath iSCSI
+      become: true
+      ansible.builtin.command:
+        argv: "{{ item }}"
+      loop:
+        - [ "storpool", "iscsi", "config", "setBaseName", "iqn.2023-03.storpool.com" ]
+        - [ "storpool", "iscsi", "config", "portalGroup", "pg", "create" ]
+        - [ "storpool", "iscsi", "config", "portalGroup", "pg", "addNet", "192.168.40.100/24" ]
+        - [ "storpool", "iscsi", "config", "portalGroup", "pg", "addNet", "192.168.50.100/24" ]
+        - [ "storpool", "iscsi", "config", "portal", "create", "portalGroup", "pg", "controller", "1", "address", "192.168.40.101" ]
+        - [ "storpool", "iscsi", "config", "portal", "create", "portalGroup", "pg", "controller", "1", "address", "192.168.50.101" ]
+
+- hosts: controller
+  vars_files:
+    - vars.yaml
+  tasks:
+    - set_fact:
+        ip_sp_api: "{{ ip_sp_api }}/24"
+        mac_node_2_iscsi0: "{{ hostvars['undercloud-client']['port_node_2_iscsi0_mac'] }}"
+        mac_node_2_iscsi1: "{{ hostvars['undercloud-client']['port_node_2_iscsi1_mac'] }}"
+        ip_node_2_iscsi0: "{{ hostvars['undercloud-client']['ip_node_2_iscsi0'] }}/24"
+        ip_node_2_iscsi1: "{{ hostvars['undercloud-client']['ip_node_2_iscsi1'] }}/24"
+
+    - set_fact:
+        systemd_networkd_iscsi: "{{ hostvars['localhost'].STORPOOL_NETPLAN.v }}"
+      no_log: true
+
+    - set_fact:
+        iscsi_mac: mac_node_2_iscsi0
+        iscsi_ip: ip_node_2_iscsi0
+
+    - name: Provision the Netplan Template for iSCSI Network 1
+      no_log: true
+      become: true
+      ansible.builtin.copy:
+        content: "{{ systemd_networkd_iscsi }}"
+        dest: /etc/systemd/network/60-storpool-iscsi-1.network
+
+    - set_fact:
+        iscsi_ip: ip_node_2_iscsi1
+        iscsi_mac: mac_node_2_iscsi1
+
+    - name: Provision the Netplan Template for iSCSI Network 2
+      no_log: true
+      become: true
+      ansible.builtin.copy:
+        content: "{{ systemd_networkd_iscsi }}"
+        dest: /etc/systemd/network/61-storpool-iscsi-2.network
+
+    - set_fact:
+        storpool_conf: "{{ hostvars['localhost'].STORPOOL_CONF_ISCSI_NODE.v }}"
+      no_log: true
+
+    - name: Provision the StorPool Template
+      no_log: true
+      ansible.builtin.copy:
+        content: "{{ storpool_conf }}"
+        dest: /etc/storpool.conf
+
+    - name: Restart systemd-networkd to Apply the Network Configuration
+      ansible.builtin.command:
+        argv: [ "systemctl", "restart", "systemd-networkd" ]
+
+    - name: Wait for the Portal Group IP 1 to Become Available
+      ansible.builtin.command:
+        argv: [ "ping", "-c1", "192.168.40.101" ]
+      register: r
+      until: r.rc == 0
+
+    - name: Wait for the Portal Group IP 2 to Become Available
+      ansible.builtin.command:
+        argv: [ "ping", "-c1", "192.168.50.101" ]
+      register: r
+      until: r.rc == 0
diff --git a/playbooks/setup-openstack-iscsi-multipath-storpool/vars.yaml b/playbooks/setup-openstack-iscsi-multipath-storpool/vars.yaml
new file mode 100644
index 0000000..86c46c0
--- /dev/null
+++ b/playbooks/setup-openstack-iscsi-multipath-storpool/vars.yaml
@@ -0,0 +1,25 @@
+storpool_node: "{{ hostvars['lab-sp-a1']['nodepool']['external_id'] }}"
+storpool_node_ip: "{{ hostvars['lab-sp-a1']['nodepool']['interface_ip'] }}"
+undercloud_client_node: "{{ hostvars['undercloud-client']['nodepool']['external_id'] }}"
+
+storpool_volume: "{{ undercloud_client_node }}-storpool-volume"
+
+network_ip_sp_api: "192.168.10.0/24"
+network_ip_sp0: "192.168.20.0/24"
+network_ip_sp1: "192.168.30.0/24"
+
+ip_sp_api: "192.168.10.3"
+ip_sp0: "192.168.20.3"
+ip_sp1: "192.168.30.3"
+
+network_sp_api: "{{ undercloud_client_node }}-network-sp-api"
+network_sp0: "{{ undercloud_client_node }}-network-sp0"
+network_sp1: "{{ undercloud_client_node }}-network-sp1"
+
+subnet_sp_api: "{{ undercloud_client_node }}-subnet-sp-api"
+subnet_sp0: "{{ undercloud_client_node }}-subnet-sp0"
+subnet_sp1: "{{ undercloud_client_node }}-subnet-sp1"
+
+port_sp_api: "{{ undercloud_client_node }}-port-sp-api"
+port_sp0: "{{ undercloud_client_node }}-port-sp0"
+port_sp1: "{{ undercloud_client_node }}-port-sp1"
diff --git a/playbooks/setup-openstack-iscsi-multipath/post.yaml b/playbooks/setup-openstack-iscsi-multipath/post.yaml
new file mode 100644
index 0000000..e11f10d
--- /dev/null
+++ b/playbooks/setup-openstack-iscsi-multipath/post.yaml
@@ -0,0 +1,33 @@
+- hosts: undercloud-client
+  vars_files:
+    - vars.yaml
+  tasks:
+    - name: Remove the Multipath iSCSI Ports from the StorPool Node
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "server", "remove", "port", "{{ storpool_node }}", "{{ item }}" ]
+      loop:
+        - "{{ port_node_1_iscsi0 }}"
+        - "{{ port_node_1_iscsi1 }}"
+
+    - name: Remove the Multipath iSCSI Ports from the OpenStack Node
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "server", "remove", "port", "{{ openstack_node }}", "{{ item }}" ]
+      loop:
+        - "{{ port_node_2_iscsi0 }}"
+        - "{{ port_node_2_iscsi1 }}"
+
+    - name: Delete the Multipath iSCSI Ports
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "port", "delete", "{{ item }}" ]
+      loop :
+        - "{{ port_node_1_iscsi0 }}"
+        - "{{ port_node_1_iscsi1 }}"
+        - "{{ port_node_2_iscsi0 }}"
+        - "{{ port_node_2_iscsi1 }}"
+
+    - name: Delete Multipath iSCSI Networks
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "network", "delete", "{{ item }}" ]
+      loop:
+        - "{{ network_1 }}"
+        - "{{ network_2 }}"
diff --git a/playbooks/setup-openstack-iscsi-multipath/pre.yaml b/playbooks/setup-openstack-iscsi-multipath/pre.yaml
new file mode 100644
index 0000000..cb1c099
--- /dev/null
+++ b/playbooks/setup-openstack-iscsi-multipath/pre.yaml
@@ -0,0 +1,111 @@
+- hosts: undercloud-client
+  vars_files:
+    - vars.yaml
+  tasks:
+    - name: Create Multipath iSCSI Networks
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "network", "create", "{{ item }}" ]
+      loop:
+        - "{{ network_1 }}"
+        - "{{ network_2 }}"
+
+    - name: Create Multipath iSCSI Subnets
+      ansible.builtin.command:
+        argv:
+          - "{{ os_venv }}/bin/openstack"
+          - --os-cloud
+          - openstack-testing
+          - subnet
+          - create
+          - --subnet-range
+          - "{{ item.ip_range }}"
+          - --network
+          - "{{ item.network }}"
+          - "{{ item.subnet }}"
+      loop:
+        - { ip_range: "{{ network_ip_iscsi0 }}", network: "{{ network_1 }}", subnet: "{{ subnet_1 }}" }
+        - { ip_range: "{{ network_ip_iscsi1 }}", network: "{{ network_2 }}", subnet: "{{ subnet_2 }}" }
+
+    # TODO: Support port security
+    - name: Create the Multipath iSCSI Ports
+      ansible.builtin.command:
+        argv:
+          - "{{ os_venv }}/bin/openstack"
+          - --os-cloud
+          - openstack-testing
+          - port
+          - create
+          - --network
+          - "{{ item.network }}"
+          - --fixed-ip
+          - "subnet={{ item.subnet }},ip-address={{ item.ip }}"
+          - --disable-port-security
+          - "{{ item.port }}"
+      loop:
+        - { network: "{{ network_1 }}", subnet: "{{ subnet_1 }}", ip: "{{ ip_node_1_iscsi0 }}", port: "{{ port_node_1_iscsi0 }}" }
+        - { network: "{{ network_2 }}", subnet: "{{ subnet_2 }}", ip: "{{ ip_node_1_iscsi1 }}", port: "{{ port_node_1_iscsi1 }}" }
+        - { network: "{{ network_1 }}", subnet: "{{ subnet_1 }}", ip: "{{ ip_node_2_iscsi0 }}", port: "{{ port_node_2_iscsi0 }}" }
+        - { network: "{{ network_2 }}", subnet: "{{ subnet_2 }}", ip: "{{ ip_node_2_iscsi1 }}", port: "{{ port_node_2_iscsi1 }}" }
+
+    - name: Attach the Multipath iSCSI Ports to the StorPool Node
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "server", "add", "port", "{{ storpool_node }}", "{{ item }}" ]
+      loop:
+        - "{{ port_node_1_iscsi0 }}"
+        - "{{ port_node_1_iscsi1 }}"
+
+    - name: Attach the Multipath iSCSI Ports to the OpenStack Node
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "server", "add", "port", "{{ openstack_node }}", "{{ item }}" ]
+      loop:
+        - "{{ port_node_2_iscsi0 }}"
+        - "{{ port_node_2_iscsi1 }}"
+
+    - name: Get Information About port_node_1_iscsi0
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "port", "show", "--format", "json", "{{ port_node_1_iscsi0 }}" ]
+      register: port_node_1_iscsi0_info
+
+    - name: Get MAC Address of port_node_1_iscsi0
+      ansible.builtin.set_fact:
+        port_node_1_iscsi0_mac: "{{ (port_node_1_iscsi0_info.stdout | from_json).mac_address }}"
+
+    - name: Get Information About port_node_1_iscsi1
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "port", "show", "--format", "json", "{{ port_node_1_iscsi1 }}" ]
+      register: port_node_1_iscsi1_info
+
+    - name: Get MAC Address of port_node_1_iscsi1
+      ansible.builtin.set_fact:
+        port_node_1_iscsi1_mac: "{{ (port_node_1_iscsi1_info.stdout | from_json).mac_address }}"
+
+    - name: Get Information About port_node_2_iscsi0
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "port", "show", "--format", "json", "{{ port_node_2_iscsi0 }}" ]
+      register: port_node_2_iscsi0_info
+
+    - name: Get MAC Address of port_node_2_iscsi0
+      ansible.builtin.set_fact:
+        port_node_2_iscsi0_mac: "{{ (port_node_2_iscsi0_info.stdout | from_json).mac_address }}"
+
+    - name: Get Information About port_node_2_iscsi1
+      ansible.builtin.command:
+        argv: [ "{{ os_venv }}/bin/openstack", "--os-cloud", "openstack-testing", "port", "show", "--format", "json", "{{ port_node_2_iscsi1 }}" ]
+      register: port_node_2_iscsi1_info
+
+    - name: Get MAC Address of port_node_2_iscsi1
+      ansible.builtin.set_fact:
+        port_node_2_iscsi1_mac: "{{ (port_node_2_iscsi1_info.stdout | from_json).mac_address }}"
+
+    - name: Dump Required Information for Next Stage
+      ansible.builtin.copy:
+        content: |
+          port_node_1_iscsi0_mac: {{ port_node_1_iscsi0_mac }}
+          port_node_1_iscsi1_mac: {{ port_node_1_iscsi1_mac }}
+          ip_node_1_iscsi0: {{ ip_node_1_iscsi0 }}
+          ip_node_1_iscsi1: {{ ip_node_1_iscsi1 }}
+          port_node_2_iscsi0_mac: {{ port_node_2_iscsi0_mac }}
+          port_node_2_iscsi1_mac: {{ port_node_2_iscsi1_mac }}
+          ip_node_2_iscsi0: {{ ip_node_2_iscsi0 }}
+          ip_node_2_iscsi1: {{ ip_node_2_iscsi1 }}
+        dest: "~/ansivars.yaml"
diff --git a/playbooks/setup-openstack-iscsi-multipath/vars.yaml b/playbooks/setup-openstack-iscsi-multipath/vars.yaml
new file mode 100644
index 0000000..10cc748
--- /dev/null
+++ b/playbooks/setup-openstack-iscsi-multipath/vars.yaml
@@ -0,0 +1,24 @@
+undercloud_client_node: "{{ hostvars['undercloud-client']['nodepool']['external_id'] }}"
+storpool_node: "{{ hostvars['lab-sp-a1']['nodepool']['external_id'] }}"
+openstack_node: "{{ hostvars['controller']['nodepool']['external_id'] }}"
+
+network_ip_iscsi0: "192.168.40.0/24"
+network_ip_iscsi1: "192.168.50.0/24"
+
+ip_node_1_iscsi0: "192.168.40.3"
+ip_node_1_iscsi1: "192.168.50.3"
+
+ip_node_2_iscsi0: "192.168.40.4"
+ip_node_2_iscsi1: "192.168.50.4"
+
+network_1: "{{ undercloud_client_node }}-network-iscsi-1"
+network_2: "{{ undercloud_client_node }}-network-iscsi-2"
+
+subnet_1: "{{ undercloud_client_node }}-subnet-iscsi-1"
+subnet_2: "{{ undercloud_client_node }}-subnet-iscsi-2"
+
+port_node_1_iscsi0: "{{ undercloud_client_node }}-port-node-1-iscsi-0"
+port_node_1_iscsi1: "{{ undercloud_client_node }}-port-node-1-iscsi-1"
+
+port_node_2_iscsi0: "{{ undercloud_client_node }}-port-node-2-iscsi-0"
+port_node_2_iscsi1: "{{ undercloud_client_node }}-port-node-2-iscsi-1"
diff --git a/zuul.d/sp-cinder-secrets.yaml b/zuul.d/sp-cinder-secrets.yaml
index 44e1715..d75d19f 100644
--- a/zuul.d/sp-cinder-secrets.yaml
+++ b/zuul.d/sp-cinder-secrets.yaml
@@ -22,3 +22,228 @@
           LOi1bVZJ48cYBcwichjSHJY4WZ1rkNm0aGsaaUrcVLGHMc0OQo1FKTj1PokVz/1L74Ke6
           oueJp2xHCDdd4bDWd/V6DzFa3wVfXpybvXU0hdx7Jw1ZGUhNXWqc8Dz2JHhl1voAFWsVr
           iYUldIZaUHiYG9Tc6xcphBX5ip8oE+2HhqGz7//IxO+I1/Y/ZCVja3fnVmDo4Q=
+
+- secret:
+    name: RESOLVED_DATA
+    data:
+      v: !encrypted/pkcs1-oaep
+        - JCVXepNtax7fPk8FDWy5D2Ijp9STlYxFYCaYW3Dz0LRe8u5B8E1mLZn2qJNsJHAT4UXPg
+          7h2cKeaBruStcjcHuBb9OE7y49hRL4WKitrPFNnUrwj+yrL4KoUSwIbHajXCB8t7aCeMj
+          j9Z40fNARDjVYFwImvc7xx1AJWtWgiT/qoLunJ+XohTWqVwP3CiZ0dj3qkn2Al9l5hTaf
+          1ofDMavlQNuFuM1nmqBa32lRvZzUvHOLXWNCgLNXhrzkJO4V6m46pAvjVp+Pb4130XLKC
+          riKam/nKiJd40Tcsw1tn1cQeVTEDER0K7QYICfQkHUfZd0oN/JQR1XmkelpMtCkUvFWJi
+          LZh5FhjMb5b8xdCx8zoPxVibfG0mrcbuHbAI1YfXpXoCT2Vav9PnOMuNt131xsakmiOe+
+          QTHZvxsG6zBAIlE2BmclASKXCeGizCH/J5IDCaLLswQA8Vol7BDXysS0dUx6GeH0C+CVn
+          knC3YG47bIKNEFvAGbr/1jCNrVFPD0YUrspnbkJrNGOoBge+YXgTea2QYpL9CkE9f4579
+          YSsFG0QJc9YZQAU9Wg94/CYyc7pZm9i6Aqvy6HVUv/NgIWteueu4hp/o+5VVKs9LRFT1q
+          ToO/dcYpdtxXbYfRRt4ZVVGHuX7X3/r1zxovtButIQBGhl1kWBU5TQiyq9qjkU=
+
+- secret:
+    name: STORPOOL_CONF
+    data:
+      v: !encrypted/pkcs1-oaep
+        - rUqq+9SIp5DuR4UmNd2VbbnOivZLMO/BBB7GyTc5f5kjB/1u8I9SH96sdHfGDmgHljCjo
+          WVB99bws7O4v7dM1JUssBQFEIgcXN0Zr9mv6YT5fXf9V9230I7p5XoHjJZMq6gmD1Jja3
+          qNIUUU9kvHXxbUkbMDQZFHyNzQa5e1LecxA3BX0X1EDCqbFI9WAbGAGafe3puDed/Mq7d
+          lkXnDQc8xSJQpRrF2MoGaA8o3Pv85HUiolQIYO0uuTvxaZ032Q1jZrO9vDLACIVmAf+ux
+          asTRifzW6skw+pDqGqQ2CVXy4JJUbPNk+9c9ANOKUxRQhdA6uxgPJyPp5B+8PKmZj6K23
+          9lIChDdlXVu93gb5O9DM2pnFZAW0R4TbdcqfzxgT7HtkHCXlcvaKjCiprP/nMGIEUo8LT
+          cFJlCSFSyUEgYv6Gfxr+EQIClT/K88TRKd2uXdLYC1PM5sbmVkzQrCHlxCw9kuQ7o/L/7
+          IirxBueAfByOWVRu/h8JBQ/C328AGqIJ2Issu7fjVORU4CbOBHLTdtlRQ9ugO+6dtUBWp
+          VhtuRaTfl6DjZB9W83pmfFZUQvEgndIatFoHAvkJWPopMzGykmFY6Mt0+H6gEiCORWJsD
+          wYfv4AQkaJ7oSJyI3ufUG1/GhwsXr20I72DhGCFv71zmU50qfSxxjGT51zqDmY=
+
+- secret:
+    name: STORPOOL_CONF_ISCSI_NODE
+    data:
+      v: !encrypted/pkcs1-oaep
+        - hANtHFqL+R8V/Ma6iA3+bzdErU3GJsh2LsUVGnU5j14GidJC38gaks4ydcjZBArBDnVZL
+          DLRLsfhNoQvCNCIibmMXooU8uRhPVI/T9hcy5IUtEIj+PkskrSNWakJ3U/qMxO6YGBgCb
+          P/BmB5oQx4yCTy0jnzsBXEQM5+TD6Xo/Y1U6p0WZ08QJeyMM6cdGbJcKttZDUHWlXuOCx
+          1hxWCvoJ3AsP7z3wnXVkKHA/ADzrDyzzEClPxJ3QD5o2ZN/pgaw+0WNZrNWuwQ4EBSxsz
+          +ftQ4qO7brec5UTpJ2Jhufb51jq3JPXlgCcSYAZ9k0qGhcH1te6uYiFQoUWyC5+enYyRl
+          omFL80mScGRR2oZuXVjCk5MX7XrNIxtPGEi/7zTK8icQV7FsPoG7Qo7XTfOftACZcScWM
+          tjsAFMovgVnyS/wy9nV5QW4uN2dFs7UE+C5EToi8nqWyNR7F0BCnsybg6RaMdQSKngXRc
+          gzhZKSeLm7AwlEayI63JAkpmDOImxFBalPD7KPVRv3QrfqiLG1Er62DbO3YyFwrkO6FNX
+          FfOnVJQQR2zt9sc/p8+WYDieiUJFSPl+/SQ2KI77mEu+zw/VB0CMMziLZkU9k+LEk3Npn
+          mcLQOYOlqqgVV3ZZbc8ZWWEPzIP8agQ5WzsxcFt+XIhSFF3zxt8d/brNEiSORQ=
+
+- secret:
+    name: STORPOOL_DEPLOY_KEY
+    data:
+      v: !encrypted/pkcs1-oaep
+        - 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
+
+- secret:
+    name: STORPOOL_DEPLOY_KEY_PUB
+    data:
+      v: !encrypted/pkcs1-oaep
+        - J3xaxqOpUh1ILZvkb61vMUzUhj/3YUFOlpOLpqq9OsN8s6dQ3AJo/PGmzGENWmseeeZqA
+          slhjFEsv7k7FWBbYHdlTPGABu/lew3z9N6CeLPudH9cbHERasrxx+jmUwsxIzsFJ8DoE1
+          6JQkDTwSGDCg1CKAbmsRc57jSp6xPCWFjoTD8V8RHs6tsS9bGEY01Bgg9wkRl9csa6lNE
+          f9K+tTPCKXJ1/2AHQFAmDrbJVDQYG8Aqm4DUgCWSeemdSqngnZ5DnehAanVHXjC75w1tm
+          xiogmODpmdgcFDC+vYEqQ6bWEp+8QKX9TWjvRvSSjWXr+CaXEvNUzFUt0WVsdw82/+VVW
+          9IwZI7tuc4+tNs21IyLtlmyj0qoABXQXg2V5BKsnry4QEWl0nuI6+Y0jbhnLZuzYft9XC
+          R/DktwDb7VYd11TfKr1D85wSXUqopyDrmYQqRjXEUdC7rd6nycihkxRq81cl0z7o5qc2v
+          gwi8ZGIoNaUQRy60zQf/SrVKnilq1urw9EswLyY1386hTrnmsXFlB+8SkUAek4zzbSC1T
+          s7zv1rjsI1Kt1qQESzC11/pkOujHnsCvlhHs0WyFKNdaR22v4UPOsq+AIT0Ft8qtA1J9C
+          ViQq49nEezln6RKjHbYE/Ph+aeaWShhvhUzKa1Tpl7pOHJs4v/qkk8P48zGkVc=
+        - q3T2frlJNVD5WLjsiCY7+H6mVRdrttndwyF1kXcfRel97SGmoBFGYf7hO+iimEOMmfbLZ
+          5vKwmBupXovojwFR6/SLUc5ScefQndi3qz/2kTHkc2V6zp3NXsjp7eOFetz6d+hrxKEbZ
+          kZ0acewXN8tDujJTJQ3owgCCqw+rxYVJ8du73re20Dss/AK3OlKoa6laOhLkMckssBWFt
+          WVPuDycgqA8OW5SotmiYFZVAxZ8sX9aXoOSedPomXRS4+A4w4YLLSAUFY+Wxu/2S5ZOQ1
+          n+ieLW0tWU0v2tkqXenV71BuhcEdj/SDluLwz3XhUgkDEYDuWpjCL5jGSH6Evz3g9qIjR
+          CT+B35eR8M4Wqf0XZ2HtyzKwYekuFzZvfhKgywhp38RN7PFNyk6/DqYYvmHiXi3aAfs7p
+          vJzbfc0RZdiAKq83YMIg4tV6ckcvKo2NQSkAnU1AoMsveCQ6wuKfLvnqZc/WJYYGDU9bk
+          8Zp51AEjwnslk6Uka+3VKsq8FBpa7DdX6qHiGLJovmkxrbEduB1d/Lu4RvPRsurA6B+qD
+          O0QcHvtPioPKryhJBlyIPiJI6Mheu4+Ckc3si4brjydjQQCTJXNwkzallw1gMw71N2GIs
+          sUAv95LJu1UwePqTN4Ozcha6PWLgHi96tLZOj1tO5q/71+vOI6N+CYpUrRdhmE=
+
+- secret:
+    name: STORPOOL_INVENTORY
+    data:
+      v: !encrypted/pkcs1-oaep
+        - MvZCbKq7QLRjOdeUt/j6MeQruprWFHDUhS2m8oFFnbpX8SkFoYeIFfbo3pGjUXnN+9L2m
+          uharjy85+xuacdkMLzWhT4hUsBdhIr++cN+2K96J05VBUV4GBHMtqFxZyOblh4m1eD6Nu
+          lXzudw8WdM8yO6iItUzFOBruApWcaM1aK3gjs6W9K5++2kRVhYoK8pmTrCsM9JOCGldMs
+          gceu6HRMT3GZNWYlHNQPnv4G5BAHRLn49LTKvEHLPGwI5bHwsb8CYcISa64gpMnmmI43b
+          XqScxCczrCx1tRJHa/X2ndu9wIaoiodnzqLcXfptJSjSQ5ql3NfoJiYEmQQ7NxRNtD38a
+          8PWYSmbQPEOfgvXRbODz1P75FxCXu8iGp4dpkzrGFLTMHVgvCB7NIArkadcDFThEuJ37k
+          nYeVCEz7IHFOaUfMF6Yi2ui/3ViRb7613zy321+8TdeHyOZFXy2AXDZNIXV+yftV24HV0
+          zg4ooq/e2dDl6c3kVFc7eyixPi7ZN5De4NS2r2l2qiXdQFGCkXW3U2l2KVokXyon7rnAx
+          JR2xiW2OVL8ChRiSQyaZPVPdMrerEAqN2XPeTaThu8v96LmIidRxp5/JjJApBMokeA7OF
+          DBI1dl/xzhRH8BhFprtb5EHBtFMFhNAbsDWEJH3YpQtYJSXmBRU0cNQH+0T+Vo=
+        - B4yhL0iiWTmvZ/WV90vnJanFFbpZyi8YdTXp/n8NevU1n8GZ8KYYaSJDH0iOZewN8HXZC
+          6wD0slgh0IH+ZEMXnyzPXInQHWAWZtEtsB25iApdM9bnNE6pQm6kScQr2jM3aTE8NLdgp
+          HKEgXSaa2CWi7J6/kp5ZLa72iRBVLpANCwk6bTknlRi4qpSMZ6efu1OG1XLE0iPa4tdYS
+          CXoyAhQnaF5iC7HgoDjiBEnVh9bhQOKRJT1nsChxEeFLxzm5RLvONfrD4kzpdEXQn4qxm
+          MSMtYKOSzJntUCZgnq2mp1HKYTgD5ovK6gWJHJC4j1p3xzN0B0Ik04FUO0qg0zVf+R0Qu
+          cMHXny1vLylr5xXP/G+RJrEh6eWxWrIAfNfQunqRVHWXZWW15sSjiDPKCrSJWydV5ZBB+
+          vDa8Yj7ed1HWD8DzhgNveKNDE2v36L8NVGb5vTN0RYze+4wsxnRCd8Uwipbok8Gs4A/lo
+          WBFzcmc8ZpU9P9zbX8M4P1WdZ/4N55M3N6gThCnRjm+OyeCqRxA3zxNQUmcm/BVuEufFR
+          gC2IfK9dtyjmZsGRProVS6tv8/rhCRR4QG8IqYpa7s+qK+03gz73na7OCqp7Wb4J+HVS4
+          ezh4Vg80J6HD+RItyqiD4vRGNlfnJER+r1KES2zKWjqc8L5BPycquABBjuQC8Y=
+
+- secret:
+    name: STORPOOL_NETPLAN
+    data:
+      v: !encrypted/pkcs1-oaep
+        - Oyunw2y8rJWqUuF0no2I62N/Pn6nRaRqbcGff+3spHZlOQL4yrw5J2OmJEc7Hq4SQZlS3
+          J1Njkp5nzlUn4T15MZKVH9SulEQusSWQ22pb9961sj/eQSPHgXMx9WJMQhpgR5vRGCNzA
+          Tv+qqQOOdrlnaPPm7nVO131w0X19MjQIyB5qMQjxqlQdaHAXOerKPN+OTFD43zOIzIPAO
+          yd70zFNwVZFj5LVY7J4af8R27+tQogs4E9KTQ9kPSq48GQooTtR1WUdbMo4SxijtvZlaN
+          HsKZ0V28PE75JYcUvgC4oxgQexK9uZk1+i/IQrTH8Wmb+so8mLVtMdfdJwtBIcHhdRm5y
+          sxr9cge1ioUQJ59FFwx0aatCv7TaXZiRjXUtLHTwjsBT8LM/r8YklsJFQCI126+qWslA0
+          RoATqQltYKOIri1X99cYFFfhpa58xdwcCSCfO186kas/72NuBCcpF/DYgW08pKP/6NkpT
+          3X6XqcQAae7OUnIWsMcf8p0mqHlwbgHy9CRv2qTtZsZyv2pwAPxY/lrbGm/SELkuDejD5
+          xZAG0/71L1PpaRhjUEGgPrsbKTyscFteqnBvKAR7J+IPLR91/y3EAhl2Rq5UygyPfA2MG
+          mUiq7zTuh9M8N8vPv+0v/zuphr89thvf5GHS4eP9J/xFOELLlgDf2FxRI+zLYI=
+        - rJTVvk90nPcZtd4TO8983L3S4hlcoAqBGih9gAzfXLYODNehblS6GV+5Zl53oXSJarkib
+          CglVDA7MIGHkdY4Iaa5Tn+07A3v3yCQTOWP9wxnuJ+26NXJdYkvbjKptFUZgHJoTzgL5N
+          HZ6GBZxEvLNTQ861FTNN5PEbdNmKuJJdgDuVFZNSJcAsmhopwhHd3z8fV2Ug9EmgVoWKq
+          N1KxvfpN7NrpvDiRtY/B1oZKbIMlogs7BQbO+0pu/NDSpoUmgup+8T9qOIJddvMfD1oi9
+          Vej3E3Ispu2XdRZNmhBF+qJrZc4kUnrTgxVxcmn+Bep6VA6sfRIOS+vvx/z51pN3+v9+W
+          tnBVjHbT3OxNBLDYvifJx48Q05ciOFP1ZD9mgOQwy7m0d6ndcbl6RmZBlwDKTUIG/J3OW
+          5LeMKY8lz7+v45VWw1ZCsuvCNq19qlmM5ZrT9lsOD6O/0BEJqa8CX//FEfASRbeBMbggg
+          7RDEn1I3CxjbMCgvCqSQjAOHEKN7Mm0nV+kbAG7JLozUW1c3KKmEGScAZxY5Xu6FqX5TN
+          Vy3b2utprj/Ne6/sUCIaIPO5r6XPm4xTg2mpxf2NKE3BubTAbQHlOJ967o1LAU/RSxK6i
+          9WfbPQaqPS50PiWBvSE/Bmjpb+iRY7hFHvdcR7GLWstBOYQWm6ePcR0nta2P1c=
+
+- secret:
+    name: STORPOOL_NETPLAN_SERVICE
+    data:
+      v: !encrypted/pkcs1-oaep
+        - VF77z9ga05C91MOk+OHHvEkwF9t1j5O6h5GXg2e/mFosbBIliTpUQ9W7RBTOAWXAWN+2A
+          X7YiXoCHvZK2FYoyxxF11LxbhR7b6SuTCx72q/jP7Y/7RzUs4NqWAYchJn+li1Sx412Uw
+          P5ULp5WAHj5e0FOVyy1ggl0qtNpbIUhYXL2p+Mg0AceZanMUQvAmGxe67eha/UNvWOSM/
+          ec28kzN6kOG9nFGpIDRc3wKxLrw/Te97PCKTf+cKBeY2Lbla6rnEnSJhtmCZvOUbgZrHD
+          LBN9FbvUdrQ8QttZbxiyZI5c481Z6jMnIPX6z74xZzhKoP4hPoF2ucA+4hsteLn2HcM64
+          QIIZkQk+n8qd7Y0CzqkMDsb4E44Hp8EgFWUtgJ9l0yp9IoV+RV6kmf6YKBcVwLtwt5fky
+          q4fEBFEhzCp5Hig0KVy9h6l7PTB6IjQr6ipIbTWVxnRtGfDIHKQXwP8lbbcCPmuPL4JfB
+          RQhcxbbXy2d2XsfCsYUiGMAnb5D5kxbI4tLEZ0iwkDi0lm4CyscBicvEyQ/mFh64kPjUm
+          ue6m/s571JRfkbaf2pyvRrhzbyD5UGXq3qEt3B6ibRtVbaIKqcw3ozGWSZ+FIAQk27/Av
+          e05TfDrdOR3q6C1/udp1Gv9N8+YV9tzF5KrhumFXgUKY1nBl0ZKz2E0SIsuhHQ=
+
+- secret:
+    name: ISCSI_NODE_SYSTEMD_NETWORKD
+    data:
+      v: !encrypted/pkcs1-oaep
+        - mLRqjw420ROofe0xS1+05M4idMHDpxz6cJK340LI7DQ5pPtVPXjAMrT/pTMrWP6uTT6Pb
+          lOFUzARSKGtN/UCESLnuH7pm7EDFaOf7sfTLypRxKo79VOV2zF0ibIoIJPZhvVBPqiZeE
+          7v64s96O7Rp2xbcTJDebcdkZQia0MQJuXtgD157Q41bWczZPbAFeFpKdoyGw8OYAQG0xt
+          9OM5GpFCeIQUg423JIPQ47psdmDYkwwk1uaj/e9C1Y/QygSk13Jd6/tHyrSzNOD5yQF6V
+          CMLfBIcjJOwSZ4tiBeuI0SmiC1Ye+jrM12/MB0m0XmzkObuJ+9hCXdAOXKwCfcR/dQsbQ
+          3ntA7ssTbLCsadx8GfInb4C6RbW8CMo3Tqf6kPC+16IMj/FQ473KmlsMtp0oR/DschfmN
+          siboG1O965M6Z9waJmcC88MUhfIyanuoFXCPwDXYDGxIk8e6gnQVWBPco7BaoYCYnronj
+          zTa+Zy3lZJiIR7PX50BALvP/YsqXUtUc2lYEOZuSHZmHGzb9SmqScsolND9s5mYaP7CSE
+          BL4fw8jWXxQkT0bIZqzo4/oB6zEiHiKVu3Uxr9Hn2mIL6n5qoVFpebc22Y3Nj3O6fLHlF
+          jucASdPp0UQjjmRGmp+4r0kiIc3EfcO7iz4kIe4gg3SIkk6sHwDhFSUeSFncBU=
diff --git a/zuul.d/sp-cinder.yaml b/zuul.d/sp-cinder.yaml
index bfffadc..53f47e8 100644
--- a/zuul.d/sp-cinder.yaml
+++ b/zuul.d/sp-cinder.yaml
@@ -77,6 +77,124 @@
 #        ^cinder_tempest_plugin
 #        (^(tempest\.((api\..*volume)|scenario\.test_encrypted_cinder_volumes|scenario\.test_volume|scenario\.test_shelve_instance))|(cinder_tempest_plugin))
 
+- nodeset:
+    name: openstack-multi-node-mixed
+    nodes:
+      - name: controller
+        label: os-ubuntu2004-large
+      - name: undercloud-client
+        label: os-ubuntu2004-small
+      - name: lab-sp-a1
+        label: os-ubuntu2004-large
+    groups:
+      - name: tempest
+        nodes:
+          - controller
+
+- job:
+    name: cinder-storpool-tempest-iscsi-multipath-parent
+    parent: tempest-full
+    pre-run:
+      - playbooks/sp-init.yaml
+      - playbooks/setup-openstack-client/pre.yaml
+      - playbooks/setup-openstack-iscsi-multipath/pre.yaml
+      - playbooks/setup-openstack-iscsi-multipath-storpool/pre.yaml
+    run: playbooks/tempest-and-cinderlib-run.yaml
+    post-run:
+      - playbooks/setup-openstack-iscsi-multipath-storpool/post.yaml
+      - playbooks/setup-openstack-iscsi-multipath/post.yaml
+      - playbooks/setup-openstack-client/post.yaml
+      - playbooks/sp-cleanup.yaml
+    required-projects:
+      - opendev.org/openstack/cinderlib
+      - opendev.org/openstack/os-brick
+      - opendev.org/openstack/tempest
+      - opendev.org/openstack/cinder-tempest-plugin
+      # - config
+      - sp-osci
+    timeout: 10800
+    nodeset: openstack-multi-node-mixed
+    attempts: 1
+    secrets:
+      # openstack-client
+      - name: OPENSTACK_DATA
+        secret: OPENSTACK_DATA
+      - name: RESOLVED_DATA
+        secret: RESOLVED_DATA
+
+      # openstack-iscsi-multipath-storpool
+      - name: STORPOOL_DEPLOY_KEY
+        secret: STORPOOL_DEPLOY_KEY
+      - name: STORPOOL_DEPLOY_KEY_PUB
+        secret: STORPOOL_DEPLOY_KEY_PUB
+      - name: STORPOOL_INVENTORY
+        secret: STORPOOL_INVENTORY
+      - name: STORPOOL_NETPLAN
+        secret: STORPOOL_NETPLAN
+      - name: STORPOOL_NETPLAN_SERVICE
+        secret: STORPOOL_NETPLAN_SERVICE
+      - name: STORPOOL_CONF
+        secret: STORPOOL_CONF
+      - name: STORPOOL_CONF_ISCSI_NODE
+        secret: STORPOOL_CONF_ISCSI_NODE
+      - name: ISCSI_NODE_SYSTEMD_NETWORKD
+        secret: ISCSI_NODE_SYSTEMD_NETWORKD
+
+    vars:
+      os_venv: "~/sp-venv-openstack"
+      devstack_localrc:
+        CINDER_ENABLED_BACKENDS: storpool:storpool
+        # CINDER_ISCSI_HELPER: tgtadm
+        ENABLE_FILE_INJECTION: false
+        TEMPEST_CONCURRENCY: 2
+        TEMPEST_EXTEND_ATTACHED_VOLUME: true
+        TEMPEST_PLUGINS: cinder-tempest-plugin
+        TEMPEST_RUN_VALIDATION: false
+        TEMPEST_VOLUME_REVERT_TO_SNAPSHOT: true
+        TEMPEST_VOLUME_VENDOR: StorPool
+        USE_PYTHON3: True
+        # cfg: block
+        TEMPEST_STORAGE_PROTOCOL: storpool
+        ENABLE_VOLUME_MULTIATTACH: true
+      devstack_local_conf:
+        post-config:
+          $CINDER_CONF:
+            storpool:
+              volume_backend_name: storpool
+              volume_driver: cinder.volume.drivers.storpool.StorPoolDriver
+              storpool_template: virtual
+              # enable_unsupported_driver: true
+      tox_envlist: all
+      tempest_test_regex: |
+        volume
+#        (^(tempest\.((api\..*volume)|scenario\.test_encrypted_cinder_volumes|scenario\.test_volume|scenario\.test_shelve_instance))|(cinder_tempest_plugin))
+
+- job:
+    name: cinder-storpool-tempest-iscsi-multipath
+    parent: cinder-storpool-tempest-iscsi-multipath-parent
+    vars:
+      sp_experimental: true
+      devstack_localrc:
+        # cfg: iscsi
+        TEMPEST_STORAGE_PROTOCOL: iscsi
+        ENABLE_VOLUME_MULTIATTACH: false
+      devstack_local_conf:
+        post-config:
+          $CINDER_CONF:
+            storpool:
+              # cfg: iscsi
+              iscsi_export_to: "\\\\*"
+              iscsi_portal_group: pg
+              use_multipath_for_image_xfer: True
+          $NOVA_CONF:
+            libvirt:
+              # cfg: iscsi
+              volume_use_multipath: True
+      tempest_test_regex: |
+        volume|^cinder_tempest_plugin
+#        ^cinder_tempest_plugin
+#        (^(tempest\.((api\..*volume)|scenario\.test_encrypted_cinder_volumes|scenario\.test_volume|scenario\.test_shelve_instance))|(cinder_tempest_plugin))
+
 - project:
     name: openstack/cinder
     check:
@@ -89,3 +207,5 @@
             branches: master
         - cinder-storpool-tempest-iscsi:
             branches: master
+        - cinder-storpool-tempest-iscsi-multipath:
+            branches: master