Repository: apache/trafficcontrol
Pull request #6745 (2022-04-12 17:11:40)

fixed JWT Auth to have Expires and renew when Mojo renews
<!--
Thank you for contributing! Please be sure to read our contribution guidelines: https://github.com/apache/trafficcontrol/blob/master/CONTRIBUTING.md
If this closes or relates to an existing issue, please reference it using one of the following:

Closes: #ISSUE
Related: #ISSUE

If this PR fixes a security vulnerability, DO NOT submit! Instead, contact
the Apache Traffic Control Security Team at security@trafficcontrol.apache.org and follow the
guidelines at https://apache.org/security regarding vulnerability disclosure.
-->

This PR updates the JWT Auth to include the Expires field and to renew the token when the mojolicious cookie is renewed

<!-- **^ Add meaningful description above** --><hr/>

## Which Traffic Control components are affected by this PR?
<!-- Please delete all components from this list that are NOT affected by this PR.
Feel free to add the name of a tool or script that is affected but not on the list.
-->

- Traffic Ops


## What is the best way to verify this PR?
<!-- Please include here ALL the steps necessary to test your PR.
If your PR has tests (and most should), provide the steps needed to run the tests.
If not, please provide step-by-step instructions to test the PR manually and explain why your PR does not need tests. -->

Login to Traffic Ops and verify that you get a mojolicious cookie and an access_token cookie.
Hit a non-login endpoint and verify that both cookies are refreshed.
Verify that the access_token has the Expired field
Verify that login works as expected using user/pass, mojo cookie, and JWT

## If this is a bugfix, which Traffic Control versions contained the bug?
<!-- Delete this section if the PR is not a bugfix, or if the bug is only in the master branch.
Examples:
- 5.1.2
- 5.1.3 (RC1)
 -->


## PR submission checklist
- [x] This PR has tests <!-- If not, please delete this text and explain why this PR does not need tests. -->
- [x] This PR has documentation <!-- If not, please delete this text and explain why this PR does not need documentation. -->
- [x] This PR has a CHANGELOG.md entry <!-- A fix for a bug from an ATC release, an improvement, or a new feature should have a changelog entry. -->
- [x] This PR **DOES NOT FIX A SERIOUS SECURITY VULNERABILITY** (see [the Apache Software Foundation's security guidelines](https://apache.org/security) for details)

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
-->



Patch:
diff --git a/.env b/.env
index 904d0e08e..52009c0b1 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,2 @@
 ATS_VERSION=9.1.2
-GO_VERSION=1.18
+GO_VERSION=1.18.1
diff --git a/.github/actions/assign-triage-role/README.rst b/.github/actions/assign-triage-role/README.rst
index cfb879b05..fa6c9b1f4 100644
--- a/.github/actions/assign-triage-role/README.rst
+++ b/.github/actions/assign-triage-role/README.rst
@@ -27,6 +27,8 @@ Environment Variables
 +============================+==================================================================================+
 | ``GITHUB_TOKEN``           | Required. ``${{ github.token }}`` or ``${{ secrets.GITHUB_TOKEN }}``             |
 +----------------------------+----------------------------------------------------------------------------------+
+| ``PR_GITHUB_TOKEN``        | Required. ``${{ github.token }}`` or another token                               |
++----------------------------+----------------------------------------------------------------------------------+
 | ``GIT_AUTHOR_NAME``        | Optional. The username to associate with the commit that updates the Go version. |
 +----------------------------+----------------------------------------------------------------------------------+
 | ``MINIMUM_COMMITS``        | Required. The lowest number of Issue-closing Pull Requests a Contributor can     |
@@ -51,7 +53,7 @@ Example usage
 	- name: Assign Triage Role
 	  run: python3 -m assign_triage_role
 	  env:
-	    GIT_AUTHOR_NAME: asfgit
+	    GIT_AUTHOR_NAME: asf-ci
 	    GITHUB_TOKEN: ${{ github.token }}
 	    MINIMUM_COMMITS: 5
 	    SINCE_DAYS_AGO: 45
diff --git a/.github/actions/assign-triage-role/assign_triage_role/constants.py b/.github/actions/assign-triage-role/assign_triage_role/constants.py
index 1e7f443f7..238237dfe 100644
--- a/.github/actions/assign-triage-role/assign_triage_role/constants.py
+++ b/.github/actions/assign-triage-role/assign_triage_role/constants.py
@@ -20,6 +20,7 @@ ENV_GIT_AUTHOR_NAME: Final[str] = "GIT_AUTHOR_NAME"
 ENV_GITHUB_REPOSITORY: Final[str] = "GITHUB_REPOSITORY"
 ENV_GITHUB_REPOSITORY_OWNER: Final[str] = "GITHUB_REPOSITORY_OWNER"
 ENV_GITHUB_TOKEN: Final[str] = "GITHUB_TOKEN"
+ENV_PR_GITHUB_TOKEN: Final[str] = "PR_GITHUB_TOKEN"
 ENV_GITHUB_REF_NAME: Final[str] = "GITHUB_REF_NAME"
 ENV_MINIMUM_COMMITS: Final[str] = "MINIMUM_COMMITS"
 ENV_SINCE_DAYS_AGO: Final[str] = "SINCE_DAYS_AGO"
@@ -51,6 +52,7 @@ GIT_AUTHOR_NAME: Final[str] = getenv(ENV_GIT_AUTHOR_NAME)
 GITHUB_REPOSITORY: Final[str] = getenv(ENV_GITHUB_REPOSITORY)
 GITHUB_REPOSITORY_OWNER: Final[str] = getenv(ENV_GITHUB_REPOSITORY_OWNER)
 GITHUB_TOKEN: Final[str] = getenv(ENV_GITHUB_TOKEN)
+PR_GITHUB_TOKEN: Final[str] = getenv(ENV_PR_GITHUB_TOKEN)
 GITHUB_REF_NAME: Final[str] = getenv(ENV_GITHUB_REF_NAME)
 MINIMUM_COMMITS: Final[str] = getenv(ENV_MINIMUM_COMMITS)
 SINCE_DAYS_AGO: Final[str] = getenv(ENV_SINCE_DAYS_AGO)
diff --git a/.github/actions/assign-triage-role/assign_triage_role/triage_role_assigner.py b/.github/actions/assign-triage-role/assign_triage_role/triage_role_assigner.py
index 622f59f73..4a5dbac24 100644
--- a/.github/actions/assign-triage-role/assign_triage_role/triage_role_assigner.py
+++ b/.github/actions/assign-triage-role/assign_triage_role/triage_role_assigner.py
@@ -36,7 +36,7 @@ from assign_triage_role.constants import (GH_TIMELINE_EVENT_TYPE_CROSS_REFERENCE
 	APACHE_LICENSE_YAML, GIT_AUTHOR_EMAIL_TEMPLATE, SINGLE_PR_TEMPLATE_FILE,
 	SINGLE_CONTRIBUTOR_TEMPLATE_FILE, PR_TEMPLATE_FILE, EMPTY_CONTRIB_LIST_LIST,
 	EMPTY_LIST_OF_CONTRIBUTORS, CONGRATS, EXPIRE, GITHUB_REPOSITORY, GIT_AUTHOR_NAME,
-	GITHUB_REPOSITORY_OWNER, MINIMUM_COMMITS, SINCE_DAYS_AGO, GITHUB_REF_NAME)
+	GITHUB_REPOSITORY_OWNER, MINIMUM_COMMITS, SINCE_DAYS_AGO, GITHUB_REF_NAME, PR_GITHUB_TOKEN)
 
 
 class TriageRoleAssigner(Github):
@@ -328,4 +328,8 @@ class TriageRoleAssigner(Github):
 		if not self.branch_exists(source_branch_name):
 			self.push_changes(source_branch_name, commit_message)
 		self.repo.get_git_ref(f"heads/{source_branch_name}")
+
+		pr_github_token = PR_GITHUB_TOKEN
+		self.__init__(login_or_token=pr_github_token)
+
 		self.create_pr(prs_by_contributor, commit_message, source_branch_name)
diff --git a/.github/actions/assign-triage-role/setup.cfg b/.github/actions/assign-triage-role/setup.cfg
index 63649ac41..16ef60c00 100644
--- a/.github/actions/assign-triage-role/setup.cfg
+++ b/.github/actions/assign-triage-role/setup.cfg
@@ -21,7 +21,7 @@ author_email = dev@trafficcontrol.apache.org
 classifiers = OSI Approved :: Apache Software License
 
 [options]
-python_requires = >=3.9
+python_requires = >=3.10
 packages = assign_triage_role
 install_requires =
 	PyGithub
diff --git a/.github/actions/go-fmt/entrypoint.sh b/.github/actions/go-fmt/entrypoint.sh
index 598e2b081..ccacfd47e 100755
--- a/.github/actions/go-fmt/entrypoint.sh
+++ b/.github/actions/go-fmt/entrypoint.sh
@@ -34,9 +34,21 @@ mkdir -p "$SRCDIR"
 ln -s "$PWD" "$SRCDIR/trafficcontrol"
 cd "$SRCDIR/trafficcontrol"
 
+printf "about to gofmt, pwd: %s\n" "$(pwd)"
+
 /usr/local/go/bin/go fmt ./...
+printf "gofmt returned %d\n" "$?"
+
+
+git config --global --add safe.directory /github/workspace
+printf "git config add safe.directory returned  %d\n" "$?"
+
+#git status
+
+printf "about to git-diff, pwd: %s\n" "$(pwd)"
 DIFF_FILE="$(mktemp)"
 git diff >"$DIFF_FILE"
+printf "git diff returned %d\n" "$?"
 
 if [ -s "$DIFF_FILE" ]; then
 	./misc/parse_diffs.py <"$DIFF_FILE";
diff --git a/.github/actions/pr-to-update-go/README.rst b/.github/actions/pr-to-update-go/README.rst
index 6814472ad..991bd08b0 100644
--- a/.github/actions/pr-to-update-go/README.rst
+++ b/.github/actions/pr-to-update-go/README.rst
@@ -40,6 +40,9 @@ Environment Variables
 +----------------------------+----------------------------------------------------------------------------------+
 | ``GITHUB_TOKEN``           | Required. ``${{ github.token }}`` or ``${{ secrets.GITHUB_TOKEN }}``             |
 +----------------------------+----------------------------------------------------------------------------------+
+| ``PR_GITHUB_TOKEN``        | Optional. A GitHub token other than GitHub Actions so that Actions will run on   |
+|                            | the generated Pull Request                                                       |
++----------------------------+----------------------------------------------------------------------------------+
 | ``GO_VERSION_FILE``        | Required. The file in the repo containing the version of Go used by the repo.    |
 +----------------------------+----------------------------------------------------------------------------------+
 
@@ -60,7 +63,7 @@ Example usage
 	- name: PR to Update Go
 	  run: python3 -m pr_to_update_go
 	  env:
-	    GIT_AUTHOR_NAME: asfgit
+	    GIT_AUTHOR_NAME: asf-ci
 	    GITHUB_TOKEN: ${{ github.token }}
 	    GO_VERSION_FILE: GO_VERSION
 
diff --git a/.github/actions/pr-to-update-go/pr_to_update_go/constants.py b/.github/actions/pr-to-update-go/pr_to_update_go/constants.py
index f7ae6ec63..a14e84249 100644
--- a/.github/actions/pr-to-update-go/pr_to_update_go/constants.py
+++ b/.github/actions/pr-to-update-go/pr_to_update_go/constants.py
@@ -19,6 +19,8 @@ Environment variable names, and the meanings of the values of those variables:
 	ENV_GITHUB_REPOSITORY       - The "name" of the repository set by GHA (e.g. octocat/Hello-World)
 	ENV_GITHUB_REPOSITORY_OWNER - The repository owner's name set by GHA (e.g. octocat)
 	ENV_GITHUB_TOKEN            - The token used to access the GitHub API - set by GHA
+	PR_GITHUB_TOKEN             - The token used to access the GitHub API for creating the Pull
+	                              Request- set by GHA
 	ENV_GO_VERSION_FILE         - The repository-relative path to the file containing the Go version
 	ENV_ENV_FILE                - The repository-relative path to an environment file containing
 	                              a line setting the variable GO_VERSION to the Go version
@@ -56,6 +58,13 @@ The name of the environment variable set to token used to access the GitHub
 API - set by GHA.
 """
 
+ENV_PR_GITHUB_TOKEN: Final = 'PR_GITHUB_TOKEN'
+"""
+The name of the environment variable set to token used to access the GitHub
+API, but only for creating the Pull Request, so that Actions will run on the
+generated Pull Request - set by GHA.
+"""
+
 ENV_GO_VERSION_FILE: Final = 'GO_VERSION_FILE'
 """
 The name of the environment variable set to repository-relative path to the file
@@ -93,6 +102,7 @@ __all__ = [
 	"ENV_GITHUB_REPOSITORY",
 	"ENV_GITHUB_REPOSITORY_OWNER",
 	"ENV_GITHUB_TOKEN",
+	"ENV_PR_GITHUB_TOKEN"
 	"ENV_GO_VERSION_FILE",
 	"ENV_ENV_FILE",
 	"GIT_AUTHOR_EMAIL_TEMPLATE",
diff --git a/.github/actions/pr-to-update-go/pr_to_update_go/go_pr_maker.py b/.github/actions/pr-to-update-go/pr_to_update_go/go_pr_maker.py
index 25fda0d98..46a441087 100644
--- a/.github/actions/pr-to-update-go/pr_to_update_go/go_pr_maker.py
+++ b/.github/actions/pr-to-update-go/pr_to_update_go/go_pr_maker.py
@@ -43,6 +43,7 @@ from github.Repository import Repository
 from pr_to_update_go.constants import (
 	ENV_ENV_FILE,
 	ENV_GITHUB_TOKEN,
+	ENV_PR_GITHUB_TOKEN,
 	GO_VERSION_URL,
 	ENV_GITHUB_REPOSITORY,
 	ENV_GITHUB_REPOSITORY_OWNER,
@@ -459,6 +460,16 @@ class GoPRMaker:
 
 		milestone_url = self.get_go_milestone(latest_go_version)
 		pr_body = _get_pr_body(latest_go_version, milestone_url)
+
+		try:
+			pr_github_token = getenv(ENV_PR_GITHUB_TOKEN)
+			self.gh_api = Github(login_or_token=pr_github_token)
+			self.repo = self.get_repo(getenv(ENV_GITHUB_REPOSITORY))
+		except KeyError:
+			print(f'Token in {ENV_PR_GITHUB_TOKEN} is invalid, creating the PR using the '
+			      f'{ENV_GITHUB_TOKEN} token')
+			pass
+
 		pull_request = self.repo.create_pull(
 			title=commit_message,
 			body=pr_body,
diff --git a/.github/actions/pr-to-update-go/setup.cfg b/.github/actions/pr-to-update-go/setup.cfg
index 2b6c0fa5b..476f1458c 100644
--- a/.github/actions/pr-to-update-go/setup.cfg
+++ b/.github/actions/pr-to-update-go/setup.cfg
@@ -21,7 +21,7 @@ author_email = dev@trafficcontrol.apache.org
 classifiers = OSI Approved :: Apache Software License
 
 [options]
-python_requires = >=3.9
+python_requires = >=3.10
 packages = pr_to_update_go
 install_requires =
 	python-dotenv
diff --git a/.github/workflows/assign-triage-role.yml b/.github/workflows/assign-triage-role.yml
index 07e75a0ae..bf0b3b3ac 100644
--- a/.github/workflows/assign-triage-role.yml
+++ b/.github/workflows/assign-triage-role.yml
@@ -56,7 +56,8 @@ jobs:
         if: ${{ steps.checkout.outcome == 'success' }}
         run: python3 -m assign_triage_role
         env:
-          GIT_AUTHOR_NAME: asfgit
-          GITHUB_TOKEN: ${{ github.token }}
+          GIT_AUTHOR_NAME: asf-ci
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          PR_GITHUB_TOKEN: ${{ secrets.ASFCI_TOKEN }}
           MINIMUM_COMMITS: '${{ github.event.inputs.minimum-commits || env.DEFAULT_MINIMUM_COMMITS }}'
           SINCE_DAYS_AGO: '${{ github.event.inputs.since-days-ago || env.DEFAULT_SINCE_DAYS_AGO }}'
diff --git a/.github/workflows/pr-to-update-go.yml b/.github/workflows/pr-to-update-go.yml
index ec63b3b44..a90c16fe3 100644
--- a/.github/workflows/pr-to-update-go.yml
+++ b/.github/workflows/pr-to-update-go.yml
@@ -27,26 +27,27 @@ on:
 jobs:
   pr-to-update-go:
     runs-on: ubuntu-latest
+    env:
+      GIT_AUTHOR_NAME: asf-ci
+      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      PR_GITHUB_TOKEN: ${{ secrets.ASFCI_TOKEN }}
+      GO_VERSION_FILE: GO_VERSION
+      ENV_FILE: .env
     steps:
       - name: Checkout repo
         uses: actions/checkout@master
         if: ${{ (github.repository_owner == 'apache' && github.ref == 'refs/heads/master' ) || github.event_name != 'schedule' }}
         id: checkout
-      - name: Install Python 3.9
+      - name: Install Python 3.10
         uses: actions/setup-python@v2
         if: ${{ steps.checkout.outcome == 'success' }}
-        with: { python-version: 3.9 }
+        with: { python-version: '3.10' }
       - name: Install dependencies
         if: ${{ steps.checkout.outcome == 'success' }}
         run: pip install .github/actions/pr-to-update-go
       - name: Update Go version
         if: ${{ steps.checkout.outcome == 'success' }}
         run: python3 -m pr_to_update_go --update-version-only
-        env:
-          GIT_AUTHOR_NAME: asfgit
-          GITHUB_TOKEN: ${{ github.token }}
-          GO_VERSION_FILE: GO_VERSION
-          ENV_FILE: .env
       - name: Check Go version
         run: echo "::set-output name=value::$(cat GO_VERSION)"
         id: go-version
@@ -57,8 +58,3 @@ jobs:
       - name: PR to Update Go
         if: ${{ steps.checkout.outcome == 'success' }}
         run: python3 -m pr_to_update_go
-        env:
-          GIT_AUTHOR_NAME: asfgit
-          GITHUB_TOKEN: ${{ github.token }}
-          GO_VERSION_FILE: GO_VERSION
-          ENV_FILE: .env
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89f9a8ddf..30e527037 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Traffic Monitor: Add support for `access.log` to TM.
 - Added functionality for login to provide a Bearer token and for that token to be later used for authorization.
 - [Traffic Ops | Traffic Go Clients | T3C] Add additional timestamp fields to server for queuing and dequeueing config and revalidate updates.
+- Added layered profile feature to 4.0 for `GET` servers/, `POST` servers/, `PUT` servers/{id} and `DELETE` servers/{id}.
 
 ### Fixed
 - Update traffic\_portal dependencies to mitigate `npm audit` issues.
@@ -56,6 +57,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Updated the Profiles Traffic Portal page to use a more performant AG-Grid-based table.
 - Updated Go version to 1.18
 - Removed the unused `deliveryservice_tmuser` table from Traffic Ops database
+- Adds updates to the trafficcontrol-health-client to, use new ATS Host status formats, detect and use proper
+  traffic_ctl commands, and adds new markup-poll-threshold config.
 
 ## [6.1.0] - 2022-01-18
 ### Added
diff --git a/GO_VERSION b/GO_VERSION
index adc97d8e2..ec6d649be 100644
--- a/GO_VERSION
+++ b/GO_VERSION
@@ -1,1 +1,1 @@
-1.18
+1.18.1
diff --git a/blueprints/layered-profile-server.md b/blueprints/layered-profile-server.md
index 6b17b9d4d..26ebaec23 100644
--- a/blueprints/layered-profile-server.md
+++ b/blueprints/layered-profile-server.md
@@ -93,7 +93,7 @@ With Layered Profiles, hundreds of profiles become a few dozen, each representin
 {
   "response": [{
     "id": 5,
-    "profiles": ["MID", "AMIGA_123", "CDN_FOO"]
+    "profileNames": ["MID", "AMIGA_123", "CDN_FOO"]
   }]
 }
 ```
@@ -102,7 +102,7 @@ With Layered Profiles, hundreds of profiles become a few dozen, each representin
 ```JSON
 {
   "response": [{
-    "profiles": ["MID", "AMIGA_123", "CDN_FOO"]
+    "profileNames": ["MID", "AMIGA_123", "CDN_FOO"]
   }]
 }
 ```
@@ -115,7 +115,7 @@ JSON **request** with the proposed change will look as follows:
 {
     "cachegroupId": 6,
     "cdnId": 2,
-    "profiles": ["MID", "AMIGA_123", "CDN_FOO"]
+    "profileNames": ["MID", "AMIGA_123", "CDN_FOO"]
 }
 ```
 
@@ -124,7 +124,7 @@ JSON **request** with the proposed change will look as follows:
 {
     "cachegroupId": 6,
     "cdnId": 2,
-    "profiles": ["MID", "AMIGA_123", "CDN_FOO"]
+    "profileNames": ["MID", "AMIGA_123", "CDN_FOO"]
 }
 ```
 
@@ -141,7 +141,7 @@ return following **response**
     "cachegroupId": 6,
     "cdnId": 2,
     "id": 5,
-    "profiles": ["MID", "AMIGA_123", "CDN_FOO"]
+    "profileNames": ["MID", "AMIGA_123", "CDN_FOO"]
   }
 }
 ```
@@ -151,7 +151,7 @@ The following table describes the top level `layered_profile` object for servers
 | field           | type                 | optionality | description                                              |
 | ----------------| ---------------------| ----------- | ---------------------------------------------------------|
 | server          | bigint               | required    | the server id associated with a given profile            |
-| profiles        | text []              | required    | the profile names associated with a server               |
+| profile_name    | text                 | required    | the profile name associated with a server                |
 | order           | bigint               | required    | the order in which a profile is applied to a server      |
 
 **API constraints**
@@ -176,10 +176,10 @@ The following table describes the top level `layered_profile` object for servers
  profile_name  | text                     |           | not null |
  order         | bigint                   |           | not null |
 Indexes:
-    "pk_server_profile" PRIMARY KEY(profile_name, server, order)
+    "pk_server_profile" PRIMARY KEY(profile_name, server)
 Foreign-key constraints:
-    "fk_server" FOREIGN KEY (server) REFERENCES server(id)
-    "fk_server" FOREIGN KEY (profile_name) REFERENCES profile(name)
+    "fk_server_id" FOREIGN KEY (server) REFERENCES public.server(id) ON DELETE CASCADE ON UPDATE CASCADE
+    "fk_server_profile_name_profile" FOREIGN KEY (profile_name) REFERENCES public.profile(name) ON DELETE RESTRICT ON UPDATE CASCADE,
 ```
 
 All profiles assigned to a given server will have the same values of:
@@ -217,7 +217,6 @@ We do not anticipate any impact on security.
 
 ### Upgrade Impact
 - A Database Migration to:
-  - drop profile column in existing server table
   - insert existing server profiles along with their order into the new table(server_profiles)
 
 ### Operations Impact
diff --git a/cache-config/t3cutil/getdata.go b/cache-config/t3cutil/getdata.go
index 419cb8db0..746262551 100644
--- a/cache-config/t3cutil/getdata.go
+++ b/cache-config/t3cutil/getdata.go
@@ -157,12 +157,12 @@ func GetPackages(cfg TCCfg) ([]Package, error) {
 	server, _, err := cfg.TOClient.GetServerByHostName(string(cfg.CacheHostName), nil)
 	if err != nil {
 		return nil, errors.New("getting server: " + err.Error())
-	} else if server.Profile == nil {
+	} else if len(server.ProfileNames) == 0 {
 		return nil, errors.New("getting server: nil profile")
 	}
-	params, _, err := cfg.TOClient.GetServerProfileParameters(*server.Profile, nil)
+	params, _, err := cfg.TOClient.GetServerProfileParameters(server.ProfileNames[0], nil)
 	if err != nil {
-		return nil, errors.New("getting server profile '" + *server.Profile + "' parameters: " + err.Error())
+		return nil, errors.New("getting server profile '" + server.ProfileNames[0] + "' parameters: " + err.Error())
 	}
 	packages := []Package{}
 	for _, param := range params {
@@ -196,12 +196,12 @@ func GetChkconfig(cfg TCCfg) ([]ChkConfigEntry, error) {
 	server, _, err := cfg.TOClient.GetServerByHostName(string(cfg.CacheHostName), nil)
 	if err != nil {
 		return nil, errors.New("getting server: " + err.Error())
-	} else if server.Profile == nil {
+	} else if len(server.ProfileNames) == 0 {
 		return nil, errors.New("getting server: nil profile")
 	}
-	params, _, err := cfg.TOClient.GetServerProfileParameters(*server.Profile, nil)
+	params, _, err := cfg.TOClient.GetServerProfileParameters(server.ProfileNames[0], nil)
 	if err != nil {
-		return nil, errors.New("getting server profile '" + *server.Profile + "' parameters: " + err.Error())
+		return nil, errors.New("getting server profile '" + server.ProfileNames[0] + "' parameters: " + err.Error())
 	}
 	chkconfig := []ChkConfigEntry{}
 	for _, param := range params {
diff --git a/cache-config/t3cutil/getdatacfg.go b/cache-config/t3cutil/getdatacfg.go
index 608bd3f8b..39c780d64 100644
--- a/cache-config/t3cutil/getdatacfg.go
+++ b/cache-config/t3cutil/getdatacfg.go
@@ -272,7 +272,7 @@ func GetConfigData(toClient *toreq.TOClient, disableProxy bool, cacheHostName st
 			return errors.New("server '" + cacheHostName + " missing CDNName")
 		} else if server.CDNID == nil {
 			return errors.New("server '" + cacheHostName + " missing CDNID")
-		} else if server.Profile == nil {
+		} else if len(server.ProfileNames) == 0 {
 			return errors.New("server '" + cacheHostName + " missing Profile")
 		}
 
@@ -468,15 +468,15 @@ func GetConfigData(toClient *toreq.TOClient, disableProxy bool, cacheHostName st
 			defer func(start time.Time) { log.Infof("serverParamsF took %v\n", time.Since(start)) }(time.Now())
 			{
 				reqHdr := (http.Header)(nil)
-				if oldCfg != nil && oldServer.Profile != nil && *oldServer.Profile == *server.Profile {
+				if oldCfg != nil && len(oldServer.ProfileNames) != 0 && oldServer.ProfileNames[0] == server.ProfileNames[0] {
 					reqHdr = MakeReqHdr(oldCfg.MetaData.ServerParams)
 				}
-				params, reqInf, err := toClient.GetServerProfileParameters(*server.Profile, reqHdr)
-				log.Infoln(toreq.RequestInfoStr(reqInf, "GetURLSigKeys("+*server.Profile+")"))
+				params, reqInf, err := toClient.GetServerProfileParameters(server.ProfileNames[0], reqHdr)
+				log.Infoln(toreq.RequestInfoStr(reqInf, "GetURLSigKeys("+server.ProfileNames[0]+")"))
 				if err != nil {
-					return errors.New("getting server profile '" + *server.Profile + "' parameters: " + err.Error())
+					return errors.New("getting server profile '" + server.ProfileNames[0] + "' parameters: " + err.Error())
 				} else if len(params) == 0 {
-					return errors.New("getting server profile '" + *server.Profile + "' parameters: no parameters (profile not found?)")
+					return errors.New("getting server profile '" + server.ProfileNames[0] + "' parameters: no parameters (profile not found?)")
 				}
 
 				if reqInf.StatusCode == http.StatusNotModified {
@@ -519,13 +519,13 @@ func GetConfigData(toClient *toreq.TOClient, disableProxy bool, cacheHostName st
 			defer func(start time.Time) { log.Infof("profileF took %v\n", time.Since(start)) }(time.Now())
 			{
 				reqHdr := (http.Header)(nil)
-				if oldCfg != nil && oldServer.Profile != nil && *oldServer.Profile == *server.Profile {
+				if oldCfg != nil && len(oldServer.ProfileNames) != 0 && oldServer.ProfileNames[0] == server.ProfileNames[0] {
 					reqHdr = MakeReqHdr(oldCfg.MetaData.Profile)
 				}
-				profile, reqInf, err := toClient.GetProfileByName(*server.Profile, reqHdr)
-				log.Infoln(toreq.RequestInfoStr(reqInf, "GetProfileByName("+*server.Profile+")"))
+				profile, reqInf, err := toClient.GetProfileByName(server.ProfileNames[0], reqHdr)
+				log.Infoln(toreq.RequestInfoStr(reqInf, "GetProfileByName("+server.ProfileNames[0]+")"))
 				if err != nil {
-					return errors.New("getting profile '" + *server.Profile + "': " + err.Error())
+					return errors.New("getting profile '" + server.ProfileNames[0] + "': " + err.Error())
 				}
 				if reqInf.StatusCode == http.StatusNotModified {
 					log.Infof("Getting config: %v not modified, using old config", "Profile")
diff --git a/cache-config/t3cutil/toreq/client.go b/cache-config/t3cutil/toreq/client.go
index 5f5d97ddf..30e45637b 100644
--- a/cache-config/t3cutil/toreq/client.go
+++ b/cache-config/t3cutil/toreq/client.go
@@ -48,8 +48,22 @@ import (
 )
 
 type TOClient struct {
-	c          *toclient.Session
-	old        *toreqold.TOClient
+	// c is the "new" Traffic Ops client, for the latest major API.
+	//
+	// This MUST NOT be accessed without checking for nil.
+	// If the Traffic Ops server doesn't support the latest API, it will fall back,
+	// and c will be nil and old will not, and old must be used.
+	//
+	// **WARNING** This MUST NOT be accessed without checking for nil. See above.
+	c *toclient.Session
+
+	// old is the "old" Traffic Ops client, for the previous major API.
+	// This will be nil unless the Traffic Ops Server doesn't support the latest API,
+	// in which case this will exist and c will be nil.
+	old *toreqold.TOClient
+
+	// NumRetries is the number of times to retry Traffic Ops server failures
+	// before giving up and returning an error to the caller.
 	NumRetries int
 }
 
@@ -68,6 +82,13 @@ func (cl *TOClient) SetURL(newURL string) {
 	}
 }
 
+func (cl *TOClient) HTTPClient() *http.Client {
+	if cl.c == nil {
+		return cl.old.HTTPClient()
+	}
+	return cl.c.Client
+}
+
 const FsCookiePath = `/var/lib/trafficcontrol-cache-config/`
 
 // New logs into Traffic Ops, returning the TOClient which contains the logged-in client.
@@ -83,6 +104,7 @@ func New(url *url.URL, user string, pass string, insecure bool, timeout time.Dur
 	log.Infoln("TO URL string: '" + toURLStr + "'")
 	log.Infoln("TO URL: '" + url.String() + "'")
 
+	latestSupported := false
 	if fsCookie.Cookies != nil {
 		toIP, err := net.LookupIP(url.Hostname())
 		if err != nil {
@@ -111,7 +133,7 @@ func New(url *url.URL, user string, pass string, insecure bool, timeout time.Dur
 		opts.UserAgent = userAgent
 		opts.RequestTimeout = timeout
 		toClient, inf, err := toclient.Login(toURLStr, user, pass, opts)
-		latestSupported := inf.StatusCode != 404 && inf.StatusCode != 501
+		latestSupported = inf.StatusCode != 404 && inf.StatusCode != 501
 
 		if err != nil && latestSupported {
 			return nil, fmt.Errorf("Logging in to Traffic Ops '%v' code %v: %v", torequtil.MaybeIPStr(inf.RemoteAddr), inf.StatusCode, err)
@@ -127,24 +149,30 @@ func New(url *url.URL, user string, pass string, insecure bool, timeout time.Dur
 				return nil, errors.New("checking Traffic Ops '" + torequtil.MaybeIPStr(toAddr) + "' support: " + err.Error())
 			}
 		}
-
 		client = &TOClient{c: toClient}
-		if !latestSupported {
-			log.Warnln("toreqnew.New Traffic Ops '" + torequtil.MaybeIPStr(inf.RemoteAddr) + "' does not support the latest client, falling back ot the previous")
+	}
 
-			oldClient, err := toreqold.New(url, user, pass, insecure, timeout, userAgent)
-			if err != nil {
-				return nil, errors.New("logging into old client: " + err.Error())
-			}
-			client.c = nil
-			client.old = oldClient
+	latestSupported, toAddr, err := IsLatestSupported(client.c)
+	if err != nil {
+		return nil, errors.New("checking Traffic Ops '" + torequtil.MaybeIPStr(toAddr) + "' support: " + err.Error())
+	}
 
-			{
-				newClient := toclient.NewNoAuthSession("", false, "", false, 0) // only used for the version, because toClient could be nil if it had an error
-				log.Infof("cache-config Traffic Ops client: %v not supported, falling back to %v\n", newClient.APIVersion(), oldClient.APIVersion())
-			}
+	if !latestSupported {
+		log.Warnln("toreqnew.New Traffic Ops '" + torequtil.MaybeIPStr(toAddr) + "' does not support the latest client, falling back ot the previous")
+
+		oldClient, err := toreqold.New(url, user, pass, insecure, timeout, userAgent)
+		if err != nil {
+			return nil, errors.New("logging into old client: " + err.Error())
+		}
+		client.c = nil
+		client.old = oldClient
+
+		{
+			newClient := toclient.NewNoAuthSession("", false, "", false, 0) // only used for the version, because toClient could be nil if it had an error
+			log.Infof("cache-config Traffic Ops (cookie login) client: %v not supported, falling back to %v\n", newClient.APIVersion(), oldClient.APIVersion())
 		}
 	}
+
 	return client, nil
 }
 
diff --git a/cache-config/t3cutil/toreq/clientfuncs.go b/cache-config/t3cutil/toreq/clientfuncs.go
index aa1e7aca2..4efeef2ad 100644
--- a/cache-config/t3cutil/toreq/clientfuncs.go
+++ b/cache-config/t3cutil/toreq/clientfuncs.go
@@ -75,7 +75,7 @@ func (cl *TOClient) WriteFsCookie(fileName string) {
 		log.Warnln("Error parsing Traffic ops URL: ", err)
 		return
 	}
-	for _, c := range cl.c.Client.Jar.Cookies(u) {
+	for _, c := range cl.HTTPClient().Jar.Cookies(u) {
 		fsCookie := torequtil.Cookie{Cookie: &http.Cookie{
 			Name:  c.Name,
 			Value: c.Value,
@@ -701,7 +701,7 @@ func (cl *TOClient) GetStatuses(reqHdr http.Header) ([]tc.Status, toclientlib.Re
 	err := torequtil.GetRetry(cl.NumRetries, "statuses", &statuses, func(obj interface{}) error {
 		toStatus, toReqInf, err := cl.c.GetStatuses(*ReqOpts(reqHdr))
 		if err != nil {
-			return errors.New("getting server update status from Traffic Ops '" + torequtil.MaybeIPStr(reqInf.RemoteAddr) + "': " + err.Error())
+			return errors.New("getting server update statuses from Traffic Ops '" + torequtil.MaybeIPStr(reqInf.RemoteAddr) + "': " + err.Error())
 		}
 		status := obj.(*[]tc.Status)
 		*status = toStatus.Response
@@ -709,7 +709,7 @@ func (cl *TOClient) GetStatuses(reqHdr http.Header) ([]tc.Status, toclientlib.Re
 		return nil
 	})
 	if err != nil {
-		return nil, reqInf, errors.New("getting server update status: " + err.Error())
+		return nil, reqInf, errors.New("getting server update statuses: " + err.Error())
 	}
 	return statuses, reqInf, nil
 }
diff --git a/cache-config/t3cutil/toreq/toreqold/client.go b/cache-config/t3cutil/toreq/toreqold/client.go
index 5b716fcba..6efc0b748 100644
--- a/cache-config/t3cutil/toreq/toreqold/client.go
+++ b/cache-config/t3cutil/toreq/toreqold/client.go
@@ -27,6 +27,7 @@ package toreqold
 
 import (
 	"errors"
+	"net/http"
 	"net/url"
 	"strconv"
 	"time"
@@ -49,6 +50,10 @@ func (cl *TOClient) SetURL(newURL string) {
 	cl.c.URL = newURL
 }
 
+func (cl *TOClient) HTTPClient() *http.Client {
+	return cl.c.Client
+}
+
 func (cl *TOClient) APIVersion() string {
 	return cl.c.APIVersion()
 }
diff --git a/cache-config/t3cutil/toreq/toreqold/clientfuncs.go b/cache-config/t3cutil/toreq/toreqold/clientfuncs.go
index 86c57a87f..cab0563f8 100644
--- a/cache-config/t3cutil/toreq/toreqold/clientfuncs.go
+++ b/cache-config/t3cutil/toreq/toreqold/clientfuncs.go
@@ -533,7 +533,7 @@ func (cl *TOClient) GetStatuses() ([]tc.Status, toclientlib.ReqInf, error) {
 	err := torequtil.GetRetry(cl.NumRetries, "statuses", &statuses, func(obj interface{}) error {
 		toStatus, toReqInf, err := cl.c.GetStatuses()
 		if err != nil {
-			return errors.New("getting server update status from Traffic Ops '" + torequtil.MaybeIPStr(reqInf.RemoteAddr) + "': " + err.Error())
+			return errors.New("getting old server update status from Traffic Ops '" + torequtil.MaybeIPStr(reqInf.RemoteAddr) + "': " + err.Error())
 		}
 		status := obj.(*[]tc.Status)
 		*status = toStatus
@@ -541,7 +541,7 @@ func (cl *TOClient) GetStatuses() ([]tc.Status, toclientlib.ReqInf, error) {
 		return nil
 	})
 	if err != nil {
-		return nil, reqInf, errors.New("getting server update status: " + err.Error())
+		return nil, reqInf, errors.New("getting old server update status: " + err.Error())
 	}
 	return statuses, reqInf, nil
 }
diff --git a/cache-config/t3cutil/toreq/toreqold/conversions.go b/cache-config/t3cutil/toreq/toreqold/conversions.go
index 79a37bc6d..a44c58b3b 100644
--- a/cache-config/t3cutil/toreq/toreqold/conversions.go
+++ b/cache-config/t3cutil/toreq/toreqold/conversions.go
@@ -41,7 +41,7 @@ func serversToLatest(svs tc.ServersV3Response) ([]atscfg.Server, error) {
 // serverToLatest converts a tc.Server to tc.ServerV30.
 // This is necessary, because the old Traffic Ops client doesn't return the same type as the latest client.
 func serverToLatest(oldSv *tc.ServerV30) (*atscfg.Server, error) {
-	sv, err := oldSv.UpgradeToV40()
+	sv, err := oldSv.UpgradeToV40([]string{*oldSv.Profile})
 	if err != nil {
 		return nil, err
 	}
diff --git a/dev/traffic_ops/seed.psql b/dev/traffic_ops/seed.psql
index 7fb5faaf7..5f30def8e 100644
--- a/dev/traffic_ops/seed.psql
+++ b/dev/traffic_ops/seed.psql
@@ -243,6 +243,11 @@ INSERT INTO "server" (
 )
 ON CONFLICT DO NOTHING;
 
+INSERT into server_profile(server, profile_name, priority)
+SELECT s.id, p.name, 0
+FROM server AS s
+    JOIN profile p ON p.id=s.profile;
+
 INSERT INTO interface (
 	monitor,
 	"name",
diff --git a/docs/source/api/v4/servers.rst b/docs/source/api/v4/servers.rst
index 548bdb80b..92bdbb602 100644
--- a/docs/source/api/v4/servers.rst
+++ b/docs/source/api/v4/servers.rst
@@ -137,9 +137,7 @@ Response Structure
 :offlineReason:   A user-entered reason why the server is in ADMIN_DOWN or OFFLINE status
 :physLocation:    The name of the physical location where the server resides
 :physLocationId:  An integral, unique identifier for the physical location where the server resides
-:profile:         The :ref:`profile-name` of the :term:`Profile` used by this server
-:profileDesc:     A :ref:`profile-description` of the :term:`Profile` used by this server
-:profileId:       The :ref:`profile-id` the :term:`Profile` used by this server
+:profileNames:    List of :ref:`profile-name` of the :term:`Profiles` used by this server
 :revalPending:    A boolean value which, if ``true`` indicates that this server has pending content invalidation/revalidation
 :revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch
 :revalApplyTime:  The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch
@@ -198,9 +196,7 @@ Response Structure
 		"offlineReason": "",
 		"physLocation": "Apachecon North America 2018",
 		"physLocationId": 1,
-		"profile": "ATS_MID_TIER_CACHE",
-		"profileDesc": "Mid Cache - Apache Traffic Server",
-		"profileId": 10,
+		"profileNames": ["ATS_MID_TIER_CACHE"],
 		"rack": "",
 		"revalPending": false,
 		"revalUpdateTime": "1969-12-31T17:00:00-07:00",
@@ -294,7 +290,7 @@ Request Structure
 		This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server.
 
 :physLocationId: An integral, unique identifier for the physical location where the server resides
-:profileId:      The :ref:`profile-id` the :term:`Profile` that shall be used by this server
+:profileNames:   List of :ref:`profile-name` of the :term:`Profiles` that shall be used by this server
 :rack:           An optional string indicating "server rack" location
 :statusId:       The integral, unique identifier of the status of this server
 
@@ -364,7 +360,7 @@ Request Structure
 		"mgmtIpNetmask": "",
 		"offlineReason": "",
 		"physLocationId": 1,
-		"profileId": 10,
+		"profileNames": ["ATS_MID_TIER_CACHE"],
 		"statusId": 3,
 		"tcpPort": 80,
 		"typeId": 12
@@ -428,9 +424,7 @@ Response Structure
 :offlineReason:   A user-entered reason why the server is in ADMIN_DOWN or OFFLINE status
 :physLocation:    The name of the :term:`Physical Location` where the server resides
 :physLocationId:  An integral, unique identifier for the :term:`Physical Location` where the server resides
-:profile:         The :ref:`profile-name` of the :term:`Profile` used by this server
-:profileDesc:     A :ref:`profile-description` of the :term:`Profile` used by this server
-:profileId:       The :ref:`profile-id` the :term:`Profile` used by this server
+:profileNames:    List of :ref:`profile-name` of the :term:`Profiles` used by this server
 :revalPending:    A boolean value which, if ``true`` indicates that this server has pending content invalidation/revalidation
 :revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch
 :revalApplyTime:  The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch
@@ -495,9 +489,7 @@ Response Structure
 		"offlineReason": "",
 		"physLocation": "Apachecon North America 2018",
 		"physLocationId": 1,
-		"profile": "ATS_MID_TIER_CACHE",
-		"profileDesc": "Mid Cache - Apache Traffic Server",
-		"profileId": 10,
+		"profileNames": ["ATS_MID_TIER_CACHE"],
 		"rack": null,
 		"revalPending": false,
 		"revalUpdateTime": "1969-12-31T17:00:00-07:00",
diff --git a/docs/source/api/v4/servers_id.rst b/docs/source/api/v4/servers_id.rst
index 62176fbdd..e2a005b6f 100644
--- a/docs/source/api/v4/servers_id.rst
+++ b/docs/source/api/v4/servers_id.rst
@@ -82,7 +82,7 @@ Request Structure
 		This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server.
 
 :physLocationId:  An integral, unique identifier for the physical location where the server resides
-:profileId:       The :ref:`profile-id` the :term:`Profile` that shall be used by this server
+:profileNames:    List of :ref:`profile-name` of the :term:`Profiles` that shall be used by this server
 :rack:            An optional string indicating "server rack" location
 :statusId:        The integral, unique identifier of the status of this server
 
@@ -152,7 +152,7 @@ Request Structure
 		"mgmtIpNetmask": "",
 		"offlineReason": "",
 		"physLocationId": 1,
-		"profileId": 10,
+		"profileNames": ["ATS_MID_TIER_CACHE"],
 		"statusId": 3,
 		"tcpPort": 80,
 		"typeId": 12
@@ -216,9 +216,7 @@ Response Structure
 :offlineReason:   A user-entered reason why the server is in ADMIN_DOWN or OFFLINE status
 :physLocation:    The name of the :term:`Physical Location` where the server resides
 :physLocationId:  An integral, unique identifier for the :term:`Physical Location` where the server resides
-:profile:         The :ref:`profile-name` of the :term:`Profile` used by this server
-:profileDesc:     A :ref:`profile-description` of the :term:`Profile` used by this server
-:profileId:       The :ref:`profile-id` the :term:`Profile` used by this server
+:profileNames:    List of :ref:`profile-name` of the :term:`Profiles` used by this server
 :revalPending:    A boolean value which, if ``true`` indicates that this server has pending content invalidation/revalidation
 :revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch
 :revalApplyTime:  The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch
@@ -287,9 +285,7 @@ Response Structure
 		"offlineReason": "",
 		"physLocation": "Apachecon North America 2018",
 		"physLocationId": 1,
-		"profile": "ATS_MID_TIER_CACHE",
-		"profileDesc": "Mid Cache - Apache Traffic Server",
-		"profileId": 10,
+		"profileNames": ["ATS_MID_TIER_CACHE"],
 		"rack": null,
 		"revalPending": false,
 		"revalUpdateTime": "1969-12-31T17:00:00-07:00",
@@ -415,9 +411,7 @@ Response Structure
 :offlineReason:   A user-entered reason why the server was in ADMIN_DOWN or OFFLINE status
 :physLocation:    The name of the physical location where the server resided
 :physLocationId:  An integral, unique identifier for the physical location where the server resided
-:profile:         The :ref:`profile-name` of the :term:`Profile` which was used by this server
-:profileDesc:     A :ref:`profile-description` of the :term:`Profile` which was used by this server
-:profileId:       The :ref:`profile-id` the :term:`Profile` which was used by this server
+:profileNames:    List of :ref:`profile-name` of the :term:`Profiles` which was used by this server
 :revalPending:    A boolean value which, if ``true`` indicates that this server has pending content invalidation/revalidation
 :revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch
 :revalApplyTime:  The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch
@@ -482,9 +476,7 @@ Response Structure
 		"offlineReason": "",
 		"physLocation": "Apachecon North America 2018",
 		"physLocationId": 1,
-		"profile": "ATS_MID_TIER_CACHE",
-		"profileDesc": "Mid Cache - Apache Traffic Server",
-		"profileId": 10,
+		"profileNames": ["ATS_MID_TIER_CACHE"],
 		"rack": null,
 		"revalPending": false,
 		"revalUpdateTime": "1969-12-31T17:00:00-07:00",
diff --git a/go.mod b/go.mod
index ba6786743..bb3806511 100644
--- a/go.mod
+++ b/go.mod
@@ -47,6 +47,9 @@ require (
 	github.com/pborman/getopt/v2 v2.1.0
 	github.com/pkg/errors v0.9.1
 	go.etcd.io/bbolt v1.3.6
+	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
+	golang.org/x/net v0.0.0-20211013171255-e13a2654a71e
+	golang.org/x/sys v0.0.0-20211013075003-97ac67df715c
 	gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0
 	gopkg.in/yaml.v2 v2.4.0
 )
@@ -80,12 +83,6 @@ require (
 	github.com/klauspost/compress v1.13.6 // indirect
 )
 
-require (
-	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
-	golang.org/x/net v0.0.0-20211013171255-e13a2654a71e
-	golang.org/x/sys v0.0.0-20211013075003-97ac67df715c
-)
-
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/eapache/go-resiliency v1.2.0 // indirect
diff --git a/infrastructure/cdn-in-a-box/enroller/server_template.json b/infrastructure/cdn-in-a-box/enroller/server_template.json
index b51ed0453..f31a5c124 100644
--- a/infrastructure/cdn-in-a-box/enroller/server_template.json
+++ b/infrastructure/cdn-in-a-box/enroller/server_template.json
@@ -12,7 +12,7 @@
   ],
   "type": "$MY_TYPE",
   "physLocation": "Apachecon North America 2018",
-  "profile": "$MY_PROFILE",
+  "profileNames": ["$MY_PROFILE"],
   "cdnName": "$MY_CDN",
   "updPending": false,
   "status": "$MY_STATUS",
diff --git a/lib/go-atscfg/astatsdotconfig.go b/lib/go-atscfg/astatsdotconfig.go
index 9dc09c7f5..ee142eaf2 100644
--- a/lib/go-atscfg/astatsdotconfig.go
+++ b/lib/go-atscfg/astatsdotconfig.go
@@ -47,7 +47,7 @@ func MakeAStatsDotConfig(
 	}
 	warnings := []string{}
 
-	if server.Profile == nil {
+	if len(server.ProfileNames) == 0 {
 		return Cfg{}, makeErr(warnings, "server missing Profile")
 	}
 
diff --git a/lib/go-atscfg/atsdotrules.go b/lib/go-atscfg/atsdotrules.go
index ea80de4fe..946fea2cb 100644
--- a/lib/go-atscfg/atsdotrules.go
+++ b/lib/go-atscfg/atsdotrules.go
@@ -46,7 +46,7 @@ func MakeATSDotRules(
 		opt = &ATSDotRulesOpts{}
 	}
 	warnings := []string{}
-	if server.Profile == nil {
+	if len(server.ProfileNames) == 0 {
 		return Cfg{}, makeErr(warnings, "server missing Profile")
 	}
 
diff --git a/lib/go-atscfg/cachedotconfig.go b/lib/go-atscfg/cachedotconfig.go
index a98da6775..cb50e6d98 100644
--- a/lib/go-atscfg/cachedotconfig.go
+++ b/lib/go-atscfg/cachedotconfig.go
@@ -67,13 +67,13 @@ func makeCacheDotConfigEdge(
 	}
 	warnings := []string{}
 
-	if server.Profile == nil {
-		return Cfg{}, makeErr(warnings, "server missing profile")
+	if len(server.ProfileNames) == 0 {
+		return Cfg{}, makeErr(warnings, "server missing profiles")
 	}
 
 	profileServerIDsMap := map[int]struct{}{}
 	for _, sv := range servers {
-		if sv.Profile == nil {
+		if len(sv.ProfileNames) == 0 {
 			warnings = append(warnings, "servers had server with nil profile, skipping!")
 			continue
 		}
@@ -81,7 +81,16 @@ func makeCacheDotConfigEdge(
 			warnings = append(warnings, "servers had server with nil id, skipping!")
 			continue
 		}
-		if *sv.Profile != *server.Profile {
+		if len(sv.ProfileNames) != len(server.ProfileNames) {
+			continue
+		}
+		profilesTheSame := true
+		for i, _ := range server.ProfileNames {
+			if sv.ProfileNames[i] != server.ProfileNames[i] {
+				profilesTheSame = false
+			}
+		}
+		if !profilesTheSame {
 			continue
 		}
 		profileServerIDsMap[*sv.ID] = struct{}{}
diff --git a/lib/go-atscfg/dropqstringdotconfig.go b/lib/go-atscfg/dropqstringdotconfig.go
index c8d8d9c84..164d0fba9 100644
--- a/lib/go-atscfg/dropqstringdotconfig.go
+++ b/lib/go-atscfg/dropqstringdotconfig.go
@@ -46,7 +46,7 @@ func MakeDropQStringDotConfig(
 	}
 	warnings := []string{}
 
-	if server.Profile == nil {
+	if len(server.ProfileNames) == 0 {
 		return Cfg{}, makeErr(warnings, "this server missing Profile")
 	}
 
diff --git a/lib/go-atscfg/facts.go b/lib/go-atscfg/facts.go
index 750e23a64..63c645989 100644
--- a/lib/go-atscfg/facts.go
+++ b/lib/go-atscfg/facts.go
@@ -19,6 +19,8 @@ package atscfg
  * under the License.
  */
 
+import "strings"
+
 const ContentType12MFacts = ContentTypeTextASCII
 const LineComment12MFacts = LineCommentHash
 
@@ -39,13 +41,13 @@ func Make12MFacts(
 	}
 	warnings := []string{}
 
-	if server.Profile == nil {
+	if len(server.ProfileNames) == 0 {
 		return Cfg{}, makeErr(warnings, "this server missing Profile")
 	}
 
 	hdr := makeHdrComment(opt.HdrComment)
 	txt := hdr
-	txt += "profile:" + *server.Profile + "\n"
+	txt += "profiles:" + strings.Join(server.ProfileNames, ", ") + "\n"
 
 	return Cfg{
 		Text:        txt,
diff --git a/lib/go-atscfg/loggingdotconfig.go b/lib/go-atscfg/loggingdotconfig.go
index 24878c9ca..f87b1e592 100644
--- a/lib/go-atscfg/loggingdotconfig.go
+++ b/lib/go-atscfg/loggingdotconfig.go
@@ -53,7 +53,7 @@ func MakeLoggingDotConfig(
 	}
 	warnings := []string{}
 
-	if server.Profile == nil {
+	if len(server.ProfileNames) == 0 {
 		return Cfg{}, makeErr(warnings, "this server missing Profile")
 	}
 
@@ -75,7 +75,7 @@ func MakeLoggingDotConfig(
 			format := paramData[logFormatField+".Format"]
 			if format == "" {
 				// TODO determine if the line should be excluded. Perl includes it anyway, without checking.
-				warnings = append(warnings, fmt.Sprintf("profile '%v' has logging.config format '%v' Name Parameter but no Format Parameter. Setting blank Format!\n", *server.Profile, logFormatField))
+				warnings = append(warnings, fmt.Sprintf("profile '%v' has logging.config format '%v' Name Parameter but no Format Parameter. Setting blank Format!\n", server.ProfileNames, logFormatField))
 			}
 			format = strings.Replace(format, `"`, `\"`, -1)
 			text += logFormatName + " = format {\n"
@@ -94,7 +94,7 @@ func MakeLoggingDotConfig(
 			filter := paramData[logFilterField+".Filter"]
 			if filter == "" {
 				// TODO determine if the line should be excluded. Perl includes it anyway, without checking.
-				warnings = append(warnings, fmt.Sprintf("profile '%v' has logging.config format '%v' Name Parameter but no Filter Parameter. Setting blank Filter!\n", *server.Profile, logFilterField))
+				warnings = append(warnings, fmt.Sprintf("profile '%v' has logging.config format '%v' Name Parameter but no Filter Parameter. Setting blank Filter!\n", server.ProfileNames, logFilterField))
 			}
 
 			filter = strings.Replace(filter, `\`, `\\`, -1)
diff --git a/lib/go-atscfg/loggingdotyaml.go b/lib/go-atscfg/loggingdotyaml.go
index 5b66b438a..ac6e279e5 100644
--- a/lib/go-atscfg/loggingdotyaml.go
+++ b/lib/go-atscfg/loggingdotyaml.go
@@ -50,7 +50,7 @@ func MakeLoggingDotYAML(
 	warnings := []string{}
 	requiredIndent := 0
 
-	if server.Profile == nil {
+	if len(server.ProfileNames) == 0 {
 		return Cfg{}, makeErr(warnings, "this server missing Profile")
 	}
 
@@ -82,7 +82,7 @@ func MakeLoggingDotYAML(
 			format := paramData[logFormatField+".Format"]
 			if format == "" {
 				// TODO determine if the line should be excluded. Perl includes it anyway, without checking.
-				warnings = append(warnings, fmt.Sprintf("profile '%v' has logging.yaml format '%v' Name Parameter but no Format Parameter. Setting blank Format!\n", *server.Profile, logFormatField))
+				warnings = append(warnings, fmt.Sprintf("profile '%v' has logging.yaml format '%v' Name Parameter but no Format Parameter. Setting blank Format!\n", server.ProfileNames, logFormatField))
 			}
 			text += indentSpaces + " - name: " + logFormatName + " \n"
 			text += indentSpaces + "   format: '" + format + "'\n"
@@ -99,7 +99,7 @@ func MakeLoggingDotYAML(
 			filter := paramData[logFilterField+".Filter"]
 			if filter == "" {
 				// TODO determine if the line should be excluded. Perl includes it anyway, without checking.
-				warnings = append(warnings, fmt.Sprintf("profile '%v' has logging.yaml filter '%v' Name Parameter but no Filter Parameter. Setting blank Filter!\n", *server.Profile, logFilterField))
+				warnings = append(warnings, fmt.Sprintf("profile '%v' has logging.yaml filter '%v' Name Parameter but no Filter Parameter. Setting blank Filter!\n", server.ProfileNames, logFilterField))
 			}
 			logFilterType := paramData[logFilterField+".Type"]
 			if logFilterType == "" {
diff --git a/lib/go-atscfg/logsdotxml.go b/lib/go-atscfg/logsdotxml.go
index a49382198..237886b72 100644
--- a/lib/go-atscfg/logsdotxml.go
+++ b/lib/go-atscfg/logsdotxml.go
@@ -49,7 +49,7 @@ func MakeLogsXMLDotConfig(
 	}
 	warnings := []string{}
 
-	if server.Profile == nil {
+	if len(server.ProfileNames) == 0 {
 		return Cfg{}, makeErr(warnings, "this server missing Profile")
 	}
 
diff --git a/lib/go-atscfg/meta.go b/lib/go-atscfg/meta.go
index cafdc9ed9..537b1bafb 100644
--- a/lib/go-atscfg/meta.go
+++ b/lib/go-atscfg/meta.go
@@ -57,8 +57,6 @@ func MakeConfigFilesList(
 		return nil, warnings, errors.New("this server missing Cachegroup")
 	} else if server.CachegroupID == nil {
 		return nil, warnings, errors.New("this server missing CachegroupID")
-	} else if server.ProfileID == nil {
-		return nil, warnings, errors.New("server missing ProfileID")
 	} else if server.TCPPort == nil {
 		return nil, warnings, errors.New("server missing TCPPort")
 	} else if server.HostName == nil {
@@ -69,7 +67,7 @@ func MakeConfigFilesList(
 		return nil, warnings, errors.New("server missing CDNName")
 	} else if server.ID == nil {
 		return nil, warnings, errors.New("server missing ID")
-	} else if server.Profile == nil {
+	} else if len(server.ProfileNames) == 0 {
 		return nil, warnings, errors.New("server missing Profile")
 	}
 
diff --git a/lib/go-atscfg/parentdotconfig.go b/lib/go-atscfg/parentdotconfig.go
index 74341117e..9adc67a6d 100644
--- a/lib/go-atscfg/parentdotconfig.go
+++ b/lib/go-atscfg/parentdotconfig.go
@@ -162,7 +162,7 @@ func makeParentDotConfigData(
 		return nil, warnings, errors.New("server CDNName missing")
 	} else if server.Cachegroup == nil || *server.Cachegroup == "" {
 		return nil, warnings, errors.New("server Cachegroup missing")
-	} else if server.Profile == nil || *server.Profile == "" {
+	} else if len(server.ProfileNames) == 0 {
 		return nil, warnings, errors.New("server Profile missing")
 	} else if server.TCPPort == nil {
 		return nil, warnings, errors.New("server TCPPort missing")
@@ -1216,7 +1216,7 @@ func serverParentageParams(sv *Server, params []parameterWithProfilesMap) (profi
 		profileCache.Port = *sv.TCPPort
 	}
 	for _, param := range params {
-		if _, ok := param.ProfileNames[*sv.Profile]; !ok {
+		if _, ok := param.ProfileNames[(sv.ProfileNames)[0]]; !ok {
 			continue
 		}
 		switch param.Name {
@@ -1694,8 +1694,8 @@ func getOriginServersAndProfileCaches(
 		} else if cgSv.TypeID == nil {
 			warnings = append(warnings, "getting origin servers: got server with nil TypeID, skipping!")
 			continue
-		} else if cgSv.ProfileID == nil {
-			warnings = append(warnings, "getting origin servers: got server with nil ProfileID, skipping!")
+		} else if len(cgSv.ProfileNames) == 0 {
+			warnings = append(warnings, "getting origin servers: got server with no profile names, skipping!")
 			continue
 		} else if cgSv.CDNID == nil {
 			warnings = append(warnings, "getting origin servers: got server with nil CDNID, skipping!")
@@ -1719,7 +1719,6 @@ func getOriginServersAndProfileCaches(
 			CacheGroupID: *cgSv.CachegroupID,
 			Status:       *cgSv.StatusID,
 			Type:         *cgSv.TypeID,
-			ProfileID:    ProfileID(*cgSv.ProfileID),
 			CDN:          *cgSv.CDNID,
 			TypeName:     cgSv.Type,
 			Domain:       *cgSv.DomainName,
@@ -1741,8 +1740,8 @@ func getOriginServersAndProfileCaches(
 		}
 
 		if _, profileCachesHasProfile := profileCaches[realCGServer.ProfileID]; !profileCachesHasProfile {
-			if profileCache, profileParamsHasProfile := profileParams[*cgSv.Profile]; !profileParamsHasProfile {
-				warnings = append(warnings, fmt.Sprintf("cachegroup has server with profile %+v but that profile has no parameters\n", *cgSv.ProfileID))
+			if profileCache, profileParamsHasProfile := profileParams[cgSv.ProfileNames[0]]; !profileParamsHasProfile {
+				warnings = append(warnings, fmt.Sprintf("cachegroup has server with profile %+v but that profile has no parameters\n", cgSv.ProfileNames[0]))
 				profileCaches[realCGServer.ProfileID] = defaultProfileCache()
 			} else {
 				profileCaches[realCGServer.ProfileID] = profileCache
@@ -1761,17 +1760,17 @@ func getParentConfigProfileParams(
 	warnings := []string{}
 	parentConfigServerCacheProfileParams := map[string]profileCache{} // map[profileName]ProfileCache
 	for _, cgServer := range cgServers {
-		if cgServer.Profile == nil {
-			warnings = append(warnings, "getting parent config profile params: server has nil profile, skipping!")
+		if len(cgServer.ProfileNames) == 0 {
+			warnings = append(warnings, "getting parent config profile params: server has nil profiles, skipping!")
 			continue
 		}
-		profileCache, ok := parentConfigServerCacheProfileParams[*cgServer.Profile]
+		profileCache, ok := parentConfigServerCacheProfileParams[cgServer.ProfileNames[0]]
 		if !ok {
 			profileCache = defaultProfileCache()
 		}
-		params, ok := profileParentConfigParams[*cgServer.Profile]
+		params, ok := profileParentConfigParams[cgServer.ProfileNames[0]]
 		if !ok {
-			parentConfigServerCacheProfileParams[*cgServer.Profile] = profileCache
+			parentConfigServerCacheProfileParams[cgServer.ProfileNames[0]] = profileCache
 			continue
 		}
 		for name, val := range params {
@@ -1803,7 +1802,7 @@ func getParentConfigProfileParams(
 				profileCache.NotAParent = val != "false"
 			}
 		}
-		parentConfigServerCacheProfileParams[*cgServer.Profile] = profileCache
+		parentConfigServerCacheProfileParams[cgServer.ProfileNames[0]] = profileCache
 	}
 	return parentConfigServerCacheProfileParams, warnings
 }
@@ -1922,8 +1921,8 @@ func getProfileParentConfigParams(tcParentConfigParams []tc.Parameter) (map[stri
 func getServerParentConfigParams(server *Server, profileParentConfigParams map[string]map[string]string) map[string]string {
 	// We only need parent.config params, don't need all the params on the server
 	serverParams := map[string]string{}
-	if server.Profile == nil || *server.Profile != "" { // TODO warn/error if false? Servers requires profiles
-		for name, val := range profileParentConfigParams[*server.Profile] {
+	if len(server.ProfileNames) != 0 || server.ProfileNames[0] != "" { // TODO warn/error if false? Servers requires profiles
+		for name, val := range profileParentConfigParams[server.ProfileNames[0]] {
 			if name == ParentConfigParamQStringHandling ||
 				name == ParentConfigParamAlgorithm ||
 				name == ParentConfigParamQString {
diff --git a/lib/go-atscfg/plugindotconfig.go b/lib/go-atscfg/plugindotconfig.go
index 1d2d43d2f..acf01fd13 100644
--- a/lib/go-atscfg/plugindotconfig.go
+++ b/lib/go-atscfg/plugindotconfig.go
@@ -45,8 +45,8 @@ func MakePluginDotConfig(
 		opt = &PluginDotConfigOpts{}
 	}
 	warnings := []string{}
-	if server.Profile == nil {
-		return Cfg{}, makeErr(warnings, "server profile missing")
+	if len(server.ProfileNames) == 0 {
+		return Cfg{}, makeErr(warnings, "server missing profiles")
 	}
 
 	paramData, paramWarns := paramsToMap(filterParams(serverParams, PluginFileName, "", "", "location"))
diff --git a/lib/go-atscfg/recordsdotconfig.go b/lib/go-atscfg/recordsdotconfig.go
index 0e708029f..997003e1b 100644
--- a/lib/go-atscfg/recordsdotconfig.go
+++ b/lib/go-atscfg/recordsdotconfig.go
@@ -66,8 +66,8 @@ func MakeRecordsDotConfig(
 		opt = &RecordsConfigOpts{}
 	}
 	warnings := []string{}
-	if server.Profile == nil {
-		return Cfg{}, makeErr(warnings, "server profile missing")
+	if len(server.ProfileNames) == 0 {
+		return Cfg{}, makeErr(warnings, "server missing profiles")
 	}
 
 	params, paramWarns := paramsToMap(filterParams(serverParams, RecordsFileName, "", "", "location"))
diff --git a/lib/go-atscfg/sslservernamedotyaml.go b/lib/go-atscfg/sslservernamedotyaml.go
index 06d76cf33..43d0856ea 100644
--- a/lib/go-atscfg/sslservernamedotyaml.go
+++ b/lib/go-atscfg/sslservernamedotyaml.go
@@ -210,8 +210,8 @@ func GetServerSSLData(
 ) ([]SSLData, []string, error) {
 	warnings := []string{}
 
-	if server.Profile == nil {
-		return nil, warnings, errors.New("this server missing Profile")
+	if len(server.ProfileNames) == 0 {
+		return nil, warnings, errors.New("this server missing Profiles")
 	}
 
 	dsRegexes := makeDSRegexMap(dsRegexArr)
diff --git a/lib/go-atscfg/storagedotconfig.go b/lib/go-atscfg/storagedotconfig.go
index 642e6609a..1dd5038ef 100644
--- a/lib/go-atscfg/storagedotconfig.go
+++ b/lib/go-atscfg/storagedotconfig.go
@@ -51,8 +51,8 @@ func MakeStorageDotConfig(
 	}
 	warnings := []string{}
 
-	if server.Profile == nil {
-		return Cfg{}, makeErr(warnings, "server missing Profile")
+	if len(server.ProfileNames) == 0 {
+		return Cfg{}, makeErr(warnings, "server missing Profiles")
 	}
 
 	paramData, paramWarns := paramsToMap(filterParams(serverParams, StorageFileName, "", "", "location"))
@@ -64,7 +64,7 @@ func MakeStorageDotConfig(
 	if drivePrefix := paramData["Drive_Prefix"]; drivePrefix != "" {
 		driveLetters := strings.TrimSpace(paramData["Drive_Letters"])
 		if driveLetters == "" {
-			warnings = append(warnings, fmt.Sprintf("profile %+v has Drive_Prefix parameter, but no Drive_Letters; creating anyway", *server.Profile))
+			warnings = append(warnings, fmt.Sprintf("profile %+v has Drive_Prefix parameter, but no Drive_Letters; creating anyway", server.ProfileNames[0]))
 		}
 		text += makeStorageVolumeText(drivePrefix, driveLetters, nextVolume)
 		nextVolume++
@@ -73,7 +73,7 @@ func MakeStorageDotConfig(
 	if ramDrivePrefix := paramData["RAM_Drive_Prefix"]; ramDrivePrefix != "" {
 		ramDriveLetters := strings.TrimSpace(paramData["RAM_Drive_Letters"])
 		if ramDriveLetters == "" {
-			warnings = append(warnings, fmt.Sprintf("profile %+v has RAM_Drive_Prefix parameter, but no RAM_Drive_Letters; creating anyway", *server.Profile))
+			warnings = append(warnings, fmt.Sprintf("profile %+v has RAM_Drive_Prefix parameter, but no RAM_Drive_Letters; creating anyway", server.ProfileNames[0]))
 		}
 		text += makeStorageVolumeText(ramDrivePrefix, ramDriveLetters, nextVolume)
 		nextVolume++
@@ -82,7 +82,7 @@ func MakeStorageDotConfig(
 	if ssdDrivePrefix := paramData["SSD_Drive_Prefix"]; ssdDrivePrefix != "" {
 		ssdDriveLetters := strings.TrimSpace(paramData["SSD_Drive_Letters"])
 		if ssdDriveLetters == "" {
-			warnings = append(warnings, fmt.Sprintf("profile %+v has SSD_Drive_Prefix parameter, but no SSD_Drive_Letters; creating anyway", *server.Profile))
+			warnings = append(warnings, fmt.Sprintf("profile %+v has SSD_Drive_Prefix parameter, but no SSD_Drive_Letters; creating anyway", server.ProfileNames[0]))
 		}
 		text += makeStorageVolumeText(ssdDrivePrefix, ssdDriveLetters, nextVolume)
 		nextVolume++
diff --git a/lib/go-atscfg/sysctldotconf.go b/lib/go-atscfg/sysctldotconf.go
index bb2e765f4..f94879d97 100644
--- a/lib/go-atscfg/sysctldotconf.go
+++ b/lib/go-atscfg/sysctldotconf.go
@@ -45,8 +45,8 @@ func MakeSysCtlDotConf(
 		opt = &SysCtlDotConfOpts{}
 	}
 	warnings := []string{}
-	if server.Profile == nil {
-		return Cfg{}, makeErr(warnings, "server missing Profile")
+	if len(server.ProfileNames) == 0 {
+		return Cfg{}, makeErr(warnings, "server missing Profiles")
 	}
 
 	paramData, paramWarns := paramsToMap(filterParams(serverParams, SysctlFileName, "", "", "location"))
diff --git a/lib/go-atscfg/urlsigconfig.go b/lib/go-atscfg/urlsigconfig.go
index 35a2b91d2..01e7bf4c3 100644
--- a/lib/go-atscfg/urlsigconfig.go
+++ b/lib/go-atscfg/urlsigconfig.go
@@ -49,8 +49,8 @@ func MakeURLSigConfig(
 	}
 	warnings := []string{}
 
-	if server.Profile == nil {
-		return Cfg{}, makeErr(warnings, "server missing Profile")
+	if len(server.ProfileNames) == 0 {
+		return Cfg{}, makeErr(warnings, "server missing Profiles")
 	}
 
 	paramData, paramWarns := paramsToMap(filterParams(serverParams, fileName, "", "", "location"))
diff --git a/lib/go-atscfg/volumedotconfig.go b/lib/go-atscfg/volumedotconfig.go
index ef58139f4..261b07b9d 100644
--- a/lib/go-atscfg/volumedotconfig.go
+++ b/lib/go-atscfg/volumedotconfig.go
@@ -49,8 +49,8 @@ func MakeVolumeDotConfig(
 	}
 	warnings := []string{}
 
-	if server.Profile == nil {
-		return Cfg{}, makeErr(warnings, "server missing Profile")
+	if len(server.ProfileNames) == 0 {
+		return Cfg{}, makeErr(warnings, "server missing Profiles")
 	}
 
 	paramData, paramWarns := paramsToMap(filterParams(serverParams, VolumeFileName, "", "", ""))
diff --git a/lib/go-tc/servers.go b/lib/go-tc/servers.go
index cb5c3b702..23971316b 100644
--- a/lib/go-tc/servers.go
+++ b/lib/go-tc/servers.go
@@ -917,11 +917,9 @@ func (s ServerNullableV2) Upgrade() (ServerV30, error) {
 //
 // Deprecated: Traffic Ops API version 3 is deprecated, new code should use
 // ServerV40 or newer structures.
-func (s ServerV30) UpgradeToV40() (ServerV40, error) {
-	upgraded := ServerV40{
-		CommonServerProperties: s.CommonServerProperties,
-		StatusLastUpdated:      s.StatusLastUpdated,
-	}
+func (s ServerV30) UpgradeToV40(profileNames []string) (ServerV40, error) {
+	upgraded := UpdateServerPropertiesV40(profileNames, s.CommonServerProperties)
+	upgraded.StatusLastUpdated = s.StatusLastUpdated
 	infs, err := ToInterfacesV4(s.Interfaces, s.RouterHostName, s.RouterPortName)
 	if err != nil {
 		return upgraded, err
@@ -936,7 +934,7 @@ func (s ServerV30) UpgradeToV40() (ServerV40, error) {
 //
 // Deprecated: Traffic Ops API version 2 is deprecated, new code should use
 // ServerV40 or newer structures.
-func (s ServerNullableV2) UpgradeToV40() (ServerV40, error) {
+func (s ServerNullableV2) UpgradeToV40(profileNames []string) (ServerV40, error) {
 	ipv4IsService := false
 	if s.IPIsService != nil {
 		ipv4IsService = *s.IPIsService
@@ -945,9 +943,7 @@ func (s ServerNullableV2) UpgradeToV40() (ServerV40, error) {
 	if s.IP6IsService != nil {
 		ipv6IsService = *s.IP6IsService
 	}
-	upgraded := ServerV40{
-		CommonServerProperties: s.CommonServerProperties,
-	}
+	upgraded := UpdateServerPropertiesV40(profileNames, s.CommonServerProperties)
 
 	infs, err := s.LegacyInterfaceDetails.ToInterfacesV4(ipv4IsService, ipv6IsService, s.RouterHostName, s.RouterPortName)
 	if err != nil {
@@ -957,9 +953,84 @@ func (s ServerNullableV2) UpgradeToV40() (ServerV40, error) {
 	return upgraded, nil
 }
 
+// UpdateServerPropertiesV40 updates CommonServerProperties of V2 and V3 to ServerV40
+func UpdateServerPropertiesV40(profileNames []string, properties CommonServerProperties) ServerV40 {
+	return ServerV40{
+		Cachegroup:       properties.Cachegroup,
+		CachegroupID:     properties.CachegroupID,
+		CDNID:            properties.CDNID,
+		CDNName:          properties.CDNName,
+		DeliveryServices: properties.DeliveryServices,
+		DomainName:       properties.DomainName,
+		FQDN:             properties.FQDN,
+		FqdnTime:         properties.FqdnTime,
+		GUID:             properties.GUID,
+		HostName:         properties.HostName,
+		HTTPSPort:        properties.HTTPSPort,
+		ID:               properties.ID,
+		ILOIPAddress:     properties.ILOIPAddress,
+		ILOIPGateway:     properties.ILOIPGateway,
+		ILOIPNetmask:     properties.ILOIPNetmask,
+		ILOPassword:      properties.ILOPassword,
+		ILOUsername:      properties.ILOUsername,
+		LastUpdated:      properties.LastUpdated,
+		MgmtIPAddress:    properties.MgmtIPAddress,
+		MgmtIPGateway:    properties.MgmtIPGateway,
+		MgmtIPNetmask:    properties.MgmtIPNetmask,
+		OfflineReason:    properties.OfflineReason,
+		ProfileNames:     profileNames,
+		PhysLocation:     properties.PhysLocation,
+		PhysLocationID:   properties.PhysLocationID,
+		Rack:             properties.Rack,
+		RevalPending:     properties.RevalPending,
+		Status:           properties.Status,
+		StatusID:         properties.StatusID,
+		TCPPort:          properties.TCPPort,
+		Type:             properties.Type,
+		TypeID:           properties.TypeID,
+		UpdPending:       properties.UpdPending,
+		XMPPID:           properties.XMPPID,
+		XMPPPasswd:       properties.XMPPPasswd,
+	}
+}
+
 // ServerV40 is the representation of a Server in version 4.0 of the Traffic Ops API.
 type ServerV40 struct {
-	CommonServerProperties
+	Cachegroup        *string                  `json:"cachegroup" db:"cachegroup"`
+	CachegroupID      *int                     `json:"cachegroupId" db:"cachegroup_id"`
+	CDNID             *int                     `json:"cdnId" db:"cdn_id"`
+	CDNName           *string                  `json:"cdnName" db:"cdn_name"`
+	DeliveryServices  *map[string][]string     `json:"deliveryServices,omitempty"`
+	DomainName        *string                  `json:"domainName" db:"domain_name"`
+	FQDN              *string                  `json:"fqdn,omitempty"`
+	FqdnTime          time.Time                `json:"-"`
+	GUID              *string                  `json:"guid" db:"guid"`
+	HostName          *string                  `json:"hostName" db:"host_name"`
+	HTTPSPort         *int                     `json:"httpsPort" db:"https_port"`
+	ID                *int                     `json:"id" db:"id"`
+	ILOIPAddress      *string                  `json:"iloIpAddress" db:"ilo_ip_address"`
+	ILOIPGateway      *string                  `json:"iloIpGateway" db:"ilo_ip_gateway"`
+	ILOIPNetmask      *string                  `json:"iloIpNetmask" db:"ilo_ip_netmask"`
+	ILOPassword       *string                  `json:"iloPassword" db:"ilo_password"`
+	ILOUsername       *string                  `json:"iloUsername" db:"ilo_username"`
+	LastUpdated       *TimeNoMod               `json:"lastUpdated" db:"last_updated"`
+	MgmtIPAddress     *string                  `json:"mgmtIpAddress" db:"mgmt_ip_address"`
+	MgmtIPGateway     *string                  `json:"mgmtIpGateway" db:"mgmt_ip_gateway"`
+	MgmtIPNetmask     *string                  `json:"mgmtIpNetmask" db:"mgmt_ip_netmask"`
+	OfflineReason     *string                  `json:"offlineReason" db:"offline_reason"`
+	PhysLocation      *string                  `json:"physLocation" db:"phys_location"`
+	PhysLocationID    *int                     `json:"physLocationId" db:"phys_location_id"`
+	ProfileNames      []string                 `json:"profileNames" db:"profile_name"`
+	Rack              *string                  `json:"rack" db:"rack"`
+	RevalPending      *bool                    `json:"revalPending" db:"reval_pending"`
+	Status            *string                  `json:"status" db:"status"`
+	StatusID          *int                     `json:"statusId" db:"status_id"`
+	TCPPort           *int                     `json:"tcpPort" db:"tcp_port"`
+	Type              string                   `json:"type" db:"server_type"`
+	TypeID            *int                     `json:"typeId" db:"server_type_id"`
+	UpdPending        *bool                    `json:"updPending" db:"upd_pending"`
+	XMPPID            *string                  `json:"xmppId" db:"xmpp_id"`
+	XMPPPasswd        *string                  `json:"xmppPasswd" db:"xmpp_passwd"`
 	Interfaces        []ServerInterfaceInfoV40 `json:"interfaces" db:"interfaces"`
 	StatusLastUpdated *time.Time               `json:"statusLastUpdated" db:"status_last_updated"`
 	ConfigUpdateTime  *time.Time               `json:"configUpdateTime" db:"config_update_time"`
@@ -1026,7 +1097,7 @@ func (s *ServerV30) ToServerV2() (ServerNullableV2, error) {
 //
 // Deprecated: Traffic Ops API version 3 is deprecated, new code should use
 // ServerV40 or newer structures.
-func (s *ServerV40) ToServerV3FromV4() (ServerV30, error) {
+func (s *ServerV40) ToServerV3FromV4(csp CommonServerProperties) (ServerV30, error) {
 	routerHostName := ""
 	routerPortName := ""
 	interfaces := make([]ServerInterfaceInfo, 0)
@@ -1046,7 +1117,7 @@ func (s *ServerV40) ToServerV3FromV4() (ServerV30, error) {
 		interfaces = append(interfaces, i)
 	}
 	serverV30 := ServerV30{
-		CommonServerProperties: s.CommonServerProperties,
+		CommonServerProperties: csp,
 		Interfaces:             interfaces,
 		StatusLastUpdated:      s.StatusLastUpdated,
 	}
@@ -1063,12 +1134,12 @@ func (s *ServerV40) ToServerV3FromV4() (ServerV30, error) {
 //
 // Deprecated: Traffic Ops API version 2 is deprecated, new code should use
 // ServerV40 or newer structures.
-func (s *ServerV40) ToServerV2FromV4() (ServerNullableV2, error) {
+func (s *ServerV40) ToServerV2FromV4(csp CommonServerProperties) (ServerNullableV2, error) {
 	routerHostName := ""
 	routerPortName := ""
 	legacyServer := ServerNullableV2{
 		ServerNullableV11: ServerNullableV11{
-			CommonServerProperties: s.CommonServerProperties,
+			CommonServerProperties: csp,
 		},
 		IPIsService:  new(bool),
 		IP6IsService: new(bool),
diff --git a/tc-health-client/README.md b/tc-health-client/README.md
index ff6d42c0d..e397b8cae 100644
--- a/tc-health-client/README.md
+++ b/tc-health-client/README.md
@@ -71,24 +71,24 @@ Requires Apache TrafficServer 8.1.0 or later.
 
 -f, -\-config-file=config-file 
   
-  Specify the config file to use.  
-  Defaults to /etc/trafficcontro-health-client/tc-health-client.json
+Specify the config file to use.  
+Defaults to /etc/trafficcontro-health-client/tc-health-client.json
 
 -h, -\-help 
 
-  Prints command line usage and exits
+Prints command line usage and exits
 
 -l, -\-logging-dir=logging-directory
 
-  Specify the directory where log files are kept.  The default location
-  is **/var/log/trafficcontrol/**
+Specify the directory where log files are kept.  The default location
+is **/var/log/trafficcontrol/**
 
 -v, -\-verbose
 
-  Logging verbosity.  Errors are logged to the default log file 
-  **/var/log/trafficcontrol/tc-health-client.log**
-  To add Warnings, use -v.  To add Warnings and Informational 
-  logging, use -vv.  Finally you may add Debug logging using -vvv.
+Logging verbosity.  Errors are logged to the default log file 
+**/var/log/trafficcontrol/tc-health-client.log**
+To add Warnings, use -v.  To add Warnings and Informational 
+logging, use -vv.  Finally you may add Debug logging using -vvv.
 
 # CONFIGURATION
 
@@ -109,6 +109,7 @@ Sample configuarion file:
     "tm-proxy-url", "http://sample-http-proxy.cdn.net:80",
     "to-login-dispersion-factor": 90,
     "unavailable-poll-threshold": 2,
+    "markup-poll-threshold": 1,
     "trafficserver-config-dir": "/opt/trafficserver/etc/trafficserver",
     "trafficserver-bin-dir": "/opt/trafficserver/bin",
     "poll-state-json-log": "/var/log/trafficcontrol/poll-state.json",
@@ -118,85 +119,91 @@ Sample configuarion file:
 
 ### cdn-name 
 
-  The name of the CDN that the Traffic Server host is a member of.
+The name of the CDN that the Traffic Server host is a member of.
 
 ### enable-active-markdowns
 
-  When enabled, the client will actively mark down Traffic Server parents.
-  When disabled, the client will only log that it would have marked down
-  Traffic Server parents.  Down Parents are always marked UP if Traffic Monitor
-  reports them available irregardless of this setting.
+When enabled, the client will actively mark down Traffic Server parents.
+When disabled, the client will only log that it would have marked down
+Traffic Server parents.  Down Parents are always marked UP if Traffic Monitor
+reports them available irregardless of this setting.
 
 ### reason-code
 
-  Use the reason code **active** or **local** when marking down Traffic Server
-  hosts in the Traffic Server **HostStatus** subsystem.
+Use the reason code **active** or **local** when marking down Traffic Server
+hosts in the Traffic Server **HostStatus** subsystem.
 
 ### to-credential-file
 
-  The file where **Traffic Ops** credentials are read.  The file should define the 
-  following variables:
+The file where **Traffic Ops** credentials are read.  The file should define the 
+following variables:
 
-  * TO_URL="https://trafficops.cdn.com"
-  * TO_USER="touser"
-  * TO_PASS="touser_password"
+* TO_URL="https://trafficops.cdn.com"
+* TO_USER="touser"
+* TO_PASS="touser_password"
 
 ### to-url
 
-  The **Traffic Ops** URL
+The **Traffic Ops** URL
 
 ### to-request-timeout-seconds
 
-  The time in seconds to wait for a query response from both **Traffic Ops** and
-  the **Traffic Monitors**
+The time in seconds to wait for a query response from both **Traffic Ops** and
+the **Traffic Monitors**
 
 ### tm-poll-interval-seconds
 
-  The polling interval in seconds used to update **Traffic Server** parent
-  status.
+The polling interval in seconds used to update **Traffic Server** parent
+status.
 
 ### tm-proxy-url
 
-  If not nil, all Traffic Monitor requests will be proxied through this 
-  proxy endpoint.  This is useful when there are large numbers of caches
-  polling a Traffic Monitor and you wish to funnel queries through a caching
-  proxy server to limit direct direct connections to Traffic Monitor.
+If not nil, all Traffic Monitor requests will be proxied through this 
+proxy endpoint.  This is useful when there are large numbers of caches
+polling a Traffic Monitor and you wish to funnel queries through a caching
+proxy server to limit direct direct connections to Traffic Monitor.
 
 ### to-login-dispersion-factor
 
-  This is used to calculate TrafficOps login dispersion.  It is related to the
-  **tm-poll-interval-seconds**.  The login dispersion is computed by multiplying
-  **tm-poll-interval-seconds** by the **to-login-dispersion-factor**.  For example
-  if to-login-dispersion-factor is 90 and the **tm-poll-interval-seconds** is 10s
-  the the dispersion modulo window is 900s.
+This is used to calculate TrafficOps login dispersion.  It is related to the
+**tm-poll-interval-seconds**.  The login dispersion is computed by multiplying
+**tm-poll-interval-seconds** by the **to-login-dispersion-factor**.  For example
+if to-login-dispersion-factor is 90 and the **tm-poll-interval-seconds** is 10s
+the the dispersion modulo window is 900s.
 
 ### unavailable-poll-threshold
 
-  This controls when an unhealthy parent is marked down.  An unhealthy parent
-  will be marked down when the number of consecutive polls reaches this threshold
-  with the parent reported as unhealthy.  The default threshold is 2.
+This controls when an unhealthy parent is marked down.  An unhealthy parent
+will be marked down when the number of consecutive polls reaches this threshold
+with the parent reported as unhealthy.  The default threshold is 2.
+
+### markup-poll-threshold
+
+This controls when a healthy parent is marked up.  An healthy parent
+will be marked up when the number of consecutive polls reaches this threshold
+with the parent reported as healthy.  The default threshold is 1.
 
 ### trafficserver-config-dir
 
-  The location on the host where **Traffic Server** configuration files are 
-  located.
+The location on the host where **Traffic Server** configuration files are 
+located.
 
 ### trafficserver-bin-dir
 
-  The location on the host where **Traffic Server** **traffic_ctl** tool may
-  be found.
+The location on the host where **Traffic Server** **traffic_ctl** tool may
+be found.
 
 ### poll-state-json-log ###
 
-  The full path to the polling state file which contains information 
-  about the current status of parents and the health client configuration.
-  Polling state data is written to this file after each polling cycle when
-  enabled, see **enable-poll-state-log**
+The full path to the polling state file which contains information 
+about the current status of parents and the health client configuration.
+Polling state data is written to this file after each polling cycle when
+enabled, see **enable-poll-state-log**
 
 ### enable-poll-state-log ###
 
-  Enable writing the Polling state to the **poll-state-json-log** after
-  eache polling cycle.  Default **false**, disabled
+Enable writing the Polling state to the **poll-state-json-log** after
+eache polling cycle.  Default **false**, disabled
 
 # Files
 
@@ -209,4 +216,3 @@ Sample configuarion file:
 * Traffic Server **strategies.yaml**
 * Traffic Server **traffic_ctl** command
   
-   
diff --git a/tc-health-client/config/config.go b/tc-health-client/config/config.go
index 5255534bb..99dc1d870 100644
--- a/tc-health-client/config/config.go
+++ b/tc-health-client/config/config.go
@@ -55,6 +55,7 @@ const (
 	DefaultTrafficServerConfigDir   = "/opt/trafficserver/etc/trafficserver"
 	DefaultTrafficServerBinDir      = "/opt/trafficserver/bin"
 	DefaultUnavailablePollThreshold = 2
+	DefaultMarkupPollThreshold      = 1
 )
 
 type Cfg struct {
@@ -70,6 +71,7 @@ type Cfg struct {
 	TmPollIntervalSeconds    string          `json:"tm-poll-interval-seconds"`
 	TOLoginDispersionFactor  int             `json:"to-login-dispersion-factor"`
 	UnavailablePollThreshold int             `json:"unavailable-poll-threshold"`
+	MarkUpPollThreshold      int             `json:"markup-poll-threshold"`
 	TrafficServerConfigDir   string          `json:"trafficserver-config-dir"`
 	TrafficServerBinDir      string          `json:"trafficserver-bin-dir"`
 	PollStateJSONLog         string          `json:"poll-state-json-log"`
diff --git a/tc-health-client/tmagent/tmagent.go b/tc-health-client/tmagent/tmagent.go
index bb4ebe08c..1783311ea 100644
--- a/tc-health-client/tmagent/tmagent.go
+++ b/tc-health-client/tmagent/tmagent.go
@@ -49,6 +49,14 @@ const (
 	StrategiesFile = "strategies.yaml"
 )
 
+// this global is used to auto select the
+// proper ATS traffic_ctl command to use
+// when querying host status. for ATS
+// version 10 and greater this will remain
+// at 0.  For ATS version 9, this will be
+// auto updated to 1
+var traffic_ctl_index = 0
+
 type ParentAvailable interface {
 	available(reasonCode string) bool
 }
@@ -85,6 +93,7 @@ type ParentStatus struct {
 	ManualReason         bool
 	LastTmPoll           int64
 	UnavailablePollCount int
+	MarkUpPollCount      int
 }
 
 // used to get the overall parent availablity from the
@@ -531,6 +540,7 @@ func (c *ParentInfo) markParent(fqdn string, cacheStatus string, available bool)
 		activeReason := pv.ActiveReason
 		localReason := pv.LocalReason
 		unavailablePollCount := pv.UnavailablePollCount
+		markUpPollCount := pv.MarkUpPollCount
 
 		log.Debugf("hostName: %s, UnavailablePollCount: %d, available: %v", hostName, unavailablePollCount, available)
 
@@ -544,22 +554,31 @@ func (c *ParentInfo) markParent(fqdn string, cacheStatus string, available bool)
 				err = c.execTrafficCtl(fqdn, available)
 				if err != nil {
 					log.Errorln(err.Error())
-				}
-				if err == nil {
+				} else {
 					hostAvailable = false
+					// reset the poll counts
+					markUpPollCount = 0
+					unavailablePollCount = 0
 					log.Infof("marked parent %s DOWN, cache status was: %s\n", hostName, cacheStatus)
 				}
 			}
 		} else { // available
 			// marking the host up
-			err = c.execTrafficCtl(fqdn, available)
-			if err == nil {
-				hostAvailable = true
-				// reset the unavilable poll count
-				unavailablePollCount = 0
-				log.Infof("marked parent %s UP, cache status was: %s\n", hostName, cacheStatus)
-			} else {
+			markUpPollCount += 1
+			if markUpPollCount < c.Cfg.MarkUpPollThreshold {
+				log.Infof("TM indicates %s is available but the MarkUpPollThreshold has not been reached", hostName)
 				hostAvailable = false
+			} else {
+				err = c.execTrafficCtl(fqdn, available)
+				if err != nil {
+					log.Errorln(err.Error())
+				} else {
+					hostAvailable = true
+					// reset the poll counts
+					unavailablePollCount = 0
+					markUpPollCount = 0
+					log.Infof("marked parent %s UP, cache status was: %s\n", hostName, cacheStatus)
+				}
 			}
 		}
 
@@ -576,6 +595,7 @@ func (c *ParentInfo) markParent(fqdn string, cacheStatus string, available bool)
 			pv.ActiveReason = activeReason
 			pv.LocalReason = localReason
 			pv.UnavailablePollCount = unavailablePollCount
+			pv.MarkUpPollCount = markUpPollCount
 			c.Parents[hostName] = pv
 			log.Debugf("Updated parent status: %v", pv)
 		}
@@ -587,15 +607,35 @@ func (c *ParentInfo) markParent(fqdn string, cacheStatus string, available bool)
 // subsystem.
 func (c *ParentInfo) readHostStatus(parentStatus map[string]ParentStatus) error {
 	tc := filepath.Join(c.TrafficServerBinDir, TrafficCtl)
-
-	cmd := exec.Command(tc, "metric", "match", "host_status")
 	var stdout bytes.Buffer
 	var stderr bytes.Buffer
-	cmd.Stdout = &stdout
-	cmd.Stderr = &stderr
-	err := cmd.Run()
-	if err != nil {
-		return fmt.Errorf("%s error: %s", TrafficCtl, stderr.String())
+
+	// auto select traffic_ctl command for ATS version 9 or 10 and later
+	for i := traffic_ctl_index; i <= 1; i++ {
+		var err error
+		switch i {
+		case 0: // ATS version 10 and later
+			cmd := exec.Command(tc, "host", "status")
+			cmd.Stdout = &stdout
+			cmd.Stderr = &stderr
+			err = cmd.Run()
+		case 1: // ATS version 9
+			cmd := exec.Command(tc, "metric", "match", "host_status")
+			cmd.Stdout = &stdout
+			cmd.Stderr = &stderr
+			err = cmd.Run()
+		}
+		if err == nil {
+			break
+		}
+		if err != nil && i == 0 {
+			log.Infof("%s command used is not for ATS version 10 or later, downgrading to ATS version 9\n", TrafficCtl)
+			traffic_ctl_index = 1
+			continue
+		}
+		if err != nil {
+			return fmt.Errorf("%s error: %s", TrafficCtl, stderr.String())
+		}
 	}
 
 	if len((stdout.Bytes())) > 0 {
@@ -607,55 +647,66 @@ func (c *ParentInfo) readHostStatus(parentStatus map[string]ParentStatus) error
 		scanner := bufio.NewScanner(bytes.NewReader(stdout.Bytes()))
 		for scanner.Scan() {
 			line := strings.TrimSpace(scanner.Text())
-			if strings.HasPrefix(line, "proxy.process.host_status.") {
-				fields := strings.Split(line, " ")
-				if len(fields) > 0 {
-					fqdnField := strings.Split(fields[0], "proxy.process.host_status.")
-					if len(fqdnField) > 0 {
-						fqdn = fqdnField[1]
+			fields := strings.Split(line, " ")
+			/*
+			 * For ATS Version 9, the host status uses internal stats and prefixes
+			 * the fqdn field from the output of the traffic_ctl host status and metric
+			 * match commands with "proxy.process.host_status".  Going forward starting
+			 * with ATS Version 10, internal stats are no-longer used and the fqdn field
+			 * is no-longer prefixed with the "proxy.process.host_status" string.
+			 */
+			if len(fields) == 2 {
+				// check for ATS version 9 output.
+				fqdnField := strings.Split(fields[0], "proxy.process.host_status.")
+				if len(fqdnField) == 2 { // ATS version 9
+					fqdn = fqdnField[1]
+				} else { // ATS version 10 and greater
+					fqdn = fqdnField[0]
+				}
+				statField := strings.Split(fields[1], ",")
+				if len(statField) == 5 {
+					if strings.HasPrefix(statField[1], "ACTIVE:UP") {
+						activeReason = true
+					} else if strings.HasPrefix(statField[1], "ACTIVE:DOWN") {
+						activeReason = false
 					}
-					statField := strings.Split(fields[1], ",")
-					if len(statField) == 5 {
-						if strings.HasPrefix(statField[1], "ACTIVE:UP") {
-							activeReason = true
-						} else if strings.HasPrefix(statField[1], "ACTIVE:DOWN") {
-							activeReason = false
-						}
-						if strings.HasPrefix(statField[2], "LOCAL:UP") {
-							localReason = true
-						} else if strings.HasPrefix(statField[2], "LOCAL:DOWN") {
-							localReason = false
-						}
-						if strings.HasPrefix(statField[3], "MANUAL:UP") {
-							manualReason = true
-						} else if strings.HasPrefix(statField[3], "MANUAL:DOWN") {
-							manualReason = false
-						}
+					if strings.HasPrefix(statField[2], "LOCAL:UP") {
+						localReason = true
+					} else if strings.HasPrefix(statField[2], "LOCAL:DOWN") {
+						localReason = false
 					}
-					pstat := ParentStatus{
-						Fqdn:                 fqdn,
-						ActiveReason:         activeReason,
-						LocalReason:          localReason,
-						ManualReason:         manualReason,
-						LastTmPoll:           0,
-						UnavailablePollCount: 0,
+					if strings.HasPrefix(statField[3], "MANUAL:UP") {
+						manualReason = true
+					} else if strings.HasPrefix(statField[3], "MANUAL:DOWN") {
+						manualReason = false
 					}
-					hostName = parseFqdn(fqdn)
-					pv, ok := parentStatus[hostName]
-					// create the ParentStatus struct and add it to the
-					// Parents map only if an entry in the map does not
-					// already exist.
-					if !ok {
+				}
+				pstat := ParentStatus{
+					Fqdn:                 fqdn,
+					ActiveReason:         activeReason,
+					LocalReason:          localReason,
+					ManualReason:         manualReason,
+					LastTmPoll:           0,
+					UnavailablePollCount: 0,
+					MarkUpPollCount:      0,
+				}
+				log.Debugf("processed host status record: %v\n", pstat)
+				hostName = parseFqdn(fqdn)
+				pv, ok := parentStatus[hostName]
+				// create the ParentStatus struct and add it to the
+				// Parents map only if an entry in the map does not
+				// already exist.
+				if !ok {
+					parentStatus[hostName] = pstat
+					log.Infof("added Host '%s' from ATS Host Status to the parents map\n", hostName)
+				} else {
+					available := pstat.available(c.Cfg.ReasonCode)
+					if pv.available(c.Cfg.ReasonCode) != available {
+						log.Infof("host status for '%s' has changed to %s\n", hostName, pstat.Status())
+						pstat.LastTmPoll = pv.LastTmPoll
+						pstat.UnavailablePollCount = pv.UnavailablePollCount
+						pstat.MarkUpPollCount = pv.MarkUpPollCount
 						parentStatus[hostName] = pstat
-						log.Infof("added Host '%s' from ATS Host Status to the parents map\n", hostName)
-					} else {
-						available := pstat.available(c.Cfg.ReasonCode)
-						if pv.available(c.Cfg.ReasonCode) != available {
-							log.Infof("host status for '%s' has changed to %s\n", hostName, pstat.Status())
-							pstat.LastTmPoll = pv.LastTmPoll
-							pstat.UnavailablePollCount = pv.UnavailablePollCount
-							parentStatus[hostName] = pstat
-						}
 					}
 				}
 			}
diff --git a/traffic_monitor/poller/monitorconfig.go b/traffic_monitor/poller/monitorconfig.go
index 639cf6a34..5f4f04eaf 100644
--- a/traffic_monitor/poller/monitorconfig.go
+++ b/traffic_monitor/poller/monitorconfig.go
@@ -112,6 +112,11 @@ func (p MonitorConfigPoller) Poll() {
 				log.Errorf("MonitorConfigPoller: %s\n %v\n", err, monitorConfig)
 				continue
 			}
+			// poll the CRConfig so that it is synchronized with the TMConfig
+			if _, err := p.Session.CRConfigRaw(p.OpsConfig.CdnName); err != nil {
+				log.Errorf("MonitorConfigPoller: error getting CRConfig: %v", err)
+				continue
+			}
 			p.writeConfig(MonitorCfg{CDN: p.OpsConfig.CdnName, Cfg: *monitorConfig})
 		}
 	}
diff --git a/traffic_ops/app/db/migrations/2022041410185700_add_server_profile_table.down.sql b/traffic_ops/app/db/migrations/2022041410185700_add_server_profile_table.down.sql
new file mode 100644
index 000000000..261b7f2ce
--- /dev/null
+++ b/traffic_ops/app/db/migrations/2022041410185700_add_server_profile_table.down.sql
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership.  The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+DROP TABLE IF EXISTS public.server_profile;
+
+CREATE TRIGGER before_update_ip_address_trigger
+    BEFORE UPDATE ON ip_address
+    FOR EACH ROW WHEN (NEW.address <> OLD.address)
+    EXECUTE PROCEDURE before_ip_address_table();
+
+CREATE TRIGGER before_create_ip_address_trigger
+    BEFORE INSERT ON ip_address
+    FOR EACH ROW EXECUTE PROCEDURE before_ip_address_table();
+
+CREATE TRIGGER before_update_server_trigger
+    BEFORE UPDATE ON server
+    FOR EACH ROW WHEN (NEW.profile <> OLD.profile)
+    EXECUTE PROCEDURE before_server_table();
+
+CREATE TRIGGER before_create_server_trigger
+    BEFORE INSERT ON server
+    FOR EACH ROW EXECUTE PROCEDURE before_server_table();
diff --git a/traffic_ops/app/db/migrations/2022041410185700_add_server_profile_table.up.sql b/traffic_ops/app/db/migrations/2022041410185700_add_server_profile_table.up.sql
new file mode 100644
index 000000000..428e678e4
--- /dev/null
+++ b/traffic_ops/app/db/migrations/2022041410185700_add_server_profile_table.up.sql
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership.  The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations undera
+ * the License.
+ */
+
+CREATE TABLE IF NOT EXISTS public.server_profile (
+                                                     server bigint NOT NULL,
+                                                     profile_name text NOT NULL,
+                                                     priority int NOT NULL CHECK (priority >= 0),
+    CONSTRAINT pk_server_profile PRIMARY KEY (profile_name, server),
+    CONSTRAINT fk_server_id FOREIGN KEY (server) REFERENCES public.server(id) ON DELETE CASCADE ON UPDATE CASCADE,
+    CONSTRAINT fk_server_profile_name_profile FOREIGN KEY (profile_name) REFERENCES public.profile(name) ON UPDATE CASCADE ON DELETE RESTRICT
+    );
+
+INSERT into public.server_profile(server, profile_name, priority)
+SELECT s.id, p.name, 0
+FROM public.server AS s
+    JOIN public.profile p ON p.id=s.profile;
+
+DROP TRIGGER IF EXISTS before_update_ip_address_trigger on ip_address;
+
+DROP TRIGGER IF EXISTS before_create_ip_address_trigger on ip_address;
+
+DROP TRIGGER IF EXISTS before_update_server_trigger ON server;
+
+DROP TRIGGER IF EXISTS before_create_server_trigger ON server;
diff --git a/traffic_ops/traffic_ops_golang/api/api.go b/traffic_ops/traffic_ops_golang/api/api.go
index 4db82e9ef..f50116f7a 100644
--- a/traffic_ops/traffic_ops_golang/api/api.go
+++ b/traffic_ops/traffic_ops_golang/api/api.go
@@ -1043,6 +1043,7 @@ func ParseDBError(ierr error) (error, error, int) {
 // This also uses the given ResponseWriter to refresh the cookie, if it was valid.
 func GetUserFromReq(w http.ResponseWriter, r *http.Request, secret string) (auth.CurrentUser, error, error, int) {
 	var cookie *http.Cookie
+	var oldToken jwt.Token
 
 	if r.Header.Get(rfc.Authorization) != "" && strings.Contains(r.Header.Get(rfc.Authorization), "Bearer") {
 		givenToken := r.Header.Get(rfc.Authorization)
@@ -1050,11 +1051,12 @@ func GetUserFromReq(w http.ResponseWriter, r *http.Request, secret string) (auth
 		if len(tokenSplit) > 1 {
 			givenToken = tokenSplit[1]
 		}
-		bearerCookie, err := getCookieFromAccessToken(givenToken, secret)
+		bearerCookie, readToken, err := getCookieFromAccessToken(givenToken, secret)
 		if err != nil {
 			return auth.CurrentUser{}, errors.New("unauthorized, please log in."), err, http.StatusUnauthorized
 		}
 		cookie = bearerCookie
+		oldToken = readToken
 	} else {
 		for _, givenCookie := range r.Cookies() {
 			if cookie != nil {
@@ -1065,11 +1067,12 @@ func GetUserFromReq(w http.ResponseWriter, r *http.Request, secret string) (auth
 			}
 			switch givenCookie.Name {
 			case AccessToken:
-				bearerCookie, err := getCookieFromAccessToken(givenCookie.Value, secret)
+				bearerCookie, readToken, err := getCookieFromAccessToken(givenCookie.Value, secret)
 				if err != nil {
 					return auth.CurrentUser{}, errors.New("unauthorized, please log in."), err, http.StatusUnauthorized
 				}
 				cookie = bearerCookie
+				oldToken = readToken
 			case tocookie.Name:
 				cookie = givenCookie
 			}
@@ -1114,17 +1117,39 @@ func GetUserFromReq(w http.ResponseWriter, r *http.Request, secret string) (auth
 	duration := tocookie.DefaultDuration
 	newCookie := tocookie.GetCookie(oldCookie.AuthData, duration, secret)
 	http.SetCookie(w, newCookie)
+
+	if oldToken != nil {
+		newToken := oldToken
+		err = newToken.Set(MojoCookie, cookie.Value)
+		if err != nil {
+			return auth.CurrentUser{}, errors.New("unauthorized, please log in."), fmt.Errorf("setting mojo cookie on access_token: %w", err), http.StatusUnauthorized
+		}
+		jwtSigned, err := jwt.Sign(newToken, jwa.HS256, []byte(cfg.Secrets[0]))
+		if err != nil {
+			return auth.CurrentUser{}, errors.New("unauthorized, please log in."), fmt.Errorf("signing renewed access_token: %w", err), http.StatusUnauthorized
+		}
+
+		http.SetCookie(w, &http.Cookie{
+			Name:     AccessToken,
+			Value:    string(jwtSigned),
+			Path:     "/",
+			MaxAge:   newCookie.MaxAge,
+			Expires:  newCookie.Expires,
+			HttpOnly: true, // prevents the cookie being accessed by Javascript. DO NOT remove, security vulnerability
+		})
+	}
+
 	return user, nil, nil, http.StatusOK
 }
 
-func getCookieFromAccessToken(bearerToken string, secret string) (*http.Cookie, error) {
+func getCookieFromAccessToken(bearerToken string, secret string) (*http.Cookie, jwt.Token, error) {
 	var cookie *http.Cookie
 	token, err := jwt.Parse([]byte(bearerToken), jwt.WithVerify(jwa.HS256, []byte(secret)))
 	if err != nil {
-		return nil, fmt.Errorf("invalid token: %w", err)
+		return nil, nil, fmt.Errorf("invalid token: %w", err)
 	}
 	if token == nil {
-		return nil, errors.New("parsing claims: parsed nil token")
+		return nil, nil, errors.New("parsing claims: parsed nil token")
 	}
 
 	for key, val := range token.PrivateClaims() {
@@ -1132,7 +1157,7 @@ func getCookieFromAccessToken(bearerToken string, secret string) (*http.Cookie,
 		case MojoCookie:
 			mojoVal, ok := val.(string)
 			if !ok {
-				return nil, errors.New("invalid token - " + MojoCookie + " must be a string")
+				return nil, nil, errors.New("invalid token - " + MojoCookie + " must be a string")
 			}
 			cookie = &http.Cookie{
 				Value: mojoVal,
@@ -1140,7 +1165,7 @@ func getCookieFromAccessToken(bearerToken string, secret string) (*http.Cookie,
 		}
 	}
 
-	return cookie, nil
+	return cookie, token, nil
 }
 
 func AddUserToReq(r *http.Request, u auth.CurrentUser) {
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index 72599ca03..024ed7fd8 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -1979,3 +1979,114 @@ WHERE server.id = $2;`
 
 	return nil
 }
+
+// GetCommonServerPropertiesFromV4 converts ServerV40 to CommonServerProperties struct.
+func GetCommonServerPropertiesFromV4(s tc.ServerV40, tx *sql.Tx) (tc.CommonServerProperties, error) {
+	var id int
+	var desc string
+	if len(s.ProfileNames) == 0 {
+		return tc.CommonServerProperties{}, fmt.Errorf("profileName doesnot exist in server: %v", *s.ID)
+	}
+	rows, err := tx.Query("SELECT id, description from profile WHERE name=$1", (s.ProfileNames)[0])
+	if err != nil {
+		return tc.CommonServerProperties{}, fmt.Errorf("querying profile id and description by profile_name: %w", err)
+	}
+	defer log.Close(rows, "closing rows in GetCommonServerPropertiesFromV4")
+
+	for rows.Next() {
+		if err := rows.Scan(&id, &desc); err != nil {
+			return tc.CommonServerProperties{}, fmt.Errorf("scanning profile: %w", err)
+		}
+	}
+
+	return tc.CommonServerProperties{
+		Cachegroup:       s.Cachegroup,
+		CachegroupID:     s.CachegroupID,
+		CDNID:            s.CDNID,
+		CDNName:          s.CDNName,
+		DeliveryServices: s.DeliveryServices,
+		DomainName:       s.DomainName,
+		FQDN:             s.FQDN,
+		FqdnTime:         s.FqdnTime,
+		GUID:             s.GUID,
+		HostName:         s.HostName,
+		HTTPSPort:        s.HTTPSPort,
+		ID:               s.ID,
+		ILOIPAddress:     s.ILOIPAddress,
+		ILOIPGateway:     s.ILOIPGateway,
+		ILOIPNetmask:     s.ILOIPNetmask,
+		ILOPassword:      s.ILOPassword,
+		ILOUsername:      s.ILOUsername,
+		LastUpdated:      s.LastUpdated,
+		MgmtIPAddress:    s.MgmtIPAddress,
+		MgmtIPGateway:    s.MgmtIPGateway,
+		MgmtIPNetmask:    s.MgmtIPNetmask,
+		OfflineReason:    s.OfflineReason,
+		Profile:          &(s.ProfileNames)[0],
+		ProfileDesc:      &desc,
+		ProfileID:        &id,
+		PhysLocation:     s.PhysLocation,
+		PhysLocationID:   s.PhysLocationID,
+		Rack:             s.Rack,
+		RevalPending:     s.RevalPending,
+		Status:           s.Status,
+		StatusID:         s.StatusID,
+		TCPPort:          s.TCPPort,
+		Type:             s.Type,
+		TypeID:           s.TypeID,
+		UpdPending:       s.UpdPending,
+		XMPPID:           s.XMPPID,
+		XMPPPasswd:       s.XMPPPasswd,
+	}, nil
+}
+
+// UpdateServerProfilesForV4 updates server_profile table via update function for APIv4.
+func UpdateServerProfilesForV4(id int, profile []string, tx *sql.Tx) error {
+	profileNames := make([]string, 0, len(profile))
+	priority := make([]int, 0, len(profile))
+	for i, _ := range profile {
+		priority = append(priority, i)
+	}
+
+	//Delete existing rows from server_profile to get the priority correct for profile_name changes
+	_, err := tx.Exec("DELETE FROM server_profile WHERE server=$1", id)
+	if err != nil {
+		return fmt.Errorf("updating server_profile by server id: %d, error: %w", id, err)
+	}
+
+	query := `WITH inserted AS (
+		INSERT INTO server_profile
+		SELECT $1, "profile_name", "priority"
+		FROM UNNEST($2::text[], $3::int[]) AS tmp("profile_name", "priority")
+		RETURNING profile_name, priority
+	)
+	SELECT ARRAY_AGG(profile_name)
+	FROM (
+		SELECT profile_name
+		FROM inserted
+		ORDER BY priority ASC
+	) AS returned(profile_name)
+`
+	err = tx.QueryRow(query, id, pq.Array(profile), pq.Array(priority)).Scan(pq.Array(&profileNames))
+	if err != nil {
+		return fmt.Errorf("failed to insert/read into/from server_profile table, %w", err)
+	}
+	return nil
+}
+
+// UpdateServerProfileTableForV2V3 updates CommonServerPropertiesV40 struct and server_profile table via Update (server) function for API v2/v3.
+func UpdateServerProfileTableForV2V3(id *int, newProfile *string, origProfile string, tx *sql.Tx) ([]string, error) {
+	var profileName []string
+	query := `UPDATE server_profile SET profile_name=$1 WHERE server=$2 AND profile_name=$3`
+	_, err := tx.Exec(query, *newProfile, *id, origProfile)
+	if err != nil {
+		return nil, fmt.Errorf("updating server_profile by profile_name: %w", err)
+	}
+
+	err = tx.QueryRow("SELECT ARRAY_AGG(profile_name) FROM server_profile WHERE server=$1", *id).Scan(pq.Array(&profileName))
+	if err == sql.ErrNoRows {
+		return nil, fmt.Errorf("selecting server_profile by profile_name: %w", err)
+	}
+
+	return profileName, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/login/login.go b/traffic_ops/traffic_ops_golang/login/login.go
index b8de76f81..b1700449e 100644
--- a/traffic_ops/traffic_ops_golang/login/login.go
+++ b/traffic_ops/traffic_ops_golang/login/login.go
@@ -190,6 +190,7 @@ func LoginHandler(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
 					Value:    string(jwtSigned),
 					Path:     "/",
 					MaxAge:   httpCookie.MaxAge,
+					Expires:  httpCookie.Expires,
 					HttpOnly: true, // prevents the cookie being accessed by Javascript. DO NOT remove, security vulnerability
 				})
 
diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go
index ca19d830c..3031e1c20 100644
--- a/traffic_ops/traffic_ops_golang/routing/routing.go
+++ b/traffic_ops/traffic_ops_golang/routing/routing.go
@@ -166,13 +166,13 @@ func CreateRouteMap(rs []Route, disabledRouteIDs []int, perlHandler http.Handler
 		versionI := indexOfApiVersion(versions, r.Version)
 		nextMajorVer := r.Version.Major + 1
 		_, isDisabledRoute := disabledRoutes[r.ID]
+		r.SetMiddleware(authBase, requestTimeout)
 		for _, version := range versions[versionI:] {
 			if version.Major >= nextMajorVer {
 				break
 			}
 			vstr := strconv.FormatUint(version.Major, 10) + "." + strconv.FormatUint(version.Minor, 10)
 			path := RoutePrefix + "/" + vstr + "/" + r.Path
-			r.SetMiddleware(authBase, requestTimeout)
 
 			if isDisabledRoute {
 				m[r.Method] = append(m[r.Method], PathHandler{Path: path, Handler: middleware.WrapAccessLog(authBase.Secret, middleware.DisabledRouteHandler()), ID: r.ID})
diff --git a/traffic_ops/traffic_ops_golang/server/servers.go b/traffic_ops/traffic_ops_golang/server/servers.go
index ad624a2a7..908195bdd 100644
--- a/traffic_ops/traffic_ops_golang/server/servers.go
+++ b/traffic_ops/traffic_ops_golang/server/servers.go
@@ -135,9 +135,7 @@ SELECT
 	s.offline_reason,
 	pl.name AS phys_location,
 	s.phys_location AS phys_location_id,
-	p.name AS profile,
-	p.description AS profile_desc,
-	s.profile AS profile_id,
+	(SELECT ARRAY_AGG(sp.profile_name) FROM server_profile AS sp where sp.server=s.id) AS profile_name,
 	s.rack,
 	s.revalidate_update_time > s.revalidate_apply_time AS reval_pending,
 	s.revalidate_update_time,
@@ -223,6 +221,7 @@ INSERT INTO server (
 ) RETURNING
 	id
 `
+
 const insertQueryV4 = `
 INSERT INTO server (
 	cachegroup,
@@ -248,30 +247,36 @@ INSERT INTO server (
 	xmpp_id,
 	xmpp_passwd
 ) VALUES (
-	:cachegroup_id,
-	:cdn_id,
-	:domain_name,
-	:host_name,
-	:https_port,
-	:ilo_ip_address,
-	:ilo_ip_netmask,
-	:ilo_ip_gateway,
-	:ilo_username,
-	:ilo_password,
-	:mgmt_ip_address,
-	:mgmt_ip_netmask,
-	:mgmt_ip_gateway,
-	:offline_reason,
-	:phys_location_id,
-	:profile_id,
-	:rack,
-	:status_id,
-	:tcp_port,
-	:server_type_id,
-	:xmpp_id,
-	:xmpp_passwd
+    $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22
 ) RETURNING
-	id
+	(SELECT name FROM cachegroup WHERE cachegroup.id=server.cachegroup) AS cachegroup,
+	cachegroup AS cachegroup_id,
+	cdn_id,
+	(SELECT name FROM cdn WHERE cdn.id=server.cdn_id) AS cdn_name,
+	domain_name,
+	guid,
+	host_name,
+	https_port,
+	id,
+	ilo_ip_address,
+	ilo_ip_gateway,
+	ilo_ip_netmask,
+	ilo_password,
+	ilo_username,
+	last_updated,
+	mgmt_ip_address,
+	mgmt_ip_gateway,
+	mgmt_ip_netmask,
+	offline_reason,
+	(SELECT name FROM phys_location WHERE phys_location.id=server.phys_location) AS phys_location,
+	phys_location AS phys_location_id,
+	(SELECT ARRAY[name] FROM profile WHERE profile.id=server.profile) AS profile_name,
+	rack,
+	(SELECT name FROM status WHERE status.id=server.status) AS status,
+	status AS status_id,
+	tcp_port,
+	(SELECT name FROM type WHERE type.id=server.type) AS server_type,
+	type AS server_type_id
 `
 
 const insertQuery = `
@@ -342,7 +347,7 @@ UPDATE server SET
 	mgmt_ip_gateway=:mgmt_ip_gateway,
 	offline_reason=:offline_reason,
 	phys_location=:phys_location_id,
-	profile=:profile_id,
+	profile=(SELECT id from profile where name=(SELECT profile_name from server_profile sp WHERE sp.server=:id and priority=0)),
 	rack=:rack,
 	status=:status_id,
 	tcp_port=:tcp_port,
@@ -351,7 +356,35 @@ UPDATE server SET
 	status_last_updated=:status_last_updated
 WHERE id=:id
 RETURNING
-	id
+	(SELECT name FROM cachegroup WHERE cachegroup.id=server.cachegroup) AS cachegroup,
+	cachegroup AS cachegroup_id,
+	cdn_id,
+	(SELECT name FROM cdn WHERE cdn.id=server.cdn_id) AS cdn_name,
+	domain_name,
+	guid,
+	host_name,
+	https_port,
+	id,
+	ilo_ip_address,
+	ilo_ip_gateway,
+	ilo_ip_netmask,
+	ilo_password,
+	ilo_username,
+	last_updated,
+	mgmt_ip_address,
+	mgmt_ip_gateway,
+	mgmt_ip_netmask,
+	offline_reason,
+	(SELECT name FROM phys_location WHERE phys_location.id=server.phys_location) AS phys_location,
+	phys_location AS phys_location_id,
+	(SELECT ARRAY_AGG(profile_name) FROM server_profile WHERE server_profile.server=server.id) AS profile_name,
+	rack,
+	(SELECT name FROM status WHERE status.id=server.status) AS status,
+	status AS status_id,
+	tcp_port,
+	(SELECT name FROM type WHERE type.id=server.type) AS server_type,
+	type AS server_type_id,
+	status_last_updated
 `
 
 const originServerQuery = `
@@ -409,10 +442,56 @@ func validateCommon(s *tc.CommonServerProperties, tx *sql.Tx) []error {
 	if cdnID != *s.CDNID {
 		errs = append(errs, fmt.Errorf("CDN id '%d' for profile '%d' does not match Server CDN '%d'", cdnID, *s.ProfileID, *s.CDNID))
 	}
-
 	return errs
 }
 
+func validateCommonV40(s *tc.ServerV40, tx *sql.Tx) ([]error, error) {
+
+	noSpaces := validation.NewStringRule(tovalidate.NoSpaces, "cannot contain spaces")
+
+	errs := tovalidate.ToErrors(validation.Errors{
+		"cachegroupId":   validation.Validate(s.CachegroupID, validation.NotNil),
+		"cdnId":          validation.Validate(s.CDNID, validation.NotNil),
+		"domainName":     validation.Validate(s.DomainName, validation.Required, noSpaces),
+		"hostName":       validation.Validate(s.HostName, validation.Required, noSpaces),
+		"physLocationId": validation.Validate(s.PhysLocationID, validation.NotNil),
+		"profileNames":   validation.Validate(s.ProfileNames, validation.NotNil),
+		"statusId":       validation.Validate(s.StatusID, validation.NotNil),
+		"typeId":         validation.Validate(s.TypeID, validation.NotNil),
+		"httpsPort":      validation.Validate(s.HTTPSPort, validation.By(tovalidate.IsValidPortNumber)),
+		"tcpPort":        validation.Validate(s.TCPPort, validation.By(tovalidate.IsValidPortNumber)),
+	})
+
+	if len(errs) > 0 {
+		return errs, nil
+	}
+
+	if _, err := tc.ValidateTypeID(tx, s.TypeID, "server"); err != nil {
+		errs = append(errs, err)
+	}
+
+	if len(s.ProfileNames) == 0 {
+		errs = append(errs, fmt.Errorf("no profiles exists"))
+	}
+
+	var cdnID int
+	if err := tx.QueryRow("SELECT cdn from profile WHERE name=$1", s.ProfileNames[0]).Scan(&cdnID); err != nil {
+		log.Errorf("could not execute select cdnID from profile: %s\n", err)
+		if errors.Is(err, sql.ErrNoRows) {
+			errs = append(errs, fmt.Errorf("no such profileName: '%s'", s.ProfileNames[0]))
+		} else {
+			return nil, fmt.Errorf("unable to get CDN ID for profile name '%s': %w", s.ProfileNames[0], err)
+		}
+		return errs, nil
+	}
+
+	log.Infof("got cdn id: %d from profile and cdn id: %d from server", cdnID, *s.CDNID)
+	if cdnID != *s.CDNID {
+		errs = append(errs, fmt.Errorf("CDN id '%d' for profile '%v' does not match Server CDN '%d'", cdnID, s.ProfileNames[0], *s.CDNID))
+	}
+	return errs, nil
+}
+
 func validateV1(s *tc.ServerNullableV11, tx *sql.Tx) error {
 	if s.IP6Address != nil && len(strings.TrimSpace(*s.IP6Address)) == 0 {
 		s.IP6Address = nil
@@ -487,9 +566,9 @@ func validateMTU(mtu interface{}) error {
 	return nil
 }
 
-func validateV4(s *tc.ServerV40, tx *sql.Tx) (string, error) {
+func validateV4(s *tc.ServerV40, tx *sql.Tx) (string, error, error) {
 	if len(s.Interfaces) == 0 {
-		return "", errors.New("a server must have at least one interface")
+		return "", errors.New("a server must have at least one interface"), nil
 	}
 	var errs []error
 	var serviceAddrV4Found bool
@@ -498,7 +577,6 @@ func validateV4(s *tc.ServerV40, tx *sql.Tx) (string, error) {
 	var ipv6 string
 	var serviceInterface string
 	for _, iface := range s.Interfaces {
-
 		ruleName := fmt.Sprintf("interface '%s' ", iface.Name)
 		errs = append(errs, tovalidate.ToErrors(validation.Errors{
 			ruleName + "name":        validation.Validate(iface.Name, validation.Required),
@@ -551,25 +629,28 @@ func validateV4(s *tc.ServerV40, tx *sql.Tx) (string, error) {
 	if !serviceAddrV6Found && !serviceAddrV4Found {
 		errs = append(errs, errors.New("a server must have at least one service address"))
 	}
-
-	if errs = append(errs, validateCommon(&s.CommonServerProperties, tx)...); errs != nil {
-		return serviceInterface, util.JoinErrs(errs)
+	usrErr, sysErr := validateCommonV40(s, tx)
+	errs = append(errs, usrErr...)
+	if sysErr != nil || len(errs) > 0 {
+		return serviceInterface, util.JoinErrs(errs), sysErr
 	}
 	query := `
-SELECT s.ID, ip.address FROM server s
-JOIN profile p on p.Id = s.Profile
-JOIN interface i on i.server = s.ID
-JOIN ip_address ip on ip.Server = s.ID and ip.interface = i.name
-WHERE ip.service_address = true
-and p.id = $1
+SELECT tmp.server, ip.address
+FROM (
+  SELECT server, ARRAY_AGG(profile_name order by priority) AS profiles
+	FROM server_profile
+	GROUP BY server
+) AS tmp
+JOIN ip_address ip on ip.server = tmp.server
+WHERE (profiles = $1::text[])
 `
 	var rows *sql.Rows
 	var err error
 	//ProfileID already validated
 	if s.ID != nil {
-		rows, err = tx.Query(query+" and s.id != $2", *s.ProfileID, *s.ID)
+		rows, err = tx.Query(query+" and tmp.server != $2", pq.Array(s.ProfileNames), *s.ID)
 	} else {
-		rows, err = tx.Query(query, *s.ProfileID)
+		rows, err = tx.Query(query, pq.Array(s.ProfileNames))
 	}
 	if err != nil {
 		errs = append(errs, errors.New("unable to determine service address uniqueness"))
@@ -587,7 +668,7 @@ and p.id = $1
 		}
 	}
 
-	return serviceInterface, util.JoinErrs(errs)
+	return serviceInterface, util.JoinErrs(errs), nil
 }
 
 func validateV3(s *tc.ServerV30, tx *sql.Tx) (string, error) {
@@ -743,9 +824,14 @@ func Read(w http.ResponseWriter, r *http.Request) {
 	if version.Major >= 3 {
 		v3Servers := make([]tc.ServerV30, 0)
 		for _, server := range servers {
-			v3Server, err := server.ToServerV3FromV4()
+			csp, err := dbhelpers.GetCommonServerPropertiesFromV4(server, tx)
+			if err != nil {
+				api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to get common server properties from V4 server struct: %w", err))
+				return
+			}
+			v3Server, err := server.ToServerV3FromV4(csp)
 			if err != nil {
-				api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to convert servers to V3 format: %v", err))
+				api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to convert servers to V3 format: %w", err))
 				return
 			}
 			v3Servers = append(v3Servers, v3Server)
@@ -756,7 +842,12 @@ func Read(w http.ResponseWriter, r *http.Request) {
 
 	legacyServers := make([]tc.ServerNullableV2, 0, len(servers))
 	for _, server := range servers {
-		legacyServer, err := server.ToServerV2FromV4()
+		csp, err := dbhelpers.GetCommonServerPropertiesFromV4(server, tx)
+		if err != nil {
+			api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to get common server properties from V4 server struct: %w", err))
+			return
+		}
+		legacyServer, err := server.ToServerV2FromV4(csp)
 		if err != nil {
 			api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to convert servers to legacy format: %v", err))
 			return
@@ -919,8 +1010,45 @@ func getServers(h http.Header, params map[string]string, tx *sqlx.Tx, user *auth
 	servers := make(map[int]tc.ServerV40)
 	ids := []int{}
 	for rows.Next() {
-		var s tc.ServerV40
-		if err = rows.StructScan(&s); err != nil {
+		s := tc.ServerV40{}
+		err := rows.Scan(&s.Cachegroup,
+			&s.CachegroupID,
+			&s.CDNID,
+			&s.CDNName,
+			&s.DomainName,
+			&s.GUID,
+			&s.HostName,
+			&s.HTTPSPort,
+			&s.ID,
+			&s.ILOIPAddress,
+			&s.ILOIPGateway,
+			&s.ILOIPNetmask,
+			&s.ILOPassword,
+			&s.ILOUsername,
+			&s.LastUpdated,
+			&s.MgmtIPAddress,
+			&s.MgmtIPGateway,
+			&s.MgmtIPNetmask,
+			&s.OfflineReason,
+			&s.PhysLocation,
+			&s.PhysLocationID,
+			pq.Array(&s.ProfileNames),
+			&s.Rack,
+			&s.RevalPending,
+			&s.RevalUpdateTime,
+			&s.RevalApplyTime,
+			&s.Status,
+			&s.StatusID,
+			&s.TCPPort,
+			&s.Type,
+			&s.TypeID,
+			&s.UpdPending,
+			&s.ConfigUpdateTime,
+			&s.ConfigApplyTime,
+			&s.XMPPID,
+			&s.XMPPPasswd,
+			&s.StatusLastUpdated)
+		if err != nil {
 			return nil, serverCount, nil, errors.New("getting servers: " + err.Error()), http.StatusInternalServerError, nil
 		}
 		if user.PrivLevel < auth.PrivLevelOperations {
@@ -1105,7 +1233,43 @@ func getMidServers(edgeIDs []int, servers map[int]tc.ServerV40, dsID int, cdnID
 	ids := []int{}
 	for rows.Next() {
 		var s tc.ServerV40
-		if err := rows.StructScan(&s); err != nil {
+		if err := rows.Scan(&s.Cachegroup,
+			&s.CachegroupID,
+			&s.CDNID,
+			&s.CDNName,
+			&s.DomainName,
+			&s.GUID,
+			&s.HostName,
+			&s.HTTPSPort,
+			&s.ID,
+			&s.ILOIPAddress,
+			&s.ILOIPGateway,
+			&s.ILOIPNetmask,
+			&s.ILOPassword,
+			&s.ILOUsername,
+			&s.LastUpdated,
+			&s.MgmtIPAddress,
+			&s.MgmtIPGateway,
+			&s.MgmtIPNetmask,
+			&s.OfflineReason,
+			&s.PhysLocation,
+			&s.PhysLocationID,
+			pq.Array(&s.ProfileNames),
+			&s.Rack,
+			&s.RevalPending,
+			&s.RevalUpdateTime,
+			&s.RevalApplyTime,
+			&s.Status,
+			&s.StatusID,
+			&s.TCPPort,
+			&s.Type,
+			&s.TypeID,
+			&s.UpdPending,
+			&s.ConfigUpdateTime,
+			&s.ConfigApplyTime,
+			&s.XMPPID,
+			&s.XMPPPasswd,
+			&s.StatusLastUpdated); err != nil {
 			log.Errorf("could not scan mid servers: %s\n", err)
 			return nil, nil, err, http.StatusInternalServerError
 		}
@@ -1126,7 +1290,7 @@ func getMidServers(edgeIDs []int, servers map[int]tc.ServerV40, dsID int, cdnID
 	return ids, nil, nil, http.StatusOK
 }
 
-func checkTypeChangeSafety(server tc.CommonServerProperties, tx *sqlx.Tx) (error, error, int) {
+func checkTypeChangeSafety(server tc.ServerV40, tx *sqlx.Tx) (error, error, int) {
 	// see if cdn or type changed
 	var cdnID int
 	var typeID int
@@ -1317,9 +1481,18 @@ func Update(w http.ResponseWriter, r *http.Request) {
 			server.StatusLastUpdated = original.StatusLastUpdated
 			statusLastUpdatedTime = *original.StatusLastUpdated
 		}
-		_, err := validateV4(&server, tx)
-		if err != nil {
-			api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+		_, userErr, sysErr := validateV4(&server, tx)
+		if userErr != nil || sysErr != nil {
+			errCode := http.StatusBadRequest
+			if sysErr != nil {
+				errCode = http.StatusInternalServerError
+			}
+			api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+			return
+		}
+		if err := dbhelpers.UpdateServerProfilesForV4(*server.ID, server.ProfileNames, tx); err != nil {
+			userErr, sysErr, errCode := api.ParseDBError(err)
+			api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
 			return
 		}
 	} else if inf.Version.Major >= 3 {
@@ -1340,7 +1513,15 @@ func Update(w http.ResponseWriter, r *http.Request) {
 			api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
 			return
 		}
-		server, err = serverV3.UpgradeToV40()
+		profileName, err := dbhelpers.UpdateServerProfileTableForV2V3(serverV3.ID, serverV3.Profile, (original.ProfileNames)[0], tx)
+		if err != nil {
+			api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to update server_profile: %w", err))
+			return
+		}
+		if len(profileName) > 1 {
+			profileName = []string{profileName[0]}
+		}
+		server, err = serverV3.UpgradeToV40(profileName)
 		if err != nil {
 			sysErr = fmt.Errorf("error upgrading valid V3 server to V4 structure: %v", err)
 			api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, sysErr)
@@ -1357,8 +1538,15 @@ func Update(w http.ResponseWriter, r *http.Request) {
 			api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
 			return
 		}
-
-		server, err = legacyServer.UpgradeToV40()
+		profileName, err := dbhelpers.UpdateServerProfileTableForV2V3(legacyServer.ID, legacyServer.Profile, (original.ProfileNames)[0], tx)
+		if err != nil {
+			api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to update server_profile: %w", err))
+			return
+		}
+		if len(profileName) > 1 {
+			profileName = []string{profileName[0]}
+		}
+		server, err = legacyServer.UpgradeToV40(profileName)
 		if err != nil {
 			sysErr = fmt.Errorf("error upgrading valid V2 server to V3 structure: %v", err)
 			api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, sysErr)
@@ -1418,7 +1606,7 @@ func Update(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	if userErr, sysErr, errCode = checkTypeChangeSafety(server.CommonServerProperties, inf.Tx); userErr != nil || sysErr != nil {
+	if userErr, sysErr, errCode = checkTypeChangeSafety(server, inf.Tx); userErr != nil || sysErr != nil {
 		api.HandleErr(w, r, tx, errCode, userErr, sysErr)
 		return
 	}
@@ -1467,7 +1655,44 @@ func Update(w http.ResponseWriter, r *http.Request) {
 	where := `WHERE s.id = $1`
 	selquery := selectQuery + where
 	var srvr tc.ServerV40
-	if err := inf.Tx.QueryRowx(selquery, serverID).StructScan(&srvr); err != nil {
+	err = inf.Tx.QueryRow(selquery, serverID).Scan(&srvr.Cachegroup,
+		&srvr.CachegroupID,
+		&srvr.CDNID,
+		&srvr.CDNName,
+		&srvr.DomainName,
+		&srvr.GUID,
+		&srvr.HostName,
+		&srvr.HTTPSPort,
+		&srvr.ID,
+		&srvr.ILOIPAddress,
+		&srvr.ILOIPGateway,
+		&srvr.ILOIPNetmask,
+		&srvr.ILOPassword,
+		&srvr.ILOUsername,
+		&srvr.LastUpdated,
+		&srvr.MgmtIPAddress,
+		&srvr.MgmtIPGateway,
+		&srvr.MgmtIPNetmask,
+		&srvr.OfflineReason,
+		&srvr.PhysLocation,
+		&srvr.PhysLocationID,
+		pq.Array(&srvr.ProfileNames),
+		&srvr.Rack,
+		&srvr.RevalPending,
+		&srvr.RevalUpdateTime,
+		&srvr.RevalApplyTime,
+		&srvr.Status,
+		&srvr.StatusID,
+		&srvr.TCPPort,
+		&srvr.Type,
+		&srvr.TypeID,
+		&srvr.UpdPending,
+		&srvr.ConfigUpdateTime,
+		&srvr.ConfigApplyTime,
+		&srvr.XMPPID,
+		&srvr.XMPPPasswd,
+		&srvr.StatusLastUpdated)
+	if err != nil {
 		api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
 		return
 	}
@@ -1490,7 +1715,16 @@ func Update(w http.ResponseWriter, r *http.Request) {
 		}
 		api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server updated", srvr)
 	} else {
-		v2Server, err := srvr.ToServerV2FromV4()
+		csp, err := dbhelpers.GetCommonServerPropertiesFromV4(server, tx)
+		if err != nil {
+			api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to get common server properties from V4 server struct: %w", err))
+			return
+		}
+		if err != nil {
+			api.HandleErr(w, r, tx, http.StatusBadRequest, nil, fmt.Errorf("failed to update server_profile: %w", err))
+			return
+		}
+		v2Server, err := srvr.ToServerV2FromV4(csp)
 		if err != nil {
 			sysErr = fmt.Errorf("converting valid v3 server to a v2 structure: %v", err)
 			api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, sysErr)
@@ -1519,7 +1753,35 @@ func updateServer(tx *sqlx.Tx, server tc.ServerV40) (int64, int, error, error) {
 	var serverId int64
 	rowsAffected := 0
 	for rows.Next() {
-		if err := rows.Scan(&serverId); err != nil {
+		if err := rows.Scan(&server.Cachegroup,
+			&server.CachegroupID,
+			&server.CDNID,
+			&server.CDNName,
+			&server.DomainName,
+			&server.GUID,
+			&server.HostName,
+			&server.HTTPSPort,
+			&serverId,
+			&server.ILOIPAddress,
+			&server.ILOIPGateway,
+			&server.ILOIPNetmask,
+			&server.ILOPassword,
+			&server.ILOUsername,
+			&server.LastUpdated,
+			&server.MgmtIPAddress,
+			&server.MgmtIPGateway,
+			&server.MgmtIPNetmask,
+			&server.OfflineReason,
+			&server.PhysLocation,
+			&server.PhysLocationID,
+			pq.Array(&server.ProfileNames),
+			&server.Rack,
+			&server.Status,
+			&server.StatusID,
+			&server.TCPPort,
+			&server.Type,
+			&server.TypeID,
+			&server.StatusLastUpdated); err != nil {
 			return 0, http.StatusNotFound, nil, fmt.Errorf("scanning lastUpdated from server insert: %v", err)
 		}
 		rowsAffected++
@@ -1535,6 +1797,26 @@ func updateServer(tx *sqlx.Tx, server tc.ServerV40) (int64, int, error, error) {
 	return serverId, http.StatusOK, nil, nil
 }
 
+func insertServerProfile(id int, pName []string, tx *sql.Tx) (error, error, int) {
+	priority := make([]int, 0, len(pName))
+	for i, _ := range pName {
+		priority = append(priority, i)
+	}
+	insertQuery := `
+	INSERT INTO server_profile (
+		server, 
+		profile_name, 
+		priority
+	)SELECT $1, profile_name, priority
+	FROM UNNEST($2::text[], $3::int[]) WITH ORDINALITY AS tmp(profile_name, priority)
+	`
+
+	if _, err := tx.Exec(insertQuery, id, pq.Array(pName), pq.Array(priority)); err != nil {
+		return api.ParseDBError(err)
+	}
+	return nil, nil, http.StatusOK
+}
+
 func createV2(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 	var server tc.ServerNullableV2
 
@@ -1577,6 +1859,7 @@ func createV2(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
+	origProfiles := []string{*server.Profile}
 	serverID, err := createServerV2(inf.Tx, server)
 	if err != nil {
 		userErr, sysErr, errCode := api.ParseDBError(err)
@@ -1595,20 +1878,68 @@ func createV2(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	userErr, sysErr, statusCode := insertServerProfile(int(serverID), origProfiles, inf.Tx.Tx)
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+		return
+	}
+
 	where := `WHERE s.id = $1`
 	selquery := selectQuery + where
 	var s4 tc.ServerV40
-	if err := inf.Tx.QueryRowx(selquery, serverID).StructScan(&s4); err != nil {
+	err = inf.Tx.QueryRow(selquery, serverID).Scan(&s4.Cachegroup,
+		&s4.CachegroupID,
+		&s4.CDNID,
+		&s4.CDNName,
+		&s4.DomainName,
+		&s4.GUID,
+		&s4.HostName,
+		&s4.HTTPSPort,
+		&s4.ID,
+		&s4.ILOIPAddress,
+		&s4.ILOIPGateway,
+		&s4.ILOIPNetmask,
+		&s4.ILOPassword,
+		&s4.ILOUsername,
+		&s4.LastUpdated,
+		&s4.MgmtIPAddress,
+		&s4.MgmtIPGateway,
+		&s4.MgmtIPNetmask,
+		&s4.OfflineReason,
+		&s4.PhysLocation,
+		&s4.PhysLocationID,
+		pq.Array(&s4.ProfileNames),
+		&s4.Rack,
+		&s4.RevalPending,
+		&s4.RevalUpdateTime,
+		&s4.RevalApplyTime,
+		&s4.Status,
+		&s4.StatusID,
+		&s4.TCPPort,
+		&s4.Type,
+		&s4.TypeID,
+		&s4.UpdPending,
+		&s4.ConfigUpdateTime,
+		&s4.ConfigApplyTime,
+		&s4.XMPPID,
+		&s4.XMPPPasswd,
+		&s4.StatusLastUpdated)
+	if err != nil {
 		api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
 		return
 	}
 	s4.Interfaces = ifaces
 
-	srvr, err := s4.ToServerV2FromV4()
+	csp, err := dbhelpers.GetCommonServerPropertiesFromV4(s4, inf.Tx.Tx)
+	if err != nil {
+		api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
+	}
+	srvr, err := s4.ToServerV2FromV4(csp)
 	if err != nil {
 		api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
 		return
 	}
+
 	alerts := tc.CreateAlerts(tc.SuccessLevel, "server was created.")
 	api.WriteAlertsObj(w, r, http.StatusOK, alerts, srvr)
 
@@ -1647,6 +1978,7 @@ func createV3(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 
 	currentTime := time.Now()
 	server.StatusLastUpdated = &currentTime
+	origProfiles := []string{*server.Profile}
 
 	if server.CDNName != nil {
 		userErr, sysErr, statusCode := dbhelpers.CheckIfCurrentUserCanModifyCDN(inf.Tx.Tx, *server.CDNName, inf.User.UserName)
@@ -1680,17 +2012,64 @@ func createV3(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	userErr, sysErr, statusCode := insertServerProfile(int(serverID), origProfiles, inf.Tx.Tx)
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+		return
+	}
+
 	where := `WHERE s.id = $1`
 	selquery := selectQuery + where
 	var s4 tc.ServerV40
-	if err := inf.Tx.QueryRowx(selquery, serverID).StructScan(&s4); err != nil {
+	err = inf.Tx.QueryRow(selquery, serverID).Scan(&s4.Cachegroup,
+		&s4.CachegroupID,
+		&s4.CDNID,
+		&s4.CDNName,
+		&s4.DomainName,
+		&s4.GUID,
+		&s4.HostName,
+		&s4.HTTPSPort,
+		&s4.ID,
+		&s4.ILOIPAddress,
+		&s4.ILOIPGateway,
+		&s4.ILOIPNetmask,
+		&s4.ILOPassword,
+		&s4.ILOUsername,
+		&s4.LastUpdated,
+		&s4.MgmtIPAddress,
+		&s4.MgmtIPGateway,
+		&s4.MgmtIPNetmask,
+		&s4.OfflineReason,
+		&s4.PhysLocation,
+		&s4.PhysLocationID,
+		pq.Array(&s4.ProfileNames),
+		&s4.Rack,
+		&s4.RevalPending,
+		&s4.RevalUpdateTime,
+		&s4.RevalApplyTime,
+		&s4.Status,
+		&s4.StatusID,
+		&s4.TCPPort,
+		&s4.Type,
+		&s4.TypeID,
+		&s4.UpdPending,
+		&s4.ConfigUpdateTime,
+		&s4.ConfigApplyTime,
+		&s4.XMPPID,
+		&s4.XMPPPasswd,
+		&s4.StatusLastUpdated)
+	if err != nil {
 		api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
 		return
 	}
 
 	s4.Interfaces = interfaces
 
-	srvr, err := s4.ToServerV3FromV4()
+	csp, err := dbhelpers.GetCommonServerPropertiesFromV4(s4, inf.Tx.Tx)
+	if err != nil {
+		api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
+	}
+	srvr, err := s4.ToServerV3FromV4(csp)
 	if err != nil {
 		api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
 		return
@@ -1726,9 +2105,13 @@ func createV4(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 
 	server.XMPPID = newUUID()
 
-	_, err := validateV4(&server, inf.Tx.Tx)
-	if err != nil {
-		api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil)
+	_, userErr, sysErr := validateV4(&server, inf.Tx.Tx)
+	if userErr != nil || sysErr != nil {
+		errCode := http.StatusBadRequest
+		if sysErr != nil {
+			errCode = http.StatusInternalServerError
+		}
+		api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
 		return
 	}
 
@@ -1749,6 +2132,7 @@ func createV4(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
+	origProfiles := server.ProfileNames
 	serverID, err := createServerV4(inf.Tx, server)
 	if err != nil {
 		userErr, sysErr, errCode := api.ParseDBError(err)
@@ -1762,10 +2146,53 @@ func createV4(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	userErr, sysErr, statusCode := insertServerProfile(int(serverID), origProfiles, inf.Tx.Tx)
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
+		return
+	}
+
 	where := `WHERE s.id = $1`
 	selquery := selectQuery + where
 	var srvr tc.ServerV40
-	if err := inf.Tx.QueryRowx(selquery, serverID).StructScan(&srvr); err != nil {
+	err = inf.Tx.QueryRow(selquery, serverID).Scan(&srvr.Cachegroup,
+		&srvr.CachegroupID,
+		&srvr.CDNID,
+		&srvr.CDNName,
+		&srvr.DomainName,
+		&srvr.GUID,
+		&srvr.HostName,
+		&srvr.HTTPSPort,
+		&srvr.ID,
+		&srvr.ILOIPAddress,
+		&srvr.ILOIPGateway,
+		&srvr.ILOIPNetmask,
+		&srvr.ILOPassword,
+		&srvr.ILOUsername,
+		&srvr.LastUpdated,
+		&srvr.MgmtIPAddress,
+		&srvr.MgmtIPGateway,
+		&srvr.MgmtIPNetmask,
+		&srvr.OfflineReason,
+		&srvr.PhysLocation,
+		&srvr.PhysLocationID,
+		pq.Array(&srvr.ProfileNames),
+		&srvr.Rack,
+		&srvr.RevalPending,
+		&srvr.RevalUpdateTime,
+		&srvr.RevalApplyTime,
+		&srvr.Status,
+		&srvr.StatusID,
+		&srvr.TCPPort,
+		&srvr.Type,
+		&srvr.TypeID,
+		&srvr.UpdPending,
+		&srvr.ConfigUpdateTime,
+		&srvr.ConfigApplyTime,
+		&srvr.XMPPID,
+		&srvr.XMPPPasswd,
+		&srvr.StatusLastUpdated)
+	if err != nil {
 		api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
 		return
 	}
@@ -1781,7 +2208,18 @@ func createV4(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 }
 
 func createServerV4(tx *sqlx.Tx, server tc.ServerV40) (int64, error) {
-	rows, err := tx.NamedQuery(insertQueryV4, server)
+	//rows, err := tx.NamedQuery(insertQueryV4, server)
+	var profileID int
+	err := tx.QueryRow("SELECT id FROM profile p WHERE name=$1", (server.ProfileNames)[0]).Scan(&profileID)
+	if err != nil {
+		return 0, fmt.Errorf("unable to get profileID for a profile name: %w", err)
+	}
+
+	rows, err := tx.Query(insertQueryV4, server.CachegroupID, server.CDNID, server.DomainName,
+		server.HostName, server.HTTPSPort, server.ILOIPAddress, server.ILOIPNetmask, server.ILOIPGateway,
+		server.ILOUsername, server.ILOPassword, server.MgmtIPAddress, server.MgmtIPNetmask, server.MgmtIPGateway,
+		server.OfflineReason, server.PhysLocationID, profileID, server.Rack, server.StatusID,
+		server.TCPPort, server.TypeID, server.XMPPID, server.XMPPPasswd)
 	if err != nil {
 		return 0, err
 	}
@@ -1791,7 +2229,35 @@ func createServerV4(tx *sqlx.Tx, server tc.ServerV40) (int64, error) {
 	var serverID int64
 	for rows.Next() {
 		rowsAffected++
-		if err := rows.Scan(&serverID); err != nil {
+		err := rows.Scan(&server.Cachegroup,
+			&server.CachegroupID,
+			&server.CDNID,
+			&server.CDNName,
+			&server.DomainName,
+			&server.GUID,
+			&server.HostName,
+			&server.HTTPSPort,
+			&serverID,
+			&server.ILOIPAddress,
+			&server.ILOIPGateway,
+			&server.ILOIPNetmask,
+			&server.ILOPassword,
+			&server.ILOUsername,
+			&server.LastUpdated,
+			&server.MgmtIPAddress,
+			&server.MgmtIPGateway,
+			&server.MgmtIPNetmask,
+			&server.OfflineReason,
+			&server.PhysLocation,
+			&server.PhysLocationID,
+			pq.Array(&server.ProfileNames),
+			&server.Rack,
+			&server.Status,
+			&server.StatusID,
+			&server.TCPPort,
+			&server.Type,
+			&server.TypeID)
+		if err != nil {
 			return 0, err
 		}
 	}
@@ -2027,8 +2493,13 @@ func Delete(w http.ResponseWriter, r *http.Request) {
 	if inf.Version.Major >= 3 {
 		api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Server deleted", server)
 	} else {
-
-		serverV2, err := server.ToServerV2FromV4()
+		csp, err := dbhelpers.GetCommonServerPropertiesFromV4(server, tx)
+		if err != nil {
+			userErr, sysErr, errCode := api.ParseDBError(err)
+			api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+			return
+		}
+		serverV2, err := server.ToServerV2FromV4(csp)
 		if err != nil {
 			api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
 			return
diff --git a/traffic_ops/v4-client/server.go b/traffic_ops/v4-client/server.go
index 272f15c31..59d300948 100644
--- a/traffic_ops/v4-client/server.go
+++ b/traffic_ops/v4-client/server.go
@@ -83,18 +83,6 @@ func (to *Session) CreateServer(server tc.ServerV4, opts RequestOptions) (tc.Ale
 		}
 		server.PhysLocationID = &ph.Response[0].ID
 	}
-	if needAndCanFetch(server.ProfileID, server.Profile) {
-		innerOpts := NewRequestOptions()
-		innerOpts.QueryParameters.Set("name", *server.Profile)
-		pr, reqInf, err := to.GetProfiles(innerOpts)
-		if err != nil {
-			return pr.Alerts, reqInf, fmt.Errorf("no Profile named %s: %w", *server.Profile, err)
-		}
-		if len(pr.Response) == 0 {
-			return pr.Alerts, reqInf, fmt.Errorf("no Profile named %s", *server.Profile)
-		}
-		server.ProfileID = &pr.Response[0].ID
-	}
 	if needAndCanFetch(server.StatusID, server.Status) {
 		innerOpts := NewRequestOptions()
 		innerOpts.QueryParameters.Set("name", *server.Status)
diff --git a/traffic_portal/app/src/common/modules/form/server/FormServerController.js b/traffic_portal/app/src/common/modules/form/server/FormServerController.js
index 0e4e16aed..138956503 100644
--- a/traffic_portal/app/src/common/modules/form/server/FormServerController.js
+++ b/traffic_portal/app/src/common/modules/form/server/FormServerController.js
@@ -22,6 +22,7 @@ var FormServerController = function(server, $scope, $location, $state, $uibModal
     $scope.IPPattern = serverUtils.IPPattern;
     $scope.IPWithCIDRPattern = serverUtils.IPWithCIDRPattern;
     $scope.IPv4Pattern = serverUtils.IPv4Pattern;
+    $scope.profiles = [];
 
     var getPhysLocations = function() {
         physLocationService.getPhysLocations()
@@ -60,6 +61,14 @@ var FormServerController = function(server, $scope, $location, $state, $uibModal
             });
     };
 
+    $scope.getProfileID = function(profileName) {
+        for (const profile of $scope.profiles) {
+            if (profile.name === profileName) {
+                return "/#!/profiles/"+profile.id
+            }
+        }
+    };
+
     var updateStatus = function(status) {
         serverService.updateStatus(server.id, { status: status.id, offlineReason: status.offlineReason })
             .then(
diff --git a/traffic_portal/app/src/common/modules/form/server/form.server.tpl.html b/traffic_portal/app/src/common/modules/form/server/form.server.tpl.html
index 2fc5182e1..5b29c7ce0 100644
--- a/traffic_portal/app/src/common/modules/form/server/form.server.tpl.html
+++ b/traffic_portal/app/src/common/modules/form/server/form.server.tpl.html
@@ -112,11 +112,11 @@ under the License.
 
             <label for="profile" ng-class="{'has-error': hasError(serverForm.profile)}">Profile *</label>
             <div ng-class="{'has-error': hasError(serverForm.profile), 'has-feedback': hasError(serverForm.profile)}">
-                <select id="profile" name="profile" class="form-control" ng-model="server.profileId" ng-options="profile.id as profile.name for profile in profiles" required>
+                <select id="profile" name="profile" class="form-control" ng-model="server.profileNames[0]" ng-options="profile.name as profile.name for profile in profiles" required>
                     <option hidden selected disabled value="">Select...</option>
                 </select>
                 <small class="input-error" ng-show="hasPropertyError(serverForm.profile, 'required')">Required</small>
-                <small ng-show="server.profileId"><a ng-href="/#!/profiles/{{server.profileId}}" target="_blank">View Details&nbsp;&nbsp;<i class="fa fs-xs fa-external-link"></i></a></small>
+                <small ng-show="server.profileNames.length>0"><a ng-href="{{getProfileID(server.profileNames[0])}}" target="_blank">View Details&nbsp;&nbsp;<i class="fa fs-xs fa-external-link"></i></a></small>
             </div>
 
             <label for="physLocation" ng-class="{'has-error': hasError(serverForm.physLocation)}">Physical Location *</label>
diff --git a/traffic_portal/app/src/common/modules/table/servers/TableServersController.js b/traffic_portal/app/src/common/modules/table/servers/TableServersController.js
index 15b6a265f..9d3fa86b1 100644
--- a/traffic_portal/app/src/common/modules/table/servers/TableServersController.js
+++ b/traffic_portal/app/src/common/modules/table/servers/TableServersController.js
@@ -162,7 +162,7 @@ var TableServersController = function(tableName, servers, filter, $scope, $state
 		},
 		{
 			headerName: "Profile",
-			field: "profile",
+			field: "profileName",
 			hide: false
 		},
 		{
@@ -365,6 +365,9 @@ var TableServersController = function(tableName, servers, filter, $scope, $state
 			x.lastUpdated = x.lastUpdated ? new Date(x.lastUpdated.replace("+00", "Z")) : x.lastUpdated;
 			x.statusLastUpdated = x.statusLastUpdated ? new Date(x.statusLastUpdated): x.statusLastUpdated;
 			Object.assign(x, serverUtils.toLegacyIPInfo(x.interfaces));
+			if (x.profileNames !== undefined) {
+				x.profileName = x.profileNames[0]
+			}
 			return x;
 	});
 
diff --git a/traffic_portal/app/src/modules/private/servers/new/index.js b/traffic_portal/app/src/modules/private/servers/new/index.js
index cb725acdd..6e7044925 100644
--- a/traffic_portal/app/src/modules/private/servers/new/index.js
+++ b/traffic_portal/app/src/modules/private/servers/new/index.js
@@ -43,7 +43,8 @@ module.exports = angular.module('trafficPortal.private.servers.new', [])
                                             ],
                                             monitor: true
                                         }
-                                    ]
+                                    ],
+                                    profileNames: []
                                 };
                             }
                         }


Test patch:
diff --git a/.github/actions/go-test/entrypoint.sh b/.github/actions/go-test/entrypoint.sh
index 3594a6846..0da9a0a56 100755
--- a/.github/actions/go-test/entrypoint.sh
+++ b/.github/actions/go-test/entrypoint.sh
@@ -37,5 +37,5 @@ fi
 
 # Need to fetch golang.org/x/* dependencies
 go mod vendor -v
-go test $INPUT_DIR
+go test --buildvcs=false $INPUT_DIR
 exit $?
diff --git a/.github/actions/to-integration-tests/action.yml b/.github/actions/to-integration-tests/action.yml
index f161d961c..ce47e07f5 100644
--- a/.github/actions/to-integration-tests/action.yml
+++ b/.github/actions/to-integration-tests/action.yml
@@ -45,3 +45,4 @@ runs:
         INPUT_SMTP_PORT: ${{ inputs.smtp_port }}
         INPUT_SMTP_ADDRESS: ${{ inputs.smtp_address }}
         INPUT_SMTP_PASSWORD: ${{ inputs.smtp_password }}
+        INPUT_VERSION: ${{ inputs.version }}
diff --git a/.github/actions/to-integration-tests/entrypoint.sh b/.github/actions/to-integration-tests/entrypoint.sh
index 717187347..68a1a007b 100755
--- a/.github/actions/to-integration-tests/entrypoint.sh
+++ b/.github/actions/to-integration-tests/entrypoint.sh
@@ -16,6 +16,8 @@
 # specific language governing permissions and limitations
 # under the License.
 
+set -o errexit
+
 gray_bg="$(printf '%s%s' $'\x1B' '[100m')";
 red_bg="$(printf '%s%s' $'\x1B' '[41m')";
 yellow_bg="$(printf '%s%s' $'\x1B' '[43m')";
diff --git a/cache-config/t3c-generate/cfgfile/cfgfile_test.go b/cache-config/t3c-generate/cfgfile/cfgfile_test.go
index 363d0a4af..6e633a135 100644
--- a/cache-config/t3c-generate/cfgfile/cfgfile_test.go
+++ b/cache-config/t3c-generate/cfgfile/cfgfile_test.go
@@ -311,9 +311,7 @@ func randServer() *atscfg.Server {
 	sv.OfflineReason = randStr()
 	sv.PhysLocation = randStr()
 	sv.PhysLocationID = randInt()
-	sv.Profile = randStr()
-	sv.ProfileDesc = randStr()
-	sv.ProfileID = randInt()
+	sv.ProfileNames = []string{*randStr()}
 	sv.Rack = randStr()
 	sv.RevalPending = randBool()
 	sv.Status = randStr()
diff --git a/cache-config/testing/docker/db_init/Dockerfile b/cache-config/testing/docker/db_init/Dockerfile
deleted file mode 100644
index 2712c6c83..000000000
--- a/cache-config/testing/docker/db_init/Dockerfile
+++ /dev/null
@@ -1,47 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-############################################################
-# Dockerfile to initialized Traffic Ops Database container
-# Based on CentOS 7.2
-############################################################
-
-FROM centos/systemd
-
-RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
-
-RUN yum -y install \
-  postgresql13 \
-  nmap-ncat && \
-  yum clean all
-
-ENV POSTGRES_HOME $POSTGRES_HOME
-ENV PGPASSWORD $PGPASSWORD
-ENV DB_USERNAME $DB_USERNAME
-ENV DB_NAME $DB_NAME
-ENV DB_USER_PASS $DB_USER_PASS
-ENV DB_SERVER $DB_SERVER
-ENV DB_PORT $DB_PORT
-
-# Uses the same DB server/port
-ENV TV_DB_NAME $TV_DB_NAME
-ENV TV_USER $TV_USER
-ENV TV_USER_PASS $TV_USER_PASS
-ENV TV_SERVER $TV_SERVER
-
-ADD db_init/dbInit.sh /
-CMD /dbInit.sh
diff --git a/cache-config/testing/docker/db_init/dbInit.sh b/cache-config/testing/docker/db_init/dbInit.sh
deleted file mode 100755
index 536ab7a74..000000000
--- a/cache-config/testing/docker/db_init/dbInit.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env bash
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-############################################################
-# Script for creating the database user account for traffic
-# ops. 
-# Used while the Docker Image is initializing itself
-############################################################
-
-while ! nc $DB_SERVER $DB_PORT </dev/null; do # &>/dev/null; do
-        echo "waiting for $DB_SERVER:$DB_PORT"
-        sleep 3
-done
-psql -h $DB_SERVER -U postgres -c "CREATE USER $DB_USER WITH ENCRYPTED PASSWORD '$DB_USER_PASS'"
-psql -h $DB_SERVER -U postgres -c "CREATE USER $TV_USER WITH ENCRYPTED PASSWORD '$TV_USER_PASS'"
-createdb $DB_NAME -h $DB_SERVER -U postgres --owner $DB_USER
-createdb $TV_DB_NAME -h $DB_SERVER -U postgres --owner $TV_USER
diff --git a/cache-config/testing/docker/docker-compose.yml b/cache-config/testing/docker/docker-compose.yml
index b7580c682..b072c72ae 100644
--- a/cache-config/testing/docker/docker-compose.yml
+++ b/cache-config/testing/docker/docker-compose.yml
@@ -42,15 +42,6 @@ services:
     ports: 
       - "5432:5432"
 
-  db_init:
-    env_file:
-      - variables.env
-    build:
-      context: .
-      dockerfile: db_init/Dockerfile
-    depends_on: 
-      - db
-
   to_server:
     env_file:
       - variables.env
@@ -65,7 +56,7 @@ services:
     volumes:
       - ../../../GO_VERSION:/GO_VERSION
     depends_on:
-      - db_init
+      - db
 
   ort_test:
     env_file:
@@ -89,6 +80,6 @@ services:
       context: .
       dockerfile: yumserver/Dockerfile
     depends_on:
-      - db_init
+      - db
     volumes:
       - ./yumserver/test-rpms:/var/www/html/traffic-control/7/x86_64/Packages
diff --git a/cache-config/testing/docker/ort_test/Dockerfile b/cache-config/testing/docker/ort_test/Dockerfile
index d86942919..40b52fdfd 100644
--- a/cache-config/testing/docker/ort_test/Dockerfile
+++ b/cache-config/testing/docker/ort_test/Dockerfile
@@ -31,7 +31,7 @@ MAINTAINER dev@trafficcontrol.apache.org
 EXPOSE 80 443
 
 RUN yum install -y epel-release && \
-	yum install -y initscripts git jq gcc lua && \
+	yum install -y initscripts git jq gcc lua nmap-ncat && \
 	yum clean all
 
 # Note that if more than one t3c RPM matches this wildcard, this Dockerfile will
diff --git a/cache-config/testing/docker/ort_test/run.sh b/cache-config/testing/docker/ort_test/run.sh
index b728594a7..ebb3d6981 100755
--- a/cache-config/testing/docker/ort_test/run.sh
+++ b/cache-config/testing/docker/ort_test/run.sh
@@ -18,9 +18,6 @@
 # under the License.
 #
 
-# wait period to insure all containers are up and running.
-WAIT="60"
-
 #
 # this seems to wake up the to container.
 #
@@ -95,8 +92,19 @@ fi
 # wake up the to_server
 ping_to
 
-echo "waiting $WAIT seconds for all containers to initialize."
-sleep $WAIT
+echo "waiting for all the to_server container to initialize."
+i=0
+sleep_time=3
+while ! nc $TO_HOSTNAME $TO_PORT </dev/null; do
+  echo "$waiting for $TO_HOSTNAME:$TO_PORT"
+  sleep $sleep_time
+  let i=i+1
+  if [ $i -gt 10 ]; then
+    let d=i*sleep_time
+    echo "$TO_HOSTNAME:$TO_PORT is unavailable after $d seconds, giving up"
+    exit 1
+  fi
+done
 
 cp /ort-tests/tc-fixtures.json /tc-fixtures.json
 (touch test.log && chmod a+rw test.log && tail -f test.log)&
diff --git a/cache-config/testing/docker/traffic_ops/Dockerfile b/cache-config/testing/docker/traffic_ops/Dockerfile
index 42de4364f..c5f9d8c89 100644
--- a/cache-config/testing/docker/traffic_ops/Dockerfile
+++ b/cache-config/testing/docker/traffic_ops/Dockerfile
@@ -66,6 +66,7 @@ RUN set -o nounset -o errexit && \
 		perl-Crypt-ScryptKDF \
 		perl-Digest-SHA1     \
 		perl-JSON-PP         \
+		procps		     \
 		python3              \
 		# rsync is used to copy certs in "Shared SSL certificate generation" step
 		rsync;               \
@@ -81,6 +82,8 @@ RUN yum -y install /traffic_ops.rpm && \
 
 ADD cache-config/testing/docker/traffic_ops/run.sh /
 ADD cache-config/testing/ort-tests /ort-tests
+RUN chmod +x /run.sh
 
 EXPOSE 443
-CMD /run.sh
+ENTRYPOINT /run.sh
+
diff --git a/cache-config/testing/docker/traffic_ops/run.sh b/cache-config/testing/docker/traffic_ops/run.sh
index 649d0efae..44fffcfdf 100755
--- a/cache-config/testing/docker/traffic_ops/run.sh
+++ b/cache-config/testing/docker/traffic_ops/run.sh
@@ -44,7 +44,7 @@ do
 done
 
 start() {
-  traffic_ops_golang_command=(./bin/traffic_ops_golang -cfg "$CDNCONF" -dbcfg "$DATABASECONF" );
+  traffic_ops_golang_command=(/opt/traffic_ops/app/bin/traffic_ops_golang -cfg "$CDNCONF" -dbcfg "$DATABASECONF" );
   "${traffic_ops_golang_command[@]}" &
 	exec tail -f $TO_LOG
 }
@@ -126,10 +126,23 @@ EOM
 source /etc/environment
 if [ -z "$INITIALIZED" ]; then init; fi
 
+i=0
+sleep_time=3
+while ! nc $DB_SERVER $DB_PORT </dev/null; do 
+  echo "waiting for $DB_SERVER:$DB_PORT" >> /var/log/traffic_ops/to_admin.log
+  sleep $sleep_time
+  let i=i+1  
+  if [ $i -gt 10 ]; then
+    let d=i*sleep_time
+    echo "$DB_SERVER:$DB_PORT is unavailable after $d seconds, giving up" >> /var/log/traffic_ops/to_admin.log
+    exit 1
+  fi
+done
+
 # create the 'traffic_ops' database, tables and runs migrations
-(mkdir -p /var/log/traffic_ops/)
-(cd /opt/traffic_ops/app && db/admin --env=production reset > /var/log/traffic_ops/to_admin.log 2>&1)
-(cd /opt/traffic_ops/app && db/admin --trafficvault --env=production reset > /var/log/traffic_ops/tv_admin.log 2>&1)
+mkdir -p /var/log/traffic_ops/
+cd /opt/traffic_ops/app && db/admin --env=production reset >> /var/log/traffic_ops/to_admin.log 2>&1
+cd /opt/traffic_ops/app && db/admin --trafficvault --env=production reset >> /var/log/traffic_ops/tv_admin.log 2>&1
 
 # start traffic_ops
 start
diff --git a/cache-config/testing/docker/variables.env b/cache-config/testing/docker/variables.env
index dba5e9701..1708a5028 100644
--- a/cache-config/testing/docker/variables.env
+++ b/cache-config/testing/docker/variables.env
@@ -43,6 +43,7 @@ LOG_WARN=/var/log/traffic_ops/warn
 TO_ADMIN_USER=admin
 TO_ADMIN_PASS=twelve
 TO_HOSTNAME=to_server
+TO_PORT=443
 TO_LOG=/var/log/traffic_ops/traffic_ops.log
 TO_DOMAIN=trafficops_default
 TO_URI=https://to_server:443
diff --git a/lib/go-atscfg/atscfg_test.go b/lib/go-atscfg/atscfg_test.go
index 03050ec3e..c582ebf70 100644
--- a/lib/go-atscfg/atscfg_test.go
+++ b/lib/go-atscfg/atscfg_test.go
@@ -145,7 +145,6 @@ func setIPInfo(sv *Server, interfaceName string, ipAddress string, ip6Address st
 
 func makeGenericServer() *Server {
 	server := &Server{}
-	server.ProfileID = util.IntPtr(42)
 	server.CDNName = util.StrPtr("myCDN")
 	server.Cachegroup = util.StrPtr("cg0")
 	server.CachegroupID = util.IntPtr(422)
@@ -155,8 +154,7 @@ func makeGenericServer() *Server {
 	server.HTTPSPort = util.IntPtr(12443)
 	server.ID = util.IntPtr(44)
 	setIP(server, "192.168.2.1")
-	server.ProfileID = util.IntPtr(46)
-	server.Profile = util.StrPtr("serverprofile")
+	server.ProfileNames = []string{"serverprofile"}
 	server.TCPPort = util.IntPtr(80)
 	server.Type = "EDGE"
 	server.TypeID = util.IntPtr(91)
diff --git a/lib/go-atscfg/atsdotrules_test.go b/lib/go-atscfg/atsdotrules_test.go
index 7bf7c1dde..a64bd1c66 100644
--- a/lib/go-atscfg/atsdotrules_test.go
+++ b/lib/go-atscfg/atsdotrules_test.go
@@ -29,7 +29,7 @@ import (
 func TestMakeATSDotRules(t *testing.T) {
 	server := makeGenericServer()
 	serverProfile := "myProfile"
-	server.Profile = &serverProfile
+	server.ProfileNames = []string{serverProfile}
 
 	hdr := "myHeaderComment"
 
diff --git a/lib/go-atscfg/cachedotconfig_test.go b/lib/go-atscfg/cachedotconfig_test.go
index 5e459478e..d9e64ebb3 100644
--- a/lib/go-atscfg/cachedotconfig_test.go
+++ b/lib/go-atscfg/cachedotconfig_test.go
@@ -30,7 +30,7 @@ import (
 func TestMakeCacheDotConfig(t *testing.T) {
 	server := makeGenericServer()
 	serverProfile := "myProfile"
-	server.Profile = &serverProfile
+	server.ProfileNames = []string{serverProfile}
 	servers := []Server{*server}
 
 	ds0 := makeGenericDS()
diff --git a/lib/go-atscfg/dropqstringdotconfig_test.go b/lib/go-atscfg/dropqstringdotconfig_test.go
index 9c0f01265..9441bf5b5 100644
--- a/lib/go-atscfg/dropqstringdotconfig_test.go
+++ b/lib/go-atscfg/dropqstringdotconfig_test.go
@@ -31,7 +31,7 @@ func TestMakeDropQStringDotConfig(t *testing.T) {
 	profileName := "myProfile"
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	params := []tc.Parameter{
 		{
diff --git a/lib/go-atscfg/facts_test.go b/lib/go-atscfg/facts_test.go
index 8af1e0d8c..ab93be1db 100644
--- a/lib/go-atscfg/facts_test.go
+++ b/lib/go-atscfg/facts_test.go
@@ -27,7 +27,7 @@ import (
 func TestMake12MFacts(t *testing.T) {
 	server := makeGenericServer()
 	profileName := "myProfile"
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	hdr := "myHeaderComment"
 
diff --git a/lib/go-atscfg/hostingdotconfig_test.go b/lib/go-atscfg/hostingdotconfig_test.go
index c58b4cf05..6b3ba99d2 100644
--- a/lib/go-atscfg/hostingdotconfig_test.go
+++ b/lib/go-atscfg/hostingdotconfig_test.go
@@ -33,8 +33,7 @@ func TestMakeHostingDotConfig(t *testing.T) {
 	server := makeGenericServer()
 	server.HostName = util.StrPtr("server0")
 	server.CDNName = &cdnName
-	server.ProfileID = util.IntPtr(46)
-	server.Profile = util.StrPtr("serverprofile")
+	server.ProfileNames = []string{"serverprofile"}
 	hdr := "myHeaderComment"
 
 	serverParams := []tc.Parameter{
@@ -42,19 +41,19 @@ func TestMakeHostingDotConfig(t *testing.T) {
 			Name:       ParamRAMDrivePrefix,
 			ConfigFile: HostingConfigParamConfigFile,
 			Value:      "ParamRAMDrivePrefix-shouldnotappearinconfig",
-			Profiles:   []byte(`["` + *server.Profile + `"]`),
+			Profiles:   []byte(`["` + server.ProfileNames[0] + `"]`),
 		},
 		tc.Parameter{
 			Name:       ParamDrivePrefix,
 			ConfigFile: HostingConfigParamConfigFile,
 			Value:      "ParamDrivePrefix-shouldnotappearinconfig",
-			Profiles:   []byte(`["` + *server.Profile + `"]`),
+			Profiles:   []byte(`["` + server.ProfileNames[0] + `"]`),
 		},
 		tc.Parameter{
 			Name:       "somethingelse",
 			ConfigFile: HostingConfigParamConfigFile,
 			Value:      "somethingelse-shouldnotappearinconfig",
-			Profiles:   []byte(`["` + *server.Profile + `"]`),
+			Profiles:   []byte(`["` + server.ProfileNames[0] + `"]`),
 		},
 	}
 
@@ -126,9 +125,8 @@ func TestMakeHostingDotConfigTopologiesIgnoreDSS(t *testing.T) {
 	server.Cachegroup = util.StrPtr("edgeCG")
 	server.CDNName = &cdnName
 	server.CDNID = util.IntPtr(400)
-	server.ProfileID = util.IntPtr(46)
 	server.ID = util.IntPtr(899)
-	server.Profile = util.StrPtr("serverprofile")
+	server.ProfileNames = []string{"serverprofile"}
 	hdr := "myHeaderComment"
 
 	serverParams := []tc.Parameter{
@@ -136,19 +134,19 @@ func TestMakeHostingDotConfigTopologiesIgnoreDSS(t *testing.T) {
 			Name:       ParamRAMDrivePrefix,
 			ConfigFile: HostingConfigParamConfigFile,
 			Value:      "ParamRAMDrivePrefix-shouldnotappearinconfig",
-			Profiles:   []byte(`["` + *server.Profile + `"]`),
+			Profiles:   []byte(`["` + server.ProfileNames[0] + `"]`),
 		},
 		tc.Parameter{
 			Name:       ParamDrivePrefix,
 			ConfigFile: HostingConfigParamConfigFile,
 			Value:      "ParamDrivePrefix-shouldnotappearinconfig",
-			Profiles:   []byte(`["` + *server.Profile + `"]`),
+			Profiles:   []byte(`["` + server.ProfileNames[0] + `"]`),
 		},
 		tc.Parameter{
 			Name:       "somethingelse",
 			ConfigFile: HostingConfigParamConfigFile,
 			Value:      "somethingelse-shouldnotappearinconfig",
-			Profiles:   []byte(`["` + *server.Profile + `"]`),
+			Profiles:   []byte(`["` + server.ProfileNames[0] + `"]`),
 		},
 	}
 
diff --git a/lib/go-atscfg/loggingdotconfig_test.go b/lib/go-atscfg/loggingdotconfig_test.go
index 8c24021da..a5100d8b9 100644
--- a/lib/go-atscfg/loggingdotconfig_test.go
+++ b/lib/go-atscfg/loggingdotconfig_test.go
@@ -29,7 +29,7 @@ func TestMakeLoggingDotConfig(t *testing.T) {
 	hdrComment := "myHeaderComment"
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	params := makeParamsFromMap("serverProfile", LoggingFileName, map[string]string{
 		"LogFormat.Name":           "myFormatName",
diff --git a/lib/go-atscfg/loggingdotyaml_test.go b/lib/go-atscfg/loggingdotyaml_test.go
index 0a0a13287..3cda3ef24 100644
--- a/lib/go-atscfg/loggingdotyaml_test.go
+++ b/lib/go-atscfg/loggingdotyaml_test.go
@@ -32,7 +32,7 @@ func TestMakeLoggingDotYAML(t *testing.T) {
 	hdr := "myHeaderComment"
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	params := makeParamsFromMap("serverProfile", LoggingYAMLFileName, map[string]string{
 		"LogFormat.Name":           "myFormatName",
@@ -101,7 +101,7 @@ func TestMakeLoggingDotYAMLMultiFormat(t *testing.T) {
 	})
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	cfg, err := MakeLoggingDotYAML(server, paramData, &LoggingDotYAMLOpts{HdrComment: hdr})
 	if err != nil {
diff --git a/lib/go-atscfg/logsdotxml_test.go b/lib/go-atscfg/logsdotxml_test.go
index 8bde2e6fd..ae2a04b27 100644
--- a/lib/go-atscfg/logsdotxml_test.go
+++ b/lib/go-atscfg/logsdotxml_test.go
@@ -37,7 +37,7 @@ func TestMakeLogsXMLDotConfig(t *testing.T) {
 	})
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	cfg, err := MakeLogsXMLDotConfig(server, paramData, &LogsXMLDotConfigOpts{HdrComment: hdr})
 	if err != nil {
diff --git a/lib/go-atscfg/meta_test.go b/lib/go-atscfg/meta_test.go
index 45355bb8d..64710d7f8 100644
--- a/lib/go-atscfg/meta_test.go
+++ b/lib/go-atscfg/meta_test.go
@@ -41,8 +41,8 @@ func TestMakeMetaConfig(t *testing.T) {
 	setIP(server, ip)
 	// server.ParentCacheGroupID=            45
 	// server.ParentCacheGroupType=          "MID_LOC"
-	server.ProfileID = util.IntPtr(46)
-	server.Profile = util.StrPtr("myserverprofile")
+	//server.ProfileID = util.IntPtr(46)
+	server.ProfileNames = []string{"myserverprofile"}
 	server.TCPPort = util.IntPtr(80)
 	// server.SecondaryParentCacheGroupID=   47
 	// server.SecondaryParentCacheGroupType= "MID_LOC"
@@ -65,7 +65,7 @@ func TestMakeMetaConfig(t *testing.T) {
 			Name:       "location",
 			ConfigFile: name,
 			Value:      "/my/location/",
-			Profiles:   []byte(`["` + *server.Profile + `"]`),
+			Profiles:   []byte(`["` + server.ProfileNames[0] + `"]`),
 		}
 	}
 
diff --git a/lib/go-atscfg/parentdotconfig_test.go b/lib/go-atscfg/parentdotconfig_test.go
index 3982bcd67..628b973cb 100644
--- a/lib/go-atscfg/parentdotconfig_test.go
+++ b/lib/go-atscfg/parentdotconfig_test.go
@@ -3152,7 +3152,6 @@ func warningsContains(warnings []string, str string) bool {
 
 func makeTestParentServer() *Server {
 	server := &Server{}
-	server.ProfileID = util.IntPtr(42)
 	server.CDNName = util.StrPtr("myCDN")
 	server.Cachegroup = util.StrPtr("cg0")
 	server.CachegroupID = util.IntPtr(422)
@@ -3162,8 +3161,7 @@ func makeTestParentServer() *Server {
 	server.HTTPSPort = util.IntPtr(12443)
 	server.ID = util.IntPtr(44)
 	setIP(server, "192.168.2.1")
-	server.ProfileID = util.IntPtr(46)
-	server.Profile = util.StrPtr("serverprofile")
+	server.ProfileNames = []string{"serverprofile"}
 	server.TCPPort = util.IntPtr(80)
 	server.Type = "EDGE"
 	server.TypeID = util.IntPtr(91)
diff --git a/lib/go-atscfg/plugindotconfig_test.go b/lib/go-atscfg/plugindotconfig_test.go
index 78aa1256e..866be4142 100644
--- a/lib/go-atscfg/plugindotconfig_test.go
+++ b/lib/go-atscfg/plugindotconfig_test.go
@@ -35,7 +35,7 @@ func TestMakePluginDotConfig(t *testing.T) {
 	})
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	cfg, err := MakePluginDotConfig(server, paramData, &PluginDotConfigOpts{HdrComment: hdr})
 	if err != nil {
diff --git a/lib/go-atscfg/recordsdotconfig_test.go b/lib/go-atscfg/recordsdotconfig_test.go
index e0c8cc930..e2ab3c62b 100644
--- a/lib/go-atscfg/recordsdotconfig_test.go
+++ b/lib/go-atscfg/recordsdotconfig_test.go
@@ -22,8 +22,6 @@ package atscfg
 import (
 	"strings"
 	"testing"
-
-	"github.com/apache/trafficcontrol/lib/go-util"
 )
 
 func TestMakeRecordsDotConfig(t *testing.T) {
@@ -45,7 +43,7 @@ func TestMakeRecordsDotConfig(t *testing.T) {
 	ip6Str := "2001:db8::9"
 	ip6CIDR := ip6Str + "/48" // set the ip to a cidr, to make sure addr logic removes it
 	setIP6(server, ip6CIDR)
-	server.Profile = util.StrPtr(profileName)
+	server.ProfileNames = []string{profileName}
 	opt := &RecordsConfigOpts{}
 	opt.DNSLocalBindServiceAddr = true
 	opt.HdrComment = hdr
@@ -184,7 +182,7 @@ func TestMakeRecordsDotConfigDNSLocalBindNoOverrideV4(t *testing.T) {
 	ip6Str := "2001:db8::9"
 	ip6CIDR := ip6Str + "/48" // set the ip to a cidr, to make sure addr logic removes it
 	setIP6(server, ip6CIDR)
-	server.Profile = util.StrPtr(profileName)
+	server.ProfileNames = []string{profileName}
 	opt := &RecordsConfigOpts{}
 	opt.DNSLocalBindServiceAddr = true
 	opt.HdrComment = hdr
@@ -227,7 +225,7 @@ func TestMakeRecordsDotConfigDNSLocalBindNoOverrideV6(t *testing.T) {
 	ip6Str := "2001:db8::9"
 	ip6CIDR := ip6Str + "/48" // set the ip to a cidr, to make sure addr logic removes it
 	setIP6(server, ip6CIDR)
-	server.Profile = util.StrPtr(profileName)
+	server.ProfileNames = []string{profileName}
 	opt := &RecordsConfigOpts{}
 	opt.HdrComment = hdr
 	opt.DNSLocalBindServiceAddr = true
@@ -271,7 +269,7 @@ func TestMakeRecordsDotConfigDNSLocalBindNoOverrideBoth(t *testing.T) {
 	ip6Str := "2001:db8::9"
 	ip6CIDR := ip6Str + "/48" // set the ip to a cidr, to make sure addr logic removes it
 	setIP6(server, ip6CIDR)
-	server.Profile = util.StrPtr(profileName)
+	server.ProfileNames = []string{profileName}
 	opt := &RecordsConfigOpts{}
 	opt.HdrComment = hdr
 	opt.DNSLocalBindServiceAddr = true
diff --git a/lib/go-atscfg/remapdotconfig_test.go b/lib/go-atscfg/remapdotconfig_test.go
index 021fd847a..c039a5fa3 100644
--- a/lib/go-atscfg/remapdotconfig_test.go
+++ b/lib/go-atscfg/remapdotconfig_test.go
@@ -29,7 +29,6 @@ import (
 
 func makeTestRemapServer() *Server {
 	server := &Server{}
-	server.ProfileID = util.IntPtr(42)
 	server.CDNName = util.StrPtr("mycdn")
 	server.Cachegroup = util.StrPtr("cg0")
 	server.DomainName = util.StrPtr("mydomain")
@@ -38,8 +37,7 @@ func makeTestRemapServer() *Server {
 	server.HTTPSPort = util.IntPtr(12345)
 	server.ID = util.IntPtr(44)
 	setIP(server, "192.168.2.4")
-	server.ProfileID = util.IntPtr(46)
-	server.Profile = util.StrPtr("MyProfile")
+	server.ProfileNames = []string{"MyProfile"}
 	server.TCPPort = util.IntPtr(12080)
 	server.Type = "MID"
 	return server
@@ -224,7 +222,7 @@ func TestMakeRemapDotConfigMidLiveLocalExcluded(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -325,7 +323,7 @@ func TestMakeRemapDotConfigMid(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -440,7 +438,7 @@ func TestMakeRemapDotConfigNilOrigin(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -541,7 +539,7 @@ func TestMakeRemapDotConfigEmptyOrigin(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -680,7 +678,7 @@ func TestMakeRemapDotConfigDuplicateOrigins(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -780,7 +778,7 @@ func TestMakeRemapDotConfigNilMidRewrite(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -882,7 +880,7 @@ func TestMakeRemapDotConfigMidHasNoEdgeRewrite(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -985,7 +983,7 @@ func TestMakeRemapDotConfigMidProfileCacheKey(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -1124,7 +1122,7 @@ func TestMakeRemapDotConfigMidBgFetchHandling(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -1265,7 +1263,7 @@ func TestMakeRemapDotConfigMidRangeRequestHandling(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -1406,7 +1404,7 @@ func TestMakeRemapDotConfigMidSlicePluginRangeRequestHandling(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -1548,7 +1546,7 @@ func TestMakeRemapDotConfigAnyMap(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -1946,7 +1944,7 @@ func TestMakeRemapDotConfigEdgeMissingRemapData(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -2051,7 +2049,7 @@ func TestMakeRemapDotConfigEdgeHostRegexReplacement(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -2173,7 +2171,7 @@ func TestMakeRemapDotConfigEdgeHostRegexReplacementHTTP(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -2295,7 +2293,7 @@ func TestMakeRemapDotConfigEdgeHostRegexReplacementHTTPS(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -2417,7 +2415,7 @@ func TestMakeRemapDotConfigEdgeHostRegexReplacementHTTPToHTTPS(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -2539,7 +2537,7 @@ func TestMakeRemapDotConfigEdgeRemapUnderscoreHTTPReplace(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -2657,13 +2655,13 @@ func TestMakeRemapDotConfigEdgeDSCPRemap(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -2781,13 +2779,13 @@ func TestMakeRemapDotConfigEdgeNoDSCPRemap(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -2905,13 +2903,13 @@ func TestMakeRemapDotConfigEdgeHeaderRewrite(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -3033,13 +3031,13 @@ func TestMakeRemapDotConfigEdgeHeaderRewriteEmpty(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -3161,13 +3159,13 @@ func TestMakeRemapDotConfigEdgeHeaderRewriteNil(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -3289,13 +3287,13 @@ func TestMakeRemapDotConfigEdgeSigningURLSig(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -3423,13 +3421,13 @@ func TestMakeRemapDotConfigEdgeSigningURISigning(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -3546,13 +3544,13 @@ func TestMakeRemapDotConfigEdgeSigningNone(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -3669,13 +3667,13 @@ func TestMakeRemapDotConfigEdgeSigningEmpty(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -3792,13 +3790,13 @@ func TestMakeRemapDotConfigEdgeSigningWrong(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -3915,13 +3913,13 @@ func TestMakeRemapDotConfigEdgeQStringDropAtEdge(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -4036,13 +4034,13 @@ func TestMakeRemapDotConfigEdgeQStringIgnorePassUp(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -4160,13 +4158,13 @@ func TestMakeRemapDotConfigEdgeQStringIgnorePassUpWithCacheKeyParameter(t *testi
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -4307,13 +4305,13 @@ func TestMakeRemapDotConfigEdgeQStringIgnorePassUpCacheURLParamCacheURL(t *testi
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -4424,13 +4422,13 @@ func TestMakeRemapDotConfigEdgeCacheKeyParams(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -4563,13 +4561,13 @@ func TestMakeRemapDotConfigEdgeRegexRemap(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -4687,13 +4685,13 @@ func TestMakeRemapDotConfigEdgeRegexRemapEmpty(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -4807,13 +4805,13 @@ func TestMakeRemapDotConfigEdgeRangeRequestNil(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -4931,13 +4929,13 @@ func TestMakeRemapDotConfigEdgeRangeRequestDontCache(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -5079,13 +5077,13 @@ func TestMakeRemapDotConfigEdgeRangeRequestBGFetch(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -5225,13 +5223,13 @@ func TestMakeRemapDotConfigEdgeRangeRequestSlice(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -5354,13 +5352,13 @@ func TestMakeRemapDotConfigMidRangeRequestSlicePparam(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -5493,13 +5491,13 @@ func TestMakeRemapDotConfigEdgeRangeRequestSlicePparam(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -5646,13 +5644,13 @@ func TestMakeRemapDotConfigRawRemapRangeDirective(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -5792,13 +5790,13 @@ func TestMakeRemapDotConfigRawRemapWithoutRangeDirective(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -5931,13 +5929,13 @@ func TestMakeRemapDotConfigEdgeRangeRequestCache(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -6059,13 +6057,13 @@ func TestMakeRemapDotConfigEdgeFQPacingNil(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -6179,13 +6177,13 @@ func TestMakeRemapDotConfigEdgeFQPacingNegative(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -6299,13 +6297,13 @@ func TestMakeRemapDotConfigEdgeFQPacingZero(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -6419,13 +6417,13 @@ func TestMakeRemapDotConfigEdgeFQPacingPositive(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -6543,13 +6541,13 @@ func TestMakeRemapDotConfigEdgeDNS(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -6663,13 +6661,13 @@ func TestMakeRemapDotConfigEdgeDNSNoRoutingName(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -6773,13 +6771,13 @@ func TestMakeRemapDotConfigEdgeRegexTypeNil(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -6888,13 +6886,13 @@ func TestMakeRemapDotConfigNoHeaderRewrite(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -7008,13 +7006,13 @@ func TestMakeRemapDotConfigMidNoHeaderRewrite(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -7128,13 +7126,13 @@ func TestMakeRemapDotConfigMidNoNoCacheRemapLine(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -7244,13 +7242,13 @@ func TestMakeRemapDotConfigEdgeHTTPSOriginHTTPRemap(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -7376,13 +7374,13 @@ func TestMakeRemapDotConfigMidHTTPSOriginHTTPRemap(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -7498,13 +7496,13 @@ func TestMakeRemapDotConfigEdgeHTTPSOriginHTTPRemapTopology(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -7683,13 +7681,13 @@ func TestMakeRemapDotConfigMidHTTPSOriginHTTPRemapTopology(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -7862,13 +7860,13 @@ func TestMakeRemapDotConfigMidLastRawRemap(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -8075,13 +8073,13 @@ func TestMakeRemapDotConfigStrategies(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -8265,13 +8263,13 @@ func TestMakeRemapDotConfigStrategiesFalseButCoreUnused(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
@@ -8463,13 +8461,13 @@ func TestMakeRemapDotConfigMidCacheParentHTTPSOrigin(t *testing.T) {
 			Name:       "serverpkgval",
 			ConfigFile: "package",
 			Value:      "serverpkgval __HOSTNAME__ foo",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 		tc.Parameter{
 			Name:       "dscp_remap_no",
 			ConfigFile: "package",
 			Value:      "notused",
-			Profiles:   []byte(*server.Profile),
+			Profiles:   []byte(server.ProfileNames[0]),
 		},
 	}
 
diff --git a/lib/go-atscfg/serverunknown_test.go b/lib/go-atscfg/serverunknown_test.go
index bb3df804d..3751e4312 100644
--- a/lib/go-atscfg/serverunknown_test.go
+++ b/lib/go-atscfg/serverunknown_test.go
@@ -31,12 +31,12 @@ func TestMakeServerUnknown(t *testing.T) {
 
 	server := makeGenericServer()
 	server.HostName = util.StrPtr("server0")
-	server.Profile = util.StrPtr("serverProfile")
+	server.ProfileNames = []string{"serverProfile"}
 	server.DomainName = util.StrPtr("example.test")
 
 	fileName := "myconfig.config"
 
-	params := makeParamsFromMapArr(*server.Profile, fileName, map[string][]string{
+	params := makeParamsFromMapArr(server.ProfileNames[0], fileName, map[string][]string{
 		"location":   []string{"locationshouldnotexist"},
 		"param0name": []string{"param0val0", "param0val1"},
 		"param1name": []string{"param1val0"},
diff --git a/lib/go-atscfg/storagedotconfig_test.go b/lib/go-atscfg/storagedotconfig_test.go
index 3e98cb361..f3a7dc8f3 100644
--- a/lib/go-atscfg/storagedotconfig_test.go
+++ b/lib/go-atscfg/storagedotconfig_test.go
@@ -29,7 +29,7 @@ func TestMakeStorageDotConfig(t *testing.T) {
 	hdr := "myHeaderComment"
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	paramData := map[string]string{
 		"Drive_Prefix":      "/dev/sd",
@@ -40,7 +40,7 @@ func TestMakeStorageDotConfig(t *testing.T) {
 		"SSD_Drive_Letters": "i,j,k",
 	}
 
-	params := makeParamsFromMap(*server.Profile, StorageFileName, paramData)
+	params := makeParamsFromMap(server.ProfileNames[0], StorageFileName, paramData)
 
 	/*
 	   # DO NOT EDIT - Generated for myProfile by myToolName (https://myto.example.net) on Thu
@@ -94,11 +94,11 @@ func TestMakeStorageDotConfigNoParams(t *testing.T) {
 	hdr := "myHeaderComment"
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	paramData := map[string]string{}
 
-	params := makeParamsFromMap(*server.Profile, StorageFileName, paramData)
+	params := makeParamsFromMap(server.ProfileNames[0], StorageFileName, paramData)
 
 	cfg, err := MakeStorageDotConfig(server, params, &StorageDotConfigOpts{HdrComment: hdr})
 	if err != nil {
@@ -131,7 +131,7 @@ func TestMakeStorageDotConfigNoDriveLetters(t *testing.T) {
 	hdr := "myHeaderComment"
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	paramData := map[string]string{
 		"Drive_Prefix":     "/dev/sd",
@@ -139,7 +139,7 @@ func TestMakeStorageDotConfigNoDriveLetters(t *testing.T) {
 		"SSD_Drive_Prefix": "/dev/ss",
 	}
 
-	params := makeParamsFromMap(*server.Profile, StorageFileName, paramData)
+	params := makeParamsFromMap(server.ProfileNames[0], StorageFileName, paramData)
 
 	cfg, err := MakeStorageDotConfig(server, params, &StorageDotConfigOpts{HdrComment: hdr})
 	if err != nil {
@@ -173,7 +173,7 @@ func TestMakeStorageDotConfigSomeDriveLetters(t *testing.T) {
 	hdr := "myHeaderComment"
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	paramData := map[string]string{
 		"Drive_Prefix":     "/dev/sd",
@@ -182,7 +182,7 @@ func TestMakeStorageDotConfigSomeDriveLetters(t *testing.T) {
 		"Drive_Letters":    "a,b,c,d,e",
 	}
 
-	params := makeParamsFromMap(*server.Profile, StorageFileName, paramData)
+	params := makeParamsFromMap(server.ProfileNames[0], StorageFileName, paramData)
 
 	cfg, err := MakeStorageDotConfig(server, params, &StorageDotConfigOpts{HdrComment: hdr})
 	if err != nil {
diff --git a/lib/go-atscfg/sysctldotconf_test.go b/lib/go-atscfg/sysctldotconf_test.go
index a93b8287c..22947f1a2 100644
--- a/lib/go-atscfg/sysctldotconf_test.go
+++ b/lib/go-atscfg/sysctldotconf_test.go
@@ -34,9 +34,9 @@ func TestMakeSysCtlDotConf(t *testing.T) {
 	}
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
-	params := makeParamsFromMap(*server.Profile, SysctlFileName, paramData)
+	params := makeParamsFromMap(server.ProfileNames[0], SysctlFileName, paramData)
 
 	cfg, err := MakeSysCtlDotConf(server, params, &SysCtlDotConfOpts{HdrComment: hdr})
 	if err != nil {
diff --git a/lib/go-atscfg/urlsigconfig_test.go b/lib/go-atscfg/urlsigconfig_test.go
index a8a95e63b..226242521 100644
--- a/lib/go-atscfg/urlsigconfig_test.go
+++ b/lib/go-atscfg/urlsigconfig_test.go
@@ -40,11 +40,11 @@ func TestMakeURLSigConfig(t *testing.T) {
 	}
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
 	fileName := "url_sig_myds.config"
 
-	params := makeParamsFromMap(*server.Profile, fileName, paramData)
+	params := makeParamsFromMap(server.ProfileNames[0], fileName, paramData)
 
 	cfg, err := MakeURLSigConfig(fileName, server, params, allURLSigKeys, &URLSigConfigOpts{HdrComment: hdr})
 	if err != nil {
diff --git a/lib/go-atscfg/volumedotconfig_test.go b/lib/go-atscfg/volumedotconfig_test.go
index 8f3191161..02ad8b0aa 100644
--- a/lib/go-atscfg/volumedotconfig_test.go
+++ b/lib/go-atscfg/volumedotconfig_test.go
@@ -37,9 +37,9 @@ func TestMakeVolumeDotConfig(t *testing.T) {
 	}
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
-	params := makeParamsFromMap(*server.Profile, VolumeFileName, paramData)
+	params := makeParamsFromMap(server.ProfileNames[0], VolumeFileName, paramData)
 
 	cfg, err := MakeVolumeDotConfig(server, params, &VolumeDotConfigOpts{HdrComment: hdr})
 	if err != nil {
@@ -59,7 +59,7 @@ func TestMakeVolumeDotConfig(t *testing.T) {
 
 	delete(paramData, "SSD_Drive_Prefix")
 	delete(paramData, "SSD_Drive_Letters")
-	params = makeParamsFromMap(*server.Profile, VolumeFileName, paramData)
+	params = makeParamsFromMap(server.ProfileNames[0], VolumeFileName, paramData)
 
 	cfg, err = MakeVolumeDotConfig(server, params, &VolumeDotConfigOpts{HdrComment: hdr})
 	if err != nil {
@@ -77,7 +77,7 @@ func TestMakeVolumeDotConfig(t *testing.T) {
 
 	delete(paramData, "RAM_Drive_Prefix")
 	delete(paramData, "RAM_Drive_Letters")
-	params = makeParamsFromMap(*server.Profile, VolumeFileName, paramData)
+	params = makeParamsFromMap(server.ProfileNames[0], VolumeFileName, paramData)
 
 	cfg, err = MakeVolumeDotConfig(server, params, &VolumeDotConfigOpts{HdrComment: hdr})
 	if err != nil {
@@ -100,9 +100,9 @@ func TestMakeVolumeDotConfigNoParams(t *testing.T) {
 	paramData := map[string]string{}
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
-	params := makeParamsFromMap(*server.Profile, VolumeFileName, paramData)
+	params := makeParamsFromMap(server.ProfileNames[0], VolumeFileName, paramData)
 
 	cfg, err := MakeVolumeDotConfig(server, params, &VolumeDotConfigOpts{HdrComment: hdr})
 	if err != nil {
@@ -136,9 +136,9 @@ func TestMakeVolumeDotConfigNoLetters(t *testing.T) {
 	}
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
-	params := makeParamsFromMap(*server.Profile, VolumeFileName, paramData)
+	params := makeParamsFromMap(server.ProfileNames[0], VolumeFileName, paramData)
 
 	cfg, err := MakeVolumeDotConfig(server, params, &VolumeDotConfigOpts{HdrComment: hdr})
 	if err != nil {
@@ -170,9 +170,9 @@ func TestMakeVolumeDotConfigSomePrefixes(t *testing.T) {
 	}
 
 	server := makeGenericServer()
-	server.Profile = &profileName
+	server.ProfileNames = []string{profileName}
 
-	params := makeParamsFromMap(*server.Profile, VolumeFileName, paramData)
+	params := makeParamsFromMap(server.ProfileNames[0], VolumeFileName, paramData)
 
 	cfg, err := MakeVolumeDotConfig(server, params, &VolumeDotConfigOpts{HdrComment: hdr})
 	if err != nil {
diff --git a/lib/go-tc/servers_test.go b/lib/go-tc/servers_test.go
index cb17e5271..9641813ba 100644
--- a/lib/go-tc/servers_test.go
+++ b/lib/go-tc/servers_test.go
@@ -19,10 +19,12 @@ package tc
  * under the License.
  */
 
-import "fmt"
-import "strings"
-import "testing"
-import "time"
+import (
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+)
 
 func ExampleLegacyInterfaceDetails_ToInterfaces() {
 	lid := LegacyInterfaceDetails{
diff --git a/traffic_ops/testing/api/v3/deliveryservicesregexes_test.go b/traffic_ops/testing/api/v3/deliveryservicesregexes_test.go
index 02c0ecdef..512bf4dbe 100644
--- a/traffic_ops/testing/api/v3/deliveryservicesregexes_test.go
+++ b/traffic_ops/testing/api/v3/deliveryservicesregexes_test.go
@@ -16,178 +16,121 @@
 package v3
 
 import (
+	"encoding/json"
 	"fmt"
 	"net/http"
+	"net/url"
 	"strconv"
 	"testing"
-	"time"
 
-	"github.com/apache/trafficcontrol/lib/go-rfc"
 	"github.com/apache/trafficcontrol/lib/go-tc"
+	"github.com/apache/trafficcontrol/traffic_ops/testing/api/assert"
+	"github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
 )
 
 func TestDeliveryServicesRegexes(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, DeliveryServices, DeliveryServicesRegexes}, func() {
-		QueryDSRegexTest(t)
-		QueryDSRegexTestIMS(t)
-		CreateTestDSRegexWithMissingPattern(t)
-	})
-}
-
-func CreateTestDeliveryServicesRegexes(t *testing.T) {
-	db, err := OpenConnection()
-	if err != nil {
-		t.Fatal("cannot open db")
-	}
-	defer func() {
-		err := db.Close()
-		if err != nil {
-			t.Errorf("unable to close connection to db, error: %v", err)
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, DeliveryServicesRegexes}, func() {
+
+		methodTests := utils.V3TestCase{
+			"GET": {
+				"OK when VALID request": {
+					EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession,
+					Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(3)),
+				},
+				"OK when VALID ID parameter": {
+					EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession,
+					RequestParams: url.Values{"id": {strconv.Itoa(getDSRegexID(t, "ds1"))}},
+					Expectations:  utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1)),
+				},
+			},
+			"POST": {
+				"BAD REQUEST when MISSING REGEX PATTERN": {
+					EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession,
+					RequestBody: map[string]interface{}{
+						"type":      GetTypeId(t, "HOST_REGEXP"),
+						"setNumber": 3,
+						"pattern":   "",
+					},
+					Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+				},
+			},
 		}
-	}()
-
-	dbRegexInsertTemplate := "INSERT INTO regex (pattern, type) VALUES ('%v', '%v');"
-	dbRegexQueryTemplate := "SELECT id FROM regex order by id desc limit 1;"
-	dbDSRegexInsertTemplate := "INSERT INTO deliveryservice_regex (deliveryservice, regex, set_number) VALUES ('%v', '%v', '%v');"
-
-	for i, regex := range testData.DeliveryServicesRegexes {
-		loadDSRegexIDs(t, &regex)
 
-		err = execSQL(db, fmt.Sprintf(dbRegexInsertTemplate, regex.Pattern, regex.Type))
-		if err != nil {
-			t.Fatalf("unable to create regex: %v", err)
+		for method, testCases := range methodTests {
+			t.Run(method, func(t *testing.T) {
+				for name, testCase := range testCases {
+					dsRegex := tc.DeliveryServiceRegexPost{}
+					params := make(map[string]string)
+					if testCase.RequestParams.Has("id") {
+						val := testCase.RequestParams.Get("id")
+						params["id"] = val
+					}
+					if testCase.RequestBody != nil {
+						dat, err := json.Marshal(testCase.RequestBody)
+						assert.NoError(t, err, "Error occurred when marshalling request body: %v", err)
+						err = json.Unmarshal(dat, &dsRegex)
+						assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err)
+					}
+
+					switch method {
+					case "GET":
+						t.Run(name, func(t *testing.T) {
+							resp, reqInf, err := testCase.ClientSession.GetDeliveryServiceRegexesByDSID(testCase.EndpointId(), params)
+							for _, check := range testCase.Expectations {
+								check(t, reqInf, resp, tc.Alerts{}, err)
+							}
+						})
+					case "POST":
+						t.Run(name, func(t *testing.T) {
+							alerts, reqInf, err := testCase.ClientSession.PostDeliveryServiceRegexesByDSID(testCase.EndpointId(), dsRegex)
+							for _, check := range testCase.Expectations {
+								check(t, reqInf, nil, alerts, err)
+							}
+						})
+
+					}
+				}
+			})
 		}
+	})
+}
 
-		row := db.QueryRow(dbRegexQueryTemplate)
-		err = row.Scan(&regex.ID)
-		if err != nil {
-			t.Fatalf("unable to query regex: %v", err)
-		}
+func getDSRegexID(t *testing.T, dsName string) int {
+	resp, _, err := TOSession.GetDeliveryServiceRegexesByDSID(GetDeliveryServiceId(t, dsName)(), nil)
+	assert.RequireNoError(t, err, "Get Delivery Service Regex failed with error: %v", err)
+	assert.RequireGreaterOrEqual(t, len(resp), 1, "Expected delivery service regex response object length 1, but got %d", len(resp))
+	assert.RequireNotNil(t, resp[0].ID, "Expected id to not be nil")
 
-		err = execSQL(db, fmt.Sprintf(dbDSRegexInsertTemplate, regex.DSID, regex.ID, regex.SetNumber))
-		if err != nil {
-			t.Fatalf("unable to create ds regex %v", err)
-		}
+	return resp[0].ID
+}
 
-		testData.DeliveryServicesRegexes[i] = regex
+func CreateTestDeliveryServicesRegexes(t *testing.T) {
+	for _, dsRegex := range testData.DeliveryServicesRegexes {
+		dsID := GetDeliveryServiceId(t, dsRegex.DSName)()
+		typeId := GetTypeId(t, dsRegex.TypeName)
+		dsRegexPost := tc.DeliveryServiceRegexPost{
+			Type:      typeId,
+			SetNumber: dsRegex.SetNumber,
+			Pattern:   dsRegex.Pattern,
+		}
+		alerts, _, err := TOSession.PostDeliveryServiceRegexesByDSID(dsID, dsRegexPost)
+		assert.NoError(t, err, "Could not create Delivery Service Regex: %v - alerts: %+v", err, alerts)
 	}
 }
 
 func DeleteTestDeliveryServicesRegexes(t *testing.T) {
 	db, err := OpenConnection()
-	if err != nil {
-		t.Fatal("cannot open db")
-	}
+	assert.RequireNoError(t, err, "Cannot open db: %v", err)
 	defer func() {
 		err := db.Close()
-		if err != nil {
-			t.Errorf("unable to close connection to db, error: %v", err)
-		}
+		assert.NoError(t, err, "Unable to close connection to db: %v", err)
 	}()
 
 	for _, regex := range testData.DeliveryServicesRegexes {
 		err = execSQL(db, fmt.Sprintf("DELETE FROM deliveryservice_regex WHERE deliveryservice = '%v' and regex ='%v';", regex.DSID, regex.ID))
-		if err != nil {
-			t.Fatalf("unable to delete deliveryservice_regex by regex %v and ds %v: %v", regex.ID, regex.DSID, err)
-		}
+		assert.RequireNoError(t, err, "Unable to delete deliveryservice_regex by regex %v and ds %v: %v", regex.ID, regex.DSID, err)
 
 		err := execSQL(db, fmt.Sprintf("DELETE FROM regex WHERE Id = '%v';", regex.ID))
-		if err != nil {
-			t.Fatalf("unable to delete regex %v: %v", regex.ID, err)
-		}
-	}
-}
-
-func CreateTestDSRegexWithMissingPattern(t *testing.T) {
-	var regex = testData.DeliveryServicesRegexes[3]
-	ds, _, err := TOSession.GetDeliveryServiceByXMLIDNullableWithHdr(regex.DSName, nil)
-	if err != nil {
-		t.Fatalf("unable to get ds %v: %v", regex.DSName, err)
-	}
-	if len(ds) == 0 {
-		t.Fatalf("unable to get ds %v", regex.DSName)
-	}
-
-	var dsID int
-	if ds[0].ID == nil {
-		t.Fatal("ds has a nil id")
-	} else {
-		dsID = *ds[0].ID
-	}
-
-	regexPost := tc.DeliveryServiceRegexPost{Type: regex.Type, SetNumber: regex.SetNumber, Pattern: regex.Pattern}
-
-	_, reqInfo, _ := TOSession.PostDeliveryServiceRegexesByDSID(dsID, regexPost)
-	if reqInfo.StatusCode != http.StatusBadRequest {
-		t.Errorf("Expected: %v, but got: %v", http.StatusBadRequest, reqInfo.StatusCode)
-	}
-}
-
-func loadDSRegexIDs(t *testing.T, test *tc.DeliveryServiceRegexesTest) {
-	dsTypes, _, err := TOSession.GetTypeByName(test.TypeName)
-	if err != nil {
-		t.Fatalf("unable to get type by name %v: %v", test.TypeName, err)
-	}
-	if len(dsTypes) < 1 {
-		t.Fatalf("could not find any types by name %v", test.TypeName)
-	}
-	test.Type = dsTypes[0].ID
-
-	dses, _, err := TOSession.GetDeliveryServiceByXMLIDNullable(test.DSName)
-	if err != nil {
-		t.Fatalf("unable to ds by xmlid %v: %v", test.DSName, err)
-	}
-	if len(dses) != 1 {
-		t.Fatalf("unable to find ds by xmlid %v", test.DSName)
-	}
-	test.DSID = *dses[0].ID
-}
-
-func QueryDSRegexTestIMS(t *testing.T) {
-	var header http.Header
-	header = make(map[string][]string)
-	futureTime := time.Now().AddDate(0, 0, 1)
-	time := futureTime.Format(time.RFC1123)
-	header.Set(rfc.IfModifiedSince, time)
-	_, reqInf, err := TOSession.GetDeliveryServiceByXMLIDNullableWithHdr("ds1", header)
-	if err != nil {
-		t.Fatalf("could not GET delivery services regex: %v", err)
-	}
-	if reqInf.StatusCode != http.StatusNotModified {
-		t.Fatalf("Expected 304 status code, got %v", reqInf.StatusCode)
-	}
-}
-
-func QueryDSRegexTest(t *testing.T) {
-	ds, _, err := TOSession.GetDeliveryServiceByXMLIDNullable("ds1")
-	if err != nil {
-		t.Fatalf("unable to get ds ds1: %v", err)
-	}
-	if len(ds) == 0 {
-		t.Fatal("unable to get ds ds1")
-	}
-	var dsID int
-	if ds[0].ID == nil {
-		t.Fatal("ds has a nil id")
-	} else {
-		dsID = *ds[0].ID
-	}
-
-	dsRegexes, _, err := TOSession.GetDeliveryServiceRegexesByDSID(dsID, nil)
-	if err != nil {
-		t.Fatal("unable to get ds_regex by id " + strconv.Itoa(dsID))
-	}
-	if len(dsRegexes) != 4 {
-		t.Fatal("expected to get 4 ds_regex, got " + strconv.Itoa(len(dsRegexes)))
-	}
-
-	params := make(map[string]string)
-	params["id"] = strconv.Itoa(dsRegexes[0].ID)
-	dsRegexes, _, err = TOSession.GetDeliveryServiceRegexesByDSID(dsID, params)
-	if err != nil {
-		t.Fatalf("unable to get ds_regex by id %v with query param %v", dsID, params["id"])
-	}
-	if len(dsRegexes) != 1 {
-		t.Fatal("expected to get 1 ds_regex, got " + strconv.Itoa(len(dsRegexes)))
+		assert.RequireNoError(t, err, "Unable to delete regex %v: %v", regex.ID, err)
 	}
 }
diff --git a/traffic_ops/testing/api/v3/tc-fixtures.json b/traffic_ops/testing/api/v3/tc-fixtures.json
index 39c20b95a..ab8616ae4 100644
--- a/traffic_ops/testing/api/v3/tc-fixtures.json
+++ b/traffic_ops/testing/api/v3/tc-fixtures.json
@@ -1546,12 +1546,6 @@
             "typeName": "HOST_REGEXP",
             "setNumber": 1,
             "pattern" : ".*"
-        },
-        {
-            "dsName": "ds1",
-            "typeName": "HOST_REGEXP",
-            "setNumber": 3,
-            "pattern" : ""
         }
     ],
     "deliveryservicesRequiredCapabilities": [
diff --git a/traffic_ops/testing/api/v4/cdn_queue_updates_by_type_profile_test.go b/traffic_ops/testing/api/v4/cdn_queue_updates_by_type_profile_test.go
index 475736059..74179ce5c 100644
--- a/traffic_ops/testing/api/v4/cdn_queue_updates_by_type_profile_test.go
+++ b/traffic_ops/testing/api/v4/cdn_queue_updates_by_type_profile_test.go
@@ -140,7 +140,7 @@ func QueueUpdatesByProfile(t *testing.T) {
 	}
 	server := testData.Servers[0]
 	opts := client.NewRequestOptions()
-	if server.CDNName == nil || server.Profile == nil {
+	if server.CDNName == nil || len(server.ProfileNames) == 0 {
 		t.Fatalf("server doesn't have a CDN name or a profile name...quitting")
 	}
 
@@ -157,7 +157,7 @@ func QueueUpdatesByProfile(t *testing.T) {
 	opts.QueryParameters.Del("name")
 
 	// Get the first server's Profile ID
-	opts.QueryParameters.Set("name", *server.Profile)
+	opts.QueryParameters.Set("name", server.ProfileNames[0])
 	profiles, _, err := TOSession.GetProfiles(opts)
 	if err != nil {
 		t.Fatalf("error while getting profiles: %v", err)
diff --git a/traffic_ops/testing/api/v4/deliveryservicesregexes_test.go b/traffic_ops/testing/api/v4/deliveryservicesregexes_test.go
index 333fb90b4..2dc0f861e 100644
--- a/traffic_ops/testing/api/v4/deliveryservicesregexes_test.go
+++ b/traffic_ops/testing/api/v4/deliveryservicesregexes_test.go
@@ -16,208 +16,118 @@
 package v4
 
 import (
+	"encoding/json"
 	"fmt"
 	"net/http"
+	"net/url"
 	"strconv"
 	"testing"
-	"time"
 
-	"github.com/apache/trafficcontrol/lib/go-rfc"
 	"github.com/apache/trafficcontrol/lib/go-tc"
+	"github.com/apache/trafficcontrol/traffic_ops/testing/api/assert"
+	"github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
 	client "github.com/apache/trafficcontrol/traffic_ops/v4-client"
 )
 
 func TestDeliveryServicesRegexes(t *testing.T) {
 	WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, DeliveryServicesRegexes}, func() {
-		QueryDSRegexTest(t)
-		QueryDSRegexTestIMS(t)
-		CreateTestDSRegexWithMissingPattern(t)
-	})
-}
 
-func CreateTestDeliveryServicesRegexes(t *testing.T) {
-	db, err := OpenConnection()
-	if err != nil {
-		t.Fatal("cannot open db")
-	}
-	defer func() {
-		err := db.Close()
-		if err != nil {
-			t.Errorf("unable to close connection to db, error: %v", err)
+		methodTests := utils.V4TestCase{
+			"GET": {
+				"OK when VALID request": {
+					EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession,
+					Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(3)),
+				},
+				"OK when VALID ID parameter": {
+					EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession,
+					RequestOpts:  client.RequestOptions{QueryParameters: url.Values{"id": {strconv.Itoa(getDSRegexID(t, "ds1"))}}},
+					Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1)),
+				},
+			},
+			"POST": {
+				"BAD REQUEST when MISSING REGEX PATTERN": {
+					EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession,
+					RequestBody: map[string]interface{}{
+						"type":      GetTypeId(t, "HOST_REGEXP"),
+						"setNumber": 3,
+						"pattern":   "",
+					},
+					Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+				},
+			},
 		}
-	}()
-
-	dbRegexInsertTemplate := "INSERT INTO regex (pattern, type) VALUES ('%v', '%v');"
-	dbRegexQueryTemplate := "SELECT id FROM regex order by id desc limit 1;"
-	dbDSRegexInsertTemplate := "INSERT INTO deliveryservice_regex (deliveryservice, regex, set_number) VALUES ('%v', '%v', '%v');"
 
-	for i, regex := range testData.DeliveryServicesRegexes {
-		loadDSRegexIDs(t, &regex)
-
-		err = execSQL(db, fmt.Sprintf(dbRegexInsertTemplate, regex.Pattern, regex.Type))
-		if err != nil {
-			t.Fatalf("unable to create regex: %v", err)
+		for method, testCases := range methodTests {
+			t.Run(method, func(t *testing.T) {
+				for name, testCase := range testCases {
+
+					dsRegex := tc.DeliveryServiceRegexPost{}
+					if testCase.RequestBody != nil {
+						dat, err := json.Marshal(testCase.RequestBody)
+						assert.NoError(t, err, "Error occurred when marshalling request body: %v", err)
+						err = json.Unmarshal(dat, &dsRegex)
+						assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err)
+					}
+
+					switch method {
+					case "GET":
+						t.Run(name, func(t *testing.T) {
+							resp, reqInf, err := testCase.ClientSession.GetDeliveryServiceRegexesByDSID(testCase.EndpointId(), testCase.RequestOpts)
+							for _, check := range testCase.Expectations {
+								check(t, reqInf, resp.Response, resp.Alerts, err)
+							}
+						})
+					case "POST":
+						t.Run(name, func(t *testing.T) {
+							alerts, reqInf, err := testCase.ClientSession.PostDeliveryServiceRegexesByDSID(testCase.EndpointId(), dsRegex, testCase.RequestOpts)
+							for _, check := range testCase.Expectations {
+								check(t, reqInf, nil, alerts, err)
+							}
+						})
+
+					}
+				}
+			})
 		}
+	})
+}
 
-		row := db.QueryRow(dbRegexQueryTemplate)
-		err = row.Scan(&regex.ID)
-		if err != nil {
-			t.Fatalf("unable to query regex: %v", err)
-		}
+func getDSRegexID(t *testing.T, dsName string) int {
+	resp, _, err := TOSession.GetDeliveryServiceRegexesByDSID(GetDeliveryServiceId(t, dsName)(), client.RequestOptions{})
+	assert.RequireNoError(t, err, "Get Delivery Service Regex failed with error: %v", err)
+	assert.RequireGreaterOrEqual(t, len(resp.Response), 1, "Expected delivery service regex response object length 1, but got %d", len(resp.Response))
+	assert.RequireNotNil(t, resp.Response[0].ID, "Expected id to not be nil")
 
-		err = execSQL(db, fmt.Sprintf(dbDSRegexInsertTemplate, regex.DSID, regex.ID, regex.SetNumber))
-		if err != nil {
-			t.Fatalf("unable to create ds regex %v", err)
-		}
+	return resp.Response[0].ID
+}
 
-		testData.DeliveryServicesRegexes[i] = regex
+func CreateTestDeliveryServicesRegexes(t *testing.T) {
+	for _, dsRegex := range testData.DeliveryServicesRegexes {
+		dsID := GetDeliveryServiceId(t, dsRegex.DSName)()
+		typeId := GetTypeId(t, dsRegex.TypeName)
+		dsRegexPost := tc.DeliveryServiceRegexPost{
+			Type:      typeId,
+			SetNumber: dsRegex.SetNumber,
+			Pattern:   dsRegex.Pattern,
+		}
+		alerts, _, err := TOSession.PostDeliveryServiceRegexesByDSID(dsID, dsRegexPost, client.RequestOptions{})
+		assert.NoError(t, err, "Could not create Delivery Service Regex: %v - alerts: %+v", err, alerts)
 	}
 }
 
 func DeleteTestDeliveryServicesRegexes(t *testing.T) {
 	db, err := OpenConnection()
-	if err != nil {
-		t.Fatal("cannot open db")
-	}
+	assert.RequireNoError(t, err, "Cannot open db: %v", err)
 	defer func() {
 		err := db.Close()
-		if err != nil {
-			t.Errorf("unable to close connection to db, error: %v", err)
-		}
+		assert.NoError(t, err, "Unable to close connection to db: %v", err)
 	}()
 
 	for _, regex := range testData.DeliveryServicesRegexes {
 		err = execSQL(db, fmt.Sprintf("DELETE FROM deliveryservice_regex WHERE deliveryservice = '%v' and regex ='%v';", regex.DSID, regex.ID))
-		if err != nil {
-			t.Fatalf("unable to delete deliveryservice_regex by regex %v and ds %v: %v", regex.ID, regex.DSID, err)
-		}
+		assert.RequireNoError(t, err, "Unable to delete deliveryservice_regex by regex %v and ds %v: %v", regex.ID, regex.DSID, err)
 
 		err := execSQL(db, fmt.Sprintf("DELETE FROM regex WHERE Id = '%v';", regex.ID))
-		if err != nil {
-			t.Fatalf("unable to delete regex %v: %v", regex.ID, err)
-		}
-	}
-}
-
-// Note that this test is liable to break if the structure of the Delivery
-// Service Regular Expressions in the test data is changed at all.
-func CreateTestDSRegexWithMissingPattern(t *testing.T) {
-	if len(testData.DeliveryServicesRegexes) < 4 {
-		t.Fatal("Need at least 4 Delivery Service Regular Expressions to test creating a Delivery Service Regular Expression with a missing pattern")
-	}
-	var regex = testData.DeliveryServicesRegexes[3]
-	opts := client.NewRequestOptions()
-	opts.QueryParameters.Set("xmlId", regex.DSName)
-	ds, _, err := TOSession.GetDeliveryServices(opts)
-	if err != nil {
-		t.Fatalf("unable to get ds '%s': %v - alerts: %+v", regex.DSName, err, ds.Alerts)
-	}
-	if len(ds.Response) == 0 {
-		t.Fatalf("unable to get ds %v", regex.DSName)
-	}
-
-	var dsID int
-	if ds.Response[0].ID == nil {
-		t.Fatal("ds has a nil id")
-	} else {
-		dsID = *ds.Response[0].ID
-	}
-
-	regexPost := tc.DeliveryServiceRegexPost{Type: regex.Type, SetNumber: regex.SetNumber, Pattern: regex.Pattern}
-
-	_, reqInfo, _ := TOSession.PostDeliveryServiceRegexesByDSID(dsID, regexPost, client.RequestOptions{})
-	if reqInfo.StatusCode != http.StatusBadRequest {
-		t.Errorf("Expected: %v, but got: %v", http.StatusBadRequest, reqInfo.StatusCode)
-	}
-}
-
-func loadDSRegexIDs(t *testing.T, test *tc.DeliveryServiceRegexesTest) {
-	if test == nil {
-		t.Error("loadDSRegexIDs called with nil test")
-		return
-	}
-	opts := client.NewRequestOptions()
-	opts.QueryParameters.Set("name", test.TypeName)
-	dsTypes, _, err := TOSession.GetTypes(opts)
-	if err != nil {
-		t.Errorf("unable to get Types filtered by name '%s': %v - alerts: %+v", test.TypeName, err, dsTypes.Alerts)
-		return
-	}
-	if len(dsTypes.Response) < 1 {
-		t.Errorf("could not find any types by name '%s'", test.TypeName)
-		return
-	}
-	test.Type = dsTypes.Response[0].ID
-
-	opts = client.NewRequestOptions()
-	opts.QueryParameters.Set("xmlId", test.DSName)
-	dses, _, err := TOSession.GetDeliveryServices(opts)
-	if err != nil {
-		t.Errorf("unable to ds by xmlid %v: %v - alerts: %+v", test.DSName, err, dses.Alerts)
-		return
-	}
-	if len(dses.Response) != 1 {
-		t.Errorf("unable to find ds by xmlid %v", test.DSName)
-		return
-	}
-	if dses.Response[0].ID == nil {
-		t.Error("Delivery Service had a null or undefined ID")
-		return
-	}
-	test.DSID = *dses.Response[0].ID
-}
-
-func QueryDSRegexTestIMS(t *testing.T) {
-	futureTime := time.Now().AddDate(0, 0, 1)
-	time := futureTime.Format(time.RFC1123)
-
-	opts := client.NewRequestOptions()
-	opts.Header.Set(rfc.IfModifiedSince, time)
-	opts.QueryParameters.Set("xmlId", "ds1")
-	resp, reqInf, err := TOSession.GetDeliveryServices(opts)
-	if err != nil {
-		t.Errorf("Unexpected error getting Delivery Services filtered by XMLID 'ds1': %v - alerts: %+v", err, resp.Alerts)
-	}
-	if reqInf.StatusCode != http.StatusNotModified {
-		t.Fatalf("Expected 304 status code, got %v", reqInf.StatusCode)
-	}
-}
-
-// Note that this test will break if the Delivery Service in the test data with
-// the XMLID 'ds1' is removed or altered such that its regular expressions are
-// different than at the time of this writing.
-func QueryDSRegexTest(t *testing.T) {
-	opts := client.NewRequestOptions()
-	opts.QueryParameters.Set("xmlId", "ds1")
-	ds, _, err := TOSession.GetDeliveryServices(opts)
-	if err != nil {
-		t.Fatalf("unable to get ds 'ds1': %v - alerts: %+v", err, ds.Alerts)
-	}
-	if len(ds.Response) == 0 {
-		t.Fatal("unable to get ds ds1")
-	}
-	var dsID int
-	if ds.Response[0].ID == nil {
-		t.Fatal("ds has a nil id")
-	} else {
-		dsID = *ds.Response[0].ID
-	}
-
-	dsRegexes, _, err := TOSession.GetDeliveryServiceRegexesByDSID(dsID, client.RequestOptions{})
-	if err != nil {
-		t.Errorf("Unexpected error fetching Regular Expressions for Delivery Service 'ds1' (#%d): %v - alerts: %+v", dsID, err, dsRegexes.Alerts)
-	}
-	if len(dsRegexes.Response) != 4 {
-		t.Fatalf("expected to get 4 Regular Expressions for Delivery Service 'ds1' (#%d), got: %d", dsID, len(dsRegexes.Response))
-	}
-	regExpID := dsRegexes.Response[0].ID
-	opts = client.NewRequestOptions()
-	opts.QueryParameters.Set("id", strconv.Itoa(regExpID))
-	dsRegexes, _, err = TOSession.GetDeliveryServiceRegexesByDSID(dsID, opts)
-	if err != nil {
-		t.Errorf("Unexpected error getting Regular Expressions for Delivery Service 'ds1' (#%d) filtered by Regular Expression ID %d: %v - alerts: %+v", dsID, regExpID, err, dsRegexes.Alerts)
-	}
-	if len(dsRegexes.Response) != 1 {
-		t.Fatalf("expected to get 1 Regular Expression for Delivery Service 'ds1' (#%d) that has ID %d, got: %d", dsID, regExpID, len(dsRegexes.Response))
+		assert.RequireNoError(t, err, "Unable to delete regex %v: %v", regex.ID, err)
 	}
 }
diff --git a/traffic_ops/testing/api/v4/servers_test.go b/traffic_ops/testing/api/v4/servers_test.go
index 597044422..4adf0d834 100644
--- a/traffic_ops/testing/api/v4/servers_test.go
+++ b/traffic_ops/testing/api/v4/servers_test.go
@@ -218,8 +218,8 @@ func LastServerInTopologyCacheGroup(t *testing.T) {
 		t.Fatalf("expected to get %d server from cdn %s from cachegroup %s in topology %s, got %d servers", expectedLength, cdnName, cacheGroupName, topologyName, len(servers.Response))
 	}
 	server := servers.Response[0]
-	if server.ID == nil || server.CDNID == nil || server.ProfileID == nil || server.CachegroupID == nil || server.HostName == nil {
-		t.Fatal("Traffic Ops returned a representation for a server with null or undefined ID and/or CDN ID and/or Profile ID and/or Cache Group ID and/or Host Name")
+	if server.ID == nil || server.CDNID == nil || len(server.ProfileNames) == 0 || server.CachegroupID == nil || server.HostName == nil {
+		t.Fatal("Traffic Ops returned a representation for a server with null or undefined ID and/or CDN ID and/or Profile Names and/or Cache Group ID and/or Host Name")
 	}
 
 	_, reqInf, err := TOSession.DeleteServer(*server.ID, client.RequestOptions{})
@@ -250,15 +250,26 @@ func LastServerInTopologyCacheGroup(t *testing.T) {
 	if len(profiles.Response) != 1 {
 		t.Fatalf("Expected exactly one Profile to exist with name 'MID1', found: %d", len(profiles.Response))
 	}
-	newProfile := profiles.Response[0].ID
-	oldProfile := *server.ProfileID
-	server.ProfileID = &newProfile
+	newProfileID := profiles.Response[0].ID
+	oldProfileName := server.ProfileNames[0]
+
+	opts.QueryParameters.Set("id", strconv.Itoa(newProfileID))
+	nps, _, err := TOSession.GetProfiles(opts)
+	if err != nil {
+		t.Fatalf("failed to query profiles: %v", err)
+	}
+	if len(nps.Response) != 1 {
+		t.Fatalf("Expected exactly one Profile to exist, found: %d", len(profiles.Response))
+	}
+	server.ProfileNames = []string{nps.Response[0].Name}
+	opts.QueryParameters.Del("id")
+
 	_, _, err = TOSession.UpdateServer(*server.ID, server, client.RequestOptions{})
 	if err == nil {
 		t.Fatalf("changing the CDN of the last server (%s) in a CDN in a cachegroup used by a topology assigned to a delivery service(s) in that CDN - expected: error, actual: nil", *server.HostName)
 	}
 	server.CDNID = &oldCDNID
-	server.ProfileID = &oldProfile
+	server.ProfileNames = []string{oldProfileName}
 
 	opts.QueryParameters.Set("name", moveToCacheGroup)
 	cgs, _, err := TOSession.GetCacheGroups(opts)
@@ -606,24 +617,23 @@ func CreateTestServerWithoutProfileID(t *testing.T) {
 	}
 
 	server := resp.Response[0]
-	if server.Profile == nil || server.ID == nil || server.HostName == nil {
+	if len(server.ProfileNames) == 0 || server.ID == nil || server.HostName == nil {
 		t.Fatal("Traffic Ops returned a representation of a server with null or undefined ID and/or Profile and/or Host Name")
 	}
-	originalProfile := *server.Profile
+	originalProfile := server.ProfileNames
 	delResp, _, err := TOSession.DeleteServer(*server.ID, client.RequestOptions{})
 	if err != nil {
 		t.Fatalf("cannot delete Server by ID %d: %v - %v", *server.ID, err, delResp)
 	}
 
-	*server.Profile = ""
-	server.ProfileID = nil
+	server.ProfileNames = []string{""}
 	_, reqInfo, _ := TOSession.CreateServer(server, client.RequestOptions{})
 	if reqInfo.StatusCode != 400 {
 		t.Fatalf("Expected status code: %v but got: %v", "400", reqInfo.StatusCode)
 	}
 
 	//Reverting it back for further tests
-	*server.Profile = originalProfile
+	server.ProfileNames = originalProfile
 	response, _, err := TOSession.CreateServer(server, client.RequestOptions{})
 	if err != nil {
 		t.Fatalf("could not create server: %v - alerts: %+v", err, response.Alerts)
@@ -950,10 +960,16 @@ func GetTestServersQueryParameters(t *testing.T) {
 		opts.QueryParameters.Del("status")
 	}
 
-	if s.ProfileID == nil {
+	opts.QueryParameters.Add("name", s.ProfileNames[0])
+	pr, _, err := TOSession.GetProfiles(opts)
+	if err != nil {
+		t.Fatalf("failed to query profile: %v", err)
+	}
+	if len(pr.Response) != 1 {
 		t.Error("Found server with no Profile ID")
 	} else {
-		opts.QueryParameters.Add("profileId", strconv.Itoa(*s.ProfileID))
+		profileID := pr.Response[0].ID
+		opts.QueryParameters.Add("profileId", strconv.Itoa(profileID))
 		if resp, _, err := TOSession.GetServers(opts); err != nil {
 			t.Errorf("Error getting servers by Profile ID: %v - alerts: %+v", err, resp.Alerts)
 		}
@@ -995,26 +1011,24 @@ func UniqueIPProfileTestServers(t *testing.T) {
 		}
 	}
 	_, _, err = TOSession.CreateServer(tc.ServerV40{
-		CommonServerProperties: tc.CommonServerProperties{
-			Cachegroup: server.Cachegroup,
-			CDNName:    server.CDNName,
-			DomainName: util.StrPtr("mydomain"),
-			FQDN:       util.StrPtr("myfqdn"),
-			FqdnTime:   time.Time{},
-			HostName:   util.StrPtr("myhostname"),
-			HTTPSPort:  util.IntPtr(443),
-			LastUpdated: &tc.TimeNoMod{
-				Time:  time.Time{},
-				Valid: false,
-			},
-			PhysLocation: server.PhysLocation,
-			Profile:      server.Profile,
-			StatusID:     server.StatusID,
-			Type:         server.Type,
-			UpdPending:   util.BoolPtr(false),
-			XMPPID:       &xmppID,
+		Cachegroup: server.Cachegroup,
+		CDNName:    server.CDNName,
+		DomainName: util.StrPtr("mydomain"),
+		FQDN:       util.StrPtr("myfqdn"),
+		FqdnTime:   time.Time{},
+		HostName:   util.StrPtr("myhostname"),
+		HTTPSPort:  util.IntPtr(443),
+		LastUpdated: &tc.TimeNoMod{
+			Time:  time.Time{},
+			Valid: false,
 		},
-		Interfaces: server.Interfaces,
+		PhysLocation: server.PhysLocation,
+		ProfileNames: server.ProfileNames,
+		StatusID:     server.StatusID,
+		Type:         server.Type,
+		UpdPending:   util.BoolPtr(false),
+		XMPPID:       &xmppID,
+		Interfaces:   server.Interfaces,
 	}, client.RequestOptions{})
 
 	if err == nil {
diff --git a/traffic_ops/testing/api/v4/tc-fixtures.json b/traffic_ops/testing/api/v4/tc-fixtures.json
index 4278eaaa0..7af7a485c 100644
--- a/traffic_ops/testing/api/v4/tc-fixtures.json
+++ b/traffic_ops/testing/api/v4/tc-fixtures.json
@@ -1771,12 +1771,6 @@
             "typeName": "HOST_REGEXP",
             "setNumber": 1,
             "pattern": ".*"
-        },
-        {
-            "dsName": "ds1",
-            "typeName": "HOST_REGEXP",
-            "setNumber": 3,
-            "pattern": ""
         }
     ],
     "deliveryservicesRequiredCapabilities": [
@@ -2646,7 +2640,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2690,7 +2684,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGEInCDN2",
+            "profileNames": ["EDGEInCDN2"],
             "rack": "",
             "revalPending": false,
             "status": "REPORTED",
@@ -2739,7 +2733,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2788,7 +2782,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2837,7 +2831,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2886,7 +2880,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2936,7 +2930,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2985,7 +2979,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3034,7 +3028,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MID2",
+            "profileNames": ["MID2"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3083,7 +3077,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3132,7 +3126,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3181,7 +3175,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3230,7 +3224,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "RASCAL1",
+            "profileNames": ["RASCAL1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3279,7 +3273,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3328,7 +3322,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "ONLINE",
@@ -3377,7 +3371,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MSO",
+            "profileNames": ["MSO"],
             "rack": "RR 119.02",
             "revalPending": true,
             "status": "REPORTED",
@@ -3426,7 +3420,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MSO",
+            "profileNames": ["MSO"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "ONLINE",
@@ -3475,7 +3469,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MSO-CDN2",
+            "profileNames": ["MSO-CDN2"],
             "rack": "RR 119.02",
             "revalPending": true,
             "status": "REPORTED",
@@ -3524,7 +3518,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3573,7 +3567,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3611,7 +3605,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3647,7 +3641,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3683,7 +3677,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3719,7 +3713,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3755,7 +3749,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3791,7 +3785,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3827,7 +3821,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3863,7 +3857,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3899,7 +3893,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3935,7 +3929,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3971,7 +3965,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4007,7 +4001,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4043,7 +4037,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4079,7 +4073,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4115,7 +4109,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4151,7 +4145,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4187,7 +4181,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4223,7 +4217,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4259,7 +4253,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4306,7 +4300,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4344,7 +4338,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4380,7 +4374,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4416,7 +4410,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4452,7 +4446,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4488,7 +4482,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4524,7 +4518,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4560,7 +4554,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4596,7 +4590,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4632,7 +4626,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4668,7 +4662,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4704,7 +4698,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": true,
             "status": "OFFLINE",
@@ -4740,7 +4734,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4776,7 +4770,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4805,7 +4799,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "revalPending": false,
             "status": "ONLINE",
             "tcpPort": 80,
@@ -4851,7 +4845,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1", "ATS_EDGE_TIER_CACHE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4900,7 +4894,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4949,7 +4943,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": true,
             "status": "REPORTED",
@@ -4998,7 +4992,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -5049,7 +5043,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -5100,7 +5094,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -5148,7 +5142,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
 			"revalUpdateTime": "2022-01-01T17:00:00-07:00",
@@ -5199,7 +5193,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
 			"revalUpdateTime": "2022-01-01T17:00:00-07:00",
 			"revalApplyTime": "1969-12-31T17:00:00-07:00",
@@ -5251,7 +5245,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
 			"revalUpdateTime": "2022-01-01T17:00:00-07:00",
@@ -5304,7 +5298,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
 			"revalUpdateTime": "2022-01-01T17:00:00-07:00",
 			"revalApplyTime": "1969-12-31T17:00:00-07:00",
@@ -5353,7 +5347,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "ADMIN_DOWN",
diff --git a/traffic_ops/traffic_ops_golang/server/servers_test.go b/traffic_ops/traffic_ops_golang/server/servers_test.go
index 4cbf215f3..94b33aa2a 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_test.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_test.go
@@ -20,7 +20,9 @@ package server
  */
 
 import (
+	"fmt"
 	"net/http"
+	"strings"
 	"testing"
 	"time"
 
@@ -29,74 +31,64 @@ import (
 
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
-	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/test"
-
 	"github.com/jmoiron/sqlx"
 	"gopkg.in/DATA-DOG/go-sqlmock.v1"
 )
 
 type ServerAndInterfaces struct {
-	Server    tc.Server
+	Server    tc.ServerV40
 	Interface tc.ServerInterfaceInfoV40
 }
 
 func getTestServers() []ServerAndInterfaces {
 	servers := []ServerAndInterfaces{}
-	testServer := tc.Server{
-		Cachegroup:     "Cachegroup",
-		CachegroupID:   1,
-		CDNID:          1,
-		CDNName:        "cdnName",
-		DomainName:     "domainName",
-		GUID:           "guid",
-		HostName:       "server1",
-		HTTPSPort:      443,
-		ID:             1,
-		ILOIPAddress:   "iloIpAddress",
-		ILOIPGateway:   "iloIpGateway",
-		ILOIPNetmask:   "iloIpNetmask",
-		ILOPassword:    "iloPassword",
-		ILOUsername:    "iloUsername",
-		InterfaceMtu:   9500,
-		InterfaceName:  "interfaceName",
-		IP6Address:     "ip6Address",
-		IP6IsService:   false,
-		IP6Gateway:     "ip6Gateway",
-		IPAddress:      "ipAddress",
-		IPIsService:    true,
-		IPGateway:      "ipGateway",
-		IPNetmask:      "ipNetmask",
-		LastUpdated:    tc.TimeNoMod{Time: time.Now()},
-		MgmtIPAddress:  "mgmtIpAddress",
-		MgmtIPGateway:  "mgmtIpGateway",
-		MgmtIPNetmask:  "mgmtIpNetmask",
-		OfflineReason:  "offlineReason",
-		PhysLocation:   "physLocation",
-		PhysLocationID: 1,
-		Profile:        "profile",
-		ProfileDesc:    "profileDesc",
-		ProfileID:      1,
-		Rack:           "rack",
-		RevalPending:   true,
-		RouterHostName: "routerHostName",
-		RouterPortName: "routerPortName",
-		Status:         "status",
-		StatusID:       1,
-		TCPPort:        80,
-		Type:           "EDGE",
-		TypeID:         1,
-		UpdPending:     true,
-		XMPPID:         "xmppId",
-		XMPPPasswd:     "xmppPasswd",
-	}
-
-	mtu := uint64(testServer.InterfaceMtu)
+	testServer := tc.ServerV40{
+		Cachegroup:        util.StrPtr("Cachegroup"),
+		CachegroupID:      util.IntPtr(1),
+		CDNID:             util.IntPtr(1),
+		CDNName:           util.StrPtr("cdnName"),
+		DomainName:        util.StrPtr("domainName"),
+		GUID:              util.StrPtr("guid"),
+		HostName:          util.StrPtr("server1"),
+		HTTPSPort:         util.IntPtr(443),
+		ID:                util.IntPtr(1),
+		ILOIPAddress:      util.StrPtr("iloIpAddress"),
+		ILOIPGateway:      util.StrPtr("iloIpGateway"),
+		ILOIPNetmask:      util.StrPtr("iloIpNetmask"),
+		ILOPassword:       util.StrPtr("iloPassword"),
+		ILOUsername:       util.StrPtr("iloUsername"),
+		LastUpdated:       &tc.TimeNoMod{Time: time.Now()},
+		MgmtIPAddress:     util.StrPtr("mgmtIpAddress"),
+		MgmtIPGateway:     util.StrPtr("mgmtIpGateway"),
+		MgmtIPNetmask:     util.StrPtr("mgmtIpNetmask"),
+		OfflineReason:     util.StrPtr("offlineReason"),
+		PhysLocation:      util.StrPtr("physLocation"),
+		PhysLocationID:    util.IntPtr(1),
+		ProfileNames:      []string{"profile"},
+		Rack:              util.StrPtr("rack"),
+		RevalPending:      util.BoolPtr(true),
+		Status:            util.StrPtr("status"),
+		StatusID:          util.IntPtr(1),
+		TCPPort:           util.IntPtr(80),
+		Type:              "EDGE",
+		TypeID:            util.IntPtr(1),
+		UpdPending:        util.BoolPtr(true),
+		XMPPID:            util.StrPtr("xmppId"),
+		XMPPPasswd:        util.StrPtr("xmppPasswd"),
+		StatusLastUpdated: &(time.Time{}),
+		ConfigUpdateTime:  &(time.Time{}),
+		ConfigApplyTime:   &(time.Time{}),
+		RevalUpdateTime:   &(time.Time{}),
+		RevalApplyTime:    &(time.Time{}),
+	}
+
+	mtu := uint64(9500)
 
 	iface := tc.ServerInterfaceInfoV40{
 		ServerInterfaceInfo: tc.ServerInterfaceInfo{
 			IPAddresses: []tc.ServerIPAddress{
 				{
-					Address:        testServer.IPAddress,
+					Address:        "ip6Address",
 					Gateway:        nil,
 					ServiceAddress: true,
 				},
@@ -104,24 +96,24 @@ func getTestServers() []ServerAndInterfaces {
 			MaxBandwidth: nil,
 			Monitor:      true,
 			MTU:          &mtu,
-			Name:         testServer.InterfaceName,
+			Name:         "interfaceName",
 		},
-		RouterHostName: testServer.RouterHostName,
-		RouterPortName: testServer.RouterPortName,
+		RouterHostName: "routerHostName",
+		RouterPortName: "routerPortName",
 	}
 
 	servers = append(servers, ServerAndInterfaces{Server: testServer, Interface: iface})
 
 	testServer2 := testServer
-	testServer2.Cachegroup = "cachegroup2"
-	testServer2.HostName = "server2"
-	testServer2.ID = 2
+	testServer2.Cachegroup = util.StrPtr("cachegroup2")
+	testServer2.HostName = util.StrPtr("server2")
+	testServer2.ID = util.IntPtr(2)
 	servers = append(servers, ServerAndInterfaces{Server: testServer2, Interface: iface})
 
 	testServer3 := testServer
-	testServer3.Cachegroup = "cachegroup3"
-	testServer3.HostName = "server3"
-	testServer3.ID = 3
+	testServer3.Cachegroup = util.StrPtr("cachegroup3")
+	testServer3.HostName = util.StrPtr("server3")
+	testServer3.ID = util.IntPtr(3)
 	servers = append(servers, ServerAndInterfaces{Server: testServer3, Interface: iface})
 
 	return servers
@@ -142,7 +134,7 @@ func TestUpdateServer(t *testing.T) {
 
 	rows := sqlmock.NewRows([]string{"type", "cdn_id"})
 	// note here that the cdnid is 5, which is not the same as the initial cdnid of the fist traffic server
-	rows.AddRow(testServers[0].Server.TypeID, 5)
+	rows.AddRow(*testServers[0].Server.TypeID, 5)
 	// Make it return a list of atleast one associated ds
 	dsrows := sqlmock.NewRows([]string{"array"})
 	dsrows.AddRow("{3}")
@@ -150,11 +142,11 @@ func TestUpdateServer(t *testing.T) {
 	mock.ExpectQuery("SELECT").WillReturnRows(rows)
 	mock.ExpectQuery("SELECT ARRAY").WillReturnRows(dsrows)
 
-	s := tc.CommonServerProperties{
-		CDNID:    &testServers[0].Server.CDNID,
+	s := tc.ServerV40{
+		CDNID:    testServers[0].Server.CDNID,
 		FqdnTime: time.Time{},
-		TypeID:   &testServers[0].Server.TypeID,
-		ID:       &testServers[0].Server.ID,
+		TypeID:   testServers[0].Server.TypeID,
+		ID:       testServers[0].Server.ID,
 	}
 
 	userErr, _, errCode := checkTypeChangeSafety(s, db.MustBegin())
@@ -182,7 +174,12 @@ func TestGetServersByCachegroup(t *testing.T) {
 	unfilteredCols := []string{"count"}
 	unfilteredRows := sqlmock.NewRows(unfilteredCols).AddRow(len(testServers))
 
-	cols := test.ColsFromStructByTag("db", tc.CommonServerProperties{})
+	cols := []string{"cachegroup", "cachegroup_id", "cdn_id", "cdn_name", "domain_name", "guid", "host_name",
+		"https_port", "id", "ilo_ip_address", "ilo_ip_gateway", "ilo_ip_netmask", "ilo_password", "ilo_username",
+		"last_updated", "mgmt_ip_address", "mgmt_ip_gateway", "mgmt_ip_netmask", "offline_reason", "phys_location",
+		"phys_location_id", "profile_name", "rack", "reval_pending", "revalidate_update_time", "revalidate_apply_time",
+		"status", "status_id", "tcp_port", "server_type", "server_type_id", "upd_pending", "config_update_time",
+		"config_apply_time", "xmpp_id", "xmpp_passwd", "status_last_updated"}
 	interfaceCols := []string{"max_bandwidth", "monitor", "mtu", "name", "server", "router_host_name", "router_port_name"}
 	rows := sqlmock.NewRows(cols)
 	interfaceRows := sqlmock.NewRows(interfaceCols)
@@ -195,49 +192,52 @@ func TestGetServersByCachegroup(t *testing.T) {
 	for _, srv := range testServers {
 		ts := srv.Server
 		rows = rows.AddRow(
-			ts.Cachegroup,
-			ts.CachegroupID,
-			ts.CDNID,
-			ts.CDNName,
-			ts.DomainName,
-			ts.GUID,
-			ts.HostName,
-			ts.HTTPSPort,
-			ts.ID,
-			ts.ILOIPAddress,
-			ts.ILOIPGateway,
-			ts.ILOIPNetmask,
-			ts.ILOPassword,
-			ts.ILOUsername,
-			ts.LastUpdated,
-			ts.MgmtIPAddress,
-			ts.MgmtIPGateway,
-			ts.MgmtIPNetmask,
-			ts.OfflineReason,
-			ts.PhysLocation,
-			ts.PhysLocationID,
-			ts.Profile,
-			ts.ProfileDesc,
-			ts.ProfileID,
-			ts.Rack,
-			ts.RevalPending,
-			ts.Status,
-			ts.StatusID,
-			ts.TCPPort,
+			*ts.Cachegroup,
+			*ts.CachegroupID,
+			*ts.CDNID,
+			*ts.CDNName,
+			*ts.DomainName,
+			*ts.GUID,
+			*ts.HostName,
+			*ts.HTTPSPort,
+			*ts.ID,
+			*ts.ILOIPAddress,
+			*ts.ILOIPGateway,
+			*ts.ILOIPNetmask,
+			*ts.ILOPassword,
+			*ts.ILOUsername,
+			*ts.LastUpdated,
+			*ts.MgmtIPAddress,
+			*ts.MgmtIPGateway,
+			*ts.MgmtIPNetmask,
+			*ts.OfflineReason,
+			*ts.PhysLocation,
+			*ts.PhysLocationID,
+			fmt.Sprintf("{%s}", strings.Join(ts.ProfileNames, ",")),
+			*ts.Rack,
+			*ts.RevalPending,
+			*ts.RevalUpdateTime,
+			*ts.RevalApplyTime,
+			*ts.Status,
+			*ts.StatusID,
+			*ts.TCPPort,
 			ts.Type,
-			ts.TypeID,
-			ts.UpdPending,
-			ts.XMPPID,
-			ts.XMPPPasswd,
+			*ts.TypeID,
+			*ts.UpdPending,
+			*ts.ConfigUpdateTime,
+			*ts.ConfigApplyTime,
+			*ts.XMPPID,
+			*ts.XMPPPasswd,
+			*ts.StatusLastUpdated,
 		)
 		interfaceRows = interfaceRows.AddRow(
 			srv.Interface.MaxBandwidth,
 			srv.Interface.Monitor,
 			srv.Interface.MTU,
 			srv.Interface.Name,
-			ts.ID,
-			srv.Server.RouterHostName,
-			srv.Server.RouterPortName,
+			*ts.ID,
+			srv.Interface.RouterHostName,
+			srv.Interface.RouterPortName,
 		)
 
 		for _, ip := range srv.Interface.IPAddresses {
@@ -245,7 +245,7 @@ func TestGetServersByCachegroup(t *testing.T) {
 				ip.Address,
 				ip.Gateway,
 				ip.ServiceAddress,
-				ts.ID,
+				*ts.ID,
 				srv.Interface.Name,
 			)
 		}
@@ -288,14 +288,19 @@ func TestGetMidServers(t *testing.T) {
 	testServers := getTestServers()
 	testServers = testServers[0:2]
 
-	testServers[1].Server.Cachegroup = "parentCacheGroup"
-	testServers[1].Server.CachegroupID = 2
+	testServers[1].Server.Cachegroup = util.StrPtr("parentCacheGroup")
+	testServers[1].Server.CachegroupID = util.IntPtr(2)
 	testServers[1].Server.Type = "MID"
 
 	unfilteredCols := []string{"count"}
 	unfilteredRows := sqlmock.NewRows(unfilteredCols).AddRow(len(testServers))
 
-	cols := test.ColsFromStructByTag("db", tc.CommonServerProperties{})
+	cols := []string{"cachegroup", "cachegroup_id", "cdn_id", "cdn_name", "domain_name", "guid", "host_name",
+		"https_port", "id", "ilo_ip_address", "ilo_ip_gateway", "ilo_ip_netmask", "ilo_password", "ilo_username",
+		"last_updated", "mgmt_ip_address", "mgmt_ip_gateway", "mgmt_ip_netmask", "offline_reason", "phys_location",
+		"phys_location_id", "profile_name", "rack", "reval_pending", "revalidate_update_time", "revalidate_apply_time",
+		"status", "status_id", "tcp_port", "server_type", "server_type_id", "upd_pending", "config_update_time",
+		"config_apply_time", "xmpp_id", "xmpp_passwd", "status_last_updated"}
 	interfaceCols := []string{"max_bandwidth", "monitor", "mtu", "name", "server", "router_host_name", "router_port_name"}
 	rows := sqlmock.NewRows(cols)
 	interfaceRows := sqlmock.NewRows(interfaceCols)
@@ -306,49 +311,52 @@ func TestGetMidServers(t *testing.T) {
 	for _, srv := range testServers {
 		ts := srv.Server
 		rows = rows.AddRow(
-			ts.Cachegroup,
-			ts.CachegroupID,
-			ts.CDNID,
-			ts.CDNName,
-			ts.DomainName,
-			ts.GUID,
-			ts.HostName,
-			ts.HTTPSPort,
-			ts.ID,
-			ts.ILOIPAddress,
-			ts.ILOIPGateway,
-			ts.ILOIPNetmask,
-			ts.ILOPassword,
-			ts.ILOUsername,
-			ts.LastUpdated,
-			ts.MgmtIPAddress,
-			ts.MgmtIPGateway,
-			ts.MgmtIPNetmask,
-			ts.OfflineReason,
-			ts.PhysLocation,
-			ts.PhysLocationID,
-			ts.Profile,
-			ts.ProfileDesc,
-			ts.ProfileID,
-			ts.Rack,
-			ts.RevalPending,
-			ts.Status,
-			ts.StatusID,
-			ts.TCPPort,
+			*ts.Cachegroup,
+			*ts.CachegroupID,
+			*ts.CDNID,
+			*ts.CDNName,
+			*ts.DomainName,
+			*ts.GUID,
+			*ts.HostName,
+			*ts.HTTPSPort,
+			*ts.ID,
+			*ts.ILOIPAddress,
+			*ts.ILOIPGateway,
+			*ts.ILOIPNetmask,
+			*ts.ILOPassword,
+			*ts.ILOUsername,
+			*ts.LastUpdated,
+			*ts.MgmtIPAddress,
+			*ts.MgmtIPGateway,
+			*ts.MgmtIPNetmask,
+			*ts.OfflineReason,
+			*ts.PhysLocation,
+			*ts.PhysLocationID,
+			fmt.Sprintf("{%s}", strings.Join(ts.ProfileNames, ",")),
+			*ts.Rack,
+			*ts.RevalPending,
+			*ts.RevalUpdateTime,
+			*ts.RevalApplyTime,
+			*ts.Status,
+			*ts.StatusID,
+			*ts.TCPPort,
 			ts.Type,
-			ts.TypeID,
-			ts.UpdPending,
-			ts.XMPPID,
-			ts.XMPPPasswd,
+			*ts.TypeID,
+			*ts.UpdPending,
+			*ts.ConfigUpdateTime,
+			*ts.ConfigApplyTime,
+			*ts.XMPPID,
+			*ts.XMPPPasswd,
+			*ts.StatusLastUpdated,
 		)
 		interfaceRows = interfaceRows.AddRow(
 			srv.Interface.MaxBandwidth,
 			srv.Interface.Monitor,
 			srv.Interface.MTU,
 			srv.Interface.Name,
-			ts.ID,
-			srv.Server.RouterHostName,
-			srv.Server.RouterPortName,
+			*ts.ID,
+			srv.Interface.RouterHostName,
+			srv.Interface.RouterPortName,
 		)
 
 		for _, ip := range srv.Interface.IPAddresses {
@@ -356,7 +364,7 @@ func TestGetMidServers(t *testing.T) {
 				ip.Address,
 				ip.Gateway,
 				ip.ServiceAddress,
-				ts.ID,
+				*ts.ID,
 				srv.Interface.Name,
 			)
 		}
@@ -371,15 +379,19 @@ func TestGetMidServers(t *testing.T) {
 	v := map[string]string{}
 
 	user := auth.CurrentUser{}
-	version := api.Version{Major: 3, Minor: 0}
+	version := api.Version{Major: 4, Minor: 0}
 	servers, _, userErr, sysErr, errCode, _ := getServers(nil, v, db.MustBegin(), &user, false, version)
 
 	if userErr != nil || sysErr != nil {
 		t.Errorf("getServers expected: no errors, actual: %v %v with status: %s", userErr, sysErr, http.StatusText(errCode))
 	}
 
-	cols2 := test.ColsFromStructByTag("db", tc.CommonServerProperties{})
-	cols2 = append(cols2, "config_update_time", "config_apply_time", "revalidate_update_time", "revalidate_apply_time")
+	cols2 := []string{"cachegroup", "cachegroup_id", "cdn_id", "cdn_name", "domain_name", "guid", "host_name",
+		"https_port", "id", "ilo_ip_address", "ilo_ip_gateway", "ilo_ip_netmask", "ilo_password", "ilo_username",
+		"last_updated", "mgmt_ip_address", "mgmt_ip_gateway", "mgmt_ip_netmask", "offline_reason", "phys_location",
+		"phys_location_id", "profile_name", "rack", "reval_pending", "revalidate_update_time", "revalidate_apply_time",
+		"status", "status_id", "tcp_port", "server_type", "server_type_id", "upd_pending", "config_update_time",
+		"config_apply_time", "xmpp_id", "xmpp_passwd", "status_last_updated"}
 	rows2 := sqlmock.NewRows(cols2)
 
 	cgs := []tc.CacheGroup{}
@@ -444,44 +456,43 @@ func TestGetMidServers(t *testing.T) {
 	}
 	*ts.ID = *ts.ID + 1
 	rows2 = rows2.AddRow(
-		ts.Cachegroup,
-		ts.CachegroupID,
-		ts.CDNID,
-		ts.CDNName,
-		ts.DomainName,
-		ts.GUID,
-		ts.HostName,
-		ts.HTTPSPort,
-		ts.ID,
-		ts.ILOIPAddress,
-		ts.ILOIPGateway,
-		ts.ILOIPNetmask,
-		ts.ILOPassword,
-		ts.ILOUsername,
-		ts.LastUpdated,
-		ts.MgmtIPAddress,
-		ts.MgmtIPGateway,
-		ts.MgmtIPNetmask,
-		ts.OfflineReason,
-		ts.PhysLocation,
-		ts.PhysLocationID,
-		ts.Profile,
-		ts.ProfileDesc,
-		ts.ProfileID,
-		ts.Rack,
-		ts.RevalPending,
-		ts.Status,
-		ts.StatusID,
-		ts.TCPPort,
+		*ts.Cachegroup,
+		*ts.CachegroupID,
+		*ts.CDNID,
+		*ts.CDNName,
+		*ts.DomainName,
+		*ts.GUID,
+		*ts.HostName,
+		*ts.HTTPSPort,
+		*ts.ID,
+		*ts.ILOIPAddress,
+		*ts.ILOIPGateway,
+		*ts.ILOIPNetmask,
+		*ts.ILOPassword,
+		*ts.ILOUsername,
+		*ts.LastUpdated,
+		*ts.MgmtIPAddress,
+		*ts.MgmtIPGateway,
+		*ts.MgmtIPNetmask,
+		*ts.OfflineReason,
+		*ts.PhysLocation,
+		*ts.PhysLocationID,
+		fmt.Sprintf("{%s}", strings.Join(ts.ProfileNames, ",")),
+		*ts.Rack,
+		*ts.RevalPending,
+		*ts.RevalUpdateTime,
+		*ts.RevalApplyTime,
+		*ts.Status,
+		*ts.StatusID,
+		*ts.TCPPort,
 		ts.Type,
-		ts.TypeID,
-		ts.UpdPending,
-		ts.XMPPID,
-		ts.XMPPPasswd,
-		ts.ConfigUpdateTime,
-		ts.ConfigApplyTime,
-		ts.RevalUpdateTime,
-		ts.RevalApplyTime,
+		*ts.TypeID,
+		*ts.UpdPending,
+		*ts.ConfigUpdateTime,
+		*ts.ConfigApplyTime,
+		*ts.XMPPID,
+		*ts.XMPPPasswd,
+		*ts.StatusLastUpdated,
 	)
 
 	mock.ExpectBegin()
diff --git a/traffic_portal/test/integration/CommonUtils/API.ts b/traffic_portal/test/integration/CommonUtils/API.ts
index 8e1a8ebea..8e4375660 100644
--- a/traffic_portal/test/integration/CommonUtils/API.ts
+++ b/traffic_portal/test/integration/CommonUtils/API.ts
@@ -337,6 +337,11 @@ export class API {
                 }
             }
         }
+        if(hasProperty(data, 'profileNames', "Array")){
+            for (const index in data.profileNames) {
+                data.profileNames[index] = data.profileNames[index]+randomize
+            }
+        }
     }
 
     public async UseAPI(data: Array<APIData>): Promise<void> {
diff --git a/traffic_portal/test/integration/Data/deliveryservices.ts b/traffic_portal/test/integration/Data/deliveryservices.ts
index 153ea8feb..c6f931689 100644
--- a/traffic_portal/test/integration/Data/deliveryservices.ts
+++ b/traffic_portal/test/integration/Data/deliveryservices.ts
@@ -266,7 +266,7 @@ export const deliveryservices = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 0,
-					profileId: 0,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -291,12 +291,6 @@ export const deliveryservices = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				}
diff --git a/traffic_portal/test/integration/Data/physlocations.ts b/traffic_portal/test/integration/Data/physlocations.ts
index b76aa5818..e6628ddd2 100644
--- a/traffic_portal/test/integration/Data/physlocations.ts
+++ b/traffic_portal/test/integration/Data/physlocations.ts
@@ -201,7 +201,7 @@ export const physLocations = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 0,
-					profileId: 0,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -226,12 +226,6 @@ export const physLocations = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				}
diff --git a/traffic_portal/test/integration/Data/servers.ts b/traffic_portal/test/integration/Data/servers.ts
index 96f822ad9..58490dea9 100644
--- a/traffic_portal/test/integration/Data/servers.ts
+++ b/traffic_portal/test/integration/Data/servers.ts
@@ -312,7 +312,7 @@ export const servers = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 0,
-					profileId: 0,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -337,12 +337,6 @@ export const servers = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				},
@@ -384,7 +378,7 @@ export const servers = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 0,
-					profileId: 0,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -409,12 +403,6 @@ export const servers = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				},
@@ -456,7 +444,7 @@ export const servers = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 0,
-					profileId: 0,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -481,12 +469,6 @@ export const servers = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				},
@@ -528,7 +510,7 @@ export const servers = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 0,
-					profileId: 0,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -553,12 +535,6 @@ export const servers = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				}
diff --git a/traffic_portal/test/integration/Data/serverservercapabilities.ts b/traffic_portal/test/integration/Data/serverservercapabilities.ts
index 46d4371b0..222fd8ace 100644
--- a/traffic_portal/test/integration/Data/serverservercapabilities.ts
+++ b/traffic_portal/test/integration/Data/serverservercapabilities.ts
@@ -269,7 +269,7 @@ export const serverServerCapabilities = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 0,
-					profileId: 0,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -294,12 +294,6 @@ export const serverServerCapabilities = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				},
@@ -341,7 +335,7 @@ export const serverServerCapabilities = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 2,
-					profileId: 9,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -366,12 +360,6 @@ export const serverServerCapabilities = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				},
@@ -413,7 +401,7 @@ export const serverServerCapabilities = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 2,
-					profileId: 9,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -438,12 +426,6 @@ export const serverServerCapabilities = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				},
@@ -485,7 +467,7 @@ export const serverServerCapabilities = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 2,
-					profileId: 9,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -510,12 +492,6 @@ export const serverServerCapabilities = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				},
@@ -557,7 +533,7 @@ export const serverServerCapabilities = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 2,
-					profileId: 9,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -582,12 +558,6 @@ export const serverServerCapabilities = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				},
@@ -629,7 +599,7 @@ export const serverServerCapabilities = {
 					mgmtIpNetmask: "",
 					offlineReason: "",
 					physLocationId: 2,
-					profileId: 9,
+					profileNames: ["testProfile"],
 					routerHostName: "",
 					routerPortName: "",
 					statusId: 3,
@@ -654,12 +624,6 @@ export const serverServerCapabilities = {
 							queryKey: "name",
 							queryValue: "testCG",
 							replace: "cachegroupId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "testProfile",
-							replace: "profileId"
 						}
 					]
 				}
diff --git a/traffic_portal/test/integration/Data/topologies.ts b/traffic_portal/test/integration/Data/topologies.ts
index 7ad779e5f..1edc77406 100644
--- a/traffic_portal/test/integration/Data/topologies.ts
+++ b/traffic_portal/test/integration/Data/topologies.ts
@@ -352,7 +352,7 @@ export const topologies = {
                     mgmtIpNetmask: "",
                     offlineReason: "",
                     physLocationId: 2,
-                    profileId: 9,
+                    profileNames: ["TopTestPf"],
                     routerHostName: "",
                     routerPortName: "",
                     statusId: 3,
@@ -376,12 +376,6 @@ export const topologies = {
 							queryKey: "name",
 							queryValue: "TopTestCDN",
 							replace: "cdnId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "TopTestPf",
-							replace: "profileId"
 						}
                     ],
                     updPending: false
@@ -424,7 +418,7 @@ export const topologies = {
                     mgmtIpNetmask: "",
                     offlineReason: "",
                     physLocationId: 2,
-                    profileId: 9,
+                    profileNames: ["TopTestPf"],
                     routerHostName: "",
                     routerPortName: "",
                     statusId: 3,
@@ -448,12 +442,6 @@ export const topologies = {
 							queryKey: "name",
 							queryValue: "TopTestCDN",
 							replace: "cdnId"
-						},
-						{
-							route: "/profiles",
-							queryKey: "name",
-							queryValue: "TopTestPf",
-							replace: "profileId"
 						}
                     ],
                     updPending: false