Repository: apache/trafficcontrol
Pull request #6544 (2022-02-08 17:26:05)

Layered profile `servers/` API {GET, POST,  PUT & DELETE}
<!--
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.
-->

PR to update API servers/ and servers/{id} [GET, POST, PUT, DELETE only] with respect to server layered_profile feature https://github.com/apache/trafficcontrol/pull/6095

<!-- **^ 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.
-->
- Documentation
- Traffic Ops
- Traffic Control Client
- Traffic Ops ORT
- Traffic Portal
-  CDN-In-A-Box

## 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. -->
Test GET, POST servers/, GET servers/{id}, PUT, DELETE servers/{id} with curl cmds and correct JSON requests.
For v4, POST and PUT have the following in json struct,
```
"profileNames": ["TRAFFIC_STATS"] 
OR 
"profileNames": ["TRAFFIC_PORTAL","GLOBAL"]
``` 
For v3, POST and PUT have the following in json struct,
```
        "profile": "TRAFFIC_OPS",
	"profileDesc": "Traffic Ops profile",
	"profileId": 3,
```

## 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.
-->


ocket8888: These TP test failures are consistent and real. I [looks like](https://github.com/apache/trafficcontrol/runs/5412320747?check_suite_focus=true#step:11:265) it's failing to create servers because of the new format/requirements.
rimashah25: > These TP test failures are consistent and real. I [looks like](https://github.com/apache/trafficcontrol/runs/5412320747?check_suite_focus=true#step:11:265) it's failing to create servers because of the new format/requirements.

Yes, I am aware and have been working on correcting that. The latest change should hopefully resolve the failure.
rimashah25: > I think the TP code gets a lot simpler if you don't ever allow servers to have the `profileName` property and just always deal with the real data.

I am not sure I follow. If you are available for huddle, lets do it.
zrhoffman: Looks like all of the review comments are addressed, is anything needed before #6544 can be rechecked for mergeworthiness?
zrhoffman: There are conflicts now that #6569 is merged.
ocket8888: I'm assuming based on the fact that @zrhoffman's last comment was about a week ago and all those comments appear to have been addressed that he's cool with me merging this.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc498e142..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.
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/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/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/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/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/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/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/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/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/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 744566dac..7af7a485c 100644
--- a/traffic_ops/testing/api/v4/tc-fixtures.json
+++ b/traffic_ops/testing/api/v4/tc-fixtures.json
@@ -2640,7 +2640,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2684,7 +2684,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGEInCDN2",
+            "profileNames": ["EDGEInCDN2"],
             "rack": "",
             "revalPending": false,
             "status": "REPORTED",
@@ -2733,7 +2733,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2782,7 +2782,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2831,7 +2831,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2880,7 +2880,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2930,7 +2930,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -2979,7 +2979,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3028,7 +3028,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MID2",
+            "profileNames": ["MID2"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3077,7 +3077,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3126,7 +3126,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3175,7 +3175,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3224,7 +3224,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "RASCAL1",
+            "profileNames": ["RASCAL1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3273,7 +3273,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3322,7 +3322,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "ONLINE",
@@ -3371,7 +3371,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MSO",
+            "profileNames": ["MSO"],
             "rack": "RR 119.02",
             "revalPending": true,
             "status": "REPORTED",
@@ -3420,7 +3420,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MSO",
+            "profileNames": ["MSO"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "ONLINE",
@@ -3469,7 +3469,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MSO-CDN2",
+            "profileNames": ["MSO-CDN2"],
             "rack": "RR 119.02",
             "revalPending": true,
             "status": "REPORTED",
@@ -3518,7 +3518,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3567,7 +3567,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3605,7 +3605,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3641,7 +3641,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3677,7 +3677,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3713,7 +3713,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3749,7 +3749,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3785,7 +3785,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3821,7 +3821,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3857,7 +3857,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3893,7 +3893,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3929,7 +3929,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -3965,7 +3965,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4001,7 +4001,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4037,7 +4037,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4073,7 +4073,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4109,7 +4109,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4145,7 +4145,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4181,7 +4181,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4217,7 +4217,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4253,7 +4253,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4300,7 +4300,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4338,7 +4338,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4374,7 +4374,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4410,7 +4410,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4446,7 +4446,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4482,7 +4482,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_EDGE",
+            "profileNames": ["CDN2_EDGE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4518,7 +4518,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4554,7 +4554,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4590,7 +4590,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4626,7 +4626,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4662,7 +4662,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4698,7 +4698,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": true,
             "status": "OFFLINE",
@@ -4734,7 +4734,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4770,7 +4770,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "CDN2_MID",
+            "profileNames": ["CDN2_MID"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4799,7 +4799,7 @@
                 }
             ],
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "revalPending": false,
             "status": "ONLINE",
             "tcpPort": 80,
@@ -4845,7 +4845,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1", "ATS_EDGE_TIER_CACHE"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4894,7 +4894,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "MID1",
+            "profileNames": ["MID1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -4943,7 +4943,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": true,
             "status": "REPORTED",
@@ -4992,7 +4992,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -5043,7 +5043,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -5094,7 +5094,7 @@
             "mgmtIpNetmask": "",
             "offlineReason": null,
             "physLocation": "Denver",
-            "profile": "EDGE1",
+            "profileNames": ["EDGE1"],
             "rack": "RR 119.02",
             "revalPending": false,
             "status": "REPORTED",
@@ -5142,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",
@@ -5193,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",
@@ -5245,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",
@@ -5298,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",
@@ -5347,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