This is an automated email from the ASF dual-hosted git repository.

ocket8888 pushed a commit to branch 5.0.x
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/5.0.x by this push:
     new 4b80904  Validate the assignment of ORG servers to topology-based DSes 
(#5260) (#5271)
4b80904 is described below

commit 4b809049075a523e9e234ec464128ad74eaeab06
Author: ocket8888 <[email protected]>
AuthorDate: Wed Nov 11 10:20:51 2020 -0700

    Validate the assignment of ORG servers to topology-based DSes (#5260) 
(#5271)
    
    * Validate the assignment of ORG servers to topology-based DSes
    
    The ORG server cachegroups must belong to the DS's topology.
    
    * Address review comments
    
    (cherry picked from commit b938e6755865d08fd64f3ae6bcef65321fac6bb1)
    
    Co-authored-by: Rawlin Peters <[email protected]>
---
 CHANGELOG.md                                       |   1 +
 lib/go-tc/servers.go                               |  11 +-
 .../testing/api/v3/deliveryserviceservers_test.go  | 134 +++++++++++++----
 .../servers_to_deliveryservice_assignment_test.go  |  85 ++++++++++-
 .../traffic_ops_golang/dbhelpers/db_helpers.go     | 126 ++++++++--------
 .../deliveryservice/servers/servers.go             | 159 ++++++++++++---------
 .../deliveryservice/servers/servers_test.go        |  11 +-
 .../server/servers_assignment.go                   |  45 ++++++
 8 files changed, 389 insertions(+), 183 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d4b73b..c883cff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,6 +65,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 - Added `--traffic_ops_insecure=<0|1>` optional option to traffic_ops_ort.pl
 - Added User-Agent string to Traffic Router log output.
 - Added default sort logic to GET API calls using Read()
+- Traffic Ops: added validation for assigning ORG servers to topology-based 
delivery services
 
 ### Fixed
 - Fixed #5188 - DSR (delivery service request) incorrectly marked as complete 
and error message not displaying when DSR fulfilled and DS update fails in 
Traffic Portal. [Related Github 
issue](https://github.com/apache/trafficcontrol/issues/5188)
diff --git a/lib/go-tc/servers.go b/lib/go-tc/servers.go
index 975e016..051fc72 100644
--- a/lib/go-tc/servers.go
+++ b/lib/go-tc/servers.go
@@ -739,11 +739,12 @@ type ServerPutStatus struct {
 }
 
 type ServerInfo struct {
-       CachegroupID int    `json:"cachegroupId" db:"cachegroup_id"`
-       CDNID        int    `json:"cdnId" db:"cdn_id"`
-       DomainName   string `json:"domainName" db:"domain_name"`
-       HostName     string `json:"hostName" db:"host_name"`
-       Type         string `json:"type" db:"server_type"`
+       Cachegroup   string
+       CachegroupID int
+       CDNID        int
+       DomainName   string
+       HostName     string
+       Type         string
 }
 
 type ServerDetail struct {
diff --git a/traffic_ops/testing/api/v3/deliveryserviceservers_test.go 
b/traffic_ops/testing/api/v3/deliveryserviceservers_test.go
index 69324c1..836f199 100644
--- a/traffic_ops/testing/api/v3/deliveryserviceservers_test.go
+++ b/traffic_ops/testing/api/v3/deliveryserviceservers_test.go
@@ -31,6 +31,7 @@ func TestDeliveryServiceServers(t *testing.T) {
        WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
DeliveryServices}, func() {
                DeleteTestDeliveryServiceServers(t)
                AssignServersToTopologyBasedDeliveryService(t)
+               AssignOriginsToTopologyBasedDeliveryServices(t)
                AssignServersToNonTopologyBasedDeliveryServiceThatUsesMidTier(t)
        })
 }
@@ -55,10 +56,10 @@ func AssignServersToTopologyBasedDeliveryService(t 
*testing.T) {
        if ds[0].Topology == nil {
                t.Fatal("expected delivery service: 'ds-top' to have a non-nil 
Topology, actual: nil")
        }
-       serversResp, _, err := TOSession.GetServers(nil)
-       servers := []tc.Server{}
-       for _, s := range serversResp {
-               if s.CDNID == *ds[0].CDNID && s.Type == 
tc.CacheTypeEdge.String() {
+       serversResp, _, err := TOSession.GetServersWithHdr(nil, nil)
+       servers := []tc.ServerV30{}
+       for _, s := range serversResp.Response {
+               if s.CDNID != nil && *s.CDNID == *ds[0].CDNID && s.Type == 
tc.CacheTypeEdge.String() {
                        servers = append(servers, s)
                }
        }
@@ -67,8 +68,8 @@ func AssignServersToTopologyBasedDeliveryService(t 
*testing.T) {
        }
        serverNames := []string{}
        for _, s := range servers {
-               if s.CDNID == *ds[0].CDNID && s.Type == 
tc.CacheTypeEdge.String() {
-                       serverNames = append(serverNames, s.HostName)
+               if s.CDNID != nil && *s.CDNID == *ds[0].CDNID && s.Type == 
tc.CacheTypeEdge.String() {
+                       serverNames = append(serverNames, *s.HostName)
                } else {
                        t.Fatalf("expected only EDGE servers in cdn '%s', 
actual: %v", *ds[0].CDNName, servers)
                }
@@ -81,7 +82,7 @@ func AssignServersToTopologyBasedDeliveryService(t 
*testing.T) {
                t.Fatalf("assigning servers to topology-based delivery service 
- expected: 400-level status code, actual: %d", reqInf.StatusCode)
        }
 
-       _, reqInf, err = TOSession.CreateDeliveryServiceServers(*ds[0].ID, 
[]int{servers[0].ID}, false)
+       _, reqInf, err = TOSession.CreateDeliveryServiceServers(*ds[0].ID, 
[]int{*servers[0].ID}, false)
        if err == nil {
                t.Fatal("creating deliveryserviceserver assignment for 
topology-based delivery service - expected: error, actual: nil error")
        }
@@ -89,6 +90,75 @@ func AssignServersToTopologyBasedDeliveryService(t 
*testing.T) {
                t.Fatalf("creating deliveryserviceserver assignment for 
topology-based delivery service - expected: 400-level status code, actual: %d", 
reqInf.StatusCode)
        }
 }
+
+func AssignOriginsToTopologyBasedDeliveryServices(t *testing.T) {
+       // attempt to assign ORG server to a topology-based DS while the ORG 
server's cachegroup doesn't belong to the topology
+       params := url.Values{}
+       params.Add("hostName", "denver-mso-org-01")
+       resp, _, err := TOSession.GetServersWithHdr(&params, nil)
+       if err != nil {
+               t.Fatalf("unable to GET server: %v", err)
+       }
+       if len(resp.Response) != 1 {
+               t.Fatalf("GET server expected length: 1, actual: %d", 
len(resp.Response))
+       }
+       orgServer := resp.Response[0]
+       _, reqInf, err := 
TOSession.AssignServersToDeliveryService([]string{*orgServer.HostName}, 
"ds-top-req-cap")
+       if err == nil {
+               t.Fatal("assigning ORG server to topology-based delivery 
service while the ORG server's cachegroup does not belong to the topology - 
expected: error, actual: nil error")
+       }
+       if reqInf.StatusCode < http.StatusBadRequest || reqInf.StatusCode >= 
http.StatusInternalServerError {
+               t.Fatalf("assigning ORG server to topology-based delivery 
service while the ORG server's cachegroup does not belong to the topology - 
expected: 400-level status code, actual: %d", reqInf.StatusCode)
+       }
+       params = url.Values{}
+       params.Set("xmlId", "ds-top-req-cap")
+       ds, _, err := TOSession.GetDeliveryServicesV30WithHdr(nil, params)
+       if err != nil {
+               t.Fatalf("cannot GET delivery service 'ds-top-req-cap': %s", 
err.Error())
+       }
+       if len(ds) != 1 {
+               t.Fatalf("expected one delivery service: 'ds-top-req-cap', 
actual: %v", len(ds))
+       }
+       if ds[0].Topology == nil {
+               t.Fatal("expected delivery service: 'ds-top-req-cap' to have a 
non-nil Topology, actual: nil")
+       }
+       _, reqInf, err = TOSession.CreateDeliveryServiceServers(*ds[0].ID, 
[]int{*orgServer.ID}, false)
+       if err == nil {
+               t.Fatal("creating deliveryserviceserver assignment for ORG 
server to topology-based delivery service while the ORG server's cachegroup 
does not belong to the topology - expected: error, actual: nil error")
+       }
+       if reqInf.StatusCode < http.StatusBadRequest || reqInf.StatusCode >= 
http.StatusInternalServerError {
+               t.Fatalf("creating deliveryserviceserver assignment for ORG 
server to topology-based delivery service while the ORG server's cachegroup 
does not belong to the topology - expected: 400-level status code, actual: %d", 
reqInf.StatusCode)
+       }
+
+       // attempt to assign ORG server to a topology-based DS while the ORG 
server's cachegroup belongs to the topology
+       _, reqInf, err = 
TOSession.AssignServersToDeliveryService([]string{*orgServer.HostName}, 
"ds-top")
+       if err != nil {
+               t.Fatalf("assigning ORG server to topology-based delivery 
service while the ORG server's cachegroup belongs to the topology - expected: 
no error, actual: %v", err)
+       }
+       if reqInf.StatusCode < http.StatusOK || reqInf.StatusCode >= 
http.StatusMultipleChoices {
+               t.Fatalf("assigning ORG server to topology-based delivery 
service while the ORG server's cachegroup belongs to the topology - expected: 
200-level status code, actual: %d", reqInf.StatusCode)
+       }
+       params = url.Values{}
+       params.Set("xmlId", "ds-top")
+       ds, _, err = TOSession.GetDeliveryServicesV30WithHdr(nil, params)
+       if err != nil {
+               t.Fatalf("cannot GET delivery service 'ds-top': %s", 
err.Error())
+       }
+       if len(ds) != 1 {
+               t.Fatalf("expected one delivery service: 'ds-top', actual: %v", 
len(ds))
+       }
+       if ds[0].Topology == nil {
+               t.Fatal("expected delivery service: 'ds-top' to have a non-nil 
Topology, actual: nil")
+       }
+       _, reqInf, err = TOSession.CreateDeliveryServiceServers(*ds[0].ID, 
[]int{*orgServer.ID}, true)
+       if err != nil {
+               t.Fatalf("creating deliveryserviceserver assignment for ORG 
server to topology-based delivery service while the ORG server's cachegroup 
belongs to the topology - expected: no error, actual: %v", err)
+       }
+       if reqInf.StatusCode < http.StatusOK || reqInf.StatusCode >= 
http.StatusMultipleChoices {
+               t.Fatalf("creating deliveryserviceserver assignment for ORG 
server to topology-based delivery service while the ORG server's cachegroup 
belongs to the topology - expected: 200-level status code, actual: %d", 
reqInf.StatusCode)
+       }
+}
+
 func AssignServersToNonTopologyBasedDeliveryServiceThatUsesMidTier(t 
*testing.T) {
        params := url.Values{}
        params.Set("xmlId", "ds1")
@@ -178,24 +248,28 @@ func 
CreateTestDeliveryServiceServersWithRequiredCapabilities(t *testing.T) {
                t.Run(ctc.description, func(t *testing.T) {
                        params := url.Values{}
                        params.Add("hostName", ctc.serverName)
-                       resp, _, err := TOSession.GetServers(&params)
+                       resp, _, err := TOSession.GetServersWithHdr(&params, 
nil)
                        if err != nil {
                                t.Fatalf("cannot GET Server by hostname: %v", 
err)
                        }
-                       server := resp[0]
+                       servers := resp.Response
+                       server := servers[0]
+                       if server.ID == nil {
+                               t.Fatalf("server %s had nil ID", ctc.serverName)
+                       }
 
                        _, _, err = 
TOSession.CreateDeliveryServicesRequiredCapability(ctc.capability)
                        if err != nil {
                                t.Fatalf("*POST delivery service required 
capability: %v", err)
                        }
 
-                       ctc.ssc.ServerID = &server.ID
+                       ctc.ssc.ServerID = server.ID
                        _, _, err = 
TOSession.CreateServerServerCapability(ctc.ssc)
                        if err != nil {
                                t.Fatalf("could not POST the server capability 
%v to server %v: %v", *ctc.ssc.ServerCapability, *ctc.ssc.Server, err)
                        }
 
-                       _, _, got := 
TOSession.CreateDeliveryServiceServers(*ctc.capability.DeliveryServiceID, 
[]int{server.ID}, true)
+                       _, _, got := 
TOSession.CreateDeliveryServiceServers(*ctc.capability.DeliveryServiceID, 
[]int{*server.ID}, true)
                        if (ctc.err == nil && got != nil) || (ctc.err != nil && 
!strings.Contains(got.Error(), ctc.err.Error())) {
                                t.Fatalf("expected ctc.err to contain %v, got 
%v", ctc.err, got)
                        }
@@ -209,7 +283,7 @@ func 
CreateTestDeliveryServiceServersWithRequiredCapabilities(t *testing.T) {
 }
 
 func CreateTestMSODSServerWithReqCap(t *testing.T) {
-       dsReqCap, _, err := 
TOSession.GetDeliveryServicesRequiredCapabilities(nil, util.StrPtr("msods1"), 
nil)
+       dsReqCap, _, err := 
TOSession.GetDeliveryServicesRequiredCapabilitiesWithHdr(nil, 
util.StrPtr("msods1"), nil, nil)
        if err != nil {
                t.Fatalf("GET delivery service required capabilites: %v", err)
        }
@@ -222,19 +296,22 @@ func CreateTestMSODSServerWithReqCap(t *testing.T) {
        // TODO: DON'T hard-code server hostnames!
        params := url.Values{}
        params.Add("hostName", "denver-mso-org-01")
-       resp, _, err := TOSession.GetServers(&params)
+       resp, _, err := TOSession.GetServersWithHdr(&params, nil)
        if err != nil {
                t.Fatalf("GET server denver-mso-org-01: %v", err)
        }
-       servers := resp
+       servers := resp.Response
        if len(servers) != 1 {
                t.Fatal("expected 1 server with hostname denver-mso-org-01")
        }
 
        s := servers[0]
+       if s.ID == nil {
+               t.Fatal("server 'denver-mso-org-01' had nil ID")
+       }
 
        // Make sure server has no caps to ensure test correctness
-       sccs, _, err := TOSession.GetServerServerCapabilities(&s.ID, nil, nil)
+       sccs, _, err := TOSession.GetServerServerCapabilitiesWithHdr(s.ID, nil, 
nil, nil)
        if err != nil {
                t.Fatalf("GET server server capabilities for denver-mso-org-01: 
%v", err)
        }
@@ -243,7 +320,7 @@ func CreateTestMSODSServerWithReqCap(t *testing.T) {
        }
 
        // Is origin included in eligible servers even though it doesnt have 
required capability
-       eServers, _, err := 
TOSession.GetDeliveryServicesEligible(*dsReqCap[0].DeliveryServiceID)
+       eServers, _, err := 
TOSession.GetDeliveryServicesEligibleWithHdr(*dsReqCap[0].DeliveryServiceID, 
nil)
        if err != nil {
                t.Fatalf("GET delivery service msods1 eligible servers: %v", 
err)
        }
@@ -258,7 +335,7 @@ func CreateTestMSODSServerWithReqCap(t *testing.T) {
                t.Fatal("expected to find origin server denver-mso-org-01 to be 
in eligible server return even though it is missing a required capability")
        }
 
-       if _, _, err = 
TOSession.CreateDeliveryServiceServers(*dsReqCap[0].DeliveryServiceID, 
[]int{s.ID}, true); err != nil {
+       if _, _, err = 
TOSession.CreateDeliveryServiceServers(*dsReqCap[0].DeliveryServiceID, 
[]int{*s.ID}, true); err != nil {
                t.Fatalf("POST delivery service origin servers without 
capabilities: %v", err)
        }
 
@@ -300,7 +377,7 @@ func DeleteTestDeliveryServiceServers(t *testing.T) {
                t.Errorf("POST delivery service servers: %v", err)
        }
 
-       dsServers, _, err := TOSession.GetDeliveryServiceServers()
+       dsServers, _, err := TOSession.GetDeliveryServiceServersWithHdr(nil)
        if err != nil {
                t.Errorf("GET delivery service servers: %v", err)
        }
@@ -320,7 +397,7 @@ func DeleteTestDeliveryServiceServers(t *testing.T) {
                t.Errorf("DELETE delivery service server: %v", err)
        }
 
-       dsServers, _, err = TOSession.GetDeliveryServiceServers()
+       dsServers, _, err = TOSession.GetDeliveryServiceServersWithHdr(nil)
        if err != nil {
                t.Errorf("GET delivery service servers: %v", err)
        }
@@ -337,8 +414,8 @@ func DeleteTestDeliveryServiceServers(t *testing.T) {
        }
 }
 
-func getServerAndDSofSameCDN(t *testing.T) (tc.DeliveryServiceNullable, 
tc.ServerV30) {
-       dses, _, err := TOSession.GetDeliveryServicesNullable()
+func getServerAndDSofSameCDN(t *testing.T) (tc.DeliveryServiceNullableV30, 
tc.ServerV30) {
+       dses, _, err := TOSession.GetDeliveryServicesV30WithHdr(nil, nil)
        if err != nil {
                t.Fatalf("cannot GET DeliveryServices: %v", err)
        }
@@ -346,28 +423,23 @@ func getServerAndDSofSameCDN(t *testing.T) 
(tc.DeliveryServiceNullable, tc.Serve
                t.Fatal("GET DeliveryServices returned no dses, must have at 
least 1 to test ds-servers")
        }
 
-       resp, _, err := TOSession.GetServers(nil)
+       resp, _, err := TOSession.GetServersWithHdr(nil, nil)
        if err != nil {
                t.Fatalf("cannot GET Servers: %v", err)
        }
-       servers := resp
+       servers := resp.Response
        if len(servers) < 1 {
                t.Fatal("GET Servers returned no dses, must have at least 1 to 
test ds-servers")
        }
 
        for _, ds := range dses {
                for _, s := range servers {
-                       if ds.CDNName != nil && *ds.CDNName == s.CDNName {
-                               upgraded, err := s.ToNullable().Upgrade()
-                               if err != nil {
-                                       t.Errorf("upgrading server: %v", err)
-                                       continue
-                               }
-                               return ds, upgraded
+                       if ds.CDNName != nil && s.CDNName != nil && *ds.CDNName 
== *s.CDNName {
+                               return ds, s
                        }
                }
        }
        t.Fatal("expected at least one delivery service and server in the same 
CDN")
 
-       return tc.DeliveryServiceNullable{}, tc.ServerV30{}
+       return tc.DeliveryServiceNullableV30{}, tc.ServerV30{}
 }
diff --git 
a/traffic_ops/testing/api/v3/servers_to_deliveryservice_assignment_test.go 
b/traffic_ops/testing/api/v3/servers_to_deliveryservice_assignment_test.go
index 74f2fe3..5004293 100644
--- a/traffic_ops/testing/api/v3/servers_to_deliveryservice_assignment_test.go
+++ b/traffic_ops/testing/api/v3/servers_to_deliveryservice_assignment_test.go
@@ -29,6 +29,7 @@ func TestAssignments(t *testing.T) {
                AssignTestDeliveryService(t)
                AssignIncorrectTestDeliveryService(t)
                AssignTopologyBasedDeliveryService(t)
+               OriginAssignTopologyBasedDeliveryService(t)
        })
 }
 
@@ -56,7 +57,7 @@ func AssignTestDeliveryService(t *testing.T) {
                t.Fatalf("Server '%s' had nil ID", *server.HostName)
        }
 
-       rd, _, err := 
TOSession.GetDeliveryServiceByXMLIDNullable(*testData.DeliveryServices[0].XMLID)
+       rd, _, err := 
TOSession.GetDeliveryServiceByXMLIDNullableWithHdr(*testData.DeliveryServices[0].XMLID,
 nil)
        if err != nil {
                t.Fatalf("Failed to fetch DS information: %v", err)
        } else if len(rd) == 0 {
@@ -73,7 +74,7 @@ func AssignTestDeliveryService(t *testing.T) {
        }
        t.Logf("alerts: %+v", alerts)
 
-       response, _, err := 
TOSession.GetServerIDDeliveryServices(*firstServer.ID)
+       response, _, err := 
TOSession.GetServerIDDeliveryServicesWithHdr(*firstServer.ID, nil)
        t.Logf("response: %+v", response)
        if err != nil {
                t.Fatalf("Couldn't get Delivery Services assigned to Server 
'%+v': %v", firstServer, err)
@@ -131,7 +132,7 @@ func AssignIncorrectTestDeliveryService(t *testing.T) {
                t.Fatalf("Server '%s' has nil ID", hostname)
        }
 
-       rd, _, err := 
TOSession.GetDeliveryServiceByXMLIDNullable(*testData.DeliveryServices[0].XMLID)
+       rd, _, err := 
TOSession.GetDeliveryServiceByXMLIDNullableWithHdr(*testData.DeliveryServices[0].XMLID,
 nil)
        if err != nil {
                t.Fatalf("Failed to fetch DS information: %v", err)
        } else if len(rd) == 0 {
@@ -147,7 +148,7 @@ func AssignIncorrectTestDeliveryService(t *testing.T) {
                t.Errorf("Expected bad assignment to fail, but it didn't! 
(alerts: %v)", alerts)
        }
 
-       response, _, err := TOSession.GetServerIDDeliveryServices(*server.ID)
+       response, _, err := 
TOSession.GetServerIDDeliveryServicesWithHdr(*server.ID, nil)
        t.Logf("response: %+v", response)
        if err != nil {
                t.Fatalf("Couldn't get Delivery Services assigned to Server 
'%+v': %v", *server, err)
@@ -192,7 +193,7 @@ func AssignTopologyBasedDeliveryService(t *testing.T) {
                t.Fatal("Server had nil ID")
        }
 
-       rd, _, err := TOSession.GetDeliveryServiceByXMLIDNullable("ds-top")
+       rd, _, err := 
TOSession.GetDeliveryServiceByXMLIDNullableWithHdr("ds-top", nil)
        if err != nil {
                t.Fatalf("Failed to fetch DS information: %v", err)
        } else if len(rd) == 0 {
@@ -211,7 +212,7 @@ func AssignTopologyBasedDeliveryService(t *testing.T) {
                t.Fatalf("assigning Topology-based delivery service to server - 
expected: non-error status code, actual: %d", reqInf.StatusCode)
        }
 
-       response, _, err := TOSession.GetServerIDDeliveryServices(*server.ID)
+       response, _, err := 
TOSession.GetServerIDDeliveryServicesWithHdr(*server.ID, nil)
        t.Logf("response: %+v", response)
        if err != nil {
                t.Fatalf("Couldn't get Delivery Services assigned to Server 
'%+v': %v", *server, err)
@@ -230,3 +231,75 @@ func AssignTopologyBasedDeliveryService(t *testing.T) {
                t.Errorf(`Valid Server/DS assignment was not created!`)
        }
 }
+
+func OriginAssignTopologyBasedDeliveryService(t *testing.T) {
+       params := url.Values{}
+       params.Add("hostName", "denver-mso-org-01")
+       rs, _, err := TOSession.GetServersWithHdr(&params, nil)
+       if err != nil {
+               t.Fatalf("Failed to fetch server information: %v", err)
+       } else if len(rs.Response) == 0 {
+               t.Fatalf("Failed to fetch server information: No results 
returned!")
+       }
+       origin := &rs.Response[0]
+       if origin.ID == nil {
+               t.Fatal("Server had nil ID")
+       }
+
+       rd, _, err := 
TOSession.GetDeliveryServiceByXMLIDNullableWithHdr("ds-top-req-cap", nil)
+       if err != nil {
+               t.Fatalf("Failed to fetch DS information: %v", err)
+       } else if len(rd) == 0 {
+               t.Fatalf("Failed to fetch DS information: No results returned!")
+       }
+       firstDS := rd[0]
+       if firstDS.ID == nil {
+               t.Fatal("Fetch DS information returned unknown ID")
+       }
+
+       // invalid assignment: ORG server cachegroup does not belong to the 
topology
+       alerts, reqInf, err := 
TOSession.AssignDeliveryServiceIDsToServerID(*origin.ID, []int{*firstDS.ID}, 
true)
+       if err == nil {
+               t.Errorf("Expected assigning ORG server to topology-based 
delivery service where the ORG server does not belong to the topology to fail, 
but it didn't! (alerts: %v)", alerts)
+       }
+       if reqInf.StatusCode < http.StatusBadRequest || reqInf.StatusCode >= 
http.StatusInternalServerError {
+               t.Fatalf("assigning Topology-based delivery service to ORG 
server that does not belong to the topology - expected: 400-level status code, 
actual: %d", reqInf.StatusCode)
+       }
+
+       // valid assignment ORG server cachegroup belongs to the topology
+       rd, _, err = 
TOSession.GetDeliveryServiceByXMLIDNullableWithHdr("ds-top", nil)
+       if err != nil {
+               t.Fatalf("Failed to fetch DS information: %v", err)
+       } else if len(rd) == 0 {
+               t.Fatalf("Failed to fetch DS information: No results returned!")
+       }
+       firstDS = rd[0]
+       if firstDS.ID == nil {
+               t.Fatal("Fetch DS information returned unknown ID")
+       }
+
+       alerts, reqInf, err = 
TOSession.AssignDeliveryServiceIDsToServerID(*origin.ID, []int{*firstDS.ID}, 
true)
+       if err != nil {
+               t.Errorf("Expected assigning ORG server to topology-based 
delivery service where the ORG server belongs to the topology to succeed, but 
it didn't! (alerts: %v, err: %v)", alerts, err)
+       }
+       if reqInf.StatusCode < http.StatusOK || reqInf.StatusCode >= 
http.StatusMultipleChoices {
+               t.Fatalf("assigning Topology-based delivery service to ORG 
server that belongs to the topology - expected: 200-level status code, actual: 
%d", reqInf.StatusCode)
+       }
+
+       response, _, err := 
TOSession.GetServerIDDeliveryServicesWithHdr(*origin.ID, nil)
+       if err != nil {
+               t.Fatalf("Couldn't get Delivery Services assigned to Server 
'%+v': %v", *origin, err)
+       }
+       var found bool
+       for _, ds := range response {
+
+               if ds.ID != nil && *ds.ID == *firstDS.ID {
+                       found = true
+                       break
+               }
+       }
+
+       if !found {
+               t.Errorf(`Valid Server/DS assignment was not created!`)
+       }
+}
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go 
b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index ae239f1..03ac0a8 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -527,36 +527,6 @@ func GetCDNDomainFromName(tx *sql.Tx, cdnName tc.CDNName) 
(string, bool, error)
        return domain, true, nil
 }
 
-// GetServerInfo returns a ServerInfo struct, whether the server exists, and 
an error (if one occurs).
-func GetServerInfo(serverID int, tx *sql.Tx) (tc.ServerInfo, bool, error) {
-       q := `
-SELECT
-  s.cachegroup as cachegroup_id,
-  s.cdn_id as cdn_id,
-  s.domain_name as domain_name,
-  s.host_name as host_name,
-  t.name as server_type
-FROM
-  server s
-JOIN type t ON s.type = t.id
-WHERE s.id = $1
-`
-       row := tc.ServerInfo{}
-       if err := tx.QueryRow(q, serverID).Scan(
-               &row.CachegroupID,
-               &row.CDNID,
-               &row.DomainName,
-               &row.HostName,
-               &row.Type,
-       ); err != nil {
-               if err == sql.ErrNoRows {
-                       return row, false, nil
-               }
-               return row, false, fmt.Errorf("querying server id %d: %v", 
serverID, err.Error())
-       }
-       return row, true, nil
-}
-
 // GetServerInterfaces, given the IDs of one or more servers, returns all of 
their network
 // interfaces mapped by their ids, or an error if one occurs during retrieval.
 func GetServersInterfaces(ids []int, tx *sql.Tx) 
(map[int]map[string]tc.ServerInterfaceInfo, error) {
@@ -708,70 +678,72 @@ func GetServerNameFromID(tx *sql.Tx, id int) (string, 
bool, error) {
        return name, true, nil
 }
 
-type ServerHostNameCDNIDAndType struct {
-       HostName string
-       CDNID    int
-       Type     string
-}
-
-// GetServerHostNamesAndTypesFromIDs returns the server's hostname, cdn ID and 
associated type name
-func GetServerHostNamesAndTypesFromIDs(tx *sql.Tx, ids []int) 
([]ServerHostNameCDNIDAndType, error) {
-       qry := `
+// language=sql
+const getServerInfoBaseQuery = `
 SELECT
+  s.cachegroup,
+  c.name,
   s.host_name,
+  s.domain_name,
   s.cdn_id,
   t.name
 FROM
   server s JOIN type t ON s.type = t.id
-WHERE
-  s.id = ANY($1)
+  JOIN cachegroup c on s.cachegroup = c.id
+`
+
+// GetServerInfosFromIDs returns the ServerInfo structs of the given server 
IDs or an error if any occur.
+func GetServerInfosFromIDs(tx *sql.Tx, ids []int) ([]tc.ServerInfo, error) {
+       qry := getServerInfoBaseQuery + `
+WHERE s.id = ANY($1)
 `
        rows, err := tx.Query(qry, pq.Array(ids))
        if err != nil {
-               return nil, errors.New("querying server host names and types: " 
+ err.Error())
-       }
-       defer log.Close(rows, "error closing rows")
-
-       servers := []ServerHostNameCDNIDAndType{}
-       for rows.Next() {
-               s := ServerHostNameCDNIDAndType{}
-               if err := rows.Scan(&s.HostName, &s.CDNID, &s.Type); err != nil 
{
-                       return nil, errors.New("scanning server host name and 
type: " + err.Error())
-               }
-               servers = append(servers, s)
+               return nil, errors.New("querying server info: " + err.Error())
        }
-       return servers, nil
+       return scanServerInfoRows(rows)
 }
 
-// GetServerTypesCdnIdFromHostNames returns the host names, server cdn and 
types of the given server host names or an error if any occur.
-func GetServerTypesCdnIdFromHostNames(tx *sql.Tx, hostNames []string) 
([]ServerHostNameCDNIDAndType, error) {
-       qry := `
-SELECT
-  s.host_name,
-  s.cdn_id,
-  t.name
-FROM
-  server s JOIN type t ON s.type = t.id
-WHERE
-  s.host_name = ANY($1)
+// GetServerInfosFromHostNames returns the ServerInfo structs of the given 
server host names or an error if any occur.
+func GetServerInfosFromHostNames(tx *sql.Tx, hostNames []string) 
([]tc.ServerInfo, error) {
+       qry := getServerInfoBaseQuery + `
+WHERE s.host_name = ANY($1)
 `
        rows, err := tx.Query(qry, pq.Array(hostNames))
        if err != nil {
-               return nil, errors.New("querying server host names and types: " 
+ err.Error())
+               return nil, errors.New("querying server info: " + err.Error())
        }
-       defer log.Close(rows, "error closing rows")
+       return scanServerInfoRows(rows)
+}
 
-       servers := []ServerHostNameCDNIDAndType{}
+func scanServerInfoRows(rows *sql.Rows) ([]tc.ServerInfo, error) {
+       defer log.Close(rows, "error closing rows")
+       servers := []tc.ServerInfo{}
        for rows.Next() {
-               s := ServerHostNameCDNIDAndType{}
-               if err := rows.Scan(&s.HostName, &s.CDNID, &s.Type); err != nil 
{
-                       return nil, errors.New("scanning server host name and 
type: " + err.Error())
+               s := tc.ServerInfo{}
+               if err := rows.Scan(&s.CachegroupID, &s.Cachegroup, 
&s.HostName, &s.DomainName, &s.CDNID, &s.Type); err != nil {
+                       return nil, errors.New("scanning server info: " + 
err.Error())
                }
                servers = append(servers, s)
        }
        return servers, nil
 }
 
+// GetServerInfo returns a ServerInfo struct, whether the server exists, and 
an error (if one occurs).
+func GetServerInfo(serverID int, tx *sql.Tx) (tc.ServerInfo, bool, error) {
+       servers, err := GetServerInfosFromIDs(tx, []int{serverID})
+       if err != nil {
+               return tc.ServerInfo{}, false, fmt.Errorf("getting server info: 
%v", err)
+       }
+       if len(servers) == 0 {
+               return tc.ServerInfo{}, false, nil
+       }
+       if len(servers) != 1 {
+               return tc.ServerInfo{}, false, fmt.Errorf("getting server info 
- expected row count: 1, actual: %d", len(servers))
+       }
+       return servers[0], true, nil
+}
+
 func GetCDNDSes(tx *sql.Tx, cdn tc.CDNName) 
(map[tc.DeliveryServiceName]struct{}, error) {
        dses := map[tc.DeliveryServiceName]struct{}{}
        qry := `SELECT xml_id from deliveryservice where cdn_id = (select id 
from cdn where name = $1)`
@@ -867,6 +839,20 @@ func TopologyExists(tx *sql.Tx, name string) (bool, error) 
{
        return count > 0, err
 }
 
+// GetTopologyCachegroups returns the set of cachegroup names for the given 
topology.
+func GetTopologyCachegroups(tx *sql.Tx, name string) ([]string, error) {
+       q := `
+       SELECT ARRAY_AGG(tc.cachegroup)
+       FROM topology_cachegroup tc
+       WHERE tc.topology = $1
+       `
+       cachegroups := []string{}
+       if err := tx.QueryRow(q, name).Scan(pq.Array(&cachegroups)); err != nil 
{
+               return nil, fmt.Errorf("querying topology '%s' cachegroups: 
%s", name, err)
+       }
+       return cachegroups, nil
+}
+
 // GetDeliveryServicesWithTopologies returns a list containing the delivery 
services in the given dsIDs
 // list that have a topology assigned. An error indicates unexpected errors 
that occurred when querying.
 func GetDeliveryServicesWithTopologies(tx *sql.Tx, dsIDs []int) ([]int, error) 
{
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
index 4cc0b79..d099a3f 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
@@ -368,7 +368,7 @@ func GetReplaceHandler(w http.ResponseWriter, r 
*http.Request) {
 
        ds, ok, err := GetDSInfo(inf.Tx.Tx, *dsId)
        if err != nil {
-               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("deliveryserviceserver getting XMLID: "+err.Error()))
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("deliveryserviceserver getting delivery service info for ID %d: 
%v", *dsId, err))
                return
        }
        if !ok {
@@ -379,20 +379,15 @@ func GetReplaceHandler(w http.ResponseWriter, r 
*http.Request) {
                api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
                return
        }
-       serverNamesCdnIdAndTypes, err := 
dbhelpers.GetServerHostNamesAndTypesFromIDs(inf.Tx.Tx, servers)
+       serverInfos, err := dbhelpers.GetServerInfosFromIDs(inf.Tx.Tx, servers)
        if err != nil {
-               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
err, nil)
-               return
-       }
-       userErr = ValidateDSSAssignments(ds, serverNamesCdnIdAndTypes)
-       if userErr != nil {
-               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, userErr, 
nil)
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, err)
                return
        }
 
-       usrErr, sysErr, status := ValidateServerCapabilities(ds.ID, 
serverNamesCdnIdAndTypes, inf.Tx.Tx)
-       if usrErr != nil || sysErr != nil {
-               api.HandleErr(w, r, inf.Tx.Tx, status, usrErr, sysErr)
+       userErr, sysErr, status := validateDSSAssignments(inf.Tx.Tx, ds, 
serverInfos)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, status, userErr, sysErr)
                return
        }
 
@@ -421,7 +416,7 @@ func GetReplaceHandler(w http.ResponseWriter, r 
*http.Request) {
                return
        }
        api.CreateChangeLogRawTx(api.ApiChange, "DS: "+ds.Name+", ID: 
"+strconv.Itoa(*dsId)+", ACTION: Replace existing servers assigned to delivery 
service", inf.User, inf.Tx.Tx)
-       api.WriteRespAlertObj(w, r, tc.SuccessLevel, "server assignements 
complete", tc.DSSMapResponse{*dsId, *payload.Replace, respServers})
+       api.WriteRespAlertObj(w, r, tc.SuccessLevel, "server assignments 
complete", tc.DSSMapResponse{*dsId, *payload.Replace, respServers})
 }
 
 type TODeliveryServiceServers tc.DeliveryServiceServers
@@ -444,7 +439,7 @@ func GetCreateHandler(w http.ResponseWriter, r 
*http.Request) {
 
        ds, ok, err := GetDSInfoByName(inf.Tx.Tx, dsName)
        if err != nil {
-               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("ds servers create scanning: "+err.Error()))
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("ds servers getting delivery service info for xmlID %s: %v", 
dsName, err))
                return
        } else if !ok {
                api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, 
errors.New("delivery service not found"))
@@ -460,21 +455,15 @@ func GetCreateHandler(w http.ResponseWriter, r 
*http.Request) {
        payload.XmlId = dsName
        serverNames := payload.ServerNames
 
-       serverNamesCdnIdAndTypes, err := 
dbhelpers.GetServerTypesCdnIdFromHostNames(inf.Tx.Tx, serverNames)
+       serverInfos, err := dbhelpers.GetServerInfosFromHostNames(inf.Tx.Tx, 
serverNames)
        if err != nil {
-               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
err, nil)
-               return
-       }
-
-       userErr = ValidateDSSAssignments(ds, serverNamesCdnIdAndTypes)
-       if userErr != nil {
-               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, userErr, 
nil)
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, err)
                return
        }
 
-       usrErr, sysErr, status := ValidateServerCapabilities(ds.ID, 
serverNamesCdnIdAndTypes, inf.Tx.Tx)
-       if usrErr != nil || sysErr != nil {
-               api.HandleErr(w, r, inf.Tx.Tx, status, usrErr, sysErr)
+       userErr, sysErr, status := validateDSSAssignments(inf.Tx.Tx, ds, 
serverInfos)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, status, userErr, sysErr)
                return
        }
 
@@ -503,26 +492,67 @@ func GetCreateHandler(w http.ResponseWriter, r 
*http.Request) {
        api.WriteResp(w, r, tc.DeliveryServiceServers{payload.ServerNames, 
payload.XmlId})
 }
 
-// ValidateDSSAssignments returns an error if the given servers cannot be 
assigned to the given delivery service.
-func ValidateDSSAssignments(ds DSInfo, servers 
[]dbhelpers.ServerHostNameCDNIDAndType) error {
+// validateDSSAssignments returns an error if the given servers cannot be 
assigned to the given delivery service.
+func validateDSSAssignments(tx *sql.Tx, ds DSInfo, serverInfos 
[]tc.ServerInfo) (error, error, int) {
+       userErr, sysErr, status := validateDSS(tx, ds, serverInfos)
+       if userErr != nil || sysErr != nil {
+               return userErr, sysErr, status
+       }
+
+       userErr, sysErr, status = ValidateServerCapabilities(tx, ds.ID, 
serverInfos)
+       if userErr != nil || sysErr != nil {
+               return userErr, sysErr, status
+       }
+       return nil, nil, http.StatusOK
+}
+
+func validateDSS(tx *sql.Tx, ds DSInfo, servers []tc.ServerInfo) (error, 
error, int) {
        if ds.Topology == nil {
                for _, s := range servers {
                        if ds.CDNID != nil && s.CDNID != *ds.CDNID {
-                               return errors.New("server and delivery service 
CDNs do not match")
+                               return errors.New("server and delivery service 
CDNs do not match"), nil, http.StatusBadRequest
                        }
                }
-               return nil
+               return nil, nil, http.StatusOK
        }
        for _, s := range servers {
                if s.Type != tc.OriginTypeName {
-                       return errors.New("only servers of type ORG may be 
assigned to topology-based delivery services")
+                       return fmt.Errorf("only servers of type %s may be 
assigned to topology-based delivery services", tc.OriginTypeName), nil, 
http.StatusBadRequest
                }
        }
+
+       cachegroups, sysErr := dbhelpers.GetTopologyCachegroups(tx, 
*ds.Topology)
+       if sysErr != nil {
+               return nil, fmt.Errorf("validating %s servers in topology %s: 
%v", tc.OriginTypeName, *ds.Topology, sysErr), http.StatusInternalServerError
+       }
+       userErr := CheckServersInCachegroups(servers, cachegroups)
+       if userErr != nil {
+               return fmt.Errorf("validating %s servers in topology %s: %v", 
tc.OriginTypeName, *ds.Topology, userErr), nil, http.StatusBadRequest
+       }
+       return nil, nil, http.StatusOK
+}
+
+// CheckServersInCachegroups checks whether or not all the given server 
cachegroups belong to the topology
+// and returns a user error (if any).
+func CheckServersInCachegroups(servers []tc.ServerInfo, cachegroups []string) 
error {
+       cgSet := make(map[string]struct{}, len(cachegroups))
+       for _, c := range cachegroups {
+               cgSet[c] = struct{}{}
+       }
+       invalid := []string{}
+       for _, s := range servers {
+               if _, ok := cgSet[s.Cachegroup]; !ok {
+                       invalid = append(invalid, s.HostName+" 
("+s.Cachegroup+")")
+               }
+       }
+       if len(invalid) > 0 {
+               return fmt.Errorf("the following servers are not in any of the 
given cachegroups (%s): %s", strings.Join(cachegroups, ", "), 
strings.Join(invalid, ", "))
+       }
        return nil
 }
 
 // ValidateServerCapabilities checks that the delivery service's requirements 
are met by each server to be assigned.
-func ValidateServerCapabilities(dsID int, serverNamesAndTypes 
[]dbhelpers.ServerHostNameCDNIDAndType, tx *sql.Tx) (error, error, int) {
+func ValidateServerCapabilities(tx *sql.Tx, dsID int, serverNamesAndTypes 
[]tc.ServerInfo) (error, error, int) {
        nonOriginServerNames := []string{}
        for _, s := range serverNamesAndTypes {
                if strings.HasPrefix(s.Type, tc.EdgeTypePrefix) {
@@ -840,10 +870,10 @@ type DSInfo struct {
        CDNID                *int
 }
 
-// GetDSInfo loads the DeliveryService fields needed by Delivery Service 
Servers from the database, from the ID. Returns the data, whether the delivery 
service was found, and any error.
-func GetDSInfo(tx *sql.Tx, id int) (DSInfo, bool, error) {
-       qry := `
+// language=sql
+const getDSInfoBaseQuery = `
 SELECT
+  ds.id,
   ds.xml_id,
   tp.name as type,
   ds.edge_header_rewrite,
@@ -857,47 +887,46 @@ SELECT
 FROM
   deliveryservice ds
   JOIN type tp ON ds.type = tp.id
-WHERE
-  ds.id = $1
 `
-       di := DSInfo{ID: id}
-       if err := tx.QueryRow(qry, id).Scan(&di.Name, &di.Type, 
&di.EdgeHeaderRewrite, &di.MidHeaderRewrite, &di.RegexRemap, 
&di.SigningAlgorithm, &di.CacheURL, &di.MaxOriginConnections, &di.Topology, 
&di.CDNID); err != nil {
+
+func scanDSInfoRow(row *sql.Row) (DSInfo, bool, error) {
+       di := DSInfo{}
+       if err := row.Scan(
+               &di.ID,
+               &di.Name,
+               &di.Type,
+               &di.EdgeHeaderRewrite,
+               &di.MidHeaderRewrite,
+               &di.RegexRemap,
+               &di.SigningAlgorithm,
+               &di.CacheURL,
+               &di.MaxOriginConnections,
+               &di.Topology,
+               &di.CDNID,
+       ); err != nil {
                if err == sql.ErrNoRows {
                        return DSInfo{}, false, nil
                }
-               return DSInfo{}, false, fmt.Errorf("querying delivery service 
server ds info '%v': %v", id, err)
+               return DSInfo{}, false, fmt.Errorf("querying delivery service 
server ds info: %v", err)
        }
        di.Type = tc.DSTypeFromString(string(di.Type))
        return di, true, nil
 }
 
-// GetDSInfoByName loads the DeliveryService fields needed by Delivery Service 
Servers from the database, from the ID. Returns the data, whether the delivery 
service was found, and any error.
+// GetDSInfo loads the DeliveryService fields needed by Delivery Service 
Servers from the database, from the ID. Returns the data, whether the delivery 
service was found, and any error.
+func GetDSInfo(tx *sql.Tx, id int) (DSInfo, bool, error) {
+       qry := getDSInfoBaseQuery + `
+WHERE ds.id = $1
+`
+       row := tx.QueryRow(qry, id)
+       return scanDSInfoRow(row)
+}
+
+// GetDSInfoByName loads the DeliveryService fields needed by Delivery Service 
Servers from the database, from the name (xml_id). Returns the data, whether 
the delivery service was found, and any error.
 func GetDSInfoByName(tx *sql.Tx, dsName string) (DSInfo, bool, error) {
-       qry := `
-SELECT
-  ds.id,
-  tp.name as type,
-  ds.edge_header_rewrite,
-  ds.mid_header_rewrite,
-  ds.regex_remap,
-  ds.signing_algorithm,
-  ds.cacheurl,
-  ds.max_origin_connections,
-  ds.topology,
-  ds.cdn_id
-FROM
-  deliveryservice ds
-  JOIN type tp ON ds.type = tp.id
-WHERE
-  ds.xml_id = $1
+       qry := getDSInfoBaseQuery + `
+WHERE ds.xml_id = $1
 `
-       di := DSInfo{Name: dsName}
-       if err := tx.QueryRow(qry, dsName).Scan(&di.ID, &di.Type, 
&di.EdgeHeaderRewrite, &di.MidHeaderRewrite, &di.RegexRemap, 
&di.SigningAlgorithm, &di.CacheURL, &di.MaxOriginConnections, &di.Topology, 
&di.CDNID); err != nil {
-               if err == sql.ErrNoRows {
-                       return DSInfo{}, false, nil
-               }
-               return DSInfo{}, false, fmt.Errorf("querying delivery service 
server ds info by name '%v': %v", dsName, err)
-       }
-       di.Type = tc.DSTypeFromString(string(di.Type))
-       return di, true, nil
+       row := tx.QueryRow(qry, dsName)
+       return scanDSInfoRow(row)
 }
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go
index 41382c1..2f2be31 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go
@@ -26,9 +26,8 @@ import (
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-util"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
-       
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
-       "github.com/jmoiron/sqlx"
 
+       "github.com/jmoiron/sqlx"
        "gopkg.in/DATA-DOG/go-sqlmock.v1"
 )
 
@@ -39,14 +38,14 @@ func TestValidateDSSAssignments(t *testing.T) {
                ID:    0,
                CDNID: &cdnID,
        }
-       var servers []dbhelpers.ServerHostNameCDNIDAndType
-       server := dbhelpers.ServerHostNameCDNIDAndType{
+       var servers []tc.ServerInfo
+       server := tc.ServerInfo{
                HostName: "serverHost",
                CDNID:    0,
                Type:     "",
        }
        servers = append(servers, server)
-       userErr := ValidateDSSAssignments(ds, servers)
+       userErr, _, _ := validateDSS(nil, ds, servers)
        if userErr == nil {
                t.Fatalf("Expected user error with mismatching ds and server 
CDN IDs, got no error instead")
        }
@@ -54,7 +53,7 @@ func TestValidateDSSAssignments(t *testing.T) {
                t.Errorf("Expected error details %v, got %v", expected, 
userErr.Error())
        }
        servers[0].CDNID = 1
-       userErr = ValidateDSSAssignments(ds, servers)
+       userErr, _, _ = validateDSS(nil, ds, servers)
        if userErr != nil {
                t.Fatalf("Expected no user error, got %v", userErr.Error())
        }
diff --git a/traffic_ops/traffic_ops_golang/server/servers_assignment.go 
b/traffic_ops/traffic_ops_golang/server/servers_assignment.go
index ef1d83d..d9c4fd9 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_assignment.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_assignment.go
@@ -112,6 +112,12 @@ func AssignDeliveryServicesToServerHandler(w 
http.ResponseWriter, r *http.Reques
                        api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
                        return
                }
+               if strings.HasPrefix(serverInfo.Type, tc.OriginTypeName) {
+                       if userErr, sysErr, status := 
checkOriginInTopologies(inf.Tx.Tx, serverInfo.Cachegroup, dsList); userErr != 
nil || sysErr != nil {
+                               api.HandleErr(w, r, inf.Tx.Tx, status, userErr, 
sysErr)
+                               return
+                       }
+               }
        }
 
        assignedDSes, err := assignDeliveryServicesToServer(server, dsList, 
replace, inf.Tx.Tx)
@@ -126,6 +132,45 @@ func AssignDeliveryServicesToServerHandler(w 
http.ResponseWriter, r *http.Reques
        api.WriteRespAlertObj(w, r, tc.SuccessLevel, "successfully assigned 
dses to server", tc.AssignedDsResponse{server, assignedDSes, replace})
 }
 
+// checkOriginInTopologies checks to make sure the given ORG server's 
cachegroup belongs
+// to the topologies of the given delivery services.
+func checkOriginInTopologies(tx *sql.Tx, originCachegroup string, dsList 
[]int) (error, error, int) {
+       // get the delivery services that don't have originCachegroup in their 
topology
+       q := `
+SELECT
+  ds.xml_id,
+  tc.topology,
+  ARRAY_AGG(tc.cachegroup)
+FROM
+  deliveryservice ds
+  JOIN topology_cachegroup tc ON tc.topology = ds.topology
+WHERE
+  ds.id = ANY($1::BIGINT[])
+GROUP BY ds.xml_id, tc.topology
+HAVING NOT ($2 = ANY(ARRAY_AGG(tc.cachegroup)))
+`
+       rows, err := tx.Query(q, pq.Array(dsList), originCachegroup)
+       if err != nil {
+               return nil, errors.New("querying deliveryservice topologies: " 
+ err.Error()), http.StatusInternalServerError
+       }
+       defer log.Close(rows, "error closing rows")
+
+       invalid := []string{}
+       for rows.Next() {
+               xmlID := ""
+               topology := ""
+               cachegroups := []string{}
+               if err := rows.Scan(&xmlID, &topology, pq.Array(&cachegroups)); 
err != nil {
+                       return nil, errors.New("scanning deliveryservice 
topologies: " + err.Error()), http.StatusInternalServerError
+               }
+               invalid = append(invalid, fmt.Sprintf("%s (%s)", topology, 
xmlID))
+       }
+       if len(invalid) > 0 {
+               return fmt.Errorf("%s server cachegroup (%s) not found in the 
following topologies: %s", tc.OriginTypeName, originCachegroup, 
strings.Join(invalid, ", ")), nil, http.StatusBadRequest
+       }
+       return nil, nil, http.StatusOK
+}
+
 func checkTenancyAndCDN(tx *sql.Tx, serverCDN string, server int, serverInfo 
tc.ServerInfo, dsList []int, user *auth.CurrentUser) (int, error, error) {
        rows, err := tx.Query(needsCheckInfoQuery, pq.Array(dsList))
        if err != nil {

Reply via email to