diff --git a/tests/init-swift.sh b/tests/init-swift.sh
new file mode 100755
index 0000000000..f0e8bcd4d6
--- /dev/null
+++ b/tests/init-swift.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+set -o xtrace
+set -o errexit
+
+
+function init_swift_logged {
+    next_port=6000
+
+    # the order is important due to port incrementation
+    for ring in object account container; do
+        # create the *.builder files
+        sudo docker run \
+            --rm \
+            -v /etc/kolla/config/swift/:/etc/kolla/config/swift/ \
+            $KOLLA_SWIFT_BASE_IMAGE \
+            swift-ring-builder \
+            /etc/kolla/config/swift/$ring.builder create 10 3 1
+
+        # add nodes to them
+        for node in ${STORAGE_NODES[@]}; do
+            sudo docker run \
+                --rm \
+                -v /etc/kolla/config/swift/:/etc/kolla/config/swift/ \
+                $KOLLA_SWIFT_BASE_IMAGE \
+                swift-ring-builder \
+                /etc/kolla/config/swift/$ring.builder add r1z1-${node}:$next_port/d0 1
+        done
+
+        # create the *.ring.gz files
+        sudo docker run \
+            --rm \
+            -v /etc/kolla/config/swift/:/etc/kolla/config/swift/ \
+            $KOLLA_SWIFT_BASE_IMAGE \
+            swift-ring-builder \
+            /etc/kolla/config/swift/$ring.builder rebalance
+
+        # display contents for debugging
+        sudo docker run \
+            --rm \
+            -v /etc/kolla/config/swift/:/etc/kolla/config/swift/ \
+            $KOLLA_SWIFT_BASE_IMAGE \
+            swift-ring-builder \
+            /etc/kolla/config/swift/$ring.builder
+
+        # next ring = next port
+        (( next_port++ ))
+    done
+}
+
+function init_swift {
+    echo "Initialising Swift"
+    init_swift_logged &> /tmp/logs/ansible/init-swift
+}
+
+
+init_swift
diff --git a/tests/run.yml b/tests/run.yml
index c1f5a13e6d..71d0508adb 100644
--- a/tests/run.yml
+++ b/tests/run.yml
@@ -20,17 +20,23 @@
         need_build_image: false
         build_image_tag: "change_{{ zuul.change | default('none') }}"
         openstack_core_enabled: "{{ openstack_core_enabled }}"
-        openstack_core_tested: "{{ scenario in ['core', 'ceph', 'cinder-lvm', 'cells'] }}"
+        openstack_core_tested: "{{ scenario in ['core', 'ceph', 'cinder-lvm', 'cells', 'swift'] }}"
         dashboard_enabled: "{{ openstack_core_enabled }}"
         # TODO(mgoddard): Remove when previous_release is ussuri.
         playbook_python_version: "{{ '2' if is_upgrade and previous_release == 'train' else '3' }}"
 
-    - name: Prepare disks for Ceph or LVM
+    - name: Install xfsprogs package for Swift filesystems
+      become: true
+      package:
+        name: xfsprogs
+      when: scenario == 'swift'
+
+    - name: Prepare disks for a storage service
       script: "setup_disks.sh {{ disk_type }}"
-      when: scenario in ['cinder-lvm', 'ceph', 'zun']
+      when: scenario in ['cinder-lvm', 'ceph', 'zun', 'swift']
       become: true
       vars:
-        disk_type: "{{ ceph_storetype if scenario == 'ceph' else 'cinder-lvm' }}"
+        disk_type: "{{ ceph_storetype if scenario == 'ceph' else scenario }}"
         ceph_storetype: "{{ hostvars[inventory_hostname].get('ceph_osd_storetype') }}"
 
 - hosts: primary
@@ -88,6 +94,7 @@
       loop:
         - nova
         - bifrost
+        - swift
 
     - name: generate configuration files
       template:
@@ -204,6 +211,12 @@
       shell:
         cmd: ansible all -i {{ kolla_inventory_path }} -e ansible_user={{ ansible_user }} -m setup > /tmp/logs/ansible/initial-setup
 
+    - name: Set facts for actions
+      set_fact:
+        # NOTE(yoctozepto): no support for upgrades for now
+        docker_image_tag: "{{ build_image_tag if need_build_image else zuul.branch | basename }}"
+        docker_image_prefix: "{{ 'primary:4000/lokolla/' if need_build_image else 'kolla/' }}"
+
     # NOTE(mgoddard): We are using the script module here and later to ensure
     # we use the local copy of these scripts, rather than the one on the remote
     # host, which could be checked out to a previous release (in an upgrade
@@ -221,6 +234,19 @@
         KOLLA_SRC_DIR: "{{ ansible_env.HOME }}/src/opendev.org/openstack/kolla"
         SCENARIO: "{{ scenario }}"
 
+    - name: Run init-swift.sh script
+      script:
+        cmd: init-swift.sh
+        executable: /bin/bash
+        chdir: "{{ kolla_ansible_src_dir }}"
+      environment:
+        KOLLA_SWIFT_BASE_IMAGE: "{{ docker_image_prefix }}{{ base_distro }}-{{ install_type }}-swift-base:{{ docker_image_tag }}"
+        # NOTE(yoctozepto): no IPv6 for now
+        STORAGE_NODES: "{{ groups['all'] | map('extract', hostvars,
+                           ['ansible_'+api_interface_name, 'ipv4', 'address'])
+                           | join(' ') }}"
+      when: scenario == 'swift'
+
     # At this point we have generated all necessary configuration, and are
     # ready to deploy the control plane services. Control flow now depends on
     # the scenario being exercised.
@@ -266,6 +292,13 @@
             chdir: "{{ kolla_ansible_src_dir }}"
           when: scenario == 'zun'
 
+        - name: Run test-swift.sh script
+          script:
+            cmd: test-swift.sh
+            executable: /bin/bash
+            chdir: "{{ kolla_ansible_src_dir }}"
+          when: scenario == 'swift'
+
         - name: Run test-scenario-nfv.sh script
           script:
             cmd: test-scenario-nfv.sh
diff --git a/tests/setup_disks.sh b/tests/setup_disks.sh
index fbac5f25b9..4f3fd9168d 100644
--- a/tests/setup_disks.sh
+++ b/tests/setup_disks.sh
@@ -1,13 +1,27 @@
+#!/bin/bash
+
+# $1: scenario / ceph store type
+
+set -o xtrace
+set -o errexit
+
 mkdir -p /opt/data/kolla
 
-if [ $1 = 'cinder-lvm' ]; then
+if [ $1 = 'cinder-lvm' ] || [ $1 = 'zun' ]; then
     # cinder-volumes volume group
     free_device=$(losetup -f)
     fallocate -l 5G /var/lib/cinder_data.img
     losetup $free_device /var/lib/cinder_data.img
     pvcreate $free_device
     vgcreate cinder-volumes $free_device
-
+elif [ $1 = 'swift' ]; then
+    # swift partition
+    free_device=$(losetup -f)
+    fallocate -l 5G /var/lib/swift_data.img
+    losetup $free_device /var/lib/swift_data.img
+    parted $free_device -s -- mklabel gpt mkpart KOLLA_SWIFT_DATA 1 -1
+    free_partition=${free_device}p1
+    mkfs.xfs -L d0 $free_partition
 elif [ $1 = 'filestore' ]; then
     #setup devices for Kolla Ceph filestore OSD
     dd if=/dev/zero of=/opt/data/kolla/ceph-osd1.img bs=5M count=1000
@@ -19,7 +33,7 @@ elif [ $1 = 'filestore' ]; then
     LOOP=$(losetup -f)
     losetup $LOOP /opt/data/kolla/ceph-journal1.img
     parted $LOOP -s -- mklabel gpt mkpart KOLLA_CEPH_OSD_BOOTSTRAP_OSD1_J 1 -1
-else
+elif [ $1 = 'bluestore' ]; then
     # Setup devices for Kolla Ceph bluestore OSD
     dd if=/dev/zero of=/opt/data/kolla/ceph-osd0.img bs=5M count=100
     LOOP=$(losetup -f)
@@ -40,7 +54,9 @@ else
     LOOP=$(losetup -f)
     losetup $LOOP /opt/data/kolla/ceph-osd0-d.img
     parted $LOOP -s -- mklabel gpt mkpart KOLLA_CEPH_OSD_BOOTSTRAP_BS_OSD0_D 1 -1
+else
+    echo "Unknown type" >&2
+    exit 1
 fi
 
 partprobe
-
diff --git a/tests/templates/globals-default.j2 b/tests/templates/globals-default.j2
index f1358d32ed..2966bf4e52 100644
--- a/tests/templates/globals-default.j2
+++ b/tests/templates/globals-default.j2
@@ -94,6 +94,10 @@ enable_cinder_backup: "no"
 enable_cinder_backend_lvm: "yes"
 {% endif %}
 
+{% if scenario == "swift" %}
+enable_swift: "yes"
+{% endif %}
+
 {% if scenario == "scenario_nfv" %}
 enable_tacker: "yes"
 enable_neutron_sfc: "yes"
diff --git a/tests/test-swift.sh b/tests/test-swift.sh
new file mode 100755
index 0000000000..0436dcb3b6
--- /dev/null
+++ b/tests/test-swift.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+set -o xtrace
+set -o errexit
+
+export PYTHONUNBUFFERED=1
+
+
+function test_swift_logged {
+    . /etc/kolla/admin-openrc.sh
+    . ~/openstackclient-venv/bin/activate
+
+    echo "TESTING: Swift"
+
+    CONTAINER_NAME=test_container
+
+    openstack --debug container create $CONTAINER_NAME
+
+    CONTENT='Hello, Swift!'
+    FILE_PATH=/tmp/swift_test_object
+
+    echo "$CONTENT" > $FILE_PATH
+
+    openstack --debug object create $CONTAINER_NAME $FILE_PATH
+
+    rm -f $FILE_PATH
+
+    openstack --debug object save $CONTAINER_NAME $FILE_PATH
+
+    SAVED_CONTENT=`cat $FILE_PATH`
+
+    rm -f $FILE_PATH
+
+    if [ "$SAVED_CONTENT" != "$CONTENT" ]; then
+        echo 'Content mismatch' >&2
+        return 1
+    fi
+
+    openstack --debug container show $CONTAINER_NAME
+
+    openstack --debug object store account show
+
+    echo "SUCCESS: Swift"
+}
+
+function test_swift {
+    echo "Testing Swift"
+    log_file=/tmp/logs/ansible/test-swift
+    if [[ -f $log_file ]]; then
+        log_file=${log_file}-upgrade
+    fi
+    test_swift_logged > $log_file 2>&1
+    result=$?
+    if [[ $result != 0 ]]; then
+        echo "Testing Swift failed. See ansible/test-swift for details"
+    else
+        echo "Successfully tested Swift. See ansible/test-swift for details"
+    fi
+    return $result
+}
+
+
+test_swift
diff --git a/zuul.d/base.yaml b/zuul.d/base.yaml
index a0ac01fa6a..d1f338cb09 100644
--- a/zuul.d/base.yaml
+++ b/zuul.d/base.yaml
@@ -74,7 +74,22 @@
     voting: false
     files:
       - ^ansible/roles/(zun|kuryr|etcd)/
+      - ^tests/setup_disks.sh
       - ^tests/test-zun.sh
       - ^tests/test-dashboard.sh
     vars:
       scenario: zun
+
+- job:
+    name: kolla-ansible-swift-base
+    parent: kolla-ansible-base
+    voting: false
+    files:
+      - ^ansible/roles/(glance|swift)/
+      - ^tests/setup_disks.sh
+      - ^tests/init-swift.sh
+      - ^tests/test-core-openstack.sh
+      - ^tests/test-dashboard.sh
+      - ^tests/test-swift.sh
+    vars:
+      scenario: swift
diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml
index 6c2d9f4d19..658b8cfec1 100644
--- a/zuul.d/jobs.yaml
+++ b/zuul.d/jobs.yaml
@@ -204,6 +204,22 @@
       base_distro: ubuntu
       install_type: source
 
+- job:
+    name: kolla-ansible-centos-source-swift
+    parent: kolla-ansible-swift-base
+    nodeset: kolla-ansible-centos-multi
+    vars:
+      base_distro: centos
+      install_type: source
+
+- job:
+    name: kolla-ansible-ubuntu-source-swift
+    parent: kolla-ansible-swift-base
+    nodeset: kolla-ansible-bionic-multi
+    vars:
+      base_distro: ubuntu
+      install_type: source
+
 - job:
     name: kolla-ansible-centos-source-scenario-nfv
     parent: kolla-ansible-base
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index ce746d7488..e4acbcdfb7 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -20,11 +20,13 @@
         - kolla-ansible-ubuntu-source-cinder-lvm:
             files:
               - ^ansible/roles/(cinder|iscsi)/
+              - ^tests/setup_disks.sh
               - ^tests/test-core-openstack.sh
               - ^tests/test-dashboard.sh
         - kolla-ansible-centos-source-cinder-lvm:
             files:
               - ^ansible/roles/(cinder|iscsi)/
+              - ^tests/setup_disks.sh
               - ^tests/test-core-openstack.sh
               - ^tests/test-dashboard.sh
         - kolla-ansible-bifrost-centos-source:
@@ -33,6 +35,8 @@
               - ^tests/test-bifrost.sh
         - kolla-ansible-centos-source-zun
         - kolla-ansible-ubuntu-source-zun
+        - kolla-ansible-centos-source-swift
+        - kolla-ansible-ubuntu-source-swift
         - kolla-ansible-centos-source-scenario-nfv:
             files:
               - ^ansible/roles/(barbican|heat|mistral|redis|tacker)/