Repository: StackStorm/st2 (language: python)

Issue:
[WIP] Interoperability updates for py3.8, py3.9 and py3.10
This branch started as an exploration for upgrading mongoengine/pymongo but has evolved into aligning module dependencies and fixing code related to updating module versions to support py3.8, py3.9 and py3.10.


Discussion / hints:
Currently attempting to get tests passing with pymongo 3.13 to display deprecation warns for python3.8 to python3.10.
`nose` is blocking python3.10 (Will attempt to run tests with pytest)
`oslo.config` is blocking python3.10 (Upgraded to latest, but behavioural changes break string output.  Further investigation required.)

Fix patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index fd3b485148..d85c934e6a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -14,6 +14,7 @@ Changed
 ~~~~~~~
 * Bumped `jsonschema` 2.6.0 -> 3.2.0 now that python3.6 is not supported. #6118
 * Bumped many deps based on the lockfile generated by pants+pex. #6181 (by @cognifloyd and @nzlosh)
+* Switch to python3's standard lib unittest from unittest2, a backport of python3 unittest features for python2. #6187 (by @nzlosh)
 
 Added
 ~~~~~
diff --git a/Makefile b/Makefile
index b6933a4441..bcdf1fbd6f 100644
--- a/Makefile
+++ b/Makefile
@@ -56,7 +56,7 @@ REQUIREMENTS := test-requirements.txt requirements.txt
 # Pin common pip version here across all the targets
 # Note! Periodic maintenance pip upgrades are required to be up-to-date with the latest pip security fixes and updates
 PIP_VERSION ?= 24.0
-SETUPTOOLS_VERSION ?= 69.2.0
+SETUPTOOLS_VERSION ?= 69.5.1
 PIP_OPTIONS := $(ST2_PIP_OPTIONS)
 
 ifndef PYLINT_CONCURRENCY
diff --git a/fixed-requirements.txt b/fixed-requirements.txt
index c9d7269748..0cb610d3ac 100644
--- a/fixed-requirements.txt
+++ b/fixed-requirements.txt
@@ -16,10 +16,10 @@ gitpython==3.1.43
 gitdb==4.0.11
 # Note: greenlet is used by eventlet
 greenlet==3.0.3
-gunicorn==21.2.0
+gunicorn==22.0.0
 jsonpath-rw==1.4.0
 jsonschema==3.2.0
-kombu==5.3.6
+kombu==5.3.7
 lockfile==0.12.2
 # Fix MarkupSafe to < 2.1.0 as 2.1.0 removes soft_unicode
 # >=0.23 was from jinja2
@@ -57,7 +57,7 @@ routes==2.5.1
 semver==3.0.2
 six==1.16.0
 argparse==1.4.0
-argcomplete==3.2.3
+argcomplete==3.3.0
 prettytable==3.10.0
 importlib-metadata==7.1.0
 typing-extensions==4.11.0
@@ -69,7 +69,7 @@ tooz==6.1.0
 # Note: virtualenv embeds wheels for pip, wheel, and setuptools. So pinning virtualenv pins those as well.
 # virtualenv==20.25.1 (<21) has pip==24.0 wheel==0.42.0 setuptools==68.0.0 and 69.1.0
 # lockfiles/st2.lock has pip==24.0 wheel==0.43.0 setuptools==69.2.0
-virtualenv==20.25.1
+virtualenv==20.25.3
 webob==1.8.7
 zake==0.2.2
 # test requirements below
@@ -81,5 +81,5 @@ nose-parallel==0.4.0
 psutil==5.9.8
 python-dateutil==2.9.0
 python-statsd==2.1.0
-orjson==3.10.0
+orjson==3.10.1
 zipp==3.18.1
diff --git a/lockfiles/st2.lock b/lockfiles/st2.lock
index 5f1c48ea8e..4a2b67c6f9 100644
--- a/lockfiles/st2.lock
+++ b/lockfiles/st2.lock
@@ -76,7 +76,6 @@
 //     "tooz",
 //     "udatetime",
 //     "ujson",
-//     "unittest2",
 //     "virtualenv",
 //     "webob",
 //     "webtest",
@@ -167,13 +166,13 @@
           "artifacts": [
             {
               "algorithm": "sha256",
-              "hash": "c12355e0494c76a2a7b73e3a59b09024ca0ba1e279fb9ed6c1b82d5b74b6a70c",
-              "url": "https://files.pythonhosted.org/packages/88/8c/61021c45428ad2ef6131c6068d14f7f0968767e972e427cd87bd25c9ea7b/argcomplete-3.2.3-py3-none-any.whl"
+              "hash": "c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54",
+              "url": "https://files.pythonhosted.org/packages/db/fb/feb8456bef211621ed410909df4a3ab66d688be821dfcb1080956158d0cb/argcomplete-3.3.0-py3-none-any.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "bf7900329262e481be5a15f56f19736b376df6f82ed27576fa893652c5de6c23",
-              "url": "https://files.pythonhosted.org/packages/3c/c0/031c507227ce3b715274c1cd1f3f9baf7a0f7cec075e22c7c8b5d4e468a9/argcomplete-3.2.3.tar.gz"
+              "hash": "fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62",
+              "url": "https://files.pythonhosted.org/packages/79/51/fd6e293a64ab6f8ce1243cf3273ded7c51cbc33ef552dce3582b6a15d587/argcomplete-3.3.0.tar.gz"
             }
           ],
           "project_name": "argcomplete",
@@ -185,7 +184,7 @@
             "wheel; extra == \"test\""
           ],
           "requires_python": ">=3.8",
-          "version": "3.2.3"
+          "version": "3.3.0"
         },
         {
           "artifacts": [
@@ -1107,13 +1106,13 @@
           "artifacts": [
             {
               "algorithm": "sha256",
-              "hash": "4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14",
-              "url": "https://files.pythonhosted.org/packages/b8/9a/5028fd52db10e600f1c4674441b968cf2ea4959085bfb5b99fb1250e5f68/exceptiongroup-1.2.0-py3-none-any.whl"
+              "hash": "5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad",
+              "url": "https://files.pythonhosted.org/packages/01/90/79fe92dd413a9cab314ef5c591b5aa9b9ba787ae4cadab75055b0ae00b33/exceptiongroup-1.2.1-py3-none-any.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68",
-              "url": "https://files.pythonhosted.org/packages/8e/1c/beef724eaf5b01bb44b6338c8c3494eff7cab376fab4904cfbbc3585dc79/exceptiongroup-1.2.0.tar.gz"
+              "hash": "a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16",
+              "url": "https://files.pythonhosted.org/packages/a0/65/d66b7fbaef021b3c954b3bbb196d21d8a4b97918ea524f82cfae474215af/exceptiongroup-1.2.1.tar.gz"
             }
           ],
           "project_name": "exceptiongroup",
@@ -1121,7 +1120,7 @@
             "pytest>=6; extra == \"test\""
           ],
           "requires_python": ">=3.7",
-          "version": "1.2.0"
+          "version": "1.2.1"
         },
         {
           "artifacts": [
@@ -1430,26 +1429,31 @@
           "artifacts": [
             {
               "algorithm": "sha256",
-              "hash": "3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0",
-              "url": "https://files.pythonhosted.org/packages/0e/2a/c3a878eccb100ccddf45c50b6b8db8cf3301a6adede6e31d48e8531cab13/gunicorn-21.2.0-py3-none-any.whl"
+              "hash": "350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9",
+              "url": "https://files.pythonhosted.org/packages/29/97/6d610ae77b5633d24b69c2ff1ac3044e0e565ecbd1ec188f02c45073054c/gunicorn-22.0.0-py3-none-any.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033",
-              "url": "https://files.pythonhosted.org/packages/06/89/acd9879fa6a5309b4bf16a5a8855f1e58f26d38e0c18ede9b3a70996b021/gunicorn-21.2.0.tar.gz"
+              "hash": "4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63",
+              "url": "https://files.pythonhosted.org/packages/1e/88/e2f93c5738a4c1f56a458fc7a5b1676fc31dcdbb182bef6b40a141c17d66/gunicorn-22.0.0.tar.gz"
             }
           ],
           "project_name": "gunicorn",
           "requires_dists": [
-            "eventlet>=0.24.1; extra == \"eventlet\"",
+            "coverage; extra == \"testing\"",
+            "eventlet!=0.36.0,>=0.24.1; extra == \"eventlet\"",
+            "eventlet; extra == \"testing\"",
+            "gevent; extra == \"testing\"",
             "gevent>=1.4.0; extra == \"gevent\"",
             "importlib-metadata; python_version < \"3.8\"",
             "packaging",
+            "pytest-cov; extra == \"testing\"",
+            "pytest; extra == \"testing\"",
             "setproctitle; extra == \"setproctitle\"",
             "tornado>=0.2; extra == \"tornado\""
           ],
-          "requires_python": ">=3.5",
-          "version": "21.2.0"
+          "requires_python": ">=3.7",
+          "version": "22.0.0"
         },
         {
           "artifacts": [
@@ -1476,19 +1480,19 @@
           "artifacts": [
             {
               "algorithm": "sha256",
-              "hash": "c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f",
-              "url": "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl"
+              "hash": "82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0",
+              "url": "https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca",
-              "url": "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz"
+              "hash": "028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc",
+              "url": "https://files.pythonhosted.org/packages/21/ed/f86a79a07470cb07819390452f178b3bef1d375f2ec021ecfc709fc7cf07/idna-3.7.tar.gz"
             }
           ],
           "project_name": "idna",
           "requires_dists": [],
           "requires_python": ">=3.5",
-          "version": "3.6"
+          "version": "3.7"
         },
         {
           "artifacts": [
@@ -1570,19 +1574,19 @@
           "artifacts": [
             {
               "algorithm": "sha256",
-              "hash": "2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
-              "url": "https://files.pythonhosted.org/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl"
+              "hash": "c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef",
+              "url": "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a",
-              "url": "https://files.pythonhosted.org/packages/7f/a1/d3fb83e7a61fa0c0d3d08ad0a94ddbeff3731c05212617dff3a94e097f08/itsdangerous-2.1.2.tar.gz"
+              "hash": "e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173",
+              "url": "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz"
             }
           ],
           "project_name": "itsdangerous",
           "requires_dists": [],
-          "requires_python": ">=3.7",
-          "version": "2.1.2"
+          "requires_python": ">=3.8",
+          "version": "2.2.0"
         },
         {
           "artifacts": [
@@ -1719,13 +1723,13 @@
           "artifacts": [
             {
               "algorithm": "sha256",
-              "hash": "49f1e62b12369045de2662f62cc584e7df83481a513db83b01f87b5b9785e378",
-              "url": "https://files.pythonhosted.org/packages/78/e8/bade4ea794047c94bb267c08aad9d5270c803c18296e876b5d97bad28daf/kombu-5.3.6-py3-none-any.whl"
+              "hash": "5634c511926309c7f9789f1433e9ed402616b56836ef9878f01bd59267b4c7a9",
+              "url": "https://files.pythonhosted.org/packages/b4/9a/1951f2261271d6994f0df5a55b3e9cdad42ed1fc3020a0dc7f6de80a4566/kombu-5.3.7-py3-none-any.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "f3da5b570a147a5da8280180aa80b03807283d63ea5081fcdb510d18242431d9",
-              "url": "https://files.pythonhosted.org/packages/3d/d9/86edccdc0f6868936deddf5892d6687481dcf047727f73214e61d31b2515/kombu-5.3.6.tar.gz"
+              "hash": "011c4cd9a355c14a1de8d35d257314a1d2456d52b7140388561acac3cf1a97bf",
+              "url": "https://files.pythonhosted.org/packages/99/3a/2fb09708fef243e35c286414f2bf78543dc311ae7d3de5d343bd8437e38d/kombu-5.3.7.tar.gz"
             }
           ],
           "project_name": "kombu",
@@ -1755,25 +1759,7 @@
             "vine"
           ],
           "requires_python": ">=3.8",
-          "version": "5.3.6"
-        },
-        {
-          "artifacts": [
-            {
-              "algorithm": "sha256",
-              "hash": "e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef",
-              "url": "https://files.pythonhosted.org/packages/c7/a3/c5da2a44c85bfbb6eebcfc1dde24933f8704441b98fdde6528f4831757a6/linecache2-1.0.0-py2.py3-none-any.whl"
-            },
-            {
-              "algorithm": "sha256",
-              "hash": "4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c",
-              "url": "https://files.pythonhosted.org/packages/44/b0/963c352372c242f9e40db02bbc6a39ae51bde15dddee8523fe4aca94a97e/linecache2-1.0.0.tar.gz"
-            }
-          ],
-          "project_name": "linecache2",
-          "requires_dists": [],
-          "requires_python": null,
-          "version": "1.0.0"
+          "version": "5.3.7"
         },
         {
           "artifacts": [
@@ -2291,94 +2277,94 @@
           "artifacts": [
             {
               "algorithm": "sha256",
-              "hash": "e62ba42bfe64c60c1bc84799944f80704e996592c6b9e14789c8e2a303279912",
-              "url": "https://files.pythonhosted.org/packages/8d/ac/02a335f7f26320bb721a1e99b345539a251a64f5fc0e000f47c9e7e8836c/orjson-3.10.0-cp39-cp39-musllinux_1_2_x86_64.whl"
+              "hash": "5be608c3972ed902e0143a5b8776d81ac1059436915d42defe5c6ae97b3137a4",
+              "url": "https://files.pythonhosted.org/packages/0d/93/f5af5d0b5fcc7a1e1f11d5154e3593e845feaab1bac1a04cebd2c64eb02b/orjson-3.10.1-cp39-cp39-musllinux_1_2_x86_64.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "cd583341218826f48bd7c6ebf3310b4126216920853cbc471e8dbeaf07b0b80e",
-              "url": "https://files.pythonhosted.org/packages/19/50/5134d52d683d6f944961ab0431d902ac01bd0806d9ee7d373f25c3de6cd1/orjson-3.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
+              "hash": "5252146b3172d75c8a6d27ebca59c9ee066ffc5a277050ccec24821e68742fdf",
+              "url": "https://files.pythonhosted.org/packages/13/75/a06d2aa52a0aab2932ac71e4a509951b0698b4ca51f750ec641f0bb4c30a/orjson-3.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "22c2f7e377ac757bd3476ecb7480c8ed79d98ef89648f0176deb1da5cd014eb7",
-              "url": "https://files.pythonhosted.org/packages/1d/ff/8e023d0e275b7d63b2f1894f72109643a81064963084556850456b3810b8/orjson-3.10.0-cp39-cp39-musllinux_1_2_aarch64.whl"
+              "hash": "53521542a6db1411b3bfa1b24ddce18605a3abdc95a28a67b33f9145f26aa8f2",
+              "url": "https://files.pythonhosted.org/packages/1c/f9/bea73fb2573537ab9c29e264f544bd6bd7765c8f2152f5c5c75722c531b9/orjson-3.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "9bf565a69e0082ea348c5657401acec3cbbb31564d89afebaee884614fba36b4",
-              "url": "https://files.pythonhosted.org/packages/21/0b/55f4e60853182a4fb341cb76c7b31e0a5a82d15fc30febd42ddca33401b5/orjson-3.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl"
+              "hash": "01234249ba19c6ab1eb0b8be89f13ea21218b2d72d496ef085cfd37e1bae9dd8",
+              "url": "https://files.pythonhosted.org/packages/3f/81/c907281043ac1847adfe711de2a03f3aa466ecc5014d8a5b760281ef0198/orjson-3.10.1-cp38-cp38-musllinux_1_2_x86_64.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "ba4d8cac5f2e2cff36bea6b6481cdb92b38c202bcec603d6f5ff91960595a1ed",
-              "url": "https://files.pythonhosted.org/packages/2c/b1/10d5314003aeac7cb27824f502eedcf4f2705efc1b38f70db247e9ff99b5/orjson-3.10.0.tar.gz"
+              "hash": "7dfed3c3e9b9199fb9c3355b9c7e4649b65f639e50ddf50efdf86b45c6de04b5",
+              "url": "https://files.pythonhosted.org/packages/3f/f7/54cb27fa43ef7940172b5d2a72cfaa14acdfaa8ec97d9fdda09fb6f5fb81/orjson-3.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "9a667769a96a72ca67237224a36faf57db0c82ab07d09c3aafc6f956196cfa1b",
-              "url": "https://files.pythonhosted.org/packages/38/76/572014c4db0cfb8f04239adfed2be6d1f54df9bb6c418214080c4871e35f/orjson-3.10.0-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl"
+              "hash": "27ff69c620a4fff33267df70cfd21e0097c2a14216e72943bd5414943e376d77",
+              "url": "https://files.pythonhosted.org/packages/58/6a/676601cff14d5a8d8cbe7396927f743c5be53d0803f7c0693a9a59d9dcee/orjson-3.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "57d017863ec8aa4589be30a328dacd13c2dc49de1c170bc8d8c8a98ece0f2925",
-              "url": "https://files.pythonhosted.org/packages/4a/af/a13d4f3224966df1c8893497fffad12968b4a56db643555c363a8f06756b/orjson-3.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
+              "hash": "2b230ec35f188f003f5b543644ae486b2998f6afa74ee3a98fc8ed2e45960afc",
+              "url": "https://files.pythonhosted.org/packages/6e/33/13899b1cc2c87a1a3c39b7945cf5b11669314f66b57b2046cd5fe3936a95/orjson-3.10.1-cp38-cp38-musllinux_1_2_aarch64.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "4050920e831a49d8782a1720d3ca2f1c49b150953667eed6e5d63a62e80f46a2",
-              "url": "https://files.pythonhosted.org/packages/61/99/1ad1c2ca9be4f4e4c6d7deb64b55e4bfab82e4e0ecd405c8e3b6b576c058/orjson-3.10.0-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl"
+              "hash": "a2c6a85c92d0e494c1ae117befc93cf8e7bca2075f7fe52e32698da650b2c6d1",
+              "url": "https://files.pythonhosted.org/packages/7c/55/54660562f8191def9d98935553c20f35f5d2e8f7ec431d1887a00b6dcf56/orjson-3.10.1-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "d2817877d0b69f78f146ab305c5975d0618df41acf8811249ee64231f5953fee",
-              "url": "https://files.pythonhosted.org/packages/66/a3/43624c1807fc7dd6552981cbb2c8a7524984170f6834d503d580dab3aa36/orjson-3.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl"
+              "hash": "79244b1456e5846d44e9846534bd9e3206712936d026ea8e6a55a7374d2c0694",
+              "url": "https://files.pythonhosted.org/packages/7d/a4/ff12594858f747b782ef0921eed97e578ec7abc9cd9ebb036543419c9ac8/orjson-3.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "b2d014cf8d4dc9f03fc9f870de191a49a03b1bcda51f2a957943fb9fafe55aac",
-              "url": "https://files.pythonhosted.org/packages/6d/0b/911b2bca5323ea0f5807b7af11e0446ec6e21d926ed8fbdf30c7e3299cec/orjson-3.10.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl"
+              "hash": "27d610df96ac18ace4931411d489637d20ab3b8f63562b0531bba16011998db0",
+              "url": "https://files.pythonhosted.org/packages/b0/9d/5920c8e2d52708729f0f9a2b1877d5097f61a5957c039c1c446a49a3a5c0/orjson-3.10.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "ade1e21dfde1d37feee8cf6464c20a2f41fa46c8bcd5251e761903e46102dc6b",
-              "url": "https://files.pythonhosted.org/packages/71/92/c66f835fd78b4e0f9d3fc6f14ebc32ac39a049d8548dc44f431132f83e45/orjson-3.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"
+              "hash": "a51fd55d4486bc5293b7a400f9acd55a2dc3b5fc8420d5ffe9b1d6bb1a056a5e",
+              "url": "https://files.pythonhosted.org/packages/b9/43/bd83ff49df7c7d29012bf8af76c0d563e8848a3382a9413dfbedbefad132/orjson-3.10.1-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "90bfc137c75c31d32308fd61951d424424426ddc39a40e367704661a9ee97095",
-              "url": "https://files.pythonhosted.org/packages/8b/c3/8b0091160fd7d764fb32633971c3198b1487ee8374e0bd3d29e39ccc76c5/orjson-3.10.0-cp38-cp38-musllinux_1_2_aarch64.whl"
+              "hash": "d751efaa8a49ae15cbebdda747a62a9ae521126e396fda8143858419f3b03610",
+              "url": "https://files.pythonhosted.org/packages/c0/2f/4ccf806acd32a6f9c68d5e1b797915e4b097f11f5cab62f16a4b5bbd2fa7/orjson-3.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "b6ebc17cfbbf741f5c1a888d1854354536f63d84bee537c9a7c0335791bb9009",
-              "url": "https://files.pythonhosted.org/packages/9d/18/072e236c3c5391a1e478d695d63cffb9f56f172d1f360351adf980260ba5/orjson-3.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl"
+              "hash": "9813f43da955197d36a7365eb99bed42b83680801729ab2487fef305b9ced866",
+              "url": "https://files.pythonhosted.org/packages/c2/5e/a4de7c063e74cd5f29091ff4e99c5a8823f82d6a15bbb5e0523ca4f519d7/orjson-3.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "13b5d3c795b09a466ec9fcf0bd3ad7b85467d91a60113885df7b8d639a9d374b",
-              "url": "https://files.pythonhosted.org/packages/a9/d8/90683ee28788222c022ffbe59f114c460780eb0e58bb2bd0e803bfca8d19/orjson-3.10.0-cp38-cp38-musllinux_1_2_x86_64.whl"
+              "hash": "536429bb02791a199d976118b95014ad66f74c58b7644d21061c54ad284e00f4",
+              "url": "https://files.pythonhosted.org/packages/d6/21/bea62f2d47950324848393f2bc56b60865817f67739e93b7e62430fc9146/orjson-3.10.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "1897aa25a944cec774ce4a0e1c8e98fb50523e97366c637b7d0cddabc42e6643",
-              "url": "https://files.pythonhosted.org/packages/b4/77/37455f0cf1df4518775c513dbf1b2abf070cdaa48f49b1a77c3d61753793/orjson-3.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"
+              "hash": "ec917b768e2b34b7084cb6c68941f6de5812cc26c6f1a9fecb728e36a3deb9e8",
+              "url": "https://files.pythonhosted.org/packages/d8/d1/44a15a0e0eb9954a1de0e243822aad0d0a16f2a1e9a61ca51dbc92830315/orjson-3.10.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "23c12bb4ced1c3308eff7ba5c63ef8f0edb3e4c43c026440247dd6c1c61cea4b",
-              "url": "https://files.pythonhosted.org/packages/e7/46/ac12f27c2cb16b120dff585703aad3f5ced0198fbb6dc665a0247ac6d20f/orjson-3.10.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl"
+              "hash": "ebc58693464146506fde0c4eb1216ff6d4e40213e61f7d40e2f0dde9b2f21650",
+              "url": "https://files.pythonhosted.org/packages/dc/cd/007d07c9fa40c0a0883109ede751b9c2236a7b5bece18b42c387214c2d69/orjson-3.10.1-cp39-cp39-musllinux_1_2_aarch64.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "eadecaa16d9783affca33597781328e4981b048615c2ddc31c47a51b833d6319",
-              "url": "https://files.pythonhosted.org/packages/eb/d3/540823cfa95e49f48c053faccc304cc48df21a7e3f04b63e921aa09c7193/orjson-3.10.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl"
+              "hash": "a883b28d73370df23ed995c466b4f6c708c1f7a9bdc400fe89165c96c7603204",
+              "url": "https://files.pythonhosted.org/packages/f5/af/0daa12a907215a5af6d97db8adf301ef14a1b1c651f7e176ee04e0998433/orjson-3.10.1.tar.gz"
             }
           ],
           "project_name": "orjson",
           "requires_dists": [],
           "requires_python": ">=3.8",
-          "version": "3.10.0"
+          "version": "3.10.1"
         },
         {
           "artifacts": [
@@ -3800,13 +3786,13 @@
           "artifacts": [
             {
               "algorithm": "sha256",
-              "hash": "c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c",
-              "url": "https://files.pythonhosted.org/packages/92/e1/1c8bb3420105e70bdf357d57dd5567202b4ef8d27f810e98bb962d950834/setuptools-69.2.0-py3-none-any.whl"
+              "hash": "c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32",
+              "url": "https://files.pythonhosted.org/packages/f7/29/13965af254e3373bceae8fb9a0e6ea0d0e571171b80d6646932131d6439b/setuptools-69.5.1-py3-none-any.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e",
-              "url": "https://files.pythonhosted.org/packages/4d/5b/dc575711b6b8f2f866131a40d053e30e962e633b332acf7cd2c24843d83d/setuptools-69.2.0.tar.gz"
+              "hash": "6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987",
+              "url": "https://files.pythonhosted.org/packages/d6/4f/b10f707e14ef7de524fe1f8988a294fb262a29c9b5b12275c7e188864aed/setuptools-69.5.1.tar.gz"
             }
           ],
           "project_name": "setuptools",
@@ -3830,26 +3816,25 @@
             "packaging>=23.2; extra == \"testing-integration\"",
             "pip>=19.1; extra == \"testing\"",
             "pygments-github-lexers==0.0.5; extra == \"docs\"",
+            "pytest!=8.1.1,>=6; extra == \"testing\"",
             "pytest-checkdocs>=2.4; extra == \"testing\"",
             "pytest-cov; platform_python_implementation != \"PyPy\" and extra == \"testing\"",
             "pytest-enabler; extra == \"testing-integration\"",
             "pytest-enabler>=2.2; extra == \"testing\"",
             "pytest-home>=0.5; extra == \"testing\"",
-            "pytest-mypy>=0.9.1; platform_python_implementation != \"PyPy\" and extra == \"testing\"",
+            "pytest-mypy; extra == \"testing\"",
             "pytest-perf; sys_platform != \"cygwin\" and extra == \"testing\"",
             "pytest-ruff>=0.2.1; sys_platform != \"cygwin\" and extra == \"testing\"",
             "pytest-timeout; extra == \"testing\"",
             "pytest-xdist; extra == \"testing-integration\"",
             "pytest-xdist>=3; extra == \"testing\"",
             "pytest; extra == \"testing-integration\"",
-            "pytest>=6; extra == \"testing\"",
             "rst.linker>=1.9; extra == \"docs\"",
             "sphinx-favicon; extra == \"docs\"",
             "sphinx-inline-tabs; extra == \"docs\"",
             "sphinx-lint; extra == \"docs\"",
             "sphinx-notfound-page<2,>=1; extra == \"docs\"",
             "sphinx-reredirects; extra == \"docs\"",
-            "sphinx<7.2.5; extra == \"docs\"",
             "sphinx>=3.5; extra == \"docs\"",
             "sphinxcontrib-towncrier; extra == \"docs\"",
             "tomli-w>=1.0.0; extra == \"testing\"",
@@ -3861,7 +3846,7 @@
             "wheel; extra == \"testing-integration\""
           ],
           "requires_python": ">=3.8",
-          "version": "69.2.0"
+          "version": "69.5.1"
         },
         {
           "artifacts": [
@@ -4262,26 +4247,6 @@
           "requires_python": ">=3.8",
           "version": "6.1.0"
         },
-        {
-          "artifacts": [
-            {
-              "algorithm": "sha256",
-              "hash": "8253cebec4b19094d67cc5ed5af99bf1dba1285292226e98a31929f87a5d6b23",
-              "url": "https://files.pythonhosted.org/packages/17/0a/6ac05a3723017a967193456a2efa0aa9ac4b51456891af1e2353bb9de21e/traceback2-1.4.0-py2.py3-none-any.whl"
-            },
-            {
-              "algorithm": "sha256",
-              "hash": "05acc67a09980c2ecfedd3423f7ae0104839eccb55fc645773e1caa0951c3030",
-              "url": "https://files.pythonhosted.org/packages/eb/7f/e20ba11390bdfc55117c8c6070838ec914e6f0053a602390a598057884eb/traceback2-1.4.0.tar.gz"
-            }
-          ],
-          "project_name": "traceback2",
-          "requires_dists": [
-            "linecache2"
-          ],
-          "requires_python": null,
-          "version": "1.4.0"
-        },
         {
           "artifacts": [
             {
@@ -4485,28 +4450,6 @@
           "requires_python": ">=3.8",
           "version": "5.9.0"
         },
-        {
-          "artifacts": [
-            {
-              "algorithm": "sha256",
-              "hash": "13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8",
-              "url": "https://files.pythonhosted.org/packages/72/20/7f0f433060a962200b7272b8c12ba90ef5b903e218174301d0abfd523813/unittest2-1.1.0-py2.py3-none-any.whl"
-            },
-            {
-              "algorithm": "sha256",
-              "hash": "22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579",
-              "url": "https://files.pythonhosted.org/packages/7f/c4/2b0e2d185d9d60772c10350d9853646832609d2f299a8300ab730f199db4/unittest2-1.1.0.tar.gz"
-            }
-          ],
-          "project_name": "unittest2",
-          "requires_dists": [
-            "argparse",
-            "six>=1.4",
-            "traceback2"
-          ],
-          "requires_python": null,
-          "version": "1.1.0"
-        },
         {
           "artifacts": [
             {
@@ -4566,13 +4509,13 @@
           "artifacts": [
             {
               "algorithm": "sha256",
-              "hash": "961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a",
-              "url": "https://files.pythonhosted.org/packages/16/65/0d0bdfdac31e2db8c6d6c18fe1e00236f0dea279f9846f94a9aafa49cfc9/virtualenv-20.25.1-py3-none-any.whl"
+              "hash": "8aac4332f2ea6ef519c648d0bc48a5b1d324994753519919bddbb1aff25a104e",
+              "url": "https://files.pythonhosted.org/packages/cd/8a/709e9994dc2f9705d1127c63b64151582655e02c953e163b317803864fc0/virtualenv-20.25.3-py3-none-any.whl"
             },
             {
               "algorithm": "sha256",
-              "hash": "e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197",
-              "url": "https://files.pythonhosted.org/packages/93/4f/a7737e177ab67c454d7e60d48a5927f16cd05623e9dd888f78183545d250/virtualenv-20.25.1.tar.gz"
+              "hash": "7bb554bbdfeaacc3349fa614ea5bff6ac300fc7c335e9facf3a3bcfc703f45be",
+              "url": "https://files.pythonhosted.org/packages/42/28/846fb3eb75955d191f13bca658fb0082ddcef8e2d4b6fd0c76146556f0be/virtualenv-20.25.3.tar.gz"
             }
           ],
           "project_name": "virtualenv",
@@ -4595,14 +4538,14 @@
             "pytest-timeout>=2.1; extra == \"test\"",
             "pytest>=7.4; extra == \"test\"",
             "setuptools>=68; extra == \"test\"",
+            "sphinx!=7.3,>=7.1.2; extra == \"docs\"",
             "sphinx-argparse>=0.4; extra == \"docs\"",
-            "sphinx>=7.1.2; extra == \"docs\"",
             "sphinxcontrib-towncrier>=0.2.1a0; extra == \"docs\"",
             "time-machine>=2.10; platform_python_implementation == \"CPython\" and extra == \"test\"",
             "towncrier>=23.6; extra == \"docs\""
           ],
           "requires_python": ">=3.7",
-          "version": "20.25.1"
+          "version": "20.25.3"
         },
         {
           "artifacts": [
@@ -5121,7 +5064,6 @@
     "tooz",
     "udatetime",
     "ujson",
-    "unittest2",
     "virtualenv",
     "webob",
     "webtest",
diff --git a/requirements-pants.txt b/requirements-pants.txt
index b3ba068002..cc0a4c14c2 100644
--- a/requirements-pants.txt
+++ b/requirements-pants.txt
@@ -79,7 +79,6 @@ tabulate
 tooz
 udatetime
 ujson
-unittest2
 virtualenv
 webob
 webtest
diff --git a/requirements.txt b/requirements.txt
index dd534b4a95..40a7416e9f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,7 +9,7 @@ MarkupSafe==2.0.1
 RandomWords
 amqp==5.2.0
 apscheduler==3.10.4
-argcomplete==3.2.3
+argcomplete==3.3.0
 bcrypt==4.1.2
 cffi==1.16.0
 chardet==3.0.4
@@ -22,12 +22,12 @@ flex==6.14.1
 gitdb==4.0.11
 gitpython==3.1.43
 greenlet==3.0.3
-gunicorn==21.2.0
+gunicorn==22.0.0
 importlib-metadata==7.1.0
 jinja2==3.1.3
 jsonpath-rw==1.4.0
 jsonschema==3.2.0
-kombu==5.3.6
+kombu==5.3.7
 lockfile==0.12.2
 logshipper@ git+https://github.com/StackStorm/logshipper.git@stackstorm_patched ; platform_system=="Linux"
 mock==5.1.0
@@ -36,7 +36,7 @@ networkx==2.8.8
 nose
 nose-parallel==0.4.0
 nose-timer==1.0.1
-orjson==3.10.0
+orjson==3.10.1
 orquesta@ git+https://github.com/StackStorm/orquesta.git@v1.6.0
 oslo.config==1.12.1
 oslo.utils==7.1.0
diff --git a/st2actions/requirements.txt b/st2actions/requirements.txt
index 2d8a29aa2b..f18a257df2 100644
--- a/st2actions/requirements.txt
+++ b/st2actions/requirements.txt
@@ -11,7 +11,7 @@ chardet==3.0.4
 eventlet==0.36.1
 gitpython==3.1.43
 jinja2==3.1.3
-kombu==5.3.6
+kombu==5.3.7
 lockfile==0.12.2
 logshipper@ git+https://github.com/StackStorm/logshipper.git@stackstorm_patched ; platform_system=="Linux"
 oslo.config==1.12.1
diff --git a/st2api/requirements.txt b/st2api/requirements.txt
index a25ba0c568..24dd541a1c 100644
--- a/st2api/requirements.txt
+++ b/st2api/requirements.txt
@@ -6,9 +6,9 @@
 # in-requirements.txt for that component and then run 'make requirements' to
 # update the component requirements.txt
 eventlet==0.36.1
-gunicorn==21.2.0
+gunicorn==22.0.0
 jsonschema==3.2.0
-kombu==5.3.6
+kombu==5.3.7
 mongoengine==0.23.1
 oslo.config==1.12.1
 oslo.utils==7.1.0
diff --git a/st2auth/requirements.txt b/st2auth/requirements.txt
index ef2ba0fba4..f9b179d5db 100644
--- a/st2auth/requirements.txt
+++ b/st2auth/requirements.txt
@@ -7,7 +7,7 @@
 # update the component requirements.txt
 bcrypt==4.1.2
 eventlet==0.36.1
-gunicorn==21.2.0
+gunicorn==22.0.0
 oslo.config==1.12.1
 passlib==1.7.4
 pymongo==3.12.3
diff --git a/st2client/requirements.txt b/st2client/requirements.txt
index aa09d1d5de..da8292fc6c 100644
--- a/st2client/requirements.txt
+++ b/st2client/requirements.txt
@@ -5,14 +5,14 @@
 # If you want to update depdencies for a single component, modify the
 # in-requirements.txt for that component and then run 'make requirements' to
 # update the component requirements.txt
-argcomplete==3.2.3
+argcomplete==3.3.0
 cffi==1.16.0
 chardet==3.0.4
 cryptography==42.0.5
 importlib-metadata==7.1.0
 jsonpath-rw==1.4.0
 jsonschema==3.2.0
-orjson==3.10.0
+orjson==3.10.1
 prettytable==3.10.0
 prompt-toolkit==1.0.18
 pyOpenSSL
diff --git a/st2common/requirements.txt b/st2common/requirements.txt
index 68adaf57b7..338095e840 100644
--- a/st2common/requirements.txt
+++ b/st2common/requirements.txt
@@ -22,11 +22,11 @@ greenlet==3.0.3
 jinja2==3.1.3
 jsonpath-rw==1.4.0
 jsonschema==3.2.0
-kombu==5.3.6
+kombu==5.3.7
 lockfile==0.12.2
 mongoengine==0.23.1
 networkx==2.8.8
-orjson==3.10.0
+orjson==3.10.1
 orquesta@ git+https://github.com/StackStorm/orquesta.git@v1.6.0
 oslo.config==1.12.1
 paramiko==3.4.0
diff --git a/st2reactor/requirements.txt b/st2reactor/requirements.txt
index 0dd9c92bf6..919e7e4ecb 100644
--- a/st2reactor/requirements.txt
+++ b/st2reactor/requirements.txt
@@ -9,7 +9,7 @@ apscheduler==3.10.4
 eventlet==0.36.1
 jsonpath-rw==1.4.0
 jsonschema==3.2.0
-kombu==5.3.6
+kombu==5.3.7
 oslo.config==1.12.1
 python-dateutil==2.9.0
 six==1.16.0
diff --git a/st2stream/requirements.txt b/st2stream/requirements.txt
index 7b882082ac..913d10472f 100644
--- a/st2stream/requirements.txt
+++ b/st2stream/requirements.txt
@@ -6,9 +6,9 @@
 # in-requirements.txt for that component and then run 'make requirements' to
 # update the component requirements.txt
 eventlet==0.36.1
-gunicorn==21.2.0
+gunicorn==22.0.0
 jsonschema==3.2.0
-kombu==5.3.6
+kombu==5.3.7
 mongoengine==0.23.1
 oslo.config==1.12.1
 oslo.utils==7.1.0


Test patch:
diff --git a/contrib/packs/tests/test_action_download.py b/contrib/packs/tests/test_action_download.py
index 3bfb9f978f..0961ff17a2 100644
--- a/contrib/packs/tests/test_action_download.py
+++ b/contrib/packs/tests/test_action_download.py
@@ -260,7 +260,7 @@ def mock_acquire(self, timeout=None):
                 fp.write("")
 
             expected_msg = "Timeout waiting to acquire lock for"
-            self.assertRaisesRegexp(
+            self.assertRaisesRegex(
                 LockTimeout,
                 expected_msg,
                 action.run,
@@ -328,7 +328,7 @@ def test_run_pack_download_invalid_version(self):
             "is not a valid version, hash, tag or branch.*?"
             "Available versions are: 1.0.0, 2.0.0."
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             action.run,
@@ -351,7 +351,7 @@ def test_download_pack_stackstorm_version_identifier_check(self):
             'Pack "test3" requires StackStorm ">=1.6.0, <2.2.0", but '
             'current version is "2.2.0"'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             action.run,
@@ -364,7 +364,7 @@ def test_download_pack_stackstorm_version_identifier_check(self):
             'Pack "test3" requires StackStorm ">=1.6.0, <2.2.0", but '
             'current version is "2.3.0"'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             action.run,
@@ -377,7 +377,7 @@ def test_download_pack_stackstorm_version_identifier_check(self):
             'Pack "test3" requires StackStorm ">=1.6.0, <2.2.0", but '
             'current version is "1.5.9"'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             action.run,
@@ -390,7 +390,7 @@ def test_download_pack_stackstorm_version_identifier_check(self):
             'Pack "test3" requires StackStorm ">=1.6.0, <2.2.0", but '
             'current version is "1.5.0"'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             action.run,
@@ -470,7 +470,7 @@ def test_download_pack_python_version_check(self):
                 r'Pack "test3" requires Python 2.x, but current Python version is '
                 '"3.5.2"'
             )
-            self.assertRaisesRegexp(
+            self.assertRaisesRegex(
                 ValueError,
                 expected_msg,
                 action.run,
@@ -497,7 +497,7 @@ def test_download_pack_python_version_check(self):
                 r'Pack "test3" requires Python 3.x, but current Python version is '
                 '"2.7.2"'
             )
-            self.assertRaisesRegexp(
+            self.assertRaisesRegex(
                 ValueError,
                 expected_msg,
                 action.run,
@@ -657,7 +657,7 @@ def test_run_pack_download_local_directory(self):
 
         # 1. Local directory doesn't exist
         expected_msg = r'Local pack directory ".*" doesn\'t exist'
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             action.run,
diff --git a/contrib/runners/action_chain_runner/tests/unit/test_actionchain.py b/contrib/runners/action_chain_runner/tests/unit/test_actionchain.py
index 964507b191..953b2d6acb 100644
--- a/contrib/runners/action_chain_runner/tests/unit/test_actionchain.py
+++ b/contrib/runners/action_chain_runner/tests/unit/test_actionchain.py
@@ -364,7 +364,7 @@ def test_chain_runner_bad_default(self, request):
         expected_msg = (
             'Unable to find node with name "bad_default" referenced in "default".'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             runnerexceptions.ActionRunnerPreRunError, expected_msg, chain_runner.pre_run
         )
 
@@ -434,7 +434,7 @@ def test_chain_runner_broken_on_success_path_static_task_name(self, request):
             'Unable to find node with name "c5" referenced in "on-success" '
             'in task "c2"'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             runnerexceptions.ActionRunnerPreRunError, expected_msg, chain_runner.pre_run
         )
 
@@ -453,7 +453,7 @@ def test_chain_runner_broken_on_failure_path_static_task_name(self, request):
             'Unable to find node with name "c6" referenced in "on-failure" '
             'in task "c2"'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             runnerexceptions.ActionRunnerPreRunError, expected_msg, chain_runner.pre_run
         )
 
@@ -896,7 +896,7 @@ def test_chain_task_passes_invalid_parameter_type_to_action(self, mock_request):
             r'Failed to cast value "stringnotanarray" \(type: str\) for parameter '
             r'"arrtype" of type "array"'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             chain_runner.run,
@@ -944,7 +944,7 @@ def test_exception_is_thrown_if_both_params_and_parameters_attributes_are_provid
             'Either "params" or "parameters" attribute needs to be provided, but '
             "not both"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             runnerexceptions.ActionRunnerPreRunError, expected_msg, chain_runner.pre_run
         )
 
diff --git a/contrib/runners/action_chain_runner/tests/unit/test_actionchain_params_rendering.py b/contrib/runners/action_chain_runner/tests/unit/test_actionchain_params_rendering.py
index d6278ca61a..b7e36e351b 100644
--- a/contrib/runners/action_chain_runner/tests/unit/test_actionchain_params_rendering.py
+++ b/contrib/runners/action_chain_runner/tests/unit/test_actionchain_params_rendering.py
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 import mock
 
@@ -24,7 +24,7 @@
 from st2common.models.system.actionchain import Node
 
 
-class ActionChainRunnerResolveParamsTests(unittest2.TestCase):
+class ActionChainRunnerResolveParamsTests(unittest.TestCase):
     def test_render_params_action_context(self):
         runner = acr.get_runner()
         chain_context = {
diff --git a/contrib/runners/announcement_runner/tests/unit/test_announcementrunner.py b/contrib/runners/announcement_runner/tests/unit/test_announcementrunner.py
index 9ad56a2115..c8feae704a 100644
--- a/contrib/runners/announcement_runner/tests/unit/test_announcementrunner.py
+++ b/contrib/runners/announcement_runner/tests/unit/test_announcementrunner.py
@@ -64,7 +64,7 @@ def test_announcement_no_experimental(self, dispatch):
         runner.liveaction = mock.Mock(context={})
 
         expected_msg = "Experimental flag is missing for action some.thing"
-        self.assertRaisesRegexp(Exception, expected_msg, runner.pre_run)
+        self.assertRaisesRegex(Exception, expected_msg, runner.pre_run)
 
     @mock.patch("st2common.models.api.trace.TraceContext.__new__")
     def test_announcement_with_trace(self, context, dispatch):
diff --git a/contrib/runners/http_runner/tests/unit/test_http_runner.py b/contrib/runners/http_runner/tests/unit/test_http_runner.py
index 9d2d99a7c1..59695ec17c 100644
--- a/contrib/runners/http_runner/tests/unit/test_http_runner.py
+++ b/contrib/runners/http_runner/tests/unit/test_http_runner.py
@@ -20,7 +20,7 @@
 
 import six
 import mock
-import unittest2
+import unittest
 
 from st2common.constants.action import LIVEACTION_STATUS_SUCCEEDED
 from http_runner.http_runner import HTTPClient
@@ -41,7 +41,7 @@ class MockResult(object):
     close = mock.Mock()
 
 
-class HTTPClientTestCase(unittest2.TestCase):
+class HTTPClientTestCase(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         tests_config.parse_args()
@@ -287,7 +287,7 @@ def test_blacklisted_url_url_hosts_blacklist_runner_parameter(self, mock_request
             client = HTTPClient(
                 url=url, method="GET", url_hosts_blacklist=url_hosts_blacklist
             )
-            self.assertRaisesRegexp(ValueError, expected_msg, client.run)
+            self.assertRaisesRegex(ValueError, expected_msg, client.run)
 
         # Non blacklisted URLs
         urls = ["https://example2.com", "http://example3.com", "http://example4.com:81"]
@@ -335,7 +335,7 @@ def test_whitelisted_url_url_hosts_whitelist_runner_parameter(self, mock_request
             client = HTTPClient(
                 url=url, method="GET", url_hosts_whitelist=url_hosts_whitelist
             )
-            self.assertRaisesRegexp(ValueError, expected_msg, client.run)
+            self.assertRaisesRegex(ValueError, expected_msg, client.run)
 
         # Whitelisted URLS
         urls = [
@@ -372,7 +372,7 @@ def test_url_host_blacklist_and_url_host_blacklist_params_are_mutually_exclusive
             r'"url_hosts_blacklist" and "url_hosts_whitelist" parameters are mutually '
             "exclusive."
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             HTTPClient,
@@ -383,7 +383,7 @@ def test_url_host_blacklist_and_url_host_blacklist_params_are_mutually_exclusive
         )
 
 
-class HTTPRunnerTestCase(unittest2.TestCase):
+class HTTPRunnerTestCase(unittest.TestCase):
     @mock.patch("http_runner.http_runner.requests")
     def test_get_success(self, mock_requests):
         mock_result = MockResult()
@@ -423,4 +423,4 @@ def test_url_host_blacklist_and_url_host_blacklist_params_are_mutually_exclusive
             r'"url_hosts_blacklist" and "url_hosts_whitelist" parameters are mutually '
             "exclusive."
         )
-        self.assertRaisesRegexp(ValueError, expected_msg, runner.run, {})
+        self.assertRaisesRegex(ValueError, expected_msg, runner.run, {})
diff --git a/contrib/runners/noop_runner/tests/unit/test_nooprunner.py b/contrib/runners/noop_runner/tests/unit/test_nooprunner.py
index 67a234585b..686a404e89 100644
--- a/contrib/runners/noop_runner/tests/unit/test_nooprunner.py
+++ b/contrib/runners/noop_runner/tests/unit/test_nooprunner.py
@@ -22,7 +22,7 @@
 
 tests_config.parse_args()
 
-from unittest2 import TestCase
+from unittest import TestCase
 from st2common.constants import action as action_constants
 from st2tests.fixtures.generic.fixture import PACK_NAME as GENERIC_PACK
 from st2tests.fixturesloader import FixturesLoader
diff --git a/contrib/runners/orquesta_runner/tests/unit/test_functions_st2kv.py b/contrib/runners/orquesta_runner/tests/unit/test_functions_st2kv.py
index 3004857bee..25adf4c1fc 100644
--- a/contrib/runners/orquesta_runner/tests/unit/test_functions_st2kv.py
+++ b/contrib/runners/orquesta_runner/tests/unit/test_functions_st2kv.py
@@ -17,7 +17,7 @@
 
 import mock
 import six
-import unittest2
+import unittest
 
 import st2tests
 
@@ -42,7 +42,7 @@
 MOCK_CTX_NO_USER = {"__vars": {"st2": {}}}
 
 
-class DatastoreFunctionTest(unittest2.TestCase):
+class DatastoreFunctionTest(unittest.TestCase):
     def test_missing_user_context(self):
         self.assertRaises(KeyError, st2kv.st2kv_, MOCK_CTX_NO_USER, "foo")
 
@@ -93,7 +93,7 @@ def test_key_exists(self):
         self.assertIsNone(st2kv.st2kv_(MOCK_CTX, "foo_null"))
 
     def test_key_does_not_exist(self):
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             exc.ExpressionEvaluationException,
             'The key ".*" does not exist in the StackStorm datastore.',
             st2kv.st2kv_,
@@ -120,7 +120,7 @@ def test_key_decrypt(self):
         kvp_util, "get_key", mock.MagicMock(side_effect=Exception("Mock failure."))
     )
     def test_get_key_exception(self):
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             exc.ExpressionEvaluationException,
             "Mock failure.",
             st2kv.st2kv_,
@@ -168,7 +168,7 @@ def test_key_exists(self):
         self.assertIsNone(st2kv.st2kv_(MOCK_CTX, "system.foo_null"))
 
     def test_key_does_not_exist(self):
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             exc.ExpressionEvaluationException,
             'The key ".*" does not exist in the StackStorm datastore.',
             st2kv.st2kv_,
@@ -197,7 +197,7 @@ def test_key_decrypt(self):
         kvp_util, "get_key", mock.MagicMock(side_effect=Exception("Mock failure."))
     )
     def test_get_key_exception(self):
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             exc.ExpressionEvaluationException,
             "Mock failure.",
             st2kv.st2kv_,
diff --git a/contrib/runners/python_runner/tests/integration/test_python_action_process_wrapper.py b/contrib/runners/python_runner/tests/integration/test_python_action_process_wrapper.py
index da939f4aae..3c609e26b5 100644
--- a/contrib/runners/python_runner/tests/integration/test_python_action_process_wrapper.py
+++ b/contrib/runners/python_runner/tests/integration/test_python_action_process_wrapper.py
@@ -36,7 +36,7 @@
 import os
 import json
 
-import unittest2
+import unittest
 from shutil import which as shutil_which
 
 from st2common.util.shell import run_command
@@ -65,8 +65,8 @@
 TIME_BINARY_AVAILABLE = TIME_BINARY_PATH is not None
 
 
-@unittest2.skipIf(not TIME_BINARY_PATH, "time binary not available")
-class PythonRunnerActionWrapperProcessTestCase(unittest2.TestCase):
+@unittest.skipIf(not TIME_BINARY_PATH, "time binary not available")
+class PythonRunnerActionWrapperProcessTestCase(unittest.TestCase):
     def test_process_wrapper_exits_in_reasonable_timeframe(self):
         # 1. Verify wrapper script path is correct and file exists
         self.assertTrue(os.path.isfile(WRAPPER_SCRIPT_PATH))
diff --git a/contrib/runners/python_runner/tests/unit/test_pythonrunner.py b/contrib/runners/python_runner/tests/unit/test_pythonrunner.py
index 27a6806e9a..a178bd20ff 100644
--- a/contrib/runners/python_runner/tests/unit/test_pythonrunner.py
+++ b/contrib/runners/python_runner/tests/unit/test_pythonrunner.py
@@ -281,7 +281,7 @@ def test_simple_action_no_entry_point(self):
         runner.entry_point = ""
 
         expected_msg = "Action .*? is missing entry_point attribute"
-        self.assertRaisesRegexp(Exception, expected_msg, runner.run, {})
+        self.assertRaisesRegex(Exception, expected_msg, runner.run, {})
 
     @mock.patch("st2common.util.concurrency.subprocess_popen")
     def test_action_with_user_supplied_env_vars(self, mock_popen):
@@ -720,7 +720,7 @@ def test_python_action_wrapper_action_script_file_doesnt_exist_friendly_error(se
         expected_msg = (
             'File "/tmp/doesnt.exist" has no action class or the file doesn\'t exist.'
         )
-        self.assertRaisesRegexp(Exception, expected_msg, wrapper._get_action_instance)
+        self.assertRaisesRegex(Exception, expected_msg, wrapper._get_action_instance)
 
         # File in a directory which is a Python package
         wrapper = PythonActionWrapper(
@@ -732,7 +732,7 @@ def test_python_action_wrapper_action_script_file_doesnt_exist_friendly_error(se
             r"\(action file most likely doesn\'t exist or contains invalid syntax\): "
             r"\[Errno 2\] No such file or directory"
         )
-        self.assertRaisesRegexp(Exception, expected_msg, wrapper._get_action_instance)
+        self.assertRaisesRegex(Exception, expected_msg, wrapper._get_action_instance)
 
     def test_python_action_wrapper_action_script_file_contains_invalid_syntax_friendly_error(
         self,
@@ -745,7 +745,7 @@ def test_python_action_wrapper_action_script_file_contains_invalid_syntax_friend
             r"\(action file most likely doesn\'t exist or contains invalid syntax\): "
             r"No module named \'?invalid\'?"
         )
-        self.assertRaisesRegexp(Exception, expected_msg, wrapper._get_action_instance)
+        self.assertRaisesRegex(Exception, expected_msg, wrapper._get_action_instance)
 
     def test_simple_action_log_messages_and_log_level_runner_param(self):
         expected_msg_1 = (
@@ -927,7 +927,7 @@ def test_content_version_success(self, mock_get_sandbox_virtualenv_path):
             '"v0.30.0" provided. Make sure that git repository is up '
             "to date and contains that revision."
         )
-        self.assertRaisesRegexp(ValueError, expected_msg, runner.pre_run)
+        self.assertRaisesRegex(ValueError, expected_msg, runner.pre_run)
 
     @mock.patch("python_runner.python_runner.get_sandbox_virtualenv_path")
     @mock.patch("st2common.util.concurrency.subprocess_popen")
@@ -978,7 +978,7 @@ def test_content_version_success_local_modules_work_fine(
             "<module '?local_module'? from '?%s/actions/local_module.py'?>.*"
             % runner.git_worktree_path
         )
-        self.assertRegexpMatches(output["stdout"].strip(), expected_stdout)
+        self.assertRegex(output["stdout"].strip(), expected_stdout)
 
     @mock.patch("st2common.runners.base.run_command")
     def test_content_version_old_git_version(self, mock_run_command):
@@ -998,7 +998,7 @@ def test_content_version_old_git_version(self, mock_run_command):
             "doesn't support git worktree command. To be able to utilize this "
             "functionality you need to use git >= 2.5.0."
         )
-        self.assertRaisesRegexp(ValueError, expected_msg, runner.pre_run)
+        self.assertRaisesRegex(ValueError, expected_msg, runner.pre_run)
 
     @mock.patch("st2common.runners.base.run_command")
     def test_content_version_pack_repo_not_git_repository(self, mock_run_command):
@@ -1020,7 +1020,7 @@ def test_content_version_pack_repo_not_git_repository(self, mock_run_command):
             "git repository. To utilize this functionality, pack directory needs to "
             "be a git repository."
         )
-        self.assertRaisesRegexp(ValueError, expected_msg, runner.pre_run)
+        self.assertRaisesRegex(ValueError, expected_msg, runner.pre_run)
 
     @mock.patch("st2common.runners.base.run_command")
     def test_content_version_invalid_git_revision(self, mock_run_command):
@@ -1040,7 +1040,7 @@ def test_content_version_invalid_git_revision(self, mock_run_command):
             '"vinvalid" provided. Make sure that git repository is up '
             "to date and contains that revision."
         )
-        self.assertRaisesRegexp(ValueError, expected_msg, runner.pre_run)
+        self.assertRaisesRegex(ValueError, expected_msg, runner.pre_run)
 
     def test_missing_config_item_user_friendly_error(self):
         runner = self._get_mock_runner_obj()
diff --git a/st2actions/tests/integration/test_actions_queue_consumer.py b/st2actions/tests/integration/test_actions_queue_consumer.py
index e4b57698de..149e94b0f3 100644
--- a/st2actions/tests/integration/test_actions_queue_consumer.py
+++ b/st2actions/tests/integration/test_actions_queue_consumer.py
@@ -19,7 +19,7 @@
 
 from kombu import Exchange
 from kombu import Queue
-from unittest2 import TestCase
+from unittest import TestCase
 
 from st2common.transport.consumers import ActionsQueueConsumer
 from st2common.transport.publishers import PoolPublisher
diff --git a/st2actions/tests/unit/test_action_runner_worker.py b/st2actions/tests/unit/test_action_runner_worker.py
index 1d0c7bbbd0..96e049b179 100644
--- a/st2actions/tests/unit/test_action_runner_worker.py
+++ b/st2actions/tests/unit/test_action_runner_worker.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-from unittest2 import TestCase
+from unittest import TestCase
 from mock import Mock
 
 from st2common.transport.consumers import ActionsQueueConsumer
diff --git a/st2actions/tests/unit/test_actions_registrar.py b/st2actions/tests/unit/test_actions_registrar.py
index a9f6eb838a..2ec0f4b30c 100644
--- a/st2actions/tests/unit/test_actions_registrar.py
+++ b/st2actions/tests/unit/test_actions_registrar.py
@@ -134,7 +134,7 @@ def test_register_action_invalid_parameter_type_attribute(self):
         #   "'list' is not valid under any of the given schemas"
         # with jsonschema 3.2.0, the underlying enum (anyOf->enum) gets reported instead:
         expected_msg = r"'list' is not one of \['array', 'boolean', 'integer', 'null', 'number', 'object', 'string'\].*"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             jsonschema.ValidationError,
             expected_msg,
             registrar._register_action,
@@ -161,7 +161,7 @@ def test_register_action_invalid_parameter_name(self):
             'Parameter name "action-name" is invalid. Valid characters for '
             "parameter name are"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             jsonschema.ValidationError,
             expected_msg,
             registrar._register_action,
diff --git a/st2actions/tests/unit/test_parallel_ssh.py b/st2actions/tests/unit/test_parallel_ssh.py
index 67052a53e0..c1ef2e998a 100644
--- a/st2actions/tests/unit/test_parallel_ssh.py
+++ b/st2actions/tests/unit/test_parallel_ssh.py
@@ -18,7 +18,7 @@
 import os
 
 from mock import patch, Mock, MagicMock
-import unittest2
+import unittest
 
 from st2common.runners.parallel_ssh import ParallelSSHClient
 from st2common.runners.paramiko_ssh import ParamikoSSHClient
@@ -35,7 +35,7 @@
 """
 
 
-class ParallelSSHTests(unittest2.TestCase):
+class ParallelSSHTests(unittest.TestCase):
     @patch("paramiko.SSHClient", Mock)
     @patch.object(
         ParamikoSSHClient,
diff --git a/st2actions/tests/unit/test_paramiko_remote_script_runner.py b/st2actions/tests/unit/test_paramiko_remote_script_runner.py
index 4f09170781..27495463ff 100644
--- a/st2actions/tests/unit/test_paramiko_remote_script_runner.py
+++ b/st2actions/tests/unit/test_paramiko_remote_script_runner.py
@@ -16,7 +16,7 @@
 from __future__ import absolute_import
 import bson
 from mock import patch, Mock, MagicMock
-import unittest2
+import unittest
 
 # XXX: There is an import dependency. Config needs to setup
 # before importing remote_script_runner classes.
@@ -47,7 +47,7 @@
 ACTION_1 = MODELS["actions"]["a1.yaml"]
 
 
-class ParamikoScriptRunnerTestCase(unittest2.TestCase):
+class ParamikoScriptRunnerTestCase(unittest.TestCase):
     @patch("st2common.runners.parallel_ssh.ParallelSSHClient", Mock)
     @patch.object(jsonify, "json_loads", MagicMock(return_value={}))
     @patch.object(ParallelSSHClient, "run", MagicMock(return_value={}))
diff --git a/st2actions/tests/unit/test_paramiko_ssh.py b/st2actions/tests/unit/test_paramiko_ssh.py
index eadc4a477a..1ccdc110a2 100644
--- a/st2actions/tests/unit/test_paramiko_ssh.py
+++ b/st2actions/tests/unit/test_paramiko_ssh.py
@@ -19,7 +19,7 @@
 
 import mock
 import paramiko
-import unittest2
+import unittest
 from oslo_config import cfg
 from mock import call, patch, Mock, MagicMock
 from six.moves import StringIO
@@ -34,7 +34,7 @@
 __all__ = ["ParamikoSSHClientTestCase"]
 
 
-class ParamikoSSHClientTestCase(unittest2.TestCase):
+class ParamikoSSHClientTestCase(unittest.TestCase):
     @patch("paramiko.SSHClient", Mock)
     def setUp(self):
         """
@@ -180,7 +180,7 @@ def test_key_files_and_key_material_arguments_are_mutual_exclusive(self):
 
         client = ParamikoSSHClient(**conn_params)
 
-        self.assertRaisesRegexp(ValueError, expected_msg, client.connect)
+        self.assertRaisesRegex(ValueError, expected_msg, client.connect)
 
     @patch("paramiko.SSHClient", Mock)
     @patch.object(
@@ -230,7 +230,7 @@ def test_key_material_argument_invalid_key(self):
         mock = ParamikoSSHClient(**conn_params)
 
         expected_msg = "Invalid or unsupported key type"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             paramiko.ssh_exception.SSHException, expected_msg, mock.connect
         )
 
@@ -247,7 +247,7 @@ def test_passphrase_no_key_provided(self):
 
         expected_msg = "passphrase should accompany private key material"
         client = ParamikoSSHClient(**conn_params)
-        self.assertRaisesRegexp(ValueError, expected_msg, client.connect)
+        self.assertRaisesRegex(ValueError, expected_msg, client.connect)
 
     @patch("paramiko.SSHClient", Mock)
     def test_passphrase_not_provided_for_encrypted_key_file(self):
@@ -330,7 +330,7 @@ def test_passphrase_and_no_key(self):
         expected_msg = "passphrase should accompany private key material"
         client = ParamikoSSHClient(**conn_params)
 
-        self.assertRaisesRegexp(ValueError, expected_msg, client.connect)
+        self.assertRaisesRegex(ValueError, expected_msg, client.connect)
 
     @patch("paramiko.SSHClient", Mock)
     @patch.object(
@@ -351,7 +351,7 @@ def test_incorrect_passphrase(self):
         mock = ParamikoSSHClient(**conn_params)
 
         expected_msg = "Invalid passphrase or invalid/unsupported key type"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             paramiko.ssh_exception.SSHException, expected_msg, mock.connect
         )
 
@@ -375,7 +375,7 @@ def test_key_material_contains_path_not_contents(self):
             conn_params["key_material"] = key_material
             mock = ParamikoSSHClient(**conn_params)
 
-            self.assertRaisesRegexp(
+            self.assertRaisesRegex(
                 paramiko.ssh_exception.SSHException, expected_msg, mock.connect
             )
 
diff --git a/st2actions/tests/unit/test_paramiko_ssh_runner.py b/st2actions/tests/unit/test_paramiko_ssh_runner.py
index f42746d602..6700bb0347 100644
--- a/st2actions/tests/unit/test_paramiko_ssh_runner.py
+++ b/st2actions/tests/unit/test_paramiko_ssh_runner.py
@@ -16,7 +16,7 @@
 from __future__ import absolute_import
 import os
 
-import unittest2
+import unittest
 import mock
 
 from st2common.runners.paramiko_ssh_runner import BaseParallelSSHRunner
@@ -38,7 +38,7 @@ def run(self):
         pass
 
 
-class ParamikoSSHRunnerTestCase(unittest2.TestCase):
+class ParamikoSSHRunnerTestCase(unittest.TestCase):
     @mock.patch("st2common.runners.paramiko_ssh_runner.ParallelSSHClient")
     def test_pre_run(self, mock_client):
         # Test case which verifies that ParamikoSSHClient is instantiated with the correct arguments
diff --git a/st2actions/tests/unit/test_remote_runners.py b/st2actions/tests/unit/test_remote_runners.py
index 7f84165dbb..19f5cb40f1 100644
--- a/st2actions/tests/unit/test_remote_runners.py
+++ b/st2actions/tests/unit/test_remote_runners.py
@@ -19,7 +19,7 @@
 
 tests_config.parse_args()
 
-from unittest2 import TestCase
+from unittest import TestCase
 
 from st2common.models.system.action import RemoteScriptAction
 
diff --git a/st2actions/tests/unit/test_runner_container.py b/st2actions/tests/unit/test_runner_container.py
index 0dcb299fde..1134e176bf 100644
--- a/st2actions/tests/unit/test_runner_container.py
+++ b/st2actions/tests/unit/test_runner_container.py
@@ -132,7 +132,7 @@ def test_pre_run_runner_is_disabled(self):
         runner.runner_type.enabled = False
 
         expected_msg = 'Runner "test-runner-1" has been disabled by the administrator'
-        self.assertRaisesRegexp(ValueError, expected_msg, runner.pre_run)
+        self.assertRaisesRegex(ValueError, expected_msg, runner.pre_run)
 
     def test_created_temporary_auth_token_is_correctly_scoped_to_user_who_ran_the_action(
         self,
diff --git a/st2actions/tests/unit/test_workflow_engine.py b/st2actions/tests/unit/test_workflow_engine.py
index e4729798fe..955f7ca2f0 100644
--- a/st2actions/tests/unit/test_workflow_engine.py
+++ b/st2actions/tests/unit/test_workflow_engine.py
@@ -240,7 +240,7 @@ def test_process_error_handling_has_error(self, mock_get_lock):
             coordination.ToozConnectionError("foobar"),
             coordination.ToozConnectionError("foobar"),
         ]
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             Exception, "Unexpected error.", workflows.get_engine().process, t1_ac_ex_db
         )
 
diff --git a/st2api/tests/integration/test_gunicorn_configs.py b/st2api/tests/integration/test_gunicorn_configs.py
index 9375cf3b85..c4c05bb155 100644
--- a/st2api/tests/integration/test_gunicorn_configs.py
+++ b/st2api/tests/integration/test_gunicorn_configs.py
@@ -17,7 +17,7 @@
 import random
 
 from six.moves import http_client
-import unittest2
+import unittest
 import requests
 import eventlet
 from eventlet.green import subprocess
@@ -32,7 +32,7 @@
 
 
 class GunicornWSGIEntryPointTestCase(IntegrationTestCase):
-    @unittest2.skipIf(profiling.is_enabled(), "Profiling is enabled")
+    @unittest.skipIf(profiling.is_enabled(), "Profiling is enabled")
     def test_st2api_wsgi_entry_point(self):
         port = random.randint(10000, 30000)
         cmd = (
@@ -51,7 +51,7 @@ def test_st2api_wsgi_entry_point(self):
         finally:
             kill_process(process)
 
-    @unittest2.skipIf(profiling.is_enabled(), "Profiling is enabled")
+    @unittest.skipIf(profiling.is_enabled(), "Profiling is enabled")
     def test_st2auth(self):
         port = random.randint(10000, 30000)
         cmd = (
diff --git a/st2api/tests/unit/controllers/v1/test_actions.py b/st2api/tests/unit/controllers/v1/test_actions.py
index 364349a798..6d263f1910 100644
--- a/st2api/tests/unit/controllers/v1/test_actions.py
+++ b/st2api/tests/unit/controllers/v1/test_actions.py
@@ -25,7 +25,7 @@
     import json
 
 import mock
-import unittest2
+import unittest
 from six.moves import http_client
 
 from st2common.persistence.action import Action
@@ -553,7 +553,7 @@ def test_post_discard_id_field(self):
         self.assertIn(b"id", post_resp.body)
         data = json.loads(post_resp.body)
         # Verify that user-provided id is discarded.
-        self.assertNotEquals(data["id"], ACTION_7["id"])
+        self.assertNotEqual(data["id"], ACTION_7["id"])
         self.__do_delete(self.__get_action_id(post_resp))
 
     @mock.patch.object(
@@ -862,14 +862,14 @@ def test_get_one_using_tag_parameter(self):
     # TODO: Re-enable those tests after we ensure DB is flushed in setUp
     # and each test starts in a clean state
 
-    @unittest2.skip("Skip because of test polution")
+    @unittest.skip("Skip because of test polution")
     def test_update_action_belonging_to_system_pack(self):
         post_resp = self.__do_post(ACTION_11)
         action_id = self.__get_action_id(post_resp)
         put_resp = self.__do_put(action_id, ACTION_11, expect_errors=True)
         self.assertEqual(put_resp.status_int, 400)
 
-    @unittest2.skip("Skip because of test polution")
+    @unittest.skip("Skip because of test polution")
     def test_delete_action_belonging_to_system_pack(self):
         post_resp = self.__do_post(ACTION_11)
         action_id = self.__get_action_id(post_resp)
diff --git a/st2api/tests/unit/controllers/v1/test_auth.py b/st2api/tests/unit/controllers/v1/test_auth.py
index a5a5aec0de..695d490faa 100644
--- a/st2api/tests/unit/controllers/v1/test_auth.py
+++ b/st2api/tests/unit/controllers/v1/test_auth.py
@@ -245,7 +245,7 @@ def test_apikey_not_found(self):
         )
         self.assertIn("application/json", response.headers["content-type"])
         self.assertEqual(response.status_int, 401)
-        self.assertRegexpMatches(
+        self.assertRegex(
             response.json_body["faultstring"],
             "^Unauthorized - ApiKey with key_hash=([a-zA-Z0-9]+) not found.$",
         )
diff --git a/st2api/tests/unit/controllers/v1/test_base.py b/st2api/tests/unit/controllers/v1/test_base.py
index c26dc0a692..f8b37e8906 100644
--- a/st2api/tests/unit/controllers/v1/test_base.py
+++ b/st2api/tests/unit/controllers/v1/test_base.py
@@ -110,6 +110,6 @@ def test_router_invalid_url_path_friendly_error(self):
         request = Request(environ={"PATH_INFO": "/v1/rules/好的".encode("utf-8")})
 
         expected_msg = "URL likely contains invalid or incorrectly URL encoded values"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             webob.exc.HTTPBadRequest, expected_msg, router.match, request
         )
diff --git a/st2api/tests/unit/test_validation_utils.py b/st2api/tests/unit/test_validation_utils.py
index b5c939221c..289f418448 100644
--- a/st2api/tests/unit/test_validation_utils.py
+++ b/st2api/tests/unit/test_validation_utils.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import unittest2
+import unittest
 from oslo_config import cfg
 
 from st2api.validation import validate_auth_cookie_is_correctly_configured
@@ -23,7 +23,7 @@
 __all__ = ["ValidationUtilsTestCase"]
 
 
-class ValidationUtilsTestCase(unittest2.TestCase):
+class ValidationUtilsTestCase(unittest.TestCase):
     def setUp(self):
         super(ValidationUtilsTestCase, self).setUp()
         tests_config.parse_args()
@@ -53,7 +53,7 @@ def test_validate_auth_cookie_is_correctly_configured_error(self):
             )
 
             expected_msg = "Valid values are: strict, lax, none, unset"
-            self.assertRaisesRegexp(
+            self.assertRaisesRegex(
                 ValueError, expected_msg, validate_auth_cookie_is_correctly_configured
             )
 
@@ -67,7 +67,7 @@ def test_validate_auth_cookie_is_correctly_configured_error(self):
             r"Failed to validate api.auth_cookie config options: Incompatible cookie attributes: "
             "when the samesite equals 'none', then the secure must be True"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError, expected_msg, validate_auth_cookie_is_correctly_configured
         )
 
@@ -83,7 +83,7 @@ def test_validate_rbac_is_correctly_configured_auth_not_enabled(self):
             "Authentication is not enabled. RBAC only works when authentication is "
             "enabled. You can either enable authentication or disable RBAC."
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError, expected_msg, validate_rbac_is_correctly_configured
         )
 
@@ -95,7 +95,7 @@ def test_validate_rbac_is_correctly_configured_non_default_backend_set(self):
         expected_msg = (
             'You have enabled RBAC, but RBAC backend is not set to "default".'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError, expected_msg, validate_rbac_is_correctly_configured
         )
 
diff --git a/st2auth/tests/unit/test_auth_backends.py b/st2auth/tests/unit/test_auth_backends.py
index e3225b33f0..58047a1097 100644
--- a/st2auth/tests/unit/test_auth_backends.py
+++ b/st2auth/tests/unit/test_auth_backends.py
@@ -13,12 +13,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import unittest2
+import unittest
 
 from st2auth.backends import get_available_backends
 
 
-class AuthenticationBackendsTestCase(unittest2.TestCase):
+class AuthenticationBackendsTestCase(unittest.TestCase):
     def test_flat_file_backend_is_available_by_default(self):
         available_backends = get_available_backends()
         self.assertIn("flat_file", available_backends)
diff --git a/st2auth/tests/unit/test_validation_utils.py b/st2auth/tests/unit/test_validation_utils.py
index 213e106625..328acc4127 100644
--- a/st2auth/tests/unit/test_validation_utils.py
+++ b/st2auth/tests/unit/test_validation_utils.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import unittest2
+import unittest
 from oslo_config import cfg
 
 from st2auth.validation import validate_auth_backend_is_correctly_configured
@@ -22,7 +22,7 @@
 __all__ = ["ValidationUtilsTestCase"]
 
 
-class ValidationUtilsTestCase(unittest2.TestCase):
+class ValidationUtilsTestCase(unittest.TestCase):
     def setUp(self):
         super(ValidationUtilsTestCase, self).setUp()
         tests_config.parse_args()
@@ -37,7 +37,7 @@ def test_validate_auth_backend_is_correctly_configured_invalid_backend(self):
             'Invalid auth mode "invalid" specified in the config. '
             "Valid modes are: proxy, standalone"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError, expected_msg, validate_auth_backend_is_correctly_configured
         )
 
@@ -57,6 +57,6 @@ def test_validate_auth_backend_is_correctly_configured_backend_doesnt_expose_gro
             "Configured auth backend doesn't expose user group information. Disable "
             "remote group synchronization or"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError, expected_msg, validate_auth_backend_is_correctly_configured
         )
diff --git a/st2client/tests/base.py b/st2client/tests/base.py
index e2ea29a3ae..193783bd02 100644
--- a/st2client/tests/base.py
+++ b/st2client/tests/base.py
@@ -20,7 +20,7 @@
 import logging
 
 import six
-import unittest2
+import unittest
 
 from st2client import models
 
@@ -70,7 +70,7 @@ def __init__(self):
         self.client = FakeClient()
 
 
-class BaseCLITestCase(unittest2.TestCase):
+class BaseCLITestCase(unittest.TestCase):
     capture_output = (
         True  # if True, stdout and stderr are saved to self.stdout and self.stderr
     )
diff --git a/st2client/tests/unit/test_app.py b/st2client/tests/unit/test_app.py
index 217d3875ad..f956c757ac 100644
--- a/st2client/tests/unit/test_app.py
+++ b/st2client/tests/unit/test_app.py
@@ -17,7 +17,7 @@
 import os
 import getpass
 
-import unittest2
+import unittest
 import mock
 
 from st2client.base import BaseCLIApp
@@ -25,7 +25,7 @@
 USER = getpass.getuser()
 
 
-class BaseCLIAppTestCase(unittest2.TestCase):
+class BaseCLIAppTestCase(unittest.TestCase):
     @mock.patch("os.path.isfile", mock.Mock())
     def test_cli_config_file_path(self):
         app = BaseCLIApp()
diff --git a/st2client/tests/unit/test_client.py b/st2client/tests/unit/test_client.py
index 01d5bb4800..11e9206056 100644
--- a/st2client/tests/unit/test_client.py
+++ b/st2client/tests/unit/test_client.py
@@ -18,7 +18,7 @@
 import os
 import json
 import logging
-import unittest2
+import unittest
 
 import six
 import mock
@@ -35,7 +35,7 @@
 NONRESOURCES = ["workflows"]
 
 
-class TestClientEndpoints(unittest2.TestCase):
+class TestClientEndpoints(unittest.TestCase):
     def tearDown(self):
         for var in [
             "ST2_BASE_URL",
@@ -192,7 +192,7 @@ def test_cacert_arg(self):
         # Invalid value, path to the bundle doesn't exist
         cacert = os.path.abspath(__file__)
         expected_msg = 'CA cert file "doesntexist" does not exist'
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             Client,
diff --git a/st2client/tests/unit/test_client_actions.py b/st2client/tests/unit/test_client_actions.py
index 141e7c8ece..a38df9ccbf 100644
--- a/st2client/tests/unit/test_client_actions.py
+++ b/st2client/tests/unit/test_client_actions.py
@@ -18,7 +18,7 @@
 import json
 import logging
 import mock
-import unittest2
+import unittest
 
 from tests import base
 
@@ -55,7 +55,7 @@
 )
 
 
-class TestActionResourceManager(unittest2.TestCase):
+class TestActionResourceManager(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         super(TestActionResourceManager, cls).setUpClass()
@@ -105,7 +105,7 @@ def test_get_action_entry_point_by_id(self):
         ),
     )
     def test_get_non_existent_action_entry_point(self):
-        with self.assertRaisesRegexp(Exception, "404 Client Error: Not Found"):
+        with self.assertRaisesRegex(Exception, "404 Client Error: Not Found"):
             self.client.actions.get_entrypoint("nonexistentpack.nonexistentaction")
 
         endpoint = "/actions/views/entry_point/%s" % "nonexistentpack.nonexistentaction"
diff --git a/st2client/tests/unit/test_client_executions.py b/st2client/tests/unit/test_client_executions.py
index a9dc19e2c3..725ea1c45a 100644
--- a/st2client/tests/unit/test_client_executions.py
+++ b/st2client/tests/unit/test_client_executions.py
@@ -19,7 +19,7 @@
 import logging
 import warnings
 import mock
-import unittest2
+import unittest
 
 from tests import base
 
@@ -55,7 +55,7 @@
 }
 
 
-class TestExecutionResourceManager(unittest2.TestCase):
+class TestExecutionResourceManager(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         super(TestExecutionResourceManager, cls).setUpClass()
diff --git a/st2client/tests/unit/test_command_actionrun.py b/st2client/tests/unit/test_command_actionrun.py
index afd6fb418d..805491f189 100644
--- a/st2client/tests/unit/test_command_actionrun.py
+++ b/st2client/tests/unit/test_command_actionrun.py
@@ -16,7 +16,7 @@
 from __future__ import absolute_import
 import copy
 
-import unittest2
+import unittest
 import six
 import mock
 
@@ -24,7 +24,7 @@
 from st2client.models.action import Action, RunnerType
 
 
-class ActionRunCommandTest(unittest2.TestCase):
+class ActionRunCommandTest(unittest.TestCase):
     def test_get_params_types(self):
         runner = RunnerType()
         runner_params = {
diff --git a/st2client/tests/unit/test_commands.py b/st2client/tests/unit/test_commands.py
index 729b671cd4..929a327e98 100644
--- a/st2client/tests/unit/test_commands.py
+++ b/st2client/tests/unit/test_commands.py
@@ -20,7 +20,7 @@
 import logging
 import argparse
 import tempfile
-import unittest2
+import unittest
 from collections import namedtuple
 
 from tests import base
@@ -102,7 +102,7 @@ def test_all_resources_get_multi(self):
             self._reset_output_streams()
 
 
-class TestResourceCommand(unittest2.TestCase):
+class TestResourceCommand(unittest.TestCase):
     def __init__(self, *args, **kwargs):
         super(TestResourceCommand, self).__init__(*args, **kwargs)
         self.parser = argparse.ArgumentParser()
@@ -401,7 +401,7 @@ def test_command_get_unicode_primary_key(self):
         self.assertEqual(actual, expected)
 
 
-class ResourceViewCommandTestCase(unittest2.TestCase):
+class ResourceViewCommandTestCase(unittest.TestCase):
     def setUp(self):
         ResourceViewCommand.display_attributes = []
 
diff --git a/st2client/tests/unit/test_config_parser.py b/st2client/tests/unit/test_config_parser.py
index eec8694442..39d5a48473 100644
--- a/st2client/tests/unit/test_config_parser.py
+++ b/st2client/tests/unit/test_config_parser.py
@@ -20,7 +20,7 @@
 
 import mock
 import six
-import unittest2
+import unittest
 
 from st2client.config_parser import CLIConfigParser
 from st2client.config_parser import CONFIG_DEFAULT_VALUES
@@ -31,7 +31,7 @@
 CONFIG_FILE_PATH_UNICODE = os.path.join(BASE_DIR, "../fixtures/test_unicode.ini")
 
 
-class CLIConfigParserTestCase(unittest2.TestCase):
+class CLIConfigParserTestCase(unittest.TestCase):
     def test_constructor(self):
         parser = CLIConfigParser(
             config_file_path="doesnotexist", validate_config_exists=False
@@ -102,7 +102,7 @@ def test_get_config_for_unicode_char(self):
             self.assertEqual(config["credentials"]["password"], "\u5bc6\u7801\u0025")
 
 
-class CLIConfigPermissionsTestCase(unittest2.TestCase):
+class CLIConfigPermissionsTestCase(unittest.TestCase):
     def setUp(self):
         self.TEMP_FILE_PATH = os.path.join("st2config", ".st2", "config")
         self.TEMP_CONFIG_DIR = os.path.dirname(self.TEMP_FILE_PATH)
diff --git a/st2client/tests/unit/test_formatters.py b/st2client/tests/unit/test_formatters.py
index d64f75a827..ab20cd110e 100644
--- a/st2client/tests/unit/test_formatters.py
+++ b/st2client/tests/unit/test_formatters.py
@@ -21,7 +21,7 @@
 import json
 import logging
 import tempfile
-import unittest2
+import unittest
 
 from io import BytesIO
 from six.moves import StringIO
@@ -84,7 +84,7 @@ def redirect_console_for_pytest(request):
 
 
 @pytest.mark.usefixtures("redirect_console_for_pytest")
-class TestExecutionResultFormatter(unittest2.TestCase):
+class TestExecutionResultFormatter(unittest.TestCase):
     def __init__(self, *args, **kwargs):
         super(TestExecutionResultFormatter, self).__init__(*args, **kwargs)
         self.shell = shell.Shell()
diff --git a/st2client/tests/unit/test_interactive.py b/st2client/tests/unit/test_interactive.py
index dce4c6748d..547782ce53 100644
--- a/st2client/tests/unit/test_interactive.py
+++ b/st2client/tests/unit/test_interactive.py
@@ -17,7 +17,7 @@
 import logging
 import mock
 import re
-import unittest2
+import unittest
 
 import prompt_toolkit
 from prompt_toolkit.document import Document
@@ -30,7 +30,7 @@
 LOG = logging.getLogger(__name__)
 
 
-class TestInteractive(unittest2.TestCase):
+class TestInteractive(unittest.TestCase):
     def assertPromptMessage(self, prompt_mock, message, msg=None):
         self.assertEqual(prompt_mock.call_args[0], (message,), msg)
 
diff --git a/st2client/tests/unit/test_keyvalue.py b/st2client/tests/unit/test_keyvalue.py
index 52c240a052..955c08738e 100644
--- a/st2client/tests/unit/test_keyvalue.py
+++ b/st2client/tests/unit/test_keyvalue.py
@@ -130,7 +130,7 @@ def test_set_keyvalue(self):
     def test_encrypt_and_encrypted_flags_are_mutually_exclusive(self):
         args = ["key", "set", "--encrypt", "--encrypted", "kv_name", "AAABBBCCC1234"]
 
-        self.assertRaisesRegexp(SystemExit, "2", self.shell.run, args)
+        self.assertRaisesRegex(SystemExit, "2", self.shell.run, args)
 
         self.stderr.seek(0)
         stderr = self.stderr.read()
diff --git a/st2client/tests/unit/test_models.py b/st2client/tests/unit/test_models.py
index 8f5d6bd6d7..31e762eb81 100644
--- a/st2client/tests/unit/test_models.py
+++ b/st2client/tests/unit/test_models.py
@@ -17,7 +17,7 @@
 import mock
 import json
 import logging
-import unittest2
+import unittest
 
 from tests import base
 
@@ -28,7 +28,7 @@
 LOG = logging.getLogger(__name__)
 
 
-class TestSerialization(unittest2.TestCase):
+class TestSerialization(unittest.TestCase):
     def test_resource_serialize(self):
         instance = base.FakeResource(id="123", name="abc")
         self.assertDictEqual(instance.serialize(), base.RESOURCES[0])
@@ -39,7 +39,7 @@ def test_resource_deserialize(self):
         self.assertEqual(instance.name, "abc")
 
 
-class TestResourceManager(unittest2.TestCase):
+class TestResourceManager(unittest.TestCase):
     @mock.patch.object(
         httpclient.HTTPClient,
         "get",
@@ -473,7 +473,7 @@ def test_resource_clone_failed(self):
         self.assertRaises(Exception, mgr.clone, source_ref, "dpack", "daction")
 
 
-class TestKeyValuePairResourceManager(unittest2.TestCase):
+class TestKeyValuePairResourceManager(unittest.TestCase):
     @mock.patch.object(
         httpclient.HTTPClient,
         "get",
diff --git a/st2client/tests/unit/test_shell.py b/st2client/tests/unit/test_shell.py
index 5eb27714ca..e3950e11f6 100644
--- a/st2client/tests/unit/test_shell.py
+++ b/st2client/tests/unit/test_shell.py
@@ -27,7 +27,7 @@
 import requests
 import six
 import mock
-import unittest2
+import unittest
 
 import st2client
 from st2client.shell import Shell
@@ -516,7 +516,7 @@ def _write_mock_package_metadata_file(self):
 
         return package_metadata_path
 
-    @unittest2.skipIf(True, "skipping until checks are re-enabled")
+    @unittest.skipIf(True, "skipping until checks are re-enabled")
     @mock.patch.object(
         requests, "get", mock.MagicMock(return_value=base.FakeResponse("{}", 200, "OK"))
     )
@@ -578,7 +578,7 @@ def test_policy_list_with_pack_option(self):
             )
 
 
-class CLITokenCachingTestCase(unittest2.TestCase):
+class CLITokenCachingTestCase(unittest.TestCase):
     def setUp(self):
         super(CLITokenCachingTestCase, self).setUp()
         self._mock_temp_dir_path = tempfile.mkdtemp()
@@ -644,7 +644,7 @@ def test_get_cached_auth_token_invalid_permissions(self):
             "Unable to retrieve cached token from .*? read access to the parent "
             "directory"
         )
-        self.assertRegexpMatches(log_message, expected_msg)
+        self.assertRegex(log_message, expected_msg)
 
         # 2. Read access on the directory, but not on the cached token file
         os.chmod(self._mock_config_directory_path, 0o777)  # nosec
@@ -662,7 +662,7 @@ def test_get_cached_auth_token_invalid_permissions(self):
         expected_msg = (
             "Unable to retrieve cached token from .*? read access to this file"
         )
-        self.assertRegexpMatches(log_message, expected_msg)
+        self.assertRegex(log_message, expected_msg)
 
         # 3. Other users also have read access to the file
         os.chmod(self._mock_config_directory_path, 0o777)  # nosec
@@ -678,7 +678,7 @@ def test_get_cached_auth_token_invalid_permissions(self):
         log_message = shell.LOG.warn.call_args[0][0]
 
         expected_msg = "Permissions .*? for cached token file .*? are too permissive.*"
-        self.assertRegexpMatches(log_message, expected_msg)
+        self.assertRegex(log_message, expected_msg)
 
     def test_cache_auth_token_invalid_permissions(self):
         shell = Shell()
@@ -707,7 +707,7 @@ def test_cache_auth_token_invalid_permissions(self):
             "Unable to write token to .*? doesn't have write access to the parent "
             "directory"
         )
-        self.assertRegexpMatches(log_message, expected_msg)
+        self.assertRegex(log_message, expected_msg)
 
         # 2. Current user has no write access to the cached token file
         os.chmod(self._mock_config_directory_path, 0o777)  # nosec
@@ -722,7 +722,7 @@ def test_cache_auth_token_invalid_permissions(self):
         expected_msg = (
             "Unable to write token to .*? doesn't have write access to this file"
         )
-        self.assertRegexpMatches(log_message, expected_msg)
+        self.assertRegex(log_message, expected_msg)
 
     def test_get_cached_auth_token_no_token_cache_file(self):
         client = Client()
@@ -746,7 +746,7 @@ def test_get_cached_auth_token_corrupted_token_cache_file(self):
             fp.write("CORRRRRUPTED!")
 
         expected_msg = "File (.+) with cached token is corrupted or invalid"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             shell._get_cached_auth_token,
diff --git a/st2client/tests/unit/test_util_date.py b/st2client/tests/unit/test_util_date.py
index 2cdeab95fc..a556709a5a 100644
--- a/st2client/tests/unit/test_util_date.py
+++ b/st2client/tests/unit/test_util_date.py
@@ -17,7 +17,7 @@
 import datetime
 
 import mock
-import unittest2
+import unittest
 
 from st2client.utils.date import add_utc_tz
 from st2client.utils.date import format_dt
@@ -25,7 +25,7 @@
 from st2client.utils.date import format_isodate_for_user_timezone
 
 
-class DateUtilsTestCase(unittest2.TestCase):
+class DateUtilsTestCase(unittest.TestCase):
     def test_format_dt(self):
         dt = datetime.datetime(2015, 10, 20, 8, 0, 0)
         dt = add_utc_tz(dt)
diff --git a/st2client/tests/unit/test_util_json.py b/st2client/tests/unit/test_util_json.py
index 2333128c2e..59373151ce 100644
--- a/st2client/tests/unit/test_util_json.py
+++ b/st2client/tests/unit/test_util_json.py
@@ -17,7 +17,7 @@
 import json
 import logging
 import mock
-import unittest2
+import unittest
 
 from st2client.utils import jsutil
 
@@ -38,7 +38,7 @@
 }
 
 
-class TestGetValue(unittest2.TestCase):
+class TestGetValue(unittest.TestCase):
     def test_dot_notation(self):
         self.assertEqual(jsutil.get_value(DOC, "a01"), 1)
         self.assertEqual(jsutil.get_value(DOC, "c01.c11"), 3)
@@ -108,7 +108,7 @@ def test_double_dot_calls_complex(self, mock__get_value_complex):
         mock__get_value_complex.assert_called_with(DOC, "c01..c11")
 
 
-class TestGetKeyValuePairs(unittest2.TestCase):
+class TestGetKeyValuePairs(unittest.TestCase):
     def test_select_kvps(self):
         self.assertEqual(jsutil.get_kvps(DOC, ["a01"]), {"a01": 1})
         self.assertEqual(jsutil.get_kvps(DOC, ["c01.c11"]), {"c01": {"c11": 3}})
diff --git a/st2client/tests/unit/test_util_misc.py b/st2client/tests/unit/test_util_misc.py
index 2e33156adc..e970fcdbbe 100644
--- a/st2client/tests/unit/test_util_misc.py
+++ b/st2client/tests/unit/test_util_misc.py
@@ -14,12 +14,12 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2client.utils.misc import merge_dicts
 
 
-class MiscUtilTestCase(unittest2.TestCase):
+class MiscUtilTestCase(unittest.TestCase):
     def test_merge_dicts(self):
         d1 = {"a": 1}
         d2 = {"a": 2}
diff --git a/st2client/tests/unit/test_util_strutil.py b/st2client/tests/unit/test_util_strutil.py
index 585e88c389..5d38758b99 100644
--- a/st2client/tests/unit/test_util_strutil.py
+++ b/st2client/tests/unit/test_util_strutil.py
@@ -16,12 +16,12 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2client.utils import strutil
 
 
-class StrUtilTestCase(unittest2.TestCase):
+class StrUtilTestCase(unittest.TestCase):
     # See https://mail.python.org/pipermail/python-list/2006-January/411909.html
 
     def test_unescape(self):
diff --git a/st2client/tests/unit/test_util_terminal.py b/st2client/tests/unit/test_util_terminal.py
index 29a8386b0b..e5b5a272f1 100644
--- a/st2client/tests/unit/test_util_terminal.py
+++ b/st2client/tests/unit/test_util_terminal.py
@@ -17,7 +17,7 @@
 
 import os
 
-import unittest2
+import unittest
 import mock
 
 from st2client.utils.terminal import DEFAULT_TERMINAL_SIZE_COLUMNS
@@ -26,7 +26,7 @@
 __all__ = ["TerminalUtilsTestCase"]
 
 
-class TerminalUtilsTestCase(unittest2.TestCase):
+class TerminalUtilsTestCase(unittest.TestCase):
     def setUp(self):
         super(TerminalUtilsTestCase, self).setUp()
 
diff --git a/st2common/tests/integration/test_rabbitmq_ssl_listener.py b/st2common/tests/integration/test_rabbitmq_ssl_listener.py
index e7b2ce1247..e56bfa65d9 100644
--- a/st2common/tests/integration/test_rabbitmq_ssl_listener.py
+++ b/st2common/tests/integration/test_rabbitmq_ssl_listener.py
@@ -20,7 +20,7 @@
 import socket
 
 import six
-import unittest2
+import unittest
 from oslo_config import cfg
 
 from st2common.transport import utils as transport_utils
@@ -37,11 +37,11 @@
 
 # NOTE: We only run those tests on the CI provider because at the moment, local
 #       vagrant dev VM doesn't expose RabbitMQ SSL listener by default
-@unittest2.skipIf(
+@unittest.skipIf(
     not ST2_CI,
     'Skipping tests because ST2_CI environment variable is not set to "true"',
 )
-class RabbitMQTLSListenerTestCase(unittest2.TestCase):
+class RabbitMQTLSListenerTestCase(unittest.TestCase):
     def setUp(self):
         # Set default values
         cfg.CONF.set_override(name="ssl", override=False, group="messaging")
@@ -135,7 +135,7 @@ def test_ssl_connection_ca_certs_provided(self):
         )
 
         expected_msg = r"\[SSL: CERTIFICATE_VERIFY_FAILED\] certificate verify failed"
-        self.assertRaisesRegexp(ssl.SSLError, expected_msg, connection.connect)
+        self.assertRaisesRegex(ssl.SSLError, expected_msg, connection.connect)
 
         # 3. Validate server cert against other CA bundle (failure)
         ca_cert_path = os.path.join("/etc/ssl/certs/SecureTrust_CA.pem")
@@ -152,7 +152,7 @@ def test_ssl_connection_ca_certs_provided(self):
         )
 
         expected_msg = r"\[SSL: CERTIFICATE_VERIFY_FAILED\] certificate verify failed"
-        self.assertRaisesRegexp(ssl.SSLError, expected_msg, connection.connect)
+        self.assertRaisesRegex(ssl.SSLError, expected_msg, connection.connect)
 
         # 4. Validate server cert against other CA bundle (failure)
         # We use invalid bundle but cert_reqs is none
@@ -231,4 +231,4 @@ def test_ssl_connect_client_side_cert_authentication(self):
         )
 
         expected_msg = r"\[X509: KEY_VALUES_MISMATCH\] key values mismatch"
-        self.assertRaisesRegexp(ssl.SSLError, expected_msg, connection.connect)
+        self.assertRaisesRegex(ssl.SSLError, expected_msg, connection.connect)
diff --git a/st2common/tests/unit/services/test_action.py b/st2common/tests/unit/services/test_action.py
index e27126ec86..85679018fa 100644
--- a/st2common/tests/unit/services/test_action.py
+++ b/st2common/tests/unit/services/test_action.py
@@ -629,7 +629,7 @@ def test_invalid_json_request_validate(self):
         parameters = {"hosts": "127.0.0.1", "cmd": "uname -a", "arg_default_value": 123}
         liveaction = LiveActionDB(action=ACTION_REF, parameters=parameters)
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             jsonschema.ValidationError,
             "123 is not of type 'string'",
             action_service.create_request,
diff --git a/st2common/tests/unit/services/test_packs.py b/st2common/tests/unit/services/test_packs.py
index e93258aa0b..69152a8398 100644
--- a/st2common/tests/unit/services/test_packs.py
+++ b/st2common/tests/unit/services/test_packs.py
@@ -20,7 +20,7 @@
 import os
 import mock
 import shutil
-import unittest2
+import unittest
 import uuid
 
 from st2common.models.db.stormbase import UIDFieldMixin
@@ -201,7 +201,7 @@
 }
 
 
-class DeleteActionFilesTest(unittest2.TestCase):
+class DeleteActionFilesTest(unittest.TestCase):
     def test_delete_action_files_from_pack(self):
         """
         Test that the action files present in the pack and removed
@@ -280,7 +280,7 @@ def test_metadata_file_does_not_exists(self):
         self.assertFalse(os.path.exists(metadata_file))
 
 
-class DeleteActionEntryPointFilesErrorTest(unittest2.TestCase):
+class DeleteActionEntryPointFilesErrorTest(unittest.TestCase):
     """
     Testing that exceptions are thrown by delete_action_files_from_pack function
     for entry point file. Here only entry point file is created and metadata
@@ -318,7 +318,7 @@ def test_permission_error_to_remove_resource_entry_point_file(self, remove):
 
         # asserting PermissionError with message on call of delete_action_files_from_pack
         # to delete entry_point file
-        with self.assertRaisesRegexp(PermissionError, expected_msg):
+        with self.assertRaisesRegex(PermissionError, expected_msg):
             delete_action_files_from_pack(TEST_PACK, entry_point, metadata_file)
 
     @mock.patch.object(os, "remove")
@@ -343,11 +343,11 @@ def test_exception_to_remove_resource_entry_point_file(self, remove):
 
         # asserting exception with message on call of delete_action_files_from_pack
         # to delete entry_point file
-        with self.assertRaisesRegexp(Exception, expected_msg):
+        with self.assertRaisesRegex(Exception, expected_msg):
             delete_action_files_from_pack(TEST_PACK, entry_point, metadata_file)
 
 
-class DeleteActionMetadataFilesErrorTest(unittest2.TestCase):
+class DeleteActionMetadataFilesErrorTest(unittest.TestCase):
     """
     Testing that exceptions are thrown by delete_action_files_from_pack function for
     metadata file. Here only metadata file is created and metadata file doesn't exist.
@@ -384,7 +384,7 @@ def test_permission_error_to_remove_resource_metadata_file(self, remove):
 
         # asserting PermissionError with message on call of delete_action_files_from_pack
         # to delete metadata file
-        with self.assertRaisesRegexp(PermissionError, expected_msg):
+        with self.assertRaisesRegex(PermissionError, expected_msg):
             delete_action_files_from_pack(TEST_PACK, entry_point, metadata_file)
 
     @mock.patch.object(os, "remove")
@@ -409,11 +409,11 @@ def test_exception_to_remove_resource_metadata_file(self, remove):
 
         # asserting exception with message on call of delete_action_files_from_pack
         # to delete metadata file
-        with self.assertRaisesRegexp(Exception, expected_msg):
+        with self.assertRaisesRegex(Exception, expected_msg):
             delete_action_files_from_pack(TEST_PACK, entry_point, metadata_file)
 
 
-class CloneActionDBAndFilesTestCase(unittest2.TestCase):
+class CloneActionDBAndFilesTestCase(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         action_files_path = os.path.join(TEST_DEST_PACK_PATH, "actions")
@@ -523,7 +523,7 @@ def test_permission_error_to_write_in_destination_file(self, mock_copy):
         CLONE_ACTION_4 = clone_action_db(
             SOURCE_ACTION_WITH_SHELL_SCRIPT_RUNNER, TEST_DEST_PACK, "clone_action_4"
         )
-        with self.assertRaisesRegexp(PermissionError, expected_msg):
+        with self.assertRaisesRegex(PermissionError, expected_msg):
             clone_action_files(
                 SOURCE_ACTION_WITH_SHELL_SCRIPT_RUNNER,
                 CLONE_ACTION_4,
@@ -546,7 +546,7 @@ def test_exceptions_to_write_in_destination_file(self, mock_copy):
             "administrator to clone the files manually."
             % cloned_action_metadata_file_path
         )
-        with self.assertRaisesRegexp(Exception, expected_msg):
+        with self.assertRaisesRegex(Exception, expected_msg):
             clone_action_files(
                 SOURCE_ACTION_WITH_LOCAL_SHELL_CMD_RUNNER,
                 CLONE_ACTION_5,
@@ -587,7 +587,7 @@ def test_workflows_directory_created_if_does_not_exist(self):
         self.assertTrue(os.path.exists(workflows_dir_path))
 
 
-class CloneActionFilesBackupTestCase(unittest2.TestCase):
+class CloneActionFilesBackupTestCase(unittest.TestCase):
     @classmethod
     def tearDownClass(cls):
         action_files_path = os.path.join(TEST_DEST_PACK_PATH, "actions")
@@ -689,7 +689,7 @@ def test_exception_remove_temp_action_files(self):
         )
         with mock.patch("shutil.rmtree") as mock_rmdir:
             mock_rmdir.side_effect = Exception
-            with self.assertRaisesRegexp(Exception, expected_msg):
+            with self.assertRaisesRegex(Exception, expected_msg):
                 remove_temp_action_files(temp_sub_dir)
 
         remove_temp_action_files(temp_sub_dir)
@@ -715,7 +715,7 @@ def test_permission_error_remove_temp_action_files(self):
         expected_msg = 'No permission to delete the "%s" directory' % temp_dir_path
         with mock.patch("shutil.rmtree") as mock_rmdir:
             mock_rmdir.side_effect = PermissionError
-            with self.assertRaisesRegexp(PermissionError, expected_msg):
+            with self.assertRaisesRegex(PermissionError, expected_msg):
                 remove_temp_action_files(temp_sub_dir)
 
         remove_temp_action_files(temp_sub_dir)
@@ -740,7 +740,7 @@ def test_exception_temp_backup_action_files(self):
         )
         with mock.patch("shutil.copy") as mock_copy:
             mock_copy.side_effect = Exception
-            with self.assertRaisesRegexp(Exception, expected_msg):
+            with self.assertRaisesRegex(Exception, expected_msg):
                 temp_backup_action_files(
                     TEST_DEST_PACK_PATH,
                     dest_action_metadata_file,
@@ -767,7 +767,7 @@ def test_permission_error_temp_backup_action_files(self):
         expected_msg = 'Unable to copy file to "%s".' % tmp_action_metadata_file_path
         with mock.patch("shutil.copy") as mock_copy:
             mock_copy.side_effect = PermissionError
-            with self.assertRaisesRegexp(PermissionError, expected_msg):
+            with self.assertRaisesRegex(PermissionError, expected_msg):
                 temp_backup_action_files(
                     TEST_DEST_PACK_PATH,
                     dest_action_metadata_file,
@@ -801,7 +801,7 @@ def test_exception_restore_temp_action_files(self):
         )
         with mock.patch("shutil.copy") as mock_copy:
             mock_copy.side_effect = Exception
-            with self.assertRaisesRegexp(Exception, expected_msg):
+            with self.assertRaisesRegex(Exception, expected_msg):
                 restore_temp_action_files(
                     TEST_DEST_PACK_PATH,
                     dest_action_metadata_file,
@@ -831,7 +831,7 @@ def test_permission_error_restore_temp_action_files(self):
         expected_msg = 'Unable to copy file to "%s".' % dest_action_metadata_file_path
         with mock.patch("shutil.copy") as mock_copy:
             mock_copy.side_effect = PermissionError
-            with self.assertRaisesRegexp(PermissionError, expected_msg):
+            with self.assertRaisesRegex(PermissionError, expected_msg):
                 restore_temp_action_files(
                     TEST_DEST_PACK_PATH,
                     dest_action_metadata_file,
diff --git a/st2common/tests/unit/services/test_synchronization.py b/st2common/tests/unit/services/test_synchronization.py
index e0c42b50f4..e6dfd2ec66 100644
--- a/st2common/tests/unit/services/test_synchronization.py
+++ b/st2common/tests/unit/services/test_synchronization.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 import uuid
 
 from oslo_config import cfg
@@ -23,7 +23,7 @@
 import st2tests.config as tests_config
 
 
-class SynchronizationTest(unittest2.TestCase):
+class SynchronizationTest(unittest.TestCase):
     coordinator = None
 
     @classmethod
diff --git a/st2common/tests/unit/services/test_trace.py b/st2common/tests/unit/services/test_trace.py
index 39db4c7ade..b7f8a2ddaf 100644
--- a/st2common/tests/unit/services/test_trace.py
+++ b/st2common/tests/unit/services/test_trace.py
@@ -19,7 +19,7 @@
 from collections import OrderedDict
 
 import bson
-from unittest2 import TestCase
+from unittest import TestCase
 
 from st2common.exceptions.db import StackStormDBObjectNotFoundError
 from st2common.exceptions.trace import UniqueTraceNotFoundException
diff --git a/st2common/tests/unit/services/test_workflow_rerun.py b/st2common/tests/unit/services/test_workflow_rerun.py
index 1fb10ace6f..b1bcb7417c 100644
--- a/st2common/tests/unit/services/test_workflow_rerun.py
+++ b/st2common/tests/unit/services/test_workflow_rerun.py
@@ -185,7 +185,7 @@ def test_request_rerun_while_original_is_still_running(self):
             "because it is not in a completed state.$"
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             wf_exc.WorkflowExecutionRerunException,
             expected_error,
             workflow_service.request_rerun,
@@ -244,7 +244,7 @@ def test_request_rerun_again_while_prev_rerun_is_still_running(self):
             "because it is not in a completed state.$"
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             wf_exc.WorkflowExecutionRerunException,
             expected_error,
             workflow_service.request_rerun,
@@ -274,7 +274,7 @@ def test_request_rerun_with_missing_workflow_execution_id(self):
             "workflow_execution_id is not provided."
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             wf_exc.WorkflowExecutionRerunException,
             expected_error,
             workflow_service.request_rerun,
@@ -304,7 +304,7 @@ def test_request_rerun_with_nonexistent_workflow_execution(self):
             '^Unable to rerun workflow execution ".*" ' "because it does not exist.$"
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             wf_exc.WorkflowExecutionRerunException,
             expected_error,
             workflow_service.request_rerun,
@@ -339,7 +339,7 @@ def test_request_rerun_with_workflow_execution_not_abended(self):
             "because it is not in a completed state.$"
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             wf_exc.WorkflowExecutionRerunException,
             expected_error,
             workflow_service.request_rerun,
@@ -373,7 +373,7 @@ def test_request_rerun_with_conductor_status_not_abended(self):
             "Unable to rerun workflow because it is not in a completed state."
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             wf_exc.WorkflowExecutionRerunException,
             expected_error,
             workflow_service.request_rerun,
@@ -403,7 +403,7 @@ def test_request_rerun_with_bad_task_name(self):
             "^Unable to rerun workflow because one or more tasks is not found: .*$"
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             wf_exc.WorkflowExecutionRerunException,
             expected_error,
             workflow_service.request_rerun,
@@ -438,7 +438,7 @@ def test_request_rerun_with_conductor_status_not_resuming(self):
             "get_workflow_status",
             mock.MagicMock(return_value=wf_statuses.FAILED),
         ):
-            self.assertRaisesRegexp(
+            self.assertRaisesRegex(
                 wf_exc.WorkflowExecutionRerunException,
                 expected_error,
                 workflow_service.request_rerun,
diff --git a/st2common/tests/unit/test_action_alias_utils.py b/st2common/tests/unit/test_action_alias_utils.py
index daad0fbe1e..ec3251070a 100644
--- a/st2common/tests/unit/test_action_alias_utils.py
+++ b/st2common/tests/unit/test_action_alias_utils.py
@@ -23,7 +23,7 @@
     AT_END_STRING,
 )
 from mock import Mock
-from unittest2 import TestCase
+from unittest import TestCase
 from st2common.exceptions.content import ParseException
 from st2common.models.utils.action_alias_utils import (
     ActionAliasFormatParser,
@@ -215,7 +215,7 @@ def test_stream_is_none_no_default_values(self):
         expected_msg = (
             'Command "" doesn\'t match format string "skip {{d}} more skip {{e}}."'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ParseException, expected_msg, parser.get_extracted_param_value
         )
 
@@ -251,7 +251,7 @@ def test_command_doesnt_match_format_string(self):
         expected_msg = (
             'Command "foo lulz ponies" doesn\'t match format string "foo bar ponies"'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ParseException, expected_msg, parser.get_extracted_param_value
         )
 
diff --git a/st2common/tests/unit/test_action_system_models.py b/st2common/tests/unit/test_action_system_models.py
index c8812acf38..3fccf70028 100644
--- a/st2common/tests/unit/test_action_system_models.py
+++ b/st2common/tests/unit/test_action_system_models.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.models.system.action import RemoteAction
 from st2common.models.system.action import RemoteScriptAction
@@ -22,7 +22,7 @@
 __all__ = ["RemoteActionTestCase", "RemoteScriptActionTestCase"]
 
 
-class RemoteActionTestCase(unittest2.TestCase):
+class RemoteActionTestCase(unittest.TestCase):
     def test_instantiation(self):
         action = RemoteAction(
             name="name",
@@ -48,7 +48,7 @@ def test_instantiation(self):
         self.assertEqual(action.timeout, 10)
 
 
-class RemoteScriptActionTestCase(unittest2.TestCase):
+class RemoteScriptActionTestCase(unittest.TestCase):
     def test_instantiation(self):
         action = RemoteScriptAction(
             name="name",
diff --git a/st2common/tests/unit/test_actionchain_schema.py b/st2common/tests/unit/test_actionchain_schema.py
index 1b1286b975..743fb45bcd 100644
--- a/st2common/tests/unit/test_actionchain_schema.py
+++ b/st2common/tests/unit/test_actionchain_schema.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from jsonschema.exceptions import ValidationError
 from st2common.models.system import actionchain
@@ -40,7 +40,7 @@
 CHAIN_WITH_PUBLISH = FIXTURES["actionchains"]["chain_with_publish.yaml"]
 
 
-class ActionChainSchemaTest(unittest2.TestCase):
+class ActionChainSchemaTest(unittest.TestCase):
     def test_actionchain_schema_valid(self):
         chain = actionchain.ActionChain(**CHAIN_1)
         self.assertEqual(len(chain.chain), len(CHAIN_1["chain"]))
diff --git a/st2common/tests/unit/test_api_model_validation.py b/st2common/tests/unit/test_api_model_validation.py
index 20eb98ce6c..bb2a04ff47 100644
--- a/st2common/tests/unit/test_api_model_validation.py
+++ b/st2common/tests/unit/test_api_model_validation.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.models.api.base import BaseAPI
 
@@ -111,7 +111,7 @@ class MockAPIModel2(BaseAPI):
     }
 
 
-class APIModelValidationTestCase(unittest2.TestCase):
+class APIModelValidationTestCase(unittest.TestCase):
     def test_validate_default_values_are_set(self):
         # no "permission_grants" attribute
         mock_model_api = MockAPIModel1(name="name")
diff --git a/st2common/tests/unit/test_casts.py b/st2common/tests/unit/test_casts.py
index 62bf0ac4e8..6adb957826 100644
--- a/st2common/tests/unit/test_casts.py
+++ b/st2common/tests/unit/test_casts.py
@@ -16,12 +16,12 @@
 from __future__ import absolute_import
 import json
 
-import unittest2
+import unittest
 
 from st2common.util.casts import get_cast
 
 
-class CastsTestCase(unittest2.TestCase):
+class CastsTestCase(unittest.TestCase):
     def test_cast_string(self):
         cast_func = get_cast("string")
 
@@ -45,7 +45,7 @@ def test_cast_string(self):
         # Non string or non, should throw a friendly exception
         value = []
         expected_msg = r'Value "\[\]" must either be a string or None. Got "list"'
-        self.assertRaisesRegexp(ValueError, expected_msg, cast_func, value)
+        self.assertRaisesRegex(ValueError, expected_msg, cast_func, value)
 
     def test_cast_array(self):
         cast_func = get_cast("array")
diff --git a/st2common/tests/unit/test_config_loader.py b/st2common/tests/unit/test_config_loader.py
index c123d52db2..c77ceba214 100644
--- a/st2common/tests/unit/test_config_loader.py
+++ b/st2common/tests/unit/test_config_loader.py
@@ -345,7 +345,7 @@ def test_get_config_dynamic_config_item_render_fails_user_friendly_exception_is_
             "<class 'jinja2.exceptions.UndefinedError'> "
             "'st2kvXX' is undefined"
         )
-        self.assertRaisesRegexp(RuntimeError, expected_msg, loader.get_config)
+        self.assertRaisesRegex(RuntimeError, expected_msg, loader.get_config)
         config_db.delete()
 
         # Renders fails on fist level item
@@ -359,7 +359,7 @@ def test_get_config_dynamic_config_item_render_fails_user_friendly_exception_is_
             " for pack \".*?\" config: <class 'jinja2.exceptions.UndefinedError'>"
             " 'st2kvXX' is undefined"
         )
-        self.assertRaisesRegexp(RuntimeError, expected_msg, loader.get_config)
+        self.assertRaisesRegex(RuntimeError, expected_msg, loader.get_config)
         config_db.delete()
 
         # Renders fails on second level item
@@ -375,7 +375,7 @@ def test_get_config_dynamic_config_item_render_fails_user_friendly_exception_is_
             " for pack \".*?\" config: <class 'jinja2.exceptions.UndefinedError'>"
             " 'st2kvXX' is undefined"
         )
-        self.assertRaisesRegexp(RuntimeError, expected_msg, loader.get_config)
+        self.assertRaisesRegex(RuntimeError, expected_msg, loader.get_config)
         config_db.delete()
 
         # Renders fails on list item
@@ -389,7 +389,7 @@ def test_get_config_dynamic_config_item_render_fails_user_friendly_exception_is_
             " for pack \".*?\" config: <class 'jinja2.exceptions.UndefinedError'>"
             " 'st2kvXX' is undefined"
         )
-        self.assertRaisesRegexp(RuntimeError, expected_msg, loader.get_config)
+        self.assertRaisesRegex(RuntimeError, expected_msg, loader.get_config)
         config_db.delete()
 
         # Renders fails on nested object in list item
@@ -403,7 +403,7 @@ def test_get_config_dynamic_config_item_render_fails_user_friendly_exception_is_
             " for pack \".*?\" config: <class 'jinja2.exceptions.UndefinedError'>"
             " 'st2kvXX' is undefined"
         )
-        self.assertRaisesRegexp(RuntimeError, expected_msg, loader.get_config)
+        self.assertRaisesRegex(RuntimeError, expected_msg, loader.get_config)
         config_db.delete()
 
         # Renders fails on invalid syntax
@@ -417,7 +417,7 @@ def test_get_config_dynamic_config_item_render_fails_user_friendly_exception_is_
             " for pack \".*?\" config: <class 'jinja2.exceptions.TemplateSyntaxError'>"
             " expected token 'end of print statement', got 'Jinja'"
         )
-        self.assertRaisesRegexp(RuntimeError, expected_msg, loader.get_config)
+        self.assertRaisesRegex(RuntimeError, expected_msg, loader.get_config)
         config_db.delete()
 
     def test_get_config_dynamic_config_item(self):
diff --git a/st2common/tests/unit/test_config_parser.py b/st2common/tests/unit/test_config_parser.py
index 883ee66260..04cd7636af 100644
--- a/st2common/tests/unit/test_config_parser.py
+++ b/st2common/tests/unit/test_config_parser.py
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-from unittest2 import TestCase
+from unittest import TestCase
 
 from st2common.util.config_parser import ContentPackConfigParser
 import st2tests.config as tests_config
diff --git a/st2common/tests/unit/test_configs_registrar.py b/st2common/tests/unit/test_configs_registrar.py
index 05b4466933..1e98dd108f 100644
--- a/st2common/tests/unit/test_configs_registrar.py
+++ b/st2common/tests/unit/test_configs_registrar.py
@@ -126,7 +126,7 @@ def test_register_all_configs_with_config_schema_validation_validation_failure_1
             "\"dummy_pack_6\" (.*?): 1000 is not of type 'array'"
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             registrar.register_from_packs,
@@ -160,7 +160,7 @@ def test_register_all_configs_with_config_schema_validation_validation_failure_2
             "'string'"
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             registrar.register_from_packs,
@@ -198,7 +198,7 @@ def test_register_all_configs_with_config_schema_validation_validation_failure_3
             "the default values in the schema."
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             registrar.register_from_packs,
@@ -236,7 +236,7 @@ def test_register_all_configs_with_config_schema_validation_validation_failure_4
             "the default values in the schema."
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             registrar.register_from_packs,
diff --git a/st2common/tests/unit/test_content_loader.py b/st2common/tests/unit/test_content_loader.py
index 663d5f7844..6edba192d4 100644
--- a/st2common/tests/unit/test_content_loader.py
+++ b/st2common/tests/unit/test_content_loader.py
@@ -18,7 +18,7 @@
 from oslo_config import cfg
 import os
 
-import unittest2
+import unittest
 
 import yaml
 
@@ -41,7 +41,7 @@
 RESOURCES_DIR = os.path.abspath(os.path.join(CURRENT_DIR, "../resources"))
 
 
-class ContentLoaderTest(unittest2.TestCase):
+class ContentLoaderTest(unittest.TestCase):
     def test_get_sensors(self):
         packs_base_path = os.path.join(RESOURCES_DIR, "packs/")
         loader = ContentPackLoader()
@@ -100,7 +100,7 @@ def test_get_content_from_pack_directory_doesnt_exist(self):
         pack_path = os.path.join(RESOURCES_DIR, "packs/pack100")
 
         message_regex = "Directory .*? doesn't exist"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             message_regex,
             loader.get_content_from_pack,
@@ -216,7 +216,7 @@ def test_get_override_invalid_exceptions_key(self):
         )
 
 
-class YamlLoaderTestCase(unittest2.TestCase):
+class YamlLoaderTestCase(unittest.TestCase):
     def test_yaml_safe_load(self):
         # Verify C version of yaml loader indeed doesn't load non-safe data
         dumped = yaml.dump(Foo)
@@ -226,14 +226,14 @@ def test_yaml_safe_load(self):
         result = yaml.load(dumped, Loader=FullLoader)
         self.assertTrue(result)
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             yaml.constructor.ConstructorError,
             "could not determine a constructor",
             yaml_safe_load,
             dumped,
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             yaml.constructor.ConstructorError,
             "could not determine a constructor",
             yaml.load,
@@ -242,7 +242,7 @@ def test_yaml_safe_load(self):
         )
 
         if CSafeLoader:
-            self.assertRaisesRegexp(
+            self.assertRaisesRegex(
                 yaml.constructor.ConstructorError,
                 "could not determine a constructor",
                 yaml.load,
diff --git a/st2common/tests/unit/test_content_utils.py b/st2common/tests/unit/test_content_utils.py
index c9ff10ff29..5bf6bea5a3 100644
--- a/st2common/tests/unit/test_content_utils.py
+++ b/st2common/tests/unit/test_content_utils.py
@@ -17,7 +17,7 @@
 import os
 import os.path
 
-import unittest2
+import unittest
 from oslo_config import cfg
 
 from st2common.constants.action import LIBS_DIR as ACTION_LIBS_DIR
@@ -38,7 +38,7 @@
 from st2tests.fixtures.packs.dummy_pack_2.fixture import PACK_PATH as DUMMY_PACK_2_PATH
 
 
-class ContentUtilsTestCase(unittest2.TestCase):
+class ContentUtilsTestCase(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         tests_config.parse_args()
@@ -113,7 +113,7 @@ def test_get_pack_resource_file_abs_path(self):
 
         # Invalid resource type
         expected_msg = "Invalid resource type: fooo"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             get_pack_resource_file_abs_path,
@@ -138,7 +138,7 @@ def test_get_pack_resource_file_abs_path(self):
                 r'pack actions directory (.*). For example "my_action.py"\.'
                 % (file_path)
             )
-            self.assertRaisesRegexp(
+            self.assertRaisesRegex(
                 ValueError,
                 expected_msg,
                 get_pack_resource_file_abs_path,
@@ -153,7 +153,7 @@ def test_get_pack_resource_file_abs_path(self):
                 r'pack sensors directory (.*). For example "my_sensor.py"\.'
                 % (file_path)
             )
-            self.assertRaisesRegexp(
+            self.assertRaisesRegex(
                 ValueError,
                 expected_msg,
                 get_pack_resource_file_abs_path,
@@ -167,7 +167,7 @@ def test_get_pack_resource_file_abs_path(self):
                 r'Invalid file path: ".*%s"\. File path needs to be relative to the '
                 r'pack directory (.*). For example "my_action.py"\.' % (file_path)
             )
-            self.assertRaisesRegexp(
+            self.assertRaisesRegex(
                 ValueError,
                 expected_msg,
                 get_pack_file_abs_path,
@@ -263,7 +263,7 @@ def test_get_relative_path_to_pack_file(self):
         expected_msg = r"file_path (.*?) is not located inside the pack directory (.*?)"
 
         file_path = os.path.join(DUMMY_PACK_2_PATH, "actions/lib/foo.py")
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             get_relative_path_to_pack_file,
@@ -272,7 +272,7 @@ def test_get_relative_path_to_pack_file(self):
         )
 
         file_path = "/tmp/foo/bar.py"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             get_relative_path_to_pack_file,
@@ -281,7 +281,7 @@ def test_get_relative_path_to_pack_file(self):
         )
 
         file_path = os.path.join(packs_base_paths, "../dummy_pack_1/pack.yaml")
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             get_relative_path_to_pack_file,
@@ -290,7 +290,7 @@ def test_get_relative_path_to_pack_file(self):
         )
 
         file_path = os.path.join(packs_base_paths, "../../dummy_pack_1/pack.yaml")
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             get_relative_path_to_pack_file,
diff --git a/st2common/tests/unit/test_crypto_utils.py b/st2common/tests/unit/test_crypto_utils.py
index 7fbbe9bf8f..2717b605ec 100644
--- a/st2common/tests/unit/test_crypto_utils.py
+++ b/st2common/tests/unit/test_crypto_utils.py
@@ -23,8 +23,8 @@
 import json
 import binascii
 
-import unittest2
-from unittest2 import TestCase
+import unittest
+from unittest import TestCase
 from six.moves import range
 from cryptography.exceptions import InvalidSignature
 
@@ -111,7 +111,7 @@ def test_decrypt_ciphertext_is_too_short(self):
 
         # Verify corrupted value results in an excpetion
         expected_msg = "Invalid or malformed ciphertext"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             cryptography_symmetric_decrypt,
@@ -136,7 +136,7 @@ def test_exception_is_thrown_on_invalid_hmac_signature(self):
 
         # Verify corrupted value results in an excpetion
         expected_msg = "Signature did not match digest"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             InvalidSignature,
             expected_msg,
             cryptography_symmetric_decrypt,
@@ -154,7 +154,7 @@ class CryptoUtilsKeyczarCompatibilityTestCase(TestCase):
     def test_aes_key_class(self):
         # 1. Unsupported mode
         expected_msg = "Unsupported mode: EBC"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             AESKey,
@@ -166,7 +166,7 @@ def test_aes_key_class(self):
 
         # 2. AES key is too small
         expected_msg = "Unsafe key size: 64"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             AESKey,
@@ -255,7 +255,7 @@ def test_symmetric_encrypt_decrypt_cryptography(self):
 
             self.assertEqual(decrypted, plaintext)
 
-    @unittest2.skipIf(six.PY3, "keyczar doesn't work under Python 3")
+    @unittest.skipIf(six.PY3, "keyczar doesn't work under Python 3")
     def test_symmetric_encrypt_decrypt_roundtrips_1(self):
         encrypt_keys = [
             AESKey.generate(),
diff --git a/st2common/tests/unit/test_date_utils.py b/st2common/tests/unit/test_date_utils.py
index d453edb8f7..f58caa54f9 100644
--- a/st2common/tests/unit/test_date_utils.py
+++ b/st2common/tests/unit/test_date_utils.py
@@ -17,12 +17,12 @@
 import datetime
 
 import pytz
-import unittest2
+import unittest
 
 from st2common.util import date as date_utils
 
 
-class DateUtilsTestCase(unittest2.TestCase):
+class DateUtilsTestCase(unittest.TestCase):
     def test_get_datetime_utc_now(self):
         date = date_utils.get_datetime_utc_now()
         self.assertEqual(date.tzinfo.tzname(None), "UTC")
diff --git a/st2common/tests/unit/test_db.py b/st2common/tests/unit/test_db.py
index 0ae1ee79f1..ed6b88649d 100644
--- a/st2common/tests/unit/test_db.py
+++ b/st2common/tests/unit/test_db.py
@@ -48,7 +48,7 @@
 from st2common.persistence.trigger import TriggerType, Trigger, TriggerInstance
 from st2tests import DbTestCase
 
-from unittest2 import TestCase
+from unittest import TestCase
 from st2tests.base import ALL_MODELS
 
 
@@ -492,7 +492,7 @@ def test_db_setup_connecting_info_logging(self, mock_log, mock_mongoengine):
         password = "pass_st2"
 
         expected_msg = "Failed to connect"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ConnectionFailure,
             expected_msg,
             db_setup,
diff --git a/st2common/tests/unit/test_db_fields.py b/st2common/tests/unit/test_db_fields.py
index 9abd587fb5..c6deff0f6c 100644
--- a/st2common/tests/unit/test_db_fields.py
+++ b/st2common/tests/unit/test_db_fields.py
@@ -20,7 +20,7 @@
 import calendar
 
 import mock
-import unittest2
+import unittest
 import orjson
 import zstandard
 
@@ -78,7 +78,7 @@ class ModelWithJSONDictFieldDB(stormbase.StormFoundationDB):
 ModelJsonDictFieldAccess = MongoDBAccess(ModelWithJSONDictFieldDB)
 
 
-class JSONDictFieldTestCase(unittest2.TestCase):
+class JSONDictFieldTestCase(unittest.TestCase):
     def test_set_to_mongo(self):
         field = JSONDictField(use_header=False)
         result = field.to_mongo({"test": {1, 2}})
@@ -147,7 +147,7 @@ def test_parse_field_value(self):
         self.assertEqual(result, {"c": "d"})
 
 
-class JSONDictFieldTestCaseWithHeader(unittest2.TestCase):
+class JSONDictFieldTestCaseWithHeader(unittest.TestCase):
     def test_to_mongo_no_compression(self):
         field = JSONDictField(use_header=True)
 
@@ -515,7 +515,7 @@ def test_field_state_changes_are_correctly_detected_save_method(self):
         self.assertEqual(retrieved_model_db.result, expected_result)
 
 
-class ComplexDateTimeFieldTestCase(unittest2.TestCase):
+class ComplexDateTimeFieldTestCase(unittest.TestCase):
     def test_what_comes_in_goes_out(self):
         field = ComplexDateTimeField()
 
diff --git a/st2common/tests/unit/test_db_model_uids.py b/st2common/tests/unit/test_db_model_uids.py
index 2dd3bfb87d..2cc1232153 100644
--- a/st2common/tests/unit/test_db_model_uids.py
+++ b/st2common/tests/unit/test_db_model_uids.py
@@ -18,7 +18,7 @@
 import hashlib
 from collections import OrderedDict
 
-import unittest2
+import unittest
 
 from st2common.models.db.pack import PackDB
 from st2common.models.db.sensor import SensorTypeDB
@@ -33,7 +33,7 @@
 __all__ = ["DBModelUIDFieldTestCase"]
 
 
-class DBModelUIDFieldTestCase(unittest2.TestCase):
+class DBModelUIDFieldTestCase(unittest.TestCase):
     def test_get_uid(self):
         pack_db = PackDB(ref="ma_pack")
         self.assertEqual(pack_db.get_uid(), "pack:ma_pack")
diff --git a/st2common/tests/unit/test_db_policy.py b/st2common/tests/unit/test_db_policy.py
index 95b682e4a4..6b7a478306 100644
--- a/st2common/tests/unit/test_db_policy.py
+++ b/st2common/tests/unit/test_db_policy.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.constants import pack as pack_constants
 from st2common.models.db.policy import PolicyTypeReference, PolicyTypeDB, PolicyDB
@@ -23,7 +23,7 @@
 from st2tests import DbModelTestCase
 
 
-class PolicyTypeReferenceTest(unittest2.TestCase):
+class PolicyTypeReferenceTest(unittest.TestCase):
     def test_is_reference(self):
         self.assertTrue(PolicyTypeReference.is_reference("action.concurrency"))
         self.assertFalse(PolicyTypeReference.is_reference("concurrency"))
diff --git a/st2common/tests/unit/test_dist_utils.py b/st2common/tests/unit/test_dist_utils.py
index e19df80604..9b6060b480 100644
--- a/st2common/tests/unit/test_dist_utils.py
+++ b/st2common/tests/unit/test_dist_utils.py
@@ -17,7 +17,7 @@
 import sys
 
 import mock
-import unittest2
+import unittest
 
 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 SCRIPTS_PATH = os.path.join(BASE_DIR, "../../../scripts/")
@@ -38,7 +38,7 @@
 VERSION_FILE_PATH = os.path.join(BASE_DIR, "../fixtures/version_file.py")
 
 
-class DistUtilsTestCase(unittest2.TestCase):
+class DistUtilsTestCase(unittest.TestCase):
     def setUp(self):
         super(DistUtilsTestCase, self).setUp()
 
diff --git a/st2common/tests/unit/test_exceptions_workflow.py b/st2common/tests/unit/test_exceptions_workflow.py
index fbb6dbd1fe..0d739396b1 100644
--- a/st2common/tests/unit/test_exceptions_workflow.py
+++ b/st2common/tests/unit/test_exceptions_workflow.py
@@ -16,7 +16,7 @@
 from __future__ import absolute_import
 
 import mongoengine
-import unittest2
+import unittest
 
 from tooz import coordination
 
@@ -25,7 +25,7 @@
 from st2common.models.db import workflow as wf_db_models
 
 
-class WorkflowExceptionTest(unittest2.TestCase):
+class WorkflowExceptionTest(unittest.TestCase):
     def test_retry_on_transient_db_errors(self):
         instance = wf_db_models.WorkflowExecutionDB()
         exc = db_exc.StackStormDBObjectWriteConflictError(instance)
diff --git a/st2common/tests/unit/test_executions_util.py b/st2common/tests/unit/test_executions_util.py
index 4c2530155a..00b89c7433 100644
--- a/st2common/tests/unit/test_executions_util.py
+++ b/st2common/tests/unit/test_executions_util.py
@@ -214,7 +214,7 @@ def test_abandon_executions_on_complete(self):
             liveaction_db.status,
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             executions_util.abandon_execution_if_incomplete,
diff --git a/st2common/tests/unit/test_greenpooldispatch.py b/st2common/tests/unit/test_greenpooldispatch.py
index 45cc568759..f77ae2937e 100644
--- a/st2common/tests/unit/test_greenpooldispatch.py
+++ b/st2common/tests/unit/test_greenpooldispatch.py
@@ -18,7 +18,7 @@
 import mock
 
 from st2common.util.greenpooldispatch import BufferedDispatcher
-from unittest2 import TestCase
+from unittest import TestCase
 from six.moves import range
 
 
@@ -36,7 +36,7 @@ def test_dispatch_simple(self):
         call_args_list = [
             (args[0][0], args[0][1]) for args in mock_handler.call_args_list
         ]
-        self.assertItemsEqual(expected, call_args_list)
+        assert expected == call_args_list
 
     def test_dispatch_starved(self):
         dispatcher = BufferedDispatcher(
@@ -55,4 +55,4 @@ def test_dispatch_starved(self):
         call_args_list = [
             (args[0][0], args[0][1]) for args in mock_handler.call_args_list
         ]
-        self.assertItemsEqual(expected, call_args_list)
+        assert expected == call_args_list
diff --git a/st2common/tests/unit/test_hash.py b/st2common/tests/unit/test_hash.py
index 234d4969da..adabb379d1 100644
--- a/st2common/tests/unit/test_hash.py
+++ b/st2common/tests/unit/test_hash.py
@@ -14,14 +14,14 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util import hash as hash_utils
 from st2common.util import auth as auth_utils
 from six.moves import range
 
 
-class TestHashWithApiKeys(unittest2.TestCase):
+class TestHashWithApiKeys(unittest.TestCase):
     def test_hash_repeatability(self):
         api_key = auth_utils.generate_api_key()
         hash1 = hash_utils.hash(api_key)
diff --git a/st2common/tests/unit/test_ip_utils.py b/st2common/tests/unit/test_ip_utils.py
index cd1339be73..be88bce599 100644
--- a/st2common/tests/unit/test_ip_utils.py
+++ b/st2common/tests/unit/test_ip_utils.py
@@ -14,12 +14,12 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util.ip_utils import split_host_port
 
 
-class IPUtilsTests(unittest2.TestCase):
+class IPUtilsTests(unittest.TestCase):
     def test_host_port_split(self):
 
         # Simple IPv4
diff --git a/st2common/tests/unit/test_jinja_render_crypto_filters.py b/st2common/tests/unit/test_jinja_render_crypto_filters.py
index f58edb1309..41972ab0b5 100644
--- a/st2common/tests/unit/test_jinja_render_crypto_filters.py
+++ b/st2common/tests/unit/test_jinja_render_crypto_filters.py
@@ -82,7 +82,7 @@ def test_filter_decrypt_kv_datastore_value_doesnt_exist(self):
             'Referenced datastore item "st2kv.system.doesnt_exist" doesn\'t exist or '
             "it contains an empty string"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError, expected_msg, self.env.from_string(template).render, context
         )
 
@@ -133,6 +133,6 @@ def test_filter_decrypt_kv_with_user_scope_value_datastore_value_doesnt_exist(se
             'Referenced datastore item "st2kv.user.doesnt_exist" doesn\'t exist or '
             "it contains an empty string"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError, expected_msg, self.env.from_string(template).render, context
         )
diff --git a/st2common/tests/unit/test_jinja_render_data_filters.py b/st2common/tests/unit/test_jinja_render_data_filters.py
index 44d2f296f9..8db175cac2 100644
--- a/st2common/tests/unit/test_jinja_render_data_filters.py
+++ b/st2common/tests/unit/test_jinja_render_data_filters.py
@@ -15,7 +15,7 @@
 
 from __future__ import absolute_import
 import json
-import unittest2
+import unittest
 import yaml
 
 from st2common.constants.keyvalue import FULL_SYSTEM_SCOPE
@@ -23,7 +23,7 @@
 from st2common.services.keyvalues import KeyValueLookup
 
 
-class JinjaUtilsDataFilterTestCase(unittest2.TestCase):
+class JinjaUtilsDataFilterTestCase(unittest.TestCase):
     def test_filter_from_json_string(self):
         env = jinja_utils.get_jinja_environment()
         expected_obj = {"a": "b", "c": {"d": "e", "f": 1, "g": True}}
diff --git a/st2common/tests/unit/test_jinja_render_json_escape_filters.py b/st2common/tests/unit/test_jinja_render_json_escape_filters.py
index 48fef776c1..62aca311e4 100644
--- a/st2common/tests/unit/test_jinja_render_json_escape_filters.py
+++ b/st2common/tests/unit/test_jinja_render_json_escape_filters.py
@@ -15,12 +15,12 @@
 
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util import jinja as jinja_utils
 
 
-class JinjaUtilsJsonEscapeTestCase(unittest2.TestCase):
+class JinjaUtilsJsonEscapeTestCase(unittest.TestCase):
     def test_doublequotes(self):
         env = jinja_utils.get_jinja_environment()
         template = "{{ test_str | json_escape }}"
diff --git a/st2common/tests/unit/test_jinja_render_jsonpath_query_filters.py b/st2common/tests/unit/test_jinja_render_jsonpath_query_filters.py
index 934aa04de8..4f3a5e85d7 100644
--- a/st2common/tests/unit/test_jinja_render_jsonpath_query_filters.py
+++ b/st2common/tests/unit/test_jinja_render_jsonpath_query_filters.py
@@ -15,12 +15,12 @@
 
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util import jinja as jinja_utils
 
 
-class JinjaUtilsJsonpathQueryTestCase(unittest2.TestCase):
+class JinjaUtilsJsonpathQueryTestCase(unittest.TestCase):
     def test_jsonpath_query_static(self):
         env = jinja_utils.get_jinja_environment()
         obj = {
diff --git a/st2common/tests/unit/test_jinja_render_path_filters.py b/st2common/tests/unit/test_jinja_render_path_filters.py
index 23507bbbc1..6dfea72676 100644
--- a/st2common/tests/unit/test_jinja_render_path_filters.py
+++ b/st2common/tests/unit/test_jinja_render_path_filters.py
@@ -15,12 +15,12 @@
 
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util import jinja as jinja_utils
 
 
-class JinjaUtilsPathFilterTestCase(unittest2.TestCase):
+class JinjaUtilsPathFilterTestCase(unittest.TestCase):
     def test_basename(self):
         env = jinja_utils.get_jinja_environment()
 
diff --git a/st2common/tests/unit/test_jinja_render_regex_filters.py b/st2common/tests/unit/test_jinja_render_regex_filters.py
index df2e347779..28835e6d65 100644
--- a/st2common/tests/unit/test_jinja_render_regex_filters.py
+++ b/st2common/tests/unit/test_jinja_render_regex_filters.py
@@ -14,12 +14,12 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util import jinja as jinja_utils
 
 
-class JinjaUtilsRegexFilterTestCase(unittest2.TestCase):
+class JinjaUtilsRegexFilterTestCase(unittest.TestCase):
     def test_filters_regex_match(self):
         env = jinja_utils.get_jinja_environment()
 
diff --git a/st2common/tests/unit/test_jinja_render_time_filters.py b/st2common/tests/unit/test_jinja_render_time_filters.py
index 2cf002a0e3..58064d00bf 100644
--- a/st2common/tests/unit/test_jinja_render_time_filters.py
+++ b/st2common/tests/unit/test_jinja_render_time_filters.py
@@ -14,12 +14,12 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util import jinja as jinja_utils
 
 
-class JinjaUtilsTimeFilterTestCase(unittest2.TestCase):
+class JinjaUtilsTimeFilterTestCase(unittest.TestCase):
     def test_to_human_time_filter(self):
         env = jinja_utils.get_jinja_environment()
 
diff --git a/st2common/tests/unit/test_jinja_render_version_filters.py b/st2common/tests/unit/test_jinja_render_version_filters.py
index 41b2b23670..102a630b2a 100644
--- a/st2common/tests/unit/test_jinja_render_version_filters.py
+++ b/st2common/tests/unit/test_jinja_render_version_filters.py
@@ -15,12 +15,12 @@
 
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util import jinja as jinja_utils
 
 
-class JinjaUtilsVersionsFilterTestCase(unittest2.TestCase):
+class JinjaUtilsVersionsFilterTestCase(unittest.TestCase):
     def test_version_compare(self):
         env = jinja_utils.get_jinja_environment()
 
diff --git a/st2common/tests/unit/test_json_schema.py b/st2common/tests/unit/test_json_schema.py
index 3e0c6e7336..9e07b7f954 100644
--- a/st2common/tests/unit/test_json_schema.py
+++ b/st2common/tests/unit/test_json_schema.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-from unittest2 import TestCase
+from unittest import TestCase
 from jsonschema.exceptions import ValidationError
 
 from st2common.util import schema as util_schema
@@ -179,7 +179,7 @@ def test_use_default_value(self):
         validator = util_schema.get_validator()
 
         expected_msg = "'arg_required_no_default' is a required property"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValidationError,
             expected_msg,
             util_schema.validate,
diff --git a/st2common/tests/unit/test_jsonify.py b/st2common/tests/unit/test_jsonify.py
index fe5de6e5e8..b4a375be69 100644
--- a/st2common/tests/unit/test_jsonify.py
+++ b/st2common/tests/unit/test_jsonify.py
@@ -17,7 +17,7 @@
 
 import json
 
-import unittest2
+import unittest
 from bson import ObjectId
 
 import st2tests.config as tests_config
@@ -27,7 +27,7 @@
 import st2common.util.jsonify as jsonify
 
 
-class JsonifyTests(unittest2.TestCase):
+class JsonifyTests(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         jsonify.DEFAULT_JSON_LIBRARY = "orjson"
diff --git a/st2common/tests/unit/test_keyvalue_system_model.py b/st2common/tests/unit/test_keyvalue_system_model.py
index a8ea10822b..14549e4985 100644
--- a/st2common/tests/unit/test_keyvalue_system_model.py
+++ b/st2common/tests/unit/test_keyvalue_system_model.py
@@ -14,13 +14,13 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.models.system.keyvalue import InvalidUserKeyReferenceError
 from st2common.models.system.keyvalue import UserKeyReference
 
 
-class UserKeyReferenceSystemModelTest(unittest2.TestCase):
+class UserKeyReferenceSystemModelTest(unittest.TestCase):
     def test_to_string_reference(self):
         key_ref = UserKeyReference.to_string_reference(user="stanley", name="foo")
         self.assertEqual(key_ref, "stanley:foo")
diff --git a/st2common/tests/unit/test_logger.py b/st2common/tests/unit/test_logger.py
index 79158b8b7f..e2126208eb 100644
--- a/st2common/tests/unit/test_logger.py
+++ b/st2common/tests/unit/test_logger.py
@@ -271,7 +271,7 @@ def test_format_secret_action_parameters_are_masked(self):
 
         message = formatter.format(record=record)
         self.assertIn("test message 1", message)
-        self.assertRegexpMatches(message, expected_msg_part)
+        self.assertRegex(message, expected_msg_part)
 
     @mock.patch(
         "st2common.logging.formatters.MASKED_ATTRIBUTES_BLACKLIST",
diff --git a/st2common/tests/unit/test_logging.py b/st2common/tests/unit/test_logging.py
index ebb75b6f1d..7775436e47 100644
--- a/st2common/tests/unit/test_logging.py
+++ b/st2common/tests/unit/test_logging.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.logging.misc import get_logger_name_for_module
 from st2reactor.cmd import sensormanager
@@ -24,7 +24,7 @@
 __all__ = ["LoggingMiscUtilsTestCase"]
 
 
-class LoggingMiscUtilsTestCase(unittest2.TestCase):
+class LoggingMiscUtilsTestCase(unittest.TestCase):
     def test_get_logger_name_for_module(self):
         logger_name = get_logger_name_for_module(sensormanager)
         self.assertEqual(logger_name, "st2reactor.cmd.sensormanager")
diff --git a/st2common/tests/unit/test_logging_middleware.py b/st2common/tests/unit/test_logging_middleware.py
index b7d34de0bc..2da4bb5875 100644
--- a/st2common/tests/unit/test_logging_middleware.py
+++ b/st2common/tests/unit/test_logging_middleware.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 import mock
-import unittest2
+import unittest
 
 from oslo_config import cfg
 
@@ -24,7 +24,7 @@
 __all__ = ["LoggingMiddlewareTestCase"]
 
 
-class LoggingMiddlewareTestCase(unittest2.TestCase):
+class LoggingMiddlewareTestCase(unittest.TestCase):
     @mock.patch("st2common.middleware.logging.LOG")
     @mock.patch("st2common.middleware.logging.Request")
     def test_secret_parameters_are_masked_in_log_message(self, mock_request, mock_log):
diff --git a/st2common/tests/unit/test_metrics.py b/st2common/tests/unit/test_metrics.py
index 4b0df66aa1..ad8167238c 100644
--- a/st2common/tests/unit/test_metrics.py
+++ b/st2common/tests/unit/test_metrics.py
@@ -17,7 +17,7 @@
 from datetime import datetime
 from datetime import timedelta
 
-import unittest2
+import unittest
 import mock
 from mock import patch, MagicMock
 
@@ -41,7 +41,7 @@
 cfg.CONF.set_override("port", 8080, group="metrics")
 
 
-class TestBaseMetricsDriver(unittest2.TestCase):
+class TestBaseMetricsDriver(unittest.TestCase):
     _driver = None
 
     def setUp(self):
@@ -57,7 +57,7 @@ def test_dec_timer(self):
         self._driver.dec_counter("test")
 
 
-class TestStatsDMetricsDriver(unittest2.TestCase):
+class TestStatsDMetricsDriver(unittest.TestCase):
     _driver = None
 
     @patch("st2common.metrics.drivers.statsd_driver.statsd")
@@ -249,7 +249,7 @@ def test_driver_socket_exceptions_are_not_fatal(self, statsd, mock_log):
         mock_gauge.assert_called_once_with(None, 1)
 
 
-class TestCounterContextManager(unittest2.TestCase):
+class TestCounterContextManager(unittest.TestCase):
     @patch("st2common.metrics.base.METRICS")
     def test_counter(self, metrics_patch):
         test_key = "test_key"
@@ -258,7 +258,7 @@ def test_counter(self, metrics_patch):
             metrics_patch.dec_counter.assert_not_called()
 
 
-class TestTimerContextManager(unittest2.TestCase):
+class TestTimerContextManager(unittest.TestCase):
     @patch("st2common.metrics.base.get_datetime_utc_now")
     @patch("st2common.metrics.base.METRICS")
     def test_time(self, metrics_patch, datetime_patch):
@@ -294,7 +294,7 @@ def test_time(self, metrics_patch, datetime_patch):
         )
 
 
-class TestCounterWithTimerContextManager(unittest2.TestCase):
+class TestCounterWithTimerContextManager(unittest.TestCase):
     def setUp(self):
         self.start_time = get_datetime_utc_now()
         self.middle_time = self.start_time + timedelta(seconds=1)
@@ -335,7 +335,7 @@ def test_time(self, metrics_patch, datetime_patch):
         )
 
 
-class TestCounterWithTimerDecorator(unittest2.TestCase):
+class TestCounterWithTimerDecorator(unittest.TestCase):
     @patch("st2common.metrics.base.get_datetime_utc_now")
     @patch("st2common.metrics.base.METRICS")
     def test_time(self, metrics_patch, datetime_patch):
@@ -378,7 +378,7 @@ def _get_tested(metrics_counter_with_timer=None):
         )
 
 
-class TestCounterDecorator(unittest2.TestCase):
+class TestCounterDecorator(unittest.TestCase):
     @patch("st2common.metrics.base.METRICS")
     def test_counter(self, metrics_patch):
         test_key = "test_key"
@@ -391,7 +391,7 @@ def _get_tested():
         _get_tested()
 
 
-class TestTimerDecorator(unittest2.TestCase):
+class TestTimerDecorator(unittest.TestCase):
     @patch("st2common.metrics.base.get_datetime_utc_now")
     @patch("st2common.metrics.base.METRICS")
     def test_time(self, metrics_patch, datetime_patch):
diff --git a/st2common/tests/unit/test_misc_utils.py b/st2common/tests/unit/test_misc_utils.py
index 4890f12756..1687bef57a 100644
--- a/st2common/tests/unit/test_misc_utils.py
+++ b/st2common/tests/unit/test_misc_utils.py
@@ -16,7 +16,7 @@
 
 from __future__ import absolute_import
 
-import unittest2
+import unittest
 
 from st2common.util.misc import rstrip_last_char
 from st2common.util.misc import strip_shell_chars
@@ -27,7 +27,7 @@
 __all__ = ["MiscUtilTestCase"]
 
 
-class MiscUtilTestCase(unittest2.TestCase):
+class MiscUtilTestCase(unittest.TestCase):
     def test_rstrip_last_char(self):
         self.assertEqual(rstrip_last_char(None, "\n"), None)
         self.assertEqual(rstrip_last_char("stuff", None), "stuff")
diff --git a/st2common/tests/unit/test_model_utils_profiling.py b/st2common/tests/unit/test_model_utils_profiling.py
index db37039c80..bbdeee8c22 100644
--- a/st2common/tests/unit/test_model_utils_profiling.py
+++ b/st2common/tests/unit/test_model_utils_profiling.py
@@ -37,7 +37,7 @@ def test_logging_profiling_is_disabled(self, mock_log):
         result = log_query_and_profile_data_for_queryset(queryset=queryset)
         self.assertEqual(queryset, result)
         call_args_list = mock_log.debug.call_args_list
-        self.assertItemsEqual(call_args_list, [])
+        assert call_args_list == []
 
     @mock.patch("st2common.models.utils.profiling.LOG")
     def test_logging_profiling_is_enabled(self, mock_log):
diff --git a/st2common/tests/unit/test_notification_helper.py b/st2common/tests/unit/test_notification_helper.py
index 9c00ea4771..9848f6386c 100644
--- a/st2common/tests/unit/test_notification_helper.py
+++ b/st2common/tests/unit/test_notification_helper.py
@@ -14,12 +14,12 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.models.api.notification import NotificationsHelper
 
 
-class NotificationsHelperTestCase(unittest2.TestCase):
+class NotificationsHelperTestCase(unittest.TestCase):
     def test_model_transformations(self):
         notify = {}
 
diff --git a/st2common/tests/unit/test_operators.py b/st2common/tests/unit/test_operators.py
index dd00eba6f7..9bb6161f91 100644
--- a/st2common/tests/unit/test_operators.py
+++ b/st2common/tests/unit/test_operators.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common import operators
 from st2common.util import date as date_utils
@@ -37,7 +37,7 @@ def list_of_dicts_strict_equal(lofd1, lofd2):
     return not t2
 
 
-class ListOfDictsStrictEqualTest(unittest2.TestCase):
+class ListOfDictsStrictEqualTest(unittest.TestCase):
     """
     Tests list_of_dicts_strict_equal
 
@@ -156,7 +156,7 @@ def test_less_simple_dicts(self):
         )
 
 
-class SearchOperatorTest(unittest2.TestCase):
+class SearchOperatorTest(unittest.TestCase):
     # The search command extends the rules engine into being a recursive descent
     # parser. As such, its tests are much more complex than other commands, so we
     # pull its tests out into their own test case.
@@ -762,7 +762,7 @@ def test_search_payload_dict(self):
         self.assertFalse(result)
 
 
-class OperatorTest(unittest2.TestCase):
+class OperatorTest(unittest.TestCase):
     def test_matchwildcard(self):
         op = operators.get_operator("matchwildcard")
         self.assertTrue(op("v1", "v1"), "Failed matchwildcard.")
@@ -1215,7 +1215,7 @@ def test_ninside(self):
         self.assertTrue(op("a", "bcd"), "Should return True")
 
 
-class GetOperatorsTest(unittest2.TestCase):
+class GetOperatorsTest(unittest.TestCase):
     def test_get_operator(self):
         self.assertTrue(operators.get_operator("equals"))
         self.assertTrue(operators.get_operator("EQUALS"))
diff --git a/st2common/tests/unit/test_pack_action_alias_unit_testing_utils.py b/st2common/tests/unit/test_pack_action_alias_unit_testing_utils.py
index 18251fb947..6364b5d216 100644
--- a/st2common/tests/unit/test_pack_action_alias_unit_testing_utils.py
+++ b/st2common/tests/unit/test_pack_action_alias_unit_testing_utils.py
@@ -56,7 +56,7 @@ def test_assertExtractedParametersMatch_command_doesnt_match_format_string(self)
             '"show last {{count}} metrics for {{server}}"'
         )
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ParseException,
             expected_msg,
             self.assertExtractedParametersMatch,
@@ -81,7 +81,7 @@ def test_assertCommandMatchesExactlyOneFormatString(self):
             'Command "foo bar a test=1" matched multiple format '
             "strings: foo bar {{bar}}, foo bar {{baz}}"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             AssertionError,
             expected_msg,
             self.assertCommandMatchesExactlyOneFormatString,
@@ -97,7 +97,7 @@ def test_assertCommandMatchesExactlyOneFormatString(self):
             'Command "does not match foo" didn\'t match any of the provided format '
             "strings"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             AssertionError,
             expected_msg,
             self.assertCommandMatchesExactlyOneFormatString,
diff --git a/st2common/tests/unit/test_pack_management.py b/st2common/tests/unit/test_pack_management.py
index b350c7d98f..2c08137746 100644
--- a/st2common/tests/unit/test_pack_management.py
+++ b/st2common/tests/unit/test_pack_management.py
@@ -18,7 +18,7 @@
 import os
 import sys
 
-import unittest2
+import unittest
 
 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 PACK_ACTIONS_DIR = os.path.join(BASE_DIR, "../../../contrib/packs/actions")
@@ -35,7 +35,7 @@
 __all__ = ["InstallPackTestCase"]
 
 
-class InstallPackTestCase(unittest2.TestCase):
+class InstallPackTestCase(unittest.TestCase):
     def test_eval_repo(self):
         result = eval_repo_url("stackstorm/st2contrib")
         self.assertEqual(result, "https://github.com/stackstorm/st2contrib")
diff --git a/st2common/tests/unit/test_param_utils.py b/st2common/tests/unit/test_param_utils.py
index ddcd2cd6ff..2aa2d05218 100644
--- a/st2common/tests/unit/test_param_utils.py
+++ b/st2common/tests/unit/test_param_utils.py
@@ -575,7 +575,7 @@ def test_get_finalized_params_param_rendering_failure(self):
         action_param_info = {"cmd": {}, "a2": {}}
 
         expected_msg = 'Failed to render parameter "cmd": .*'
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ParamException,
             expected_msg,
             param_utils.get_finalized_params,
@@ -606,7 +606,7 @@ def test_get_finalized_param_object_contains_template_notation_in_the_value(self
     def test_cast_param_referenced_action_doesnt_exist(self):
         # Make sure the function throws if the action doesnt exist
         expected_msg = 'Action with ref "foo.doesntexist" doesn\'t exist'
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             action_param_utils.cast_params,
@@ -765,7 +765,7 @@ def test_cyclic_dependency_friendly_error_message(self):
         expected_msg = (
             "Cyclic dependency found in the following variables: cyclic, morecyclic"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ParamException,
             expected_msg,
             param_utils.render_live_params,
@@ -789,7 +789,7 @@ def test_unsatisfied_dependency_friendly_error_message(self):
         action_context = {"user": None}
 
         expected_msg = 'Dependency unsatisfied in variable "variable_not_defined"'
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ParamException,
             expected_msg,
             param_utils.render_live_params,
diff --git a/st2common/tests/unit/test_paramiko_command_action_model.py b/st2common/tests/unit/test_paramiko_command_action_model.py
index 0d023d4f8a..00d7356b0e 100644
--- a/st2common/tests/unit/test_paramiko_command_action_model.py
+++ b/st2common/tests/unit/test_paramiko_command_action_model.py
@@ -14,14 +14,14 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.models.system.paramiko_command_action import ParamikoRemoteCommandAction
 
 __all__ = ["ParamikoRemoteCommandActionTestCase"]
 
 
-class ParamikoRemoteCommandActionTestCase(unittest2.TestCase):
+class ParamikoRemoteCommandActionTestCase(unittest.TestCase):
     def test_get_command_string_no_env_vars(self):
         cmd_action = ParamikoRemoteCommandActionTestCase._get_test_command_action(
             "echo boo bah baz"
diff --git a/st2common/tests/unit/test_paramiko_script_action_model.py b/st2common/tests/unit/test_paramiko_script_action_model.py
index 3efae1053f..28a117299b 100644
--- a/st2common/tests/unit/test_paramiko_script_action_model.py
+++ b/st2common/tests/unit/test_paramiko_script_action_model.py
@@ -14,14 +14,14 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.models.system.paramiko_script_action import ParamikoRemoteScriptAction
 
 __all__ = ["ParamikoRemoteScriptActionTestCase"]
 
 
-class ParamikoRemoteScriptActionTestCase(unittest2.TestCase):
+class ParamikoRemoteScriptActionTestCase(unittest.TestCase):
     def test_get_command_string_no_env_vars(self):
         script_action = ParamikoRemoteScriptActionTestCase._get_test_script_action()
         ex = "cd /tmp && /tmp/remote_script.sh song='b s' 'taylor swift'"
diff --git a/st2common/tests/unit/test_plugin_loader.py b/st2common/tests/unit/test_plugin_loader.py
index 4641b66e9c..e5a2822406 100644
--- a/st2common/tests/unit/test_plugin_loader.py
+++ b/st2common/tests/unit/test_plugin_loader.py
@@ -19,7 +19,7 @@
 import os
 import six
 import sys
-import unittest2
+import unittest
 
 import st2common.util.loader as plugin_loader
 
@@ -29,7 +29,7 @@
 SRC_ROOT = os.path.join(os.path.abspath(os.path.dirname(__file__)), SRC_RELATIVE)
 
 
-class LoaderTest(unittest2.TestCase):
+class LoaderTest(unittest.TestCase):
     sys_path = None
 
     @six.add_metaclass(abc.ABCMeta)
@@ -96,7 +96,7 @@ def test_register_plugin_class_class_doesnt_exist(self):
         file_path = os.path.join(SRC_ROOT, "plugin/sampleplugin3.py")
 
         expected_msg = 'doesn\'t expose class named "SamplePluginNotExists"'
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             Exception,
             expected_msg,
             plugin_loader.register_plugin_class,
@@ -111,7 +111,7 @@ def test_register_plugin_class_abstract_method_not_implemented(self):
         expected_msg = (
             'doesn\'t implement required "do_work" method from the base class'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             plugin_loader.IncompatiblePluginException,
             expected_msg,
             plugin_loader.register_plugin_class,
diff --git a/st2common/tests/unit/test_policies_registrar.py b/st2common/tests/unit/test_policies_registrar.py
index 3a435ab0f3..ed22185d8d 100644
--- a/st2common/tests/unit/test_policies_registrar.py
+++ b/st2common/tests/unit/test_policies_registrar.py
@@ -121,7 +121,7 @@ def test_register_policy_invalid_policy_type_references(self):
         policy_path = os.path.join(DUMMY_PACK_1_PATH, "policies/policy_2.yaml")
 
         expected_msg = 'Referenced policy_type "action.mock_policy_error" doesnt exist'
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             registrar._register_policy,
@@ -135,7 +135,7 @@ def test_make_sure_policy_parameters_are_validated_during_register(self):
         policy_path = os.path.join(DUMMY_PACK_2_PATH, "policies/policy_3.yaml")
 
         expected_msg = "100 is greater than the maximum of 5"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             jsonschema.ValidationError,
             expected_msg,
             registrar._register_policy,
diff --git a/st2common/tests/unit/test_purge_executions.py b/st2common/tests/unit/test_purge_executions.py
index f43266a121..bc2c43504f 100644
--- a/st2common/tests/unit/test_purge_executions.py
+++ b/st2common/tests/unit/test_purge_executions.py
@@ -79,7 +79,7 @@ def test_no_timestamp_doesnt_delete_things(self):
         self.assertEqual(len(stderr_dbs), 3)
 
         expected_msg = "Specify a valid timestamp"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError, expected_msg, purge_executions, logger=LOG, timestamp=None
         )
         execs = ActionExecution.get_all()
diff --git a/st2common/tests/unit/test_purge_rule_enforcement.py b/st2common/tests/unit/test_purge_rule_enforcement.py
index f4ecb3ea14..90b4d23799 100644
--- a/st2common/tests/unit/test_purge_rule_enforcement.py
+++ b/st2common/tests/unit/test_purge_rule_enforcement.py
@@ -43,7 +43,7 @@ def test_no_timestamp_doesnt_delete(self):
 
         self.assertEqual(len(RuleEnforcement.get_all()), 1)
         expected_msg = "Specify a valid timestamp"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             purge_rule_enforcements,
diff --git a/st2common/tests/unit/test_purge_task_executions.py b/st2common/tests/unit/test_purge_task_executions.py
index a3a35196fd..b0c7cd8bc2 100644
--- a/st2common/tests/unit/test_purge_task_executions.py
+++ b/st2common/tests/unit/test_purge_task_executions.py
@@ -46,7 +46,7 @@ def test_no_timestamp_doesnt_delete(self):
 
         self.assertEqual(len(TaskExecution.get_all()), 1)
         expected_msg = "Specify a valid timestamp"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError, expected_msg, purge_task_executions, logger=LOG, timestamp=None
         )
         self.assertEqual(len(TaskExecution.get_all()), 1)
diff --git a/st2common/tests/unit/test_purge_token.py b/st2common/tests/unit/test_purge_token.py
index 3485419777..75c24e62cd 100644
--- a/st2common/tests/unit/test_purge_token.py
+++ b/st2common/tests/unit/test_purge_token.py
@@ -43,7 +43,7 @@ def test_no_timestamp_doesnt_delete(self):
 
         self.assertEqual(len(Token.get_all()), 1)
         expected_msg = "Specify a valid timestamp"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             purge_tokens,
diff --git a/st2common/tests/unit/test_purge_trace.py b/st2common/tests/unit/test_purge_trace.py
index d734974f5e..9a819b4018 100644
--- a/st2common/tests/unit/test_purge_trace.py
+++ b/st2common/tests/unit/test_purge_trace.py
@@ -47,7 +47,7 @@ def test_no_timestamp_doesnt_delete(self):
 
         self.assertEqual(len(Trace.get_all()), 1)
         expected_msg = "Specify a valid timestamp"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             purge_traces,
diff --git a/st2common/tests/unit/test_purge_trigger_instances.py b/st2common/tests/unit/test_purge_trigger_instances.py
index 8848ef5f11..c38bf8fa2a 100644
--- a/st2common/tests/unit/test_purge_trigger_instances.py
+++ b/st2common/tests/unit/test_purge_trigger_instances.py
@@ -55,7 +55,7 @@ def test_no_timestamp_doesnt_delete(self):
 
         self.assertEqual(len(TriggerInstance.get_all()), 1)
         expected_msg = "Specify a valid timestamp"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             purge_trigger_instances,
diff --git a/st2common/tests/unit/test_purge_worklows.py b/st2common/tests/unit/test_purge_worklows.py
index 713a0d1341..2975c504cf 100644
--- a/st2common/tests/unit/test_purge_worklows.py
+++ b/st2common/tests/unit/test_purge_worklows.py
@@ -46,7 +46,7 @@ def test_no_timestamp_doesnt_delete(self):
 
         self.assertEqual(len(WorkflowExecution.get_all()), 1)
         expected_msg = "Specify a valid timestamp"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             purge_workflow_executions,
diff --git a/st2common/tests/unit/test_queue_utils.py b/st2common/tests/unit/test_queue_utils.py
index db77fc01c2..a5a808377b 100644
--- a/st2common/tests/unit/test_queue_utils.py
+++ b/st2common/tests/unit/test_queue_utils.py
@@ -16,7 +16,7 @@
 from __future__ import absolute_import
 import re
 
-from unittest2 import TestCase
+from unittest import TestCase
 
 import st2common.util.queues as queue_utils
 
diff --git a/st2common/tests/unit/test_rbac_types.py b/st2common/tests/unit/test_rbac_types.py
index 895208210d..c997906f95 100644
--- a/st2common/tests/unit/test_rbac_types.py
+++ b/st2common/tests/unit/test_rbac_types.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-from unittest2 import TestCase
+from unittest import TestCase
 
 from st2common.constants.types import ResourceType as SystemType
 from st2common.rbac.types import PermissionType
diff --git a/st2common/tests/unit/test_resource_reference.py b/st2common/tests/unit/test_resource_reference.py
index 95533022ed..7a4c0a94c1 100644
--- a/st2common/tests/unit/test_resource_reference.py
+++ b/st2common/tests/unit/test_resource_reference.py
@@ -14,13 +14,13 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.models.system.common import ResourceReference
 from st2common.models.system.common import InvalidResourceReferenceError
 
 
-class ResourceReferenceTestCase(unittest2.TestCase):
+class ResourceReferenceTestCase(unittest.TestCase):
     def test_resource_reference_success(self):
         value = "pack1.name1"
         ref = ResourceReference.from_string_reference(ref=value)
@@ -53,7 +53,7 @@ def test_to_string_reference(self):
         self.assertEqual(ref, "mapack.moname")
 
         expected_msg = r'Pack name should not contain "\."'
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             ResourceReference.to_string_reference,
@@ -62,7 +62,7 @@ def test_to_string_reference(self):
         )
 
         expected_msg = "Both pack and name needed for building"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             ResourceReference.to_string_reference,
@@ -71,7 +71,7 @@ def test_to_string_reference(self):
         )
 
         expected_msg = "Both pack and name needed for building"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             ResourceReference.to_string_reference,
diff --git a/st2common/tests/unit/test_resource_registrar.py b/st2common/tests/unit/test_resource_registrar.py
index b03cf7f645..93c0b9588d 100644
--- a/st2common/tests/unit/test_resource_registrar.py
+++ b/st2common/tests/unit/test_resource_registrar.py
@@ -164,7 +164,7 @@ def test_register_pack_pack_ref(self):
 
         # "ref" is not provided and "name" contains invalid characters
         expected_msg = "contains invalid characters"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             registrar._register_pack_db,
@@ -180,7 +180,7 @@ def test_register_pack_invalid_ref_name_friendly_error_message(self):
             r"Pack ref / name can only contain valid word characters .*?,"
             " dashes are not allowed."
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValidationError,
             expected_msg,
             registrar._register_pack_db,
@@ -202,7 +202,7 @@ def test_register_pack_invalid_ref_name_friendly_error_message(self):
             r'Pack name "dummy pack 14" contains invalid characters and "ref" '
             "attribute is not available. You either need to add"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             registrar._register_pack_db,
@@ -235,7 +235,7 @@ def test_register_pack_pack_stackstorm_version_and_future_parameters(self):
 
         # Wrong characters in the required st2 version
         expected_msg = "'wrongstackstormversion' does not match"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValidationError,
             expected_msg,
             registrar._register_pack_db,
@@ -252,7 +252,7 @@ def test_register_pack_empty_and_invalid_config_schema(self):
         expected_msg = (
             'Config schema ".*?dummy_pack_17/config.schema.yaml" is empty and invalid.'
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             registrar.register_packs,
@@ -268,7 +268,7 @@ def test_register_pack_invalid_config_schema_invalid_attribute(self):
         expected_msg = (
             r"Additional properties are not allowed \(\'invalid\' was unexpected\)"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             registrar.register_packs,
@@ -282,7 +282,7 @@ def test_register_pack_invalid_python_versions_attribute(self):
         packs_base_paths = content_utils.get_packs_base_paths()
 
         expected_msg = r"'4' is not one of \['2', '3'\]"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ValueError,
             expected_msg,
             registrar.register_packs,
diff --git a/st2common/tests/unit/test_runners_base.py b/st2common/tests/unit/test_runners_base.py
index 7490b40cd6..b2a885de7e 100644
--- a/st2common/tests/unit/test_runners_base.py
+++ b/st2common/tests/unit/test_runners_base.py
@@ -29,6 +29,6 @@ def test_get_runner_success(self):
 
     def test_get_runner_failure_not_found(self):
         expected_msg = "Failed to find runner invalid-name-not-found.*"
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ActionRunnerCreateError, expected_msg, get_runner, "invalid-name-not-found"
         )
diff --git a/st2common/tests/unit/test_sensor_type_utils.py b/st2common/tests/unit/test_sensor_type_utils.py
index 657054c453..c82c97c95a 100644
--- a/st2common/tests/unit/test_sensor_type_utils.py
+++ b/st2common/tests/unit/test_sensor_type_utils.py
@@ -15,13 +15,13 @@
 
 from __future__ import absolute_import
 import mock
-import unittest2
+import unittest
 
 from st2common.models.api.sensor import SensorTypeAPI
 from st2common.models.utils import sensor_type_utils
 
 
-class SensorTypeUtilsTestCase(unittest2.TestCase):
+class SensorTypeUtilsTestCase(unittest.TestCase):
     def test_to_sensor_db_model_no_trigger_types(self):
         sensor_meta = {
             "artifact_uri": "file:///data/st2contrib/packs/jira/sensors/jira_sensor.py",
diff --git a/st2common/tests/unit/test_sensor_watcher.py b/st2common/tests/unit/test_sensor_watcher.py
index 2379f81562..760a9147c7 100644
--- a/st2common/tests/unit/test_sensor_watcher.py
+++ b/st2common/tests/unit/test_sensor_watcher.py
@@ -16,7 +16,7 @@
 from __future__ import absolute_import
 from kombu.message import Message
 import mock
-import unittest2
+import unittest
 
 from st2common.services.sensor_watcher import SensorWatcher
 from st2common.models.db.sensor import SensorTypeDB
@@ -25,7 +25,7 @@
 MOCK_SENSOR_DB = SensorTypeDB(name="foo", pack="test")
 
 
-class SensorWatcherTests(unittest2.TestCase):
+class SensorWatcherTests(unittest.TestCase):
     @mock.patch.object(Message, "ack", mock.MagicMock())
     @mock.patch.object(PoolPublisher, "publish", mock.MagicMock())
     def test_assert_handlers_called(self):
diff --git a/st2common/tests/unit/test_service_setup.py b/st2common/tests/unit/test_service_setup.py
index 49573c6170..0fa413ca5d 100644
--- a/st2common/tests/unit/test_service_setup.py
+++ b/st2common/tests/unit/test_service_setup.py
@@ -105,7 +105,7 @@ def test_no_logging_config_found(self):
 
         expected_msg = ".*KeyError:.*"
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             Exception,
             expected_msg,
             service_setup.setup,
@@ -133,7 +133,7 @@ def mock_get_logging_config_path():
         expected_msg = "ValueError: Unknown level: 'invalid_log_level'"
         exc_type = ValueError
 
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             exc_type,
             expected_msg,
             service_setup.setup,
@@ -175,7 +175,7 @@ def mock_get_logging_config_path():
         expected_msg = "Failed to find some config files: %s" % (
             MOCK_DEFAULT_CONFIG_FILE_PATH
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ConfigFilesNotFoundError,
             expected_msg,
             service_setup.setup,
@@ -193,7 +193,7 @@ def mock_get_logging_config_path():
         # 2. --config-file should still override default config file path option
         config_file_path = "/etc/st2/config.override.test"
         expected_msg = "Failed to find some config files: %s" % (config_file_path)
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             ConfigFilesNotFoundError,
             expected_msg,
             service_setup.setup,
diff --git a/st2common/tests/unit/test_shell_action_system_model.py b/st2common/tests/unit/test_shell_action_system_model.py
index 28cd78a00b..e171f54276 100644
--- a/st2common/tests/unit/test_shell_action_system_model.py
+++ b/st2common/tests/unit/test_shell_action_system_model.py
@@ -21,7 +21,7 @@
 import copy
 from collections import OrderedDict
 
-import unittest2
+import unittest
 
 from st2common.models.system.action import ShellCommandAction
 from st2common.models.system.action import ShellScriptAction
@@ -38,7 +38,7 @@
 __all__ = ["ShellCommandActionTestCase", "ShellScriptActionTestCase"]
 
 
-class ShellCommandActionTestCase(unittest2.TestCase):
+class ShellCommandActionTestCase(unittest.TestCase):
     def setUp(self):
         self._base_kwargs = {
             "name": "test action",
@@ -97,7 +97,7 @@ def test_user_argument(self):
         self.assertEqual(command, expected_command)
 
 
-class ShellScriptActionTestCase(unittest2.TestCase):
+class ShellScriptActionTestCase(unittest.TestCase):
     def setUp(self):
         self._base_kwargs = {
             "name": "test action",
diff --git a/st2common/tests/unit/test_spec_loader.py b/st2common/tests/unit/test_spec_loader.py
index 284a38e838..474fb78c4d 100644
--- a/st2common/tests/unit/test_spec_loader.py
+++ b/st2common/tests/unit/test_spec_loader.py
@@ -14,7 +14,7 @@
 
 from __future__ import absolute_import
 
-import unittest2
+import unittest
 import yaml
 
 from st2common.util import spec_loader
@@ -23,7 +23,7 @@
 from st2tests.fixtures.specs import __package__ as specs_fixture_package
 
 
-class SpecLoaderTest(unittest2.TestCase):
+class SpecLoaderTest(unittest.TestCase):
     def test_spec_loader(self):
         self.assertTrue(
             isinstance(spec_loader.load_spec("st2common", "openapi.yaml.j2"), dict)
diff --git a/st2common/tests/unit/test_stream_generator.py b/st2common/tests/unit/test_stream_generator.py
index a184220b80..3a691dd1b8 100644
--- a/st2common/tests/unit/test_stream_generator.py
+++ b/st2common/tests/unit/test_stream_generator.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 import mock
-import unittest2
+import unittest
 
 from st2common.stream import listener
 
@@ -52,7 +52,7 @@ def get_consumers(self, consumer, channel):
         pass
 
 
-class TestStream(unittest2.TestCase):
+class TestStream(unittest.TestCase):
     @mock.patch("st2common.stream.listener.BaseListener._get_action_ref_for_body")
     @mock.patch("eventlet.Queue")
     def test_generator(self, mock_queue, get_action_ref_for_body):
diff --git a/st2common/tests/unit/test_time_jinja_filters.py b/st2common/tests/unit/test_time_jinja_filters.py
index 5a343a5c29..b7e461e64b 100644
--- a/st2common/tests/unit/test_time_jinja_filters.py
+++ b/st2common/tests/unit/test_time_jinja_filters.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-from unittest2 import TestCase
+from unittest import TestCase
 
 from st2common.expressions.functions import time
 
diff --git a/st2common/tests/unit/test_transport.py b/st2common/tests/unit/test_transport.py
index c8148f686e..ae12b1ea9d 100644
--- a/st2common/tests/unit/test_transport.py
+++ b/st2common/tests/unit/test_transport.py
@@ -20,7 +20,7 @@
 import ssl
 import random
 
-import unittest2
+import unittest
 import eventlet
 
 from bson.objectid import ObjectId
@@ -56,7 +56,7 @@ def process_task(self, body, message):
         message.ack()
 
 
-class TransportUtilsTestCase(unittest2.TestCase):
+class TransportUtilsTestCase(unittest.TestCase):
     def tearDown(self):
         super(TransportUtilsTestCase, self).tearDown()
         cfg.CONF.set_override(name="compression", group="messaging", override=None)
diff --git a/st2common/tests/unit/test_unit_testing_mocks.py b/st2common/tests/unit/test_unit_testing_mocks.py
index 742ca85da1..a860ace294 100644
--- a/st2common/tests/unit/test_unit_testing_mocks.py
+++ b/st2common/tests/unit/test_unit_testing_mocks.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2tests.base import BaseSensorTestCase
 from st2tests.mocks.sensor import MockSensorWrapper
@@ -34,7 +34,7 @@ class MockSensorClass(object):
 
 
 class BaseMockResourceServiceTestCase(object):
-    class TestCase(unittest2.TestCase):
+    class TestCase(unittest.TestCase):
         def test_get_user_info(self):
             result = self.mock_service.get_user_info()
             self.assertEqual(result["username"], "admin")
@@ -79,7 +79,7 @@ def test_dispatch_and_assertTriggerDispatched(self):
         sensor_service = self.sensor_service
 
         expected_msg = 'Trigger "nope" hasn\'t been dispatched'
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             AssertionError, expected_msg, self.assertTriggerDispatched, trigger="nope"
         )
 
@@ -89,7 +89,7 @@ def test_dispatch_and_assertTriggerDispatched(self):
         result = self.assertTriggerDispatched(trigger="test1", payload={"a": "b"})
         self.assertTrue(result)
         expected_msg = 'Trigger "test1" hasn\'t been dispatched'
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             AssertionError,
             expected_msg,
             self.assertTriggerDispatched,
diff --git a/st2common/tests/unit/test_util_actionalias_helpstrings.py b/st2common/tests/unit/test_util_actionalias_helpstrings.py
index e543bd471a..ee8368cd12 100644
--- a/st2common/tests/unit/test_util_actionalias_helpstrings.py
+++ b/st2common/tests/unit/test_util_actionalias_helpstrings.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 import mock
 
 from st2common.models.db.actionalias import ActionAliasDB
@@ -115,7 +115,7 @@
 
 
 @mock.patch.object(MemoryActionAliasDB, "get_uid")
-class ActionAliasTestCase(unittest2.TestCase):
+class ActionAliasTestCase(unittest.TestCase):
     """
     Test scenarios must consist of 80s movie quotes.
     """
diff --git a/st2common/tests/unit/test_util_actionalias_matching.py b/st2common/tests/unit/test_util_actionalias_matching.py
index 082fa40b98..058ff91d08 100644
--- a/st2common/tests/unit/test_util_actionalias_matching.py
+++ b/st2common/tests/unit/test_util_actionalias_matching.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 import mock
 
 from st2common.models.db.actionalias import ActionAliasDB
@@ -25,7 +25,7 @@
 
 
 @mock.patch.object(MemoryActionAliasDB, "get_uid")
-class ActionAliasTestCase(unittest2.TestCase):
+class ActionAliasTestCase(unittest.TestCase):
     """
     Test scenarios must consist of 80s movie quotes.
     """
diff --git a/st2common/tests/unit/test_util_api.py b/st2common/tests/unit/test_util_api.py
index 2333939b13..d5d8e51a6d 100644
--- a/st2common/tests/unit/test_util_api.py
+++ b/st2common/tests/unit/test_util_api.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from oslo_config import cfg
 
@@ -27,7 +27,7 @@
 parse_args()
 
 
-class APIUtilsTestCase(unittest2.TestCase):
+class APIUtilsTestCase(unittest.TestCase):
     def test_get_base_public_api_url(self):
         values = [
             "http://foo.bar.com",
diff --git a/st2common/tests/unit/test_util_compat.py b/st2common/tests/unit/test_util_compat.py
index 74face7ea6..86bf64a3e5 100644
--- a/st2common/tests/unit/test_util_compat.py
+++ b/st2common/tests/unit/test_util_compat.py
@@ -15,14 +15,14 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util.compat import to_ascii
 
 __all__ = ["CompatUtilsTestCase"]
 
 
-class CompatUtilsTestCase(unittest2.TestCase):
+class CompatUtilsTestCase(unittest.TestCase):
     def test_to_ascii(self):
         expected_values = [
             ("already ascii", "already ascii"),
diff --git a/st2common/tests/unit/test_util_db.py b/st2common/tests/unit/test_util_db.py
index f94a2fe39a..2a41ed1a80 100644
--- a/st2common/tests/unit/test_util_db.py
+++ b/st2common/tests/unit/test_util_db.py
@@ -16,12 +16,12 @@
 from __future__ import absolute_import
 
 import mongoengine
-import unittest2
+import unittest
 
 from st2common.util import db as db_util
 
 
-class DatabaseUtilTestCase(unittest2.TestCase):
+class DatabaseUtilTestCase(unittest.TestCase):
     def test_noop_mongodb_to_python_types(self):
         data = [123, 999.99, True, [10, 20, 30], {"a": 1, "b": 2}, None]
 
diff --git a/st2common/tests/unit/test_util_file_system.py b/st2common/tests/unit/test_util_file_system.py
index 9ae8d1bc7a..f45498702b 100644
--- a/st2common/tests/unit/test_util_file_system.py
+++ b/st2common/tests/unit/test_util_file_system.py
@@ -17,7 +17,7 @@
 import os
 import os.path
 
-import unittest2
+import unittest
 
 from st2common.util.file_system import get_file_list
 
@@ -25,7 +25,7 @@
 ST2TESTS_DIR = os.path.join(CURRENT_DIR, "../../../st2tests/st2tests")
 
 
-class FileSystemUtilsTestCase(unittest2.TestCase):
+class FileSystemUtilsTestCase(unittest.TestCase):
     def test_get_file_list(self):
         # Standard exclude pattern
         directory = os.path.join(ST2TESTS_DIR, "policies")
@@ -39,8 +39,13 @@ def test_get_file_list(self):
             "meta/concurrency.yaml",
             "meta/__init__.py",
         ]
-        result = get_file_list(directory=directory, exclude_patterns=["*.pyc"])
-        self.assertItemsEqual(expected, result)
+        result = get_file_list(
+            directory=directory, exclude_patterns=["*.pyc", "__pycache__"]
+        )
+        # directory listings are sorted because the item order must be exact for assert
+        # to validate equivalence.  Directory item order doesn't matter in general and may
+        # even change on different platforms or locales.
+        assert sorted(expected) == sorted(result)
 
         # Custom exclude pattern
         expected = [
@@ -52,4 +57,7 @@ def test_get_file_list(self):
         result = get_file_list(
             directory=directory, exclude_patterns=["*.pyc", "*.yaml", "*BUILD"]
         )
-        self.assertItemsEqual(expected, result)
+        # directory listings are sorted because the item order must be exact for assert
+        # to validate equivalence.  Directory item order doesn't matter in general and may
+        # even change on different platforms or locales.
+        assert sorted(expected) == sorted(result)
diff --git a/st2common/tests/unit/test_util_http.py b/st2common/tests/unit/test_util_http.py
index a97aa8c7f1..d4220e3eeb 100644
--- a/st2common/tests/unit/test_util_http.py
+++ b/st2common/tests/unit/test_util_http.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util.http import parse_content_type_header
 from six.moves import zip
@@ -22,7 +22,7 @@
 __all__ = ["HTTPUtilTestCase"]
 
 
-class HTTPUtilTestCase(unittest2.TestCase):
+class HTTPUtilTestCase(unittest.TestCase):
     def test_parse_content_type_header(self):
         values = [
             "application/json",
diff --git a/st2common/tests/unit/test_util_jinja.py b/st2common/tests/unit/test_util_jinja.py
index 127570f54b..187260c818 100644
--- a/st2common/tests/unit/test_util_jinja.py
+++ b/st2common/tests/unit/test_util_jinja.py
@@ -15,12 +15,12 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util import jinja as jinja_utils
 
 
-class JinjaUtilsRenderTestCase(unittest2.TestCase):
+class JinjaUtilsRenderTestCase(unittest.TestCase):
     def test_render_values(self):
         actual = jinja_utils.render_values(
             mapping={"k1": "{{a}}", "k2": "{{b}}"}, context={"a": "v1", "b": "v2"}
diff --git a/st2common/tests/unit/test_util_keyvalue.py b/st2common/tests/unit/test_util_keyvalue.py
index 14c0996143..a39f1a5c76 100644
--- a/st2common/tests/unit/test_util_keyvalue.py
+++ b/st2common/tests/unit/test_util_keyvalue.py
@@ -15,7 +15,7 @@
 
 import mock
 
-import unittest2
+import unittest
 from oslo_config import cfg
 
 from st2common.util import keyvalue as kv_utl
@@ -44,7 +44,7 @@
 )
 
 
-class TestKeyValueUtil(unittest2.TestCase):
+class TestKeyValueUtil(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         super(TestKeyValueUtil, cls).setUpClass()
diff --git a/st2common/tests/unit/test_util_output_schema.py b/st2common/tests/unit/test_util_output_schema.py
index 9e27bff61e..8756151bc1 100644
--- a/st2common/tests/unit/test_util_output_schema.py
+++ b/st2common/tests/unit/test_util_output_schema.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 import copy
-import unittest2
+import unittest
 
 from st2common.util import output_schema
 
@@ -186,7 +186,7 @@
 }
 
 
-class OutputSchemaTestCase(unittest2.TestCase):
+class OutputSchemaTestCase(unittest.TestCase):
     def test_valid_schema(self):
         result, status = output_schema.validate_output(
             copy.deepcopy(RUNNER_OUTPUT_SCHEMA),
diff --git a/st2common/tests/unit/test_util_pack.py b/st2common/tests/unit/test_util_pack.py
index 8e9dd59884..8ccf23440a 100644
--- a/st2common/tests/unit/test_util_pack.py
+++ b/st2common/tests/unit/test_util_pack.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.models.db.pack import PackDB
 from st2common.util.pack import get_pack_common_libs_path_for_pack_db
@@ -22,7 +22,7 @@
 from st2common.util.pack import get_pack_ref_from_metadata
 
 
-class PackUtilsTestCase(unittest2.TestCase):
+class PackUtilsTestCase(unittest.TestCase):
     def test_get_pack_common_libs_path_for_pack_db(self):
         pack_model_args = {
             "name": "Yolo CI",
diff --git a/st2common/tests/unit/test_util_payload.py b/st2common/tests/unit/test_util_payload.py
index 2621e3de91..d6629d6d57 100644
--- a/st2common/tests/unit/test_util_payload.py
+++ b/st2common/tests/unit/test_util_payload.py
@@ -15,14 +15,14 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util.payload import PayloadLookup
 
 __all__ = ["PayloadLookupTestCase"]
 
 
-class PayloadLookupTestCase(unittest2.TestCase):
+class PayloadLookupTestCase(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         cls.payload = PayloadLookup(
diff --git a/st2common/tests/unit/test_util_secrets.py b/st2common/tests/unit/test_util_secrets.py
index 8c77c34f49..17d92aea4b 100644
--- a/st2common/tests/unit/test_util_secrets.py
+++ b/st2common/tests/unit/test_util_secrets.py
@@ -14,7 +14,7 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.constants.secrets import MASKED_ATTRIBUTE_VALUE
 from st2common.util import secrets
@@ -444,7 +444,7 @@
 ################################################################################
 
 
-class SecretUtilsTestCase(unittest2.TestCase):
+class SecretUtilsTestCase(unittest.TestCase):
     def test_get_secret_parameters_flat(self):
         result = secrets.get_secret_parameters(TEST_FLAT_SCHEMA)
         self.assertEqual(TEST_FLAT_SECRET_PARAMS, result)
diff --git a/st2common/tests/unit/test_util_shell.py b/st2common/tests/unit/test_util_shell.py
index 4a2a00e343..c6f02ae72f 100644
--- a/st2common/tests/unit/test_util_shell.py
+++ b/st2common/tests/unit/test_util_shell.py
@@ -14,14 +14,14 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util.shell import quote_unix
 from st2common.util.shell import quote_windows
 from six.moves import zip
 
 
-class ShellUtilsTestCase(unittest2.TestCase):
+class ShellUtilsTestCase(unittest.TestCase):
     def test_quote_unix(self):
         arguments = ["foo", "foo bar", "foo1 bar1", '"foo"', '"foo" "bar"', "'foo bar'"]
         expected_values = [
diff --git a/st2common/tests/unit/test_util_types.py b/st2common/tests/unit/test_util_types.py
index 8b7ef78864..a9a3cbed7f 100644
--- a/st2common/tests/unit/test_util_types.py
+++ b/st2common/tests/unit/test_util_types.py
@@ -13,14 +13,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import unittest2
+import unittest
 
 from st2common.util.types import OrderedSet
 
 __all__ = ["OrderedTestTypeTestCase"]
 
 
-class OrderedTestTypeTestCase(unittest2.TestCase):
+class OrderedTestTypeTestCase(unittest.TestCase):
     def test_ordered_set(self):
         set1 = OrderedSet([1, 2, 3, 3, 4, 2, 1, 5])
         self.assertEqual(set1, [1, 2, 3, 4, 5])
diff --git a/st2common/tests/unit/test_util_url.py b/st2common/tests/unit/test_util_url.py
index 8b23619593..26907fce08 100644
--- a/st2common/tests/unit/test_util_url.py
+++ b/st2common/tests/unit/test_util_url.py
@@ -14,13 +14,13 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util.url import get_url_without_trailing_slash
 from six.moves import zip
 
 
-class URLUtilsTestCase(unittest2.TestCase):
+class URLUtilsTestCase(unittest.TestCase):
     def test_get_url_without_trailing_slash(self):
         values = [
             "http://localhost:1818/foo/bar/",
diff --git a/st2common/tests/unit/test_versioning_utils.py b/st2common/tests/unit/test_versioning_utils.py
index de7bbbfeaf..f35b6fd026 100644
--- a/st2common/tests/unit/test_versioning_utils.py
+++ b/st2common/tests/unit/test_versioning_utils.py
@@ -14,13 +14,13 @@
 # limitations under the License.
 
 from __future__ import absolute_import
-import unittest2
+import unittest
 
 from st2common.util.versioning import complex_semver_match
 from st2common.util.pack import normalize_pack_version
 
 
-class VersioningUtilsTestCase(unittest2.TestCase):
+class VersioningUtilsTestCase(unittest.TestCase):
     def test_complex_semver_match(self):
         # Positive test case
         self.assertTrue(complex_semver_match("1.6.0", ">=1.6.0, <2.2.0"))
diff --git a/st2reactor/tests/unit/test_process_container.py b/st2reactor/tests/unit/test_process_container.py
index d1bcfdfe64..b05175b805 100644
--- a/st2reactor/tests/unit/test_process_container.py
+++ b/st2reactor/tests/unit/test_process_container.py
@@ -18,7 +18,7 @@
 import time
 
 from mock import MagicMock, Mock, patch
-import unittest2
+import unittest
 
 from st2reactor.container.process_container import ProcessSensorContainer
 from st2common.util import concurrency
@@ -37,7 +37,7 @@
 )
 
 
-class ProcessContainerTests(unittest2.TestCase):
+class ProcessContainerTests(unittest.TestCase):
     def test_no_sensors_dont_quit(self):
         process_container = ProcessSensorContainer(None, poll_interval=0.1)
         process_container_thread = concurrency.spawn(process_container.run)
diff --git a/st2reactor/tests/unit/test_sensor_service.py b/st2reactor/tests/unit/test_sensor_service.py
index 9d1e245e10..6ba0e6236b 100644
--- a/st2reactor/tests/unit/test_sensor_service.py
+++ b/st2reactor/tests/unit/test_sensor_service.py
@@ -15,7 +15,7 @@
 
 from __future__ import absolute_import
 import mock
-import unittest2
+import unittest
 
 from oslo_config import cfg
 
@@ -51,7 +51,7 @@ def __init__(self, type=None):
         self.type = type
 
 
-class SensorServiceTestCase(unittest2.TestCase):
+class SensorServiceTestCase(unittest.TestCase):
     def setUp(self):
         def side_effect(trigger, payload, trace_context):
             self._dispatched_count += 1
diff --git a/st2reactor/tests/unit/test_sensor_wrapper.py b/st2reactor/tests/unit/test_sensor_wrapper.py
index 7d7ce7f1d2..3a9d3b9ced 100644
--- a/st2reactor/tests/unit/test_sensor_wrapper.py
+++ b/st2reactor/tests/unit/test_sensor_wrapper.py
@@ -20,7 +20,7 @@
 monkey_patch()
 
 import os
-import unittest2
+import unittest
 
 import six
 import mock
@@ -39,7 +39,7 @@
 __all__ = ["SensorWrapperTestCase"]
 
 
-class SensorWrapperTestCase(unittest2.TestCase):
+class SensorWrapperTestCase(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         super(SensorWrapperTestCase, cls).setUpClass()
@@ -152,7 +152,7 @@ def test_sensor_init_fails_file_doesnt_exist(self):
         expected_msg = (
             "Failed to load sensor class from file.*? No such file or directory"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             IOError,
             expected_msg,
             SensorWrapper,
@@ -171,7 +171,7 @@ def test_sensor_init_fails_sensor_code_contains_typo(self):
         expected_msg = (
             "Failed to load sensor class from file.*? 'typobar' is not defined"
         )
-        self.assertRaisesRegexp(
+        self.assertRaisesRegex(
             NameError,
             expected_msg,
             SensorWrapper,
diff --git a/st2reactor/tests/unit/test_timer.py b/st2reactor/tests/unit/test_timer.py
index f4311d18d8..9ec00be5ec 100644
--- a/st2reactor/tests/unit/test_timer.py
+++ b/st2reactor/tests/unit/test_timer.py
@@ -31,8 +31,9 @@ def test_trigger_types_are_registered_on_start(self):
         timer = St2Timer()
         timer._scheduler = mock.Mock()
 
-        # Verify there are no TriggerType in the db when we start
-        self.assertItemsEqual(TriggerType.get_all(), [])
+        # Verify there are no TriggerType objects in the db when we start
+        # and cast Mongo QuerySet iterator cast to list for evaluation.
+        assert list(TriggerType.get_all()) == []
 
         timer.start()
 
@@ -55,8 +56,8 @@ def test_existing_rules_are_loaded_on_start(self):
         timer._trigger_watcher.run = mock.Mock()
 
         # Verify there are no Trigger and TriggerType in the db wh:w
-        self.assertItemsEqual(Trigger.get_all(), [])
-        self.assertItemsEqual(TriggerType.get_all(), [])
+        assert list(Trigger.get_all()) == []
+        assert list(TriggerType.get_all()) == []
 
         # Add a dummy timer Trigger object
         type_ = list(TIMER_TRIGGER_TYPES.keys())[0]
diff --git a/st2tests/integration/orquesta/base.py b/st2tests/integration/orquesta/base.py
index 15d1cefbfd..1d4a8c95e2 100644
--- a/st2tests/integration/orquesta/base.py
+++ b/st2tests/integration/orquesta/base.py
@@ -24,7 +24,7 @@
 import shutil
 import six
 import tempfile
-import unittest2
+import unittest
 
 from st2client import client as st2
 from st2client import models
@@ -59,7 +59,7 @@ def _delete_temp_file(self, temp_file_path):
                 os.remove(temp_file_path)
 
 
-class TestWorkflowExecution(unittest2.TestCase):
+class TestWorkflowExecution(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         cls.st2client = st2.Client(base_url="http://127.0.0.1")
diff --git a/st2tests/st2tests/api.py b/st2tests/st2tests/api.py
index dae8eb7831..1500a0b321 100644
--- a/st2tests/st2tests/api.py
+++ b/st2tests/st2tests/api.py
@@ -222,7 +222,7 @@ def test_get_all_exclude_attributes_and_include_attributes_are_mutually_exclusiv
             "exclude.*? and include.*? arguments are mutually exclusive. "
             "You need to provide either one or another, but not both."
         )
-        self.assertRegexpMatches(resp.json["faultstring"], expected_msg)
+        self.assertRegex(resp.json["faultstring"], expected_msg)
 
     def test_get_all_invalid_exclude_and_include_parameter(self):
         if self.rbac_enabled:
@@ -236,7 +236,7 @@ def test_get_all_invalid_exclude_and_include_parameter(self):
             "Invalid or unsupported exclude attribute specified: .*invalid_field.*"
         )
         self.assertEqual(resp.status_int, 400)
-        self.assertRegexpMatches(resp.json["faultstring"], expected_msg)
+        self.assertRegex(resp.json["faultstring"], expected_msg)
 
         # 2. Invalid include_attributes field
         url = self.get_all_path + "?include_attributes=invalid_field"
@@ -246,7 +246,7 @@ def test_get_all_invalid_exclude_and_include_parameter(self):
             "Invalid or unsupported include attribute specified: .*invalid_field.*"
         )
         self.assertEqual(resp.status_int, 400)
-        self.assertRegexpMatches(resp.json["faultstring"], expected_msg)
+        self.assertRegex(resp.json["faultstring"], expected_msg)
 
     def test_get_all_include_attributes_filter(self):
         if self.rbac_enabled:
diff --git a/st2tests/st2tests/base.py b/st2tests/st2tests/base.py
index c8a480fdb6..6c3a04fb52 100644
--- a/st2tests/st2tests/base.py
+++ b/st2tests/st2tests/base.py
@@ -40,8 +40,8 @@
 import psutil
 import mock
 from oslo_config import cfg
-from unittest2 import TestCase
-import unittest2
+from unittest import TestCase
+import unittest
 
 from orquesta import conducting
 from orquesta import events
@@ -139,7 +139,7 @@
 TESTS_CONFIG_PATH = os.path.join(BASE_DIR, "../conf/st2.conf")
 
 
-class RunnerTestCase(unittest2.TestCase):
+class RunnerTestCase(unittest.TestCase):
     meta_loader = MetaLoader()
 
     def assertCommonSt2EnvVarsAvailableInEnv(self, env):
diff --git a/st2tests/st2tests/pack_resource.py b/st2tests/st2tests/pack_resource.py
index 51f5899218..7b72d72a9d 100644
--- a/st2tests/st2tests/pack_resource.py
+++ b/st2tests/st2tests/pack_resource.py
@@ -17,7 +17,7 @@
 import os
 import inspect
 
-from unittest2 import TestCase
+from unittest import TestCase
 
 __all__ = ["BasePackResourceTestCase"]
 
diff --git a/test-requirements.txt b/test-requirements.txt
index 9b23e1e544..41a7aca8df 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -12,7 +12,6 @@ isort>=4.2.5
 mock==5.1.0
 nose>=1.3.7
 tabulate
-unittest2
 # # 4.5.0 required for Jinja-3.1.3 support but >5.0 required by rstcheck and lower than 7.2 which drops py3.8 support
 sphinx>=5.0.0,<7.2.0
 sphinx-autobuild