diff --git a/.zuul.yaml b/.zuul.yaml
index 7a85266eaa..3945faf82e 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -66,6 +66,16 @@
         nodes:
           - controller
 
+- nodeset:
+    name: devstack-single-node-centos-9-stream
+    nodes:
+      - name: controller
+        label: centos-9-stream
+    groups:
+      - name: tempest
+        nodes:
+          - controller
+
 - nodeset:
     name: devstack-single-node-opensuse-15
     nodes:
@@ -622,6 +632,16 @@
     vars:
       configure_swap_size: 4096
 
+- job:
+    name: devstack-platform-centos-9-stream
+    parent: tempest-full-py3
+    description: CentOS 9 Stream platform test
+    nodeset: devstack-single-node-centos-9-stream
+    voting: false
+    timeout: 9000
+    vars:
+      configure_swap_size: 4096
+
 - job:
     name: devstack-platform-debian-bullseye
     parent: tempest-full-py3
@@ -766,6 +786,7 @@
         - devstack-enforce-scope
         - devstack-platform-fedora-latest
         - devstack-platform-centos-8-stream
+        - devstack-platform-centos-9-stream
         - devstack-platform-debian-bullseye
         - devstack-multinode
         - devstack-unit-tests
diff --git a/files/rpms/ceph b/files/rpms/ceph
index 64befc5f00..33a55f80ea 100644
--- a/files/rpms/ceph
+++ b/files/rpms/ceph
@@ -1,3 +1,3 @@
 ceph    # NOPRIME
-redhat-lsb-core
+redhat-lsb-core # not:rhel9
 xfsprogs
diff --git a/files/rpms/n-cpu b/files/rpms/n-cpu
index 68e5472685..7ce5a72d6b 100644
--- a/files/rpms/n-cpu
+++ b/files/rpms/n-cpu
@@ -1,9 +1,10 @@
 cryptsetup
 dosfstools
-genisoimage
+genisoimage # not:rhel9
 iscsi-initiator-utils
 libosinfo
 lvm2
 sg3_utils
 # Stuff for diablo volumes
 sysfsutils
+xorriso # not:rhel8
diff --git a/files/rpms/nova b/files/rpms/nova
index 8ea8ccc5ca..9522e5729d 100644
--- a/files/rpms/nova
+++ b/files/rpms/nova
@@ -3,7 +3,7 @@ curl
 dnsmasq # for q-dhcp
 dnsmasq-utils # for dhcp_release
 ebtables
-genisoimage # required for config_drive
+genisoimage # not:rhel9 required for config_drive
 iptables
 iputils
 kernel-modules
@@ -13,3 +13,4 @@ polkit
 rabbitmq-server # NOPRIME
 sqlite
 sudo
+xorriso # not:rhel8
diff --git a/files/rpms/swift b/files/rpms/swift
index 18c957c08a..b6009a321e 100644
--- a/files/rpms/swift
+++ b/files/rpms/swift
@@ -4,4 +4,4 @@ memcached
 rsync-daemon
 sqlite
 xfsprogs
-xinetd # not:f34
+xinetd # not:f34,rhel9
diff --git a/functions-common b/functions-common
index b1ca6ad3c0..bd029dd700 100644
--- a/functions-common
+++ b/functions-common
@@ -368,12 +368,19 @@ function _ensure_lsb_release {
 #  - os_VENDOR
 #  - os_PACKAGE
 function GetOSVersion {
-    # We only support distros that provide a sane lsb_release
-    _ensure_lsb_release
+    # CentOS Stream 9 does not provide lsb_release
+    source /etc/os-release
+    if [[ "${ID}${VERSION}" == "centos9" ]]; then
+        os_RELEASE=${VERSION_ID}
+        os_CODENAME="n/a"
+        os_VENDOR=$(echo $NAME | tr -d '[:space:]')
+    else
+        _ensure_lsb_release
 
-    os_RELEASE=$(lsb_release -r -s)
-    os_CODENAME=$(lsb_release -c -s)
-    os_VENDOR=$(lsb_release -i -s)
+        os_RELEASE=$(lsb_release -r -s)
+        os_CODENAME=$(lsb_release -c -s)
+        os_VENDOR=$(lsb_release -i -s)
+    fi
 
     if [[ $os_VENDOR =~ (Debian|Ubuntu|LinuxMint) ]]; then
         os_PACKAGE="deb"
diff --git a/lib/nova b/lib/nova
index 9aae2c4a9c..31b7642efc 100644
--- a/lib/nova
+++ b/lib/nova
@@ -479,7 +479,8 @@ function create_nova_conf {
     fi
 
     # nova defaults to genisoimage but only mkisofs is available for 15.0+
-    if is_suse; then
+    # rhel provides mkisofs symlink to genisoimage or xorriso appropiately
+    if is_suse || is_fedora; then
         iniset $NOVA_CONF DEFAULT mkisofs_cmd /usr/bin/mkisofs
     fi
 
diff --git a/stack.sh b/stack.sh
index b5ad81b081..fa4e7e9006 100755
--- a/stack.sh
+++ b/stack.sh
@@ -227,7 +227,7 @@ write_devstack_version
 
 # Warn users who aren't on an explicitly supported distro, but allow them to
 # override check and attempt installation with ``FORCE=yes ./stack``
-SUPPORTED_DISTROS="bullseye|focal|f34|opensuse-15.2|opensuse-tumbleweed|rhel8"
+SUPPORTED_DISTROS="bullseye|focal|f34|opensuse-15.2|opensuse-tumbleweed|rhel8|rhel9"
 
 if [[ ! ${DISTRO} =~ $SUPPORTED_DISTROS ]]; then
     echo "WARNING: this script has not been tested on $DISTRO"
@@ -300,13 +300,17 @@ function _install_epel {
 }
 
 function _install_rdo {
-    if [[ "$TARGET_BRANCH" == "master" ]]; then
-        # rdo-release.el8.rpm points to latest RDO release, use that for master
-        sudo dnf -y install https://rdoproject.org/repos/rdo-release.el8.rpm
-    else
-        # For stable branches use corresponding release rpm
-        rdo_release=$(echo $TARGET_BRANCH | sed "s|stable/||g")
-        sudo dnf -y install https://rdoproject.org/repos/openstack-${rdo_release}/rdo-release-${rdo_release}.el8.rpm
+    if [[ $DISTRO == "rhel8" ]]; then
+        if [[ "$TARGET_BRANCH" == "master" ]]; then
+            # rdo-release.el8.rpm points to latest RDO release, use that for master
+            sudo dnf -y install https://rdoproject.org/repos/rdo-release.el8.rpm
+        else
+            # For stable branches use corresponding release rpm
+            rdo_release=$(echo $TARGET_BRANCH | sed "s|stable/||g")
+            sudo dnf -y install https://rdoproject.org/repos/openstack-${rdo_release}/rdo-release-${rdo_release}.el8.rpm
+        fi
+    elif [[ $DISTRO == "rhel9" ]]; then
+        sudo curl -L -o /etc/yum.repos.d/delorean-deps.repo http://trunk.rdoproject.org/centos9-master/delorean-deps.repo
     fi
     sudo dnf -y update
 }
@@ -385,6 +389,10 @@ if [[ $DISTRO == "rhel8" ]]; then
     # RHBZ: https://bugzilla.redhat.com/show_bug.cgi?id=1154272
     # Patch: https://github.com/rpm-software-management/dnf/pull/1448
     echo "[]" | sudo tee /var/cache/dnf/expired_repos.json
+elif [[ $DISTRO == "rhel9" ]]; then
+    sudo dnf config-manager --set-enabled crb
+    # rabbitmq and other packages are provided by RDO repositories.
+    _install_rdo
 fi
 
 # Ensure python is installed
diff --git a/tools/install_pip.sh b/tools/install_pip.sh
index c72dc89a55..259375a150 100755
--- a/tools/install_pip.sh
+++ b/tools/install_pip.sh
@@ -118,7 +118,7 @@ if [[ -n $PYPI_ALTERNATIVE_URL ]]; then
     configure_pypi_alternative_url
 fi
 
-if is_fedora && [[ ${DISTRO} == f* ]]; then
+if is_fedora && [[ ${DISTRO} == f* || ${DISTRO} == rhel9 ]]; then
     # get-pip.py will not install over the python3-pip package in
     # Fedora 34 any more.
     #  https://bugzilla.redhat.com/show_bug.cgi?id=1988935