diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3601a0ef..f07f2aa6ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Traffic Ops: Added an API 3.0 endpoint, /api/3.0/topologies, to create, read, update and delete flexible topologies. - Traffic Ops: Added new `topology` field to the /api/3.0/deliveryservices APIs - Traffic Ops: Added support for `topology` query parameter to `GET /api/3.0/cachegroups` to return all cachegroups used in the given topology. + - Traffic Ops: Added validation to prohibit assigning caches to topology-based delivery services - Traffic Portal: Added the ability to create, read, update and delete flexible topologies. - Traffic Portal: Added the ability to assign topologies to delivery services - Updated /servers/details to use multiple interfaces in API v3 diff --git a/lib/go-tc/deliveryservices.go b/lib/go-tc/deliveryservices.go index d8bc0d299d..7d040be7b6 100644 --- a/lib/go-tc/deliveryservices.go +++ b/lib/go-tc/deliveryservices.go @@ -614,13 +614,13 @@ type DSServerIDs struct { } type CachegroupPostDSReq struct { - DeliveryServices []int64 `json:"deliveryServices"` + DeliveryServices []int `json:"deliveryServices"` } type CacheGroupPostDSResp struct { ID util.JSONIntStr `json:"id"` ServerNames []CacheName `json:"serverNames"` - DeliveryServices []int64 `json:"deliveryServices"` + DeliveryServices []int `json:"deliveryServices"` } type CacheGroupPostDSRespResponse struct { diff --git a/traffic_ops/client/cachegroup.go b/traffic_ops/client/cachegroup.go index a0d66b6802..be2d5c5095 100644 --- a/traffic_ops/client/cachegroup.go +++ b/traffic_ops/client/cachegroup.go @@ -22,6 +22,7 @@ import ( "net" "net/http" "net/url" + "strconv" "github.com/apache/trafficcontrol/lib/go-log" "github.com/apache/trafficcontrol/lib/go-tc" @@ -212,3 +213,27 @@ func (to *Session) GetCacheGroupsByQueryParams(qparams url.Values) ([]tc.CacheGr return data.Response, reqInf, nil } + +func (to *Session) SetCachegroupDeliveryServices(cgID int, dsIDs []int) (tc.CacheGroupPostDSRespResponse, ReqInf, error) { + uri := apiBase + `/cachegroups/` + strconv.Itoa(cgID) + `/deliveryservices` + req := tc.CachegroupPostDSReq{DeliveryServices: dsIDs} + reqBody, err := json.Marshal(req) + if err != nil { + return tc.CacheGroupPostDSRespResponse{}, ReqInf{}, err + } + reqResp, remoteAddr, err := to.request(http.MethodPost, uri, reqBody) + reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} + if reqResp != nil { + reqInf.StatusCode = reqResp.StatusCode + } + if err != nil { + return tc.CacheGroupPostDSRespResponse{}, reqInf, errors.New("requesting from Traffic Ops: " + err.Error()) + } + defer log.Close(reqResp.Body, "unable to close response body") + + resp := tc.CacheGroupPostDSRespResponse{} + if err := json.NewDecoder(reqResp.Body).Decode(&resp); err != nil { + return tc.CacheGroupPostDSRespResponse{}, reqInf, errors.New("decoding response: " + err.Error()) + } + return resp, reqInf, nil +} diff --git a/traffic_ops/client/coordinate.go b/traffic_ops/client/coordinate.go index 715917b6ad..223219cdf5 100644 --- a/traffic_ops/client/coordinate.go +++ b/traffic_ops/client/coordinate.go @@ -17,11 +17,9 @@ package client import ( "encoding/json" - "errors" "fmt" "net" "net/http" - "strconv" "github.com/apache/trafficcontrol/lib/go-tc" ) @@ -132,24 +130,3 @@ func (to *Session) DeleteCoordinateByID(id int) (tc.Alerts, ReqInf, error) { err = json.NewDecoder(resp.Body).Decode(&alerts) return alerts, reqInf, nil } - -func (to *Session) SetCachegroupDeliveryServices(cgID int, dsIDs []int64) (tc.CacheGroupPostDSRespResponse, ReqInf, error) { - uri := apiBase + `/cachegroups/` + strconv.Itoa(cgID) + `/deliveryservices` - req := tc.CachegroupPostDSReq{DeliveryServices: dsIDs} - reqBody, err := json.Marshal(req) - reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss} - if err != nil { - return tc.CacheGroupPostDSRespResponse{}, reqInf, err - } - reqResp, _, err := to.request(http.MethodPost, uri, reqBody) - if err != nil { - return tc.CacheGroupPostDSRespResponse{}, reqInf, errors.New("requesting from Traffic Ops: " + err.Error()) - } - defer reqResp.Body.Close() - - resp := tc.CacheGroupPostDSRespResponse{} - if err := json.NewDecoder(reqResp.Body).Decode(&resp); err != nil { - return tc.CacheGroupPostDSRespResponse{}, reqInf, errors.New("decoding response: " + err.Error()) - } - return resp, reqInf, nil -} diff --git a/traffic_ops/client/deliveryservice.go b/traffic_ops/client/deliveryservice.go index 02649fc486..5cd866d36a 100644 --- a/traffic_ops/client/deliveryservice.go +++ b/traffic_ops/client/deliveryservice.go @@ -103,6 +103,11 @@ const ( // to the associations between Delivery Services and their assigned Server(s). // See Also: https://traffic-control-cdn.readthedocs.io/en/latest/api/v3/deliveryserviceserver.html API_DELIVERY_SERVICE_SERVER = apiBase + "/deliveryserviceserver" + + // API_DELIVERY_SERVICES_SERVERS is the API path on which Traffic Ops serves functionality related + // to the associations between a Delivery Service and its assigned Server(s). + // See Also: https://traffic-control-cdn.readthedocs.io/en/latest/api/v3/deliveryservices_xmlid_servers.html + API_DELIVERY_SERVICES_SERVERS = apiBase + "/deliveryservices/%s/servers" ) // GetDeliveryServicesByServer returns all of the (tenant-visible) Delivery Services assigned to diff --git a/traffic_ops/client/deliveryserviceserver.go b/traffic_ops/client/deliveryserviceserver.go index cb81ff3823..d1d92e3389 100644 --- a/traffic_ops/client/deliveryserviceserver.go +++ b/traffic_ops/client/deliveryserviceserver.go @@ -18,17 +18,19 @@ package client import ( "encoding/json" "errors" + "fmt" "net/http" "net/url" "strconv" "strings" + "github.com/apache/trafficcontrol/lib/go-log" "github.com/apache/trafficcontrol/lib/go-tc" "github.com/apache/trafficcontrol/lib/go-util" ) // CreateDeliveryServiceServers associates the given servers with the given delivery services. If replace is true, it deletes any existing associations for the given delivery service. -func (to *Session) CreateDeliveryServiceServers(dsID int, serverIDs []int, replace bool) (*tc.DSServerIDs, error) { +func (to *Session) CreateDeliveryServiceServers(dsID int, serverIDs []int, replace bool) (*tc.DSServerIDs, ReqInf, error) { path := apiBase + `/deliveryserviceserver` req := tc.DSServerIDs{ DeliveryServiceID: util.IntPtr(dsID), @@ -37,15 +39,16 @@ func (to *Session) CreateDeliveryServiceServers(dsID int, serverIDs []int, repla } jsonReq, err := json.Marshal(&req) if err != nil { - return nil, err + return nil, ReqInf{}, err } resp := struct { Response tc.DSServerIDs `json:"response"` }{} - if _, err := post(to, path, jsonReq, &resp); err != nil { - return nil, err + reqInf, err := post(to, path, jsonReq, &resp) + if err != nil { + return nil, reqInf, err } - return &resp.Response, nil + return &resp.Response, reqInf, nil } func (to *Session) DeleteDeliveryServiceServer(dsID int, serverID int) (tc.Alerts, ReqInf, error) { @@ -63,6 +66,27 @@ func (to *Session) DeleteDeliveryServiceServer(dsID int, serverID int) (tc.Alert return resp, reqInf, nil } +// AssignServersToDeliveryService assigns the given list of servers to the delivery service with the given xmlId. +func (to *Session) AssignServersToDeliveryService(servers []string, xmlId string) (tc.Alerts, ReqInf, error) { + route := fmt.Sprintf(API_DELIVERY_SERVICES_SERVERS, xmlId) + dss := tc.DeliveryServiceServers{ServerNames: servers, XmlId: xmlId} + reqBody, err := json.Marshal(&dss) + reqResp, remoteAddr, err := to.request(http.MethodPost, route, reqBody) + reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} + if reqResp != nil { + reqInf.StatusCode = reqResp.StatusCode + } + if err != nil { + return tc.Alerts{}, reqInf, errors.New("requesting from Traffic Ops: " + err.Error()) + } + defer log.Close(reqResp.Body, "unable to close response body") + resp := tc.Alerts{} + if err = json.NewDecoder(reqResp.Body).Decode(&resp); err != nil { + return tc.Alerts{}, reqInf, errors.New("decoding response: " + err.Error()) + } + return resp, reqInf, nil +} + // GetDeliveryServiceServers gets all delivery service servers, with the default API limit. func (to *Session) GetDeliveryServiceServers() (tc.DeliveryServiceServerResponse, ReqInf, error) { return to.getDeliveryServiceServers(url.Values{}) diff --git a/traffic_ops/client/server.go b/traffic_ops/client/server.go index e66605290d..9f688ecbf1 100644 --- a/traffic_ops/client/server.go +++ b/traffic_ops/client/server.go @@ -24,6 +24,7 @@ import ( "net/url" "strings" + "github.com/apache/trafficcontrol/lib/go-log" "github.com/apache/trafficcontrol/lib/go-tc" ) @@ -301,10 +302,13 @@ func (to *Session) AssignDeliveryServiceIDsToServerID(server int, dsIDs []int, r } resp, remoteAddr, err := to.request(http.MethodPost, endpoint, reqBody) + if resp != nil { + reqInf.StatusCode = resp.StatusCode + } if err != nil { return tc.Alerts{}, reqInf, err } - defer resp.Body.Close() + defer log.Close(resp.Body, "unable to close response body") reqInf.RemoteAddr = remoteAddr var alerts tc.Alerts err = json.NewDecoder(resp.Body).Decode(&alerts) diff --git a/traffic_ops/client/util.go b/traffic_ops/client/util.go index ec718c9cfc..72d6d9e9f8 100644 --- a/traffic_ops/client/util.go +++ b/traffic_ops/client/util.go @@ -19,6 +19,8 @@ import ( "encoding/json" "errors" "io/ioutil" + + "github.com/apache/trafficcontrol/lib/go-log" ) func get(to *Session, endpoint string, respStruct interface{}) (ReqInf, error) { @@ -40,10 +42,13 @@ func del(to *Session, endpoint string, respStruct interface{}) (ReqInf, error) { func makeReq(to *Session, method, endpoint string, body []byte, respStruct interface{}) (ReqInf, error) { resp, remoteAddr, err := to.request(method, endpoint, body) // TODO change to getBytesWithTTL reqInf := ReqInf{RemoteAddr: remoteAddr, CacheHitStatus: CacheHitStatusMiss} + if resp != nil { + reqInf.StatusCode = resp.StatusCode + } if err != nil { return reqInf, err } - defer resp.Body.Close() + defer log.Close(resp.Body, "unable to close response body") bts, err := ioutil.ReadAll(resp.Body) if err != nil { diff --git a/traffic_ops/ort/atstccfg/toreq/vendor/github.com/apache/trafficcontrol/traffic_ops/client/coordinate.go b/traffic_ops/ort/atstccfg/toreq/vendor/github.com/apache/trafficcontrol/traffic_ops/client/coordinate.go index ab209bd8af..4ae9a898a5 100644 --- a/traffic_ops/ort/atstccfg/toreq/vendor/github.com/apache/trafficcontrol/traffic_ops/client/coordinate.go +++ b/traffic_ops/ort/atstccfg/toreq/vendor/github.com/apache/trafficcontrol/traffic_ops/client/coordinate.go @@ -133,7 +133,7 @@ func (to *Session) DeleteCoordinateByID(id int) (tc.Alerts, ReqInf, error) { return alerts, reqInf, nil } -func (to *Session) SetCachegroupDeliveryServices(cgID int, dsIDs []int64) (tc.CacheGroupPostDSRespResponse, ReqInf, error) { +func (to *Session) SetCachegroupDeliveryServices(cgID int, dsIDs []int) (tc.CacheGroupPostDSRespResponse, ReqInf, error) { uri := apiBase + `/cachegroups/` + strconv.Itoa(cgID) + `/deliveryservices` req := tc.CachegroupPostDSReq{DeliveryServices: dsIDs} reqBody, err := json.Marshal(req) diff --git a/traffic_ops/testing/api/v1/cachegroupsdeliveryservices_test.go b/traffic_ops/testing/api/v1/cachegroupsdeliveryservices_test.go index a377d5249a..0e8f618d49 100644 --- a/traffic_ops/testing/api/v1/cachegroupsdeliveryservices_test.go +++ b/traffic_ops/testing/api/v1/cachegroupsdeliveryservices_test.go @@ -58,10 +58,10 @@ func CreateTestCachegroupsDeliveryServices(t *testing.T) { } cgID := *clientCG.ID - dsIDs := []int64{} + dsIDs := []int{} for _, ds := range dses { if ds.CDNName == "cdn1" { - dsIDs = append(dsIDs, int64(ds.ID)) + dsIDs = append(dsIDs, ds.ID) } } if len(dsIDs) < 1 { diff --git a/traffic_ops/testing/api/v2/cachegroupsdeliveryservices_test.go b/traffic_ops/testing/api/v2/cachegroupsdeliveryservices_test.go index 4bc2dcc6b3..7e25bdb324 100644 --- a/traffic_ops/testing/api/v2/cachegroupsdeliveryservices_test.go +++ b/traffic_ops/testing/api/v2/cachegroupsdeliveryservices_test.go @@ -58,10 +58,10 @@ func CreateTestCachegroupsDeliveryServices(t *testing.T) { } cgID := *clientCG.ID - dsIDs := []int64{} + dsIDs := []int{} for _, ds := range dses { if *ds.CDNName == "cdn1" { - dsIDs = append(dsIDs, int64(*ds.ID)) + dsIDs = append(dsIDs, *ds.ID) } } if len(dsIDs) < 1 { diff --git a/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go b/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go index 0e59e3bed5..f245328d66 100644 --- a/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go +++ b/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go @@ -16,6 +16,7 @@ package v3 */ import ( + "net/http" "testing" ) @@ -58,16 +59,31 @@ func CreateTestCachegroupsDeliveryServices(t *testing.T) { } cgID := *clientCG.ID - dsIDs := []int64{} + dsIDs := []int{} + topologyDsIDs := []int{} for _, ds := range dses { - if *ds.CDNName == "cdn1" { - dsIDs = append(dsIDs, int64(*ds.ID)) + if *ds.CDNName == "cdn1" && ds.Topology == nil { + dsIDs = append(dsIDs, *ds.ID) + } else if *ds.CDNName == "cdn1" && ds.Topology != nil { + topologyDsIDs = append(topologyDsIDs, *ds.ID) } } if len(dsIDs) < 1 { t.Fatal("No Delivery Services found in CDN 'cdn1', cannot continue.") } + if len(topologyDsIDs) < 1 { + t.Fatal("No Topology-based Delivery Services found in CDN 'cdn1', cannot continue.") + } + + _, reqInf, err := TOSession.SetCachegroupDeliveryServices(cgID, topologyDsIDs) + if err == nil { + t.Fatal("assigning Topology-based delivery service to cachegroup - expected: error, actual: nil") + } + if reqInf.StatusCode < http.StatusBadRequest || reqInf.StatusCode >= http.StatusInternalServerError { + t.Fatalf("assigning Topology-based delivery service to cachegroup - expected: 400-level status code, actual: %d", reqInf.StatusCode) + } + resp, _, err := TOSession.SetCachegroupDeliveryServices(cgID, dsIDs) if err != nil { t.Fatalf("setting cachegroup delivery services returned error: %v", err) diff --git a/traffic_ops/testing/api/v3/crconfig_test.go b/traffic_ops/testing/api/v3/crconfig_test.go index a01b894f68..d4e3309369 100644 --- a/traffic_ops/testing/api/v3/crconfig_test.go +++ b/traffic_ops/testing/api/v3/crconfig_test.go @@ -76,7 +76,7 @@ func UpdateTestCRConfigSnapshot(t *testing.T) { t.Error("GetDeliveryServiceByXMLIDNullable got unknown delivery service id") } anymapDSID := *res[0].ID - _, err = TOSession.CreateDeliveryServiceServers(anymapDSID, []int{serverID}, true) + _, _, err = TOSession.CreateDeliveryServiceServers(anymapDSID, []int{serverID}, true) if err != nil { t.Errorf("POST delivery service servers: %v", err) } diff --git a/traffic_ops/testing/api/v3/deliveryservices_required_capabilities_test.go b/traffic_ops/testing/api/v3/deliveryservices_required_capabilities_test.go index d3283a56f3..61f6fd4008 100644 --- a/traffic_ops/testing/api/v3/deliveryservices_required_capabilities_test.go +++ b/traffic_ops/testing/api/v3/deliveryservices_required_capabilities_test.go @@ -206,7 +206,7 @@ func InvalidDeliveryServicesRequiredCapabilityAddition(t *testing.T) { } // Assign server to ds - _, err = TOSession.CreateDeliveryServiceServers(*dsID, []int{sID}, false) + _, _, err = TOSession.CreateDeliveryServiceServers(*dsID, []int{sID}, false) if err != nil { t.Fatalf("cannot CREATE server delivery service assignement: %v", err) } diff --git a/traffic_ops/testing/api/v3/deliveryserviceservers_test.go b/traffic_ops/testing/api/v3/deliveryserviceservers_test.go index 1a173dcae9..9b0f444b01 100644 --- a/traffic_ops/testing/api/v3/deliveryserviceservers_test.go +++ b/traffic_ops/testing/api/v3/deliveryserviceservers_test.go @@ -17,6 +17,7 @@ package v3 import ( "errors" + "net/http" "strings" "testing" @@ -27,6 +28,7 @@ import ( 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) }) } @@ -37,6 +39,52 @@ func TestDeliveryServiceServersWithRequiredCapabilities(t *testing.T) { }) } +func AssignServersToTopologyBasedDeliveryService(t *testing.T) { + ds, _, err := TOSession.GetDeliveryServiceByXMLIDNullable("ds-top") + 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", ds) + } + if ds[0].Topology == nil { + t.Fatal("expected delivery service: 'ds-top' to have a non-nil Topology, actual: nil") + } + serversResp, _, err := TOSession.GetServers() + servers := []tc.Server{} + for _, s := range serversResp { + if s.CDNID == *ds[0].CDNID && s.Type == tc.CacheTypeEdge.String() { + servers = append(servers, s) + } + } + if len(servers) < 1 { + t.Fatalf("expected: at least one EDGE in cdn %s, actual: %v", *ds[0].CDNName, servers) + } + serverNames := []string{} + for _, s := range servers { + if 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) + } + } + _, reqInf, err := TOSession.AssignServersToDeliveryService(serverNames, "ds-top") + if err == nil { + t.Fatal("assigning servers to topology-based delivery service - expected: error, actual: nil error") + } + if reqInf.StatusCode < http.StatusBadRequest || reqInf.StatusCode >= http.StatusInternalServerError { + 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) + if err == nil { + t.Fatal("creating deliveryserviceserver assignment for topology-based delivery service - expected: error, actual: nil error") + } + if reqInf.StatusCode < http.StatusBadRequest || reqInf.StatusCode >= http.StatusInternalServerError { + t.Fatalf("creating deliveryserviceserver assignment for topology-based delivery service - expected: 400-level status code, actual: %d", reqInf.StatusCode) + } +} + func CreateTestDeliveryServiceServersWithRequiredCapabilities(t *testing.T) { sscs := testData.ServerServerCapabilities @@ -89,7 +137,7 @@ func CreateTestDeliveryServiceServersWithRequiredCapabilities(t *testing.T) { 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) } @@ -149,7 +197,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) } @@ -181,7 +229,7 @@ func DeleteTestDeliveryServiceServers(t *testing.T) { dses, servers := getServersAndDSes(t) ds, server := dses[0], servers[0] - _, err := TOSession.CreateDeliveryServiceServers(*ds.ID, []int{server.ID}, true) + _, _, err := TOSession.CreateDeliveryServiceServers(*ds.ID, []int{server.ID}, true) if err != nil { t.Errorf("POST delivery service servers: %v", err) } diff --git a/traffic_ops/testing/api/v3/servers_test.go b/traffic_ops/testing/api/v3/servers_test.go index fc96503c3a..84b6ae9339 100644 --- a/traffic_ops/testing/api/v3/servers_test.go +++ b/traffic_ops/testing/api/v3/servers_test.go @@ -121,7 +121,7 @@ func UpdateTestServers(t *testing.T) { } // Assign server to DS - _, err = TOSession.CreateDeliveryServiceServers(*dses[0].ID, []int{remoteServer.ID}, true) + _, _, err = TOSession.CreateDeliveryServiceServers(*dses[0].ID, []int{remoteServer.ID}, true) if err != nil { t.Fatalf("POST delivery service servers: %v", err) } 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 625e80300d..8afd7ff654 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 @@ -15,6 +15,7 @@ package v3 */ import ( + "net/http" "testing" "github.com/apache/trafficcontrol/lib/go-tc" @@ -24,6 +25,7 @@ func TestAssignments(t *testing.T) { WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Tenants, Topologies, DeliveryServices}, func() { AssignTestDeliveryService(t) AssignIncorrectTestDeliveryService(t) + AssignTopologyBasedDeliveryService(t) }) } @@ -127,3 +129,62 @@ func AssignIncorrectTestDeliveryService(t *testing.T) { t.Errorf(`Invalid Server/DS assignment was created!`) } } + +func AssignTopologyBasedDeliveryService(t *testing.T) { + var server *tc.Server + for _, s := range testData.Servers { + if s.CDNName == "cdn1" && s.Type == string(tc.CacheTypeEdge) { + server = &s + break + } + } + if server == nil { + t.Fatalf("Couldn't find an EDGE server in CDN 'cdn1'!") + } + + rs, _, err := TOSession.GetServerByHostName(server.HostName) + if err != nil { + t.Fatalf("Failed to fetch server information: %v", err) + } else if len(rs) == 0 { + t.Fatalf("Failed to fetch server information: No results returned!") + } + server = &rs[0] + + rd, _, err := TOSession.GetDeliveryServiceByXMLIDNullable("ds-top") + 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(server.ID, []int{*firstDS.ID}, false) + if err == nil { + t.Errorf("Expected bad assignment 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 server - expected: 400-level status code, actual: %d", reqInf.StatusCode) + } + + response, _, err := TOSession.GetServerIDDeliveryServices(server.ID) + t.Logf("response: %+v", response) + if err != nil { + t.Fatalf("Couldn't get Delivery Services assigned to Server '%+v': %v", *server, err) + } + + var found bool + for _, ds := range response { + + if ds.ID != nil && *ds.ID == *firstDS.ID { + found = true + break + } + } + + if found { + t.Errorf(`Invalid Server/DS assignment was created!`) + } +} diff --git a/traffic_ops/testing/api/v3/serverservercapability_test.go b/traffic_ops/testing/api/v3/serverservercapability_test.go index adcc789b80..457117e55e 100644 --- a/traffic_ops/testing/api/v3/serverservercapability_test.go +++ b/traffic_ops/testing/api/v3/serverservercapability_test.go @@ -186,7 +186,7 @@ func DeleteTestServerServerCapabilities(t *testing.T) { dsReqCap := dsReqCapResp[0] // Assign server to ds - _, err = TOSession.CreateDeliveryServiceServers(*dsReqCap.DeliveryServiceID, []int{*ssc.ServerID}, false) + _, _, err = TOSession.CreateDeliveryServiceServers(*dsReqCap.DeliveryServiceID, []int{*ssc.ServerID}, false) if err != nil { t.Fatalf("cannot CREATE server delivery service assignment: %v", err) } diff --git a/traffic_ops/traffic_ops_golang/cachegroup/dspost.go b/traffic_ops/traffic_ops_golang/cachegroup/dspost.go index 47d72845c1..2c12064b74 100644 --- a/traffic_ops/traffic_ops_golang/cachegroup/dspost.go +++ b/traffic_ops/traffic_ops_golang/cachegroup/dspost.go @@ -23,6 +23,7 @@ import ( "database/sql" "encoding/json" "errors" + "fmt" "net/http" "strconv" @@ -53,7 +54,7 @@ func DSPostHandler(w http.ResponseWriter, r *http.Request) { "alerts": tc.CreateAlerts(tc.SuccessLevel, "Delivery services successfully assigned to all the servers of cache group "+strconv.Itoa(inf.IntParams["id"])+".").Alerts, } - resp, userErr, sysErr, errCode := postDSes(inf.Tx.Tx, inf.User, int64(inf.IntParams["id"]), req.DeliveryServices) + resp, userErr, sysErr, errCode := postDSes(inf.Tx.Tx, inf.User, inf.IntParams["id"], req.DeliveryServices) if userErr != nil || sysErr != nil { api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) return @@ -62,7 +63,7 @@ func DSPostHandler(w http.ResponseWriter, r *http.Request) { } // postDSes returns the post response, any user error, any system error, and the HTTP status code to be returned in the event of an error. -func postDSes(tx *sql.Tx, user *auth.CurrentUser, cgID int64, dsIDs []int64) (tc.CacheGroupPostDSResp, error, error, int) { +func postDSes(tx *sql.Tx, user *auth.CurrentUser, cgID int, dsIDs []int) (tc.CacheGroupPostDSResp, error, error, int) { cdnName, usrErr, sysErr, errCode := getCachegroupCDN(tx, cgID) if sysErr != nil { sysErr = errors.New("getting cachegroup CDN: " + sysErr.Error()) @@ -73,7 +74,7 @@ func postDSes(tx *sql.Tx, user *auth.CurrentUser, cgID int64, dsIDs []int64) (tc tenantIDs, err := getDSTenants(tx, dsIDs) if err != nil { - return tc.CacheGroupPostDSResp{}, nil, errors.New("getting delivery service tennat IDs: " + err.Error()), http.StatusInternalServerError + return tc.CacheGroupPostDSResp{}, nil, errors.New("getting delivery service tenant IDs: " + err.Error()), http.StatusInternalServerError } for _, tenantID := range tenantIDs { ok, err := tenant.IsResourceAuthorizedToUserTx(int(tenantID), user, tx) @@ -81,15 +82,23 @@ func postDSes(tx *sql.Tx, user *auth.CurrentUser, cgID int64, dsIDs []int64) (tc return tc.CacheGroupPostDSResp{}, nil, errors.New("checking tenancy: " + err.Error()), http.StatusInternalServerError } if !ok { - return tc.CacheGroupPostDSResp{}, errors.New("not authorized for delivery service tenant " + strconv.FormatInt(tenantID, 10)), nil, http.StatusForbidden + return tc.CacheGroupPostDSResp{}, fmt.Errorf("not authorized for delivery service tenant %d", tenantID), nil, http.StatusForbidden } } cgName, ok, err := dbhelpers.GetCacheGroupNameFromID(tx, cgID) if err != nil { - return tc.CacheGroupPostDSResp{}, nil, errors.New("getting cachegroup name from ID '" + string(cgID) + "': " + err.Error()), http.StatusInternalServerError + return tc.CacheGroupPostDSResp{}, nil, fmt.Errorf("getting cachegroup name from ID %d: %s", cgID, err.Error()), http.StatusInternalServerError } else if !ok { - return tc.CacheGroupPostDSResp{}, errors.New("cachegroup " + string(cgID) + " does not exist"), nil, http.StatusNotFound + return tc.CacheGroupPostDSResp{}, fmt.Errorf("cachegroup %d does not exist", cgID), nil, http.StatusNotFound + } + + topologyDSes, err := dbhelpers.GetDeliveryServicesWithTopologies(tx, dsIDs) + if err != nil { + return tc.CacheGroupPostDSResp{}, nil, errors.New("getting delivery services with topologies: " + err.Error()), http.StatusInternalServerError + } + if len(topologyDSes) > 0 { + return tc.CacheGroupPostDSResp{}, fmt.Errorf("delivery services %v are already assigned to a topology", topologyDSes), nil, http.StatusBadRequest } if err := verifyDSesCDN(tx, dsIDs, cdnName); err != nil { @@ -106,11 +115,11 @@ func postDSes(tx *sql.Tx, user *auth.CurrentUser, cgID int64, dsIDs []int64) (tc if err := updateParams(tx, dsIDs); err != nil { return tc.CacheGroupPostDSResp{}, nil, errors.New("updating delivery service parameters: " + err.Error()), http.StatusInternalServerError } - api.CreateChangeLogRawTx(api.ApiChange, "CACHEGROUP: "+string(cgName)+", ID: "+strconv.FormatInt(cgID, 10)+", ACTION: Assign DSes to CacheGroup servers", user, tx) + api.CreateChangeLogRawTx(api.ApiChange, "CACHEGROUP: "+string(cgName)+", ID: "+strconv.Itoa(cgID)+", ACTION: Assign DSes to CacheGroup servers", user, tx) return tc.CacheGroupPostDSResp{ID: util.JSONIntStr(cgID), ServerNames: cgServers, DeliveryServices: dsIDs}, nil, nil, http.StatusOK } -func insertCachegroupDSes(tx *sql.Tx, cgID int64, dsIDs []int64) error { +func insertCachegroupDSes(tx *sql.Tx, cgID int, dsIDs []int) error { _, err := tx.Exec(` INSERT INTO deliveryservice_server (deliveryservice, server) ( SELECT unnest($1::int[]), server.id @@ -126,7 +135,7 @@ INSERT INTO deliveryservice_server (deliveryservice, server) ( return nil } -func getCachegroupServers(tx *sql.Tx, cgID int64) ([]tc.CacheName, error) { +func getCachegroupServers(tx *sql.Tx, cgID int) ([]tc.CacheName, error) { q := ` SELECT server.host_name FROM server JOIN type on type.id = server.type @@ -149,7 +158,7 @@ AND (type.name LIKE 'EDGE%' OR type.name LIKE 'ORG%') return names, nil } -func verifyDSesCDN(tx *sql.Tx, dsIDs []int64, cdn string) error { +func verifyDSesCDN(tx *sql.Tx, dsIDs []int, cdn string) error { q := ` SELECT count(cdn.name) FROM cdn @@ -167,7 +176,7 @@ AND cdn.name <> $2::text return nil } -func getCachegroupCDN(tx *sql.Tx, cgID int64) (string, error, error, int) { +func getCachegroupCDN(tx *sql.Tx, cgID int) (string, error, error, int) { q := ` SELECT cdn.name FROM cdn @@ -195,13 +204,13 @@ AND (type.name LIKE 'EDGE%' OR type.name LIKE 'ORG%') } } if cdn == "" { - return "", errors.New("no edge or origin servers found on cachegroup " + strconv.FormatInt(cgID, 10)), nil, http.StatusBadRequest + return "", fmt.Errorf("no edge or origin servers found on cachegroup %d", cgID), nil, http.StatusBadRequest } return cdn, nil, nil, http.StatusOK } // updateParams updated the header rewrite, cacheurl, and regex remap params for the given edge caches, on the given delivery services. NOTE it does not update Mid params. -func updateParams(tx *sql.Tx, dsIDs []int64) error { +func updateParams(tx *sql.Tx, dsIDs []int) error { if err := updateDSParam(tx, dsIDs, "hdr_rw_", "edge_header_rewrite"); err != nil { return err } @@ -214,7 +223,7 @@ func updateParams(tx *sql.Tx, dsIDs []int64) error { return nil } -func updateDSParam(tx *sql.Tx, dsIDs []int64, paramPrefix string, dsField string) error { +func updateDSParam(tx *sql.Tx, dsIDs []int, paramPrefix string, dsField string) error { _, err := tx.Exec(` DELETE FROM parameter WHERE name = 'location' @@ -244,9 +253,9 @@ INSERT INTO parameter (name, config_file, value) ( if err != nil { return errors.New("inserting parameters: " + err.Error()) } - ids := []int64{} + ids := []int{} for rows.Next() { - id := int64(0) + id := 0 if err := rows.Scan(&id); err != nil { return errors.New("scanning inserted parameters: " + err.Error()) } @@ -268,7 +277,7 @@ INSERT INTO profile_parameter (parameter, profile) ( return nil } -func getDSTenants(tx *sql.Tx, dsIDs []int64) ([]int64, error) { +func getDSTenants(tx *sql.Tx, dsIDs []int) ([]int, error) { q := ` SELECT COALESCE(tenant_id, 0) FROM deliveryservice WHERE deliveryservice.id = ANY($1) @@ -278,9 +287,9 @@ WHERE deliveryservice.id = ANY($1) return nil, errors.New("selecting delivery service tenants: " + err.Error()) } defer rows.Close() - tenantIDs := []int64{} + tenantIDs := []int{} for rows.Next() { - id := int64(0) + id := 0 if err := rows.Scan(&id); err != nil { return nil, errors.New("querying cachegroup delivery service tenants: " + err.Error()) } diff --git a/traffic_ops/traffic_ops_golang/cachegroup/queueupdate.go b/traffic_ops/traffic_ops_golang/cachegroup/queueupdate.go index 4661d705ff..b0ffc3931b 100644 --- a/traffic_ops/traffic_ops_golang/cachegroup/queueupdate.go +++ b/traffic_ops/traffic_ops_golang/cachegroup/queueupdate.go @@ -65,7 +65,7 @@ func QueueUpdates(w http.ResponseWriter, r *http.Request) { } reqObj.CDN = &cdn } - cgID := int64(inf.IntParams["id"]) + cgID := inf.IntParams["id"] cgName, ok, err := dbhelpers.GetCacheGroupNameFromID(inf.Tx.Tx, cgID) if err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting cachegroup name from ID '"+inf.Params["id"]+"': "+err.Error())) @@ -88,7 +88,7 @@ func QueueUpdates(w http.ResponseWriter, r *http.Request) { CDN: *reqObj.CDN, CacheGroupID: cgID, }) - api.CreateChangeLogRawTx(api.ApiChange, "CACHEGROUP: "+string(cgName)+", ID: "+strconv.FormatInt(cgID, 10)+", ACTION: "+strings.Title(reqObj.Action)+"d CacheGroup server updates to the "+string(*reqObj.CDN)+" CDN", inf.User, inf.Tx.Tx) + api.CreateChangeLogRawTx(api.ApiChange, "CACHEGROUP: "+string(cgName)+", ID: "+strconv.Itoa(cgID)+", ACTION: "+strings.Title(reqObj.Action)+"d CacheGroup server updates to the "+string(*reqObj.CDN)+" CDN", inf.User, inf.Tx.Tx) } type QueueUpdatesResp struct { @@ -96,10 +96,10 @@ type QueueUpdatesResp struct { Action string `json:"action"` ServerNames []tc.CacheName `json:"serverNames"` CDN tc.CDNName `json:"cdn"` - CacheGroupID int64 `json:"cachegroupID"` + CacheGroupID int `json:"cachegroupID"` } -func queueUpdates(tx *sql.Tx, cgID int64, cdn tc.CDNName, queue bool) ([]tc.CacheName, error) { +func queueUpdates(tx *sql.Tx, cgID int, cdn tc.CDNName, queue bool) ([]tc.CacheName, error) { q := ` UPDATE server SET upd_pending = $1 WHERE server.cachegroup = $2 diff --git a/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters.go b/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters.go index 26d4674a06..a1dddac8c3 100644 --- a/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters.go +++ b/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters.go @@ -76,7 +76,7 @@ func (cgparam *TOCacheGroupParameter) Read() ([]interface{}, error, error, int) return nil, errors.New("cache group id must be an integer"), nil, http.StatusBadRequest } - _, ok, err := dbhelpers.GetCacheGroupNameFromID(cgparam.ReqInfo.Tx.Tx, int64(cgID)) + _, ok, err := dbhelpers.GetCacheGroupNameFromID(cgparam.ReqInfo.Tx.Tx, cgID) if err != nil { return nil, nil, err, http.StatusInternalServerError } else if !ok { @@ -170,7 +170,7 @@ func (cgparam *TOCacheGroupParameter) GetKeys() (map[string]interface{}, bool) { // Delete implements the api.CRUDer interface. func (cgparam *TOCacheGroupParameter) Delete() (error, error, int) { - _, ok, err := dbhelpers.GetCacheGroupNameFromID(cgparam.ReqInfo.Tx.Tx, int64(cgparam.CacheGroupID)) + _, ok, err := dbhelpers.GetCacheGroupNameFromID(cgparam.ReqInfo.Tx.Tx, cgparam.CacheGroupID) if err != nil { return nil, err, http.StatusInternalServerError } else if !ok { diff --git a/traffic_ops/traffic_ops_golang/cachegroupparameter/unassigned_parameters.go b/traffic_ops/traffic_ops_golang/cachegroupparameter/unassigned_parameters.go index 76fdec1e70..3f1e0c0d5c 100644 --- a/traffic_ops/traffic_ops_golang/cachegroupparameter/unassigned_parameters.go +++ b/traffic_ops/traffic_ops_golang/cachegroupparameter/unassigned_parameters.go @@ -64,7 +64,7 @@ func (cgunparam *TOCacheGroupUnassignedParameter) Read() ([]interface{}, error, return nil, errors.New("cache group id must be an integer"), nil, http.StatusBadRequest } - _, ok, err := dbhelpers.GetCacheGroupNameFromID(cgunparam.ReqInfo.Tx.Tx, int64(cgID)) + _, ok, err := dbhelpers.GetCacheGroupNameFromID(cgunparam.ReqInfo.Tx.Tx, cgID) if err != nil { return nil, nil, err, http.StatusInternalServerError } else if !ok { diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go index b2df723372..0ce7be9c88 100644 --- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go +++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go @@ -598,6 +598,66 @@ func GetServerNameFromID(tx *sql.Tx, id int) (string, bool, error) { return name, true, nil } +type ServerHostNameAndType struct { + HostName string + Type string +} + +func GetServerHostNamesAndTypesFromIDs(tx *sql.Tx, ids []int) ([]ServerHostNameAndType, error) { + qry := ` +SELECT + s.host_name, + t.name +FROM + server s JOIN type t ON s.type = t.id +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 := []ServerHostNameAndType{} + for rows.Next() { + s := ServerHostNameAndType{} + if err := rows.Scan(&s.HostName, &s.Type); err != nil { + return nil, errors.New("scanning server host name and type: " + err.Error()) + } + servers = append(servers, s) + } + return servers, nil +} + +// GetServerTypesFromHostNames returns the host names and types of the given server host names or an error if any occur. +func GetServerTypesFromHostNames(tx *sql.Tx, hostNames []string) ([]ServerHostNameAndType, error) { + qry := ` +SELECT + s.host_name, + t.name +FROM + server s JOIN type t ON s.type = t.id +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()) + } + defer log.Close(rows, "error closing rows") + + servers := []ServerHostNameAndType{} + for rows.Next() { + s := ServerHostNameAndType{} + if err := rows.Scan(&s.HostName, &s.Type); err != nil { + return nil, errors.New("scanning server host name and type: " + err.Error()) + } + servers = append(servers, s) + } + return servers, 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)` @@ -666,7 +726,7 @@ func GetParamNameByID(tx *sql.Tx, id int) (string, bool, error) { } // GetCacheGroupNameFromID Get Cache Group name from a given ID -func GetCacheGroupNameFromID(tx *sql.Tx, id int64) (tc.CacheGroupName, bool, error) { +func GetCacheGroupNameFromID(tx *sql.Tx, id int) (tc.CacheGroupName, bool, error) { name := "" if err := tx.QueryRow(`SELECT name FROM cachegroup WHERE id = $1`, id).Scan(&name); err != nil { if err == sql.ErrNoRows { @@ -677,6 +737,34 @@ func GetCacheGroupNameFromID(tx *sql.Tx, id int64) (tc.CacheGroupName, bool, err return tc.CacheGroupName(name), true, 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) { + q := ` +SELECT + id +FROM + deliveryservice +WHERE + id = ANY($1::bigint[]) + AND topology IS NOT NULL +` + rows, err := tx.Query(q, pq.Array(dsIDs)) + if err != nil { + return nil, errors.New("querying deliveryservice topologies: " + err.Error()) + } + defer log.Close(rows, "error closing rows") + dses := make([]int, 0) + for rows.Next() { + id := 0 + if err := rows.Scan(&id); err != nil { + return nil, errors.New("scanning deliveryservice id: " + err.Error()) + } + dses = append(dses, id) + } + return dses, nil +} + // GetFederationIDForUserIDByXMLID retrieves the ID of the Federation assigned to the user defined by // userID on the Delivery Service identified by xmlid. If no such federation exists, the boolean // returned will be 'false', while the error indicates unexpected errors that occurred when querying. diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go index 3826d7ab01..493cb8014f 100644 --- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go +++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go @@ -126,7 +126,7 @@ func TestGetCacheGroupByName(t *testing.T) { mock.ExpectQuery("cachegroup").WillReturnRows(rows) } mock.ExpectCommit() - _, exists, err := GetCacheGroupNameFromID(db.MustBegin().Tx, int64(1)) + _, exists, err := GetCacheGroupNameFromID(db.MustBegin().Tx, 1) if testCase.storageError != nil && err == nil { t.Errorf("Storage error expected: received no storage error") } diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go index e781a7fcc9..191fb17218 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go @@ -319,17 +319,19 @@ func GetReplaceHandler(w http.ResponseWriter, r *http.Request) { api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) return } - serverNames := []string{} - for _, s := range servers { - name, _, err := dbhelpers.GetServerNameFromID(inf.Tx.Tx, s) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, err, nil) - return - } - serverNames = append(serverNames, name) + serverNamesAndTypes, err := dbhelpers.GetServerHostNamesAndTypesFromIDs(inf.Tx.Tx, servers) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, err, nil) + return } - usrErr, sysErr, status := ValidateServerCapabilities(ds.ID, serverNames, inf.Tx.Tx) + userErr = ValidateDSSAssignments(ds, serverNamesAndTypes) + if userErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, userErr, nil) + return + } + + usrErr, sysErr, status := ValidateServerCapabilities(ds.ID, serverNamesAndTypes, inf.Tx.Tx) if usrErr != nil || sysErr != nil { api.HandleErr(w, r, inf.Tx.Tx, status, usrErr, sysErr) return @@ -399,7 +401,19 @@ func GetCreateHandler(w http.ResponseWriter, r *http.Request) { payload.XmlId = dsName serverNames := payload.ServerNames - usrErr, sysErr, status := ValidateServerCapabilities(ds.ID, serverNames, inf.Tx.Tx) + serverNamesAndTypes, err := dbhelpers.GetServerTypesFromHostNames(inf.Tx.Tx, serverNames) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, err, nil) + return + } + + userErr = ValidateDSSAssignments(ds, serverNamesAndTypes) + if userErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, userErr, nil) + return + } + + usrErr, sysErr, status := ValidateServerCapabilities(ds.ID, serverNamesAndTypes, inf.Tx.Tx) if usrErr != nil || sysErr != nil { api.HandleErr(w, r, inf.Tx.Tx, status, usrErr, sysErr) return @@ -430,24 +444,26 @@ 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.ServerHostNameAndType) error { + if ds.Topology == nil { + return nil + } + 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 nil +} + // ValidateServerCapabilities checks that the delivery service's requirements are met by each server to be assigned. -func ValidateServerCapabilities(dsID int, serverNames []string, tx *sql.Tx) (error, error, int) { +func ValidateServerCapabilities(dsID int, serverNamesAndTypes []dbhelpers.ServerHostNameAndType, tx *sql.Tx) (error, error, int) { nonOriginServerNames := []string{} - nonOriginTypeQuery := ` - SELECT ARRAY( - SELECT s.host_name - FROM server s - JOIN type t ON s.type = t.id - WHERE t.name LIKE 'EDGE%' AND s.host_name = ANY($1) - )` - - serverNamePqArray := pq.Array(serverNames) - - if err := tx.QueryRow(nonOriginTypeQuery, serverNamePqArray).Scan(pq.Array(&nonOriginServerNames)); err != nil { - if err == sql.ErrNoRows { - return nil, nil, http.StatusOK + for _, s := range serverNamesAndTypes { + if strings.HasPrefix(s.Type, tc.EdgeTypePrefix) { + nonOriginServerNames = append(nonOriginServerNames, s.HostName) } - return nil, err, http.StatusInternalServerError } var sCaps []string @@ -665,6 +681,7 @@ type DSInfo struct { SigningAlgorithm *string CacheURL *string MaxOriginConnections *int + Topology *string } // 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. @@ -678,7 +695,8 @@ SELECT ds.regex_remap, ds.signing_algorithm, ds.cacheurl, - ds.max_origin_connections + ds.max_origin_connections, + ds.topology FROM deliveryservice ds JOIN type tp ON ds.type = tp.id @@ -686,7 +704,7 @@ 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); err != nil { + 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); err != nil { if err == sql.ErrNoRows { return DSInfo{}, false, nil } @@ -707,7 +725,8 @@ SELECT ds.regex_remap, ds.signing_algorithm, ds.cacheurl, - ds.max_origin_connections + ds.max_origin_connections, + ds.topology FROM deliveryservice ds JOIN type tp ON ds.type = tp.id @@ -715,7 +734,7 @@ 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); err != nil { + 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); err != nil { if err == sql.ErrNoRows { return DSInfo{}, false, nil } diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index e676d219b5..1763f719df 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -295,6 +295,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {api.Version{3, 0}, http.MethodDelete, `deliveryserviceserver/{dsid}/{serverid}`, dsserver.Delete, auth.PrivLevelOperations, Authenticated, nil, 25321845233, noPerlBypass}, {api.Version{3, 0}, http.MethodPost, `deliveryservices/{xml_id}/servers$`, dsserver.GetCreateHandler, auth.PrivLevelOperations, Authenticated, nil, 24281812063, noPerlBypass}, {api.Version{3, 0}, http.MethodGet, `servers/{id}/deliveryservices$`, api.ReadHandler(&dsserver.TODSSDeliveryService{}), auth.PrivLevelReadOnly, Authenticated, nil, 2331154113, noPerlBypass}, + {api.Version{3, 0}, http.MethodPost, `servers/{id}/deliveryservices$`, server.AssignDeliveryServicesToServerHandler, auth.PrivLevelOperations, Authenticated, nil, 2801282533, noPerlBypass}, {api.Version{3, 0}, http.MethodGet, `deliveryservices/{id}/servers$`, dsserver.GetReadAssigned, auth.PrivLevelReadOnly, Authenticated, nil, 23451212233, noPerlBypass}, {api.Version{3, 0}, http.MethodPost, `deliveryservices/request`, deliveryservicerequests.Request, auth.PrivLevelPortal, Authenticated, nil, 2408752993, noPerlBypass}, @@ -314,6 +315,8 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { //Server status {api.Version{3, 0}, http.MethodPut, `servers/{id}/status$`, server.UpdateStatusHandler, auth.PrivLevelOperations, Authenticated, nil, 2766638513, noPerlBypass}, {api.Version{3, 0}, http.MethodPost, `servers/{id}/queue_update$`, server.QueueUpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 21894713, noPerlBypass}, + {api.Version{3, 0}, http.MethodGet, `servers/{host_name}/update_status$`, server.GetServerUpdateStatusHandler, auth.PrivLevelReadOnly, Authenticated, nil, 2384515993, noPerlBypass}, + {api.Version{3, 0}, http.MethodPost, `servers/{id-or-name}/update$`, server.UpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 143813233, noPerlBypass}, //Server: CRUD {api.Version{3, 0}, http.MethodGet, `servers/?$`, api.ReadHandler(&server.TOServer{}), auth.PrivLevelReadOnly, Authenticated, nil, 27209592853, noPerlBypass}, @@ -415,11 +418,6 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {api.Version{3, 0}, http.MethodPut, `deliveryservices/{dsid}/regexes/{regexid}?$`, deliveryservicesregexes.Put, auth.PrivLevelOperations, Authenticated, nil, 22483396913, noPerlBypass}, {api.Version{3, 0}, http.MethodDelete, `deliveryservices/{dsid}/regexes/{regexid}?$`, deliveryservicesregexes.Delete, auth.PrivLevelOperations, Authenticated, nil, 22467316633, noPerlBypass}, - //Servers - {api.Version{3, 0}, http.MethodPost, `servers/{id}/deliveryservices$`, server.AssignDeliveryServicesToServerHandler, auth.PrivLevelOperations, Authenticated, nil, 2801282533, noPerlBypass}, - {api.Version{3, 0}, http.MethodGet, `servers/{host_name}/update_status$`, server.GetServerUpdateStatusHandler, auth.PrivLevelReadOnly, Authenticated, nil, 2384515993, noPerlBypass}, - {api.Version{3, 0}, http.MethodPost, `servers/{id-or-name}/update$`, server.UpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 143813233, noPerlBypass}, - //StaticDNSEntries {api.Version{3, 0}, http.MethodGet, `staticdnsentries/?$`, api.ReadHandler(&staticdnsentry.TOStaticDNSEntry{}), auth.PrivLevelReadOnly, Authenticated, nil, 2289394773, noPerlBypass}, {api.Version{3, 0}, http.MethodPut, `staticdnsentries/?$`, api.UpdateHandler(&staticdnsentry.TOStaticDNSEntry{}), auth.PrivLevelOperations, Authenticated, nil, 2424571113, noPerlBypass}, @@ -661,6 +659,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {api.Version{2, 0}, http.MethodDelete, `deliveryserviceserver/{dsid}/{serverid}`, dsserver.Delete, auth.PrivLevelOperations, Authenticated, nil, 2532184523, noPerlBypass}, {api.Version{2, 0}, http.MethodPost, `deliveryservices/{xml_id}/servers$`, dsserver.GetCreateHandler, auth.PrivLevelOperations, Authenticated, nil, 2428181206, noPerlBypass}, {api.Version{2, 0}, http.MethodGet, `servers/{id}/deliveryservices$`, api.ReadHandler(&dsserver.TODSSDeliveryService{}), auth.PrivLevelReadOnly, Authenticated, nil, 233115411, noPerlBypass}, + {api.Version{2, 0}, http.MethodPost, `servers/{id}/deliveryservices$`, server.AssignDeliveryServicesToServerHandler, auth.PrivLevelOperations, Authenticated, nil, 280128253, noPerlBypass}, {api.Version{2, 0}, http.MethodGet, `deliveryservices/{id}/servers$`, dsserver.GetReadAssigned, auth.PrivLevelReadOnly, Authenticated, nil, 2345121223, noPerlBypass}, {api.Version{2, 0}, http.MethodPost, `deliveryservices/request`, deliveryservicerequests.Request, auth.PrivLevelPortal, Authenticated, nil, 240875299, noPerlBypass}, @@ -680,6 +679,8 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { //Server status {api.Version{2, 0}, http.MethodPut, `servers/{id}/status$`, server.UpdateStatusHandler, auth.PrivLevelOperations, Authenticated, nil, 276663851, noPerlBypass}, {api.Version{2, 0}, http.MethodPost, `servers/{id}/queue_update$`, server.QueueUpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 2189471, noPerlBypass}, + {api.Version{2, 0}, http.MethodGet, `servers/{host_name}/update_status$`, server.GetServerUpdateStatusHandler, auth.PrivLevelReadOnly, Authenticated, nil, 238451599, noPerlBypass}, + {api.Version{2, 0}, http.MethodPost, `servers/{id-or-name}/update$`, server.UpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 14381323, noPerlBypass}, //Server: CRUD {api.Version{2, 0}, http.MethodGet, `servers/?$`, api.ReadHandler(&server.TOServer{}), auth.PrivLevelReadOnly, Authenticated, nil, 2720959285, noPerlBypass}, @@ -781,11 +782,6 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {api.Version{2, 0}, http.MethodPut, `deliveryservices/{dsid}/regexes/{regexid}?$`, deliveryservicesregexes.Put, auth.PrivLevelOperations, Authenticated, nil, 2248339691, noPerlBypass}, {api.Version{2, 0}, http.MethodDelete, `deliveryservices/{dsid}/regexes/{regexid}?$`, deliveryservicesregexes.Delete, auth.PrivLevelOperations, Authenticated, nil, 2246731663, noPerlBypass}, - //Servers - {api.Version{2, 0}, http.MethodPost, `servers/{id}/deliveryservices$`, server.AssignDeliveryServicesToServerHandler, auth.PrivLevelOperations, Authenticated, nil, 280128253, noPerlBypass}, - {api.Version{2, 0}, http.MethodGet, `servers/{host_name}/update_status$`, server.GetServerUpdateStatusHandler, auth.PrivLevelReadOnly, Authenticated, nil, 238451599, noPerlBypass}, - {api.Version{2, 0}, http.MethodPost, `servers/{id-or-name}/update$`, server.UpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 14381323, noPerlBypass}, - //StaticDNSEntries {api.Version{2, 0}, http.MethodGet, `staticdnsentries/?$`, api.ReadHandler(&staticdnsentry.TOStaticDNSEntry{}), auth.PrivLevelReadOnly, Authenticated, nil, 228939477, noPerlBypass}, {api.Version{2, 0}, http.MethodPut, `staticdnsentries/?$`, api.UpdateHandler(&staticdnsentry.TOStaticDNSEntry{}), auth.PrivLevelOperations, Authenticated, nil, 242457111, noPerlBypass}, @@ -1058,6 +1054,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {api.Version{1, 1}, http.MethodPost, `deliveryserviceserver$`, dsserver.GetReplaceHandler, auth.PrivLevelOperations, Authenticated, nil, 429799788, noPerlBypass}, {api.Version{1, 1}, http.MethodPost, `deliveryservices/{xml_id}/servers$`, dsserver.GetCreateHandler, auth.PrivLevelOperations, Authenticated, nil, 1428181206, noPerlBypass}, {api.Version{1, 1}, http.MethodGet, `servers/{id}/deliveryservices?(\/.json)?$`, api.ReadHandler(&dsserver.TODSSDeliveryService{}), auth.PrivLevelReadOnly, Authenticated, nil, 133115411, noPerlBypass}, + {api.Version{1, 3}, http.MethodPost, `servers/{id}/deliveryservices$`, server.AssignDeliveryServicesToServerHandler, auth.PrivLevelOperations, Authenticated, nil, 880128253, noPerlBypass}, {api.Version{1, 1}, http.MethodGet, `deliveryservices/{id}/servers$`, dsserver.GetReadAssigned, auth.PrivLevelReadOnly, Authenticated, nil, 1345121223, noPerlBypass}, {api.Version{1, 1}, http.MethodGet, `deliveryservices/{id}/unassigned_servers$`, dsserver.GetReadUnassigned, auth.PrivLevelReadOnly, Authenticated, nil, 2023944221, noPerlBypass}, {api.Version{1, 1}, http.MethodPost, `deliveryservices/request`, deliveryservicerequests.Request, auth.PrivLevelPortal, Authenticated, nil, 740875299, perlBypass}, @@ -1080,6 +1077,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { //Server status {api.Version{1, 1}, http.MethodPut, `servers/{id}/status$`, server.UpdateStatusHandler, auth.PrivLevelOperations, Authenticated, nil, 776663851, perlBypass}, {api.Version{1, 1}, http.MethodPost, `servers/{id}/queue_update$`, server.QueueUpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 9189471, perlBypass}, + {api.Version{1, 3}, http.MethodGet, `servers/{host_name}/update_status$`, server.GetServerUpdateStatusHandler, auth.PrivLevelReadOnly, Authenticated, nil, 438451599, noPerlBypass}, //Server: CRUD {api.Version{1, 1}, http.MethodGet, `servers/?(\.json)?$`, api.ReadHandler(&server.TOServer{}), auth.PrivLevelReadOnly, Authenticated, nil, 1720959285, noPerlBypass}, @@ -1190,10 +1188,6 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {api.Version{1, 1}, http.MethodPut, `deliveryservices/{dsid}/regexes/{regexid}?(\.json)?$`, deliveryservicesregexes.Put, auth.PrivLevelOperations, Authenticated, nil, 1248339691, noPerlBypass}, {api.Version{1, 1}, http.MethodDelete, `deliveryservices/{dsid}/regexes/{regexid}?(\.json)?$`, deliveryservicesregexes.Delete, auth.PrivLevelOperations, Authenticated, nil, 2046731663, noPerlBypass}, - //Servers - {api.Version{1, 3}, http.MethodPost, `servers/{id}/deliveryservices$`, server.AssignDeliveryServicesToServerHandler, auth.PrivLevelOperations, Authenticated, nil, 880128253, noPerlBypass}, - {api.Version{1, 3}, http.MethodGet, `servers/{host_name}/update_status$`, server.GetServerUpdateStatusHandler, auth.PrivLevelReadOnly, Authenticated, nil, 438451599, noPerlBypass}, - //StaticDNSEntries {api.Version{1, 1}, http.MethodGet, `staticdnsentries/?(\.json)?$`, api.ReadHandler(&staticdnsentry.TOStaticDNSEntry{}), auth.PrivLevelReadOnly, Authenticated, nil, 258939477, noPerlBypass}, {api.Version{1, 3}, http.MethodGet, `staticdnsentries/?$`, api.ReadHandler(&staticdnsentry.TOStaticDNSEntry{}), auth.PrivLevelReadOnly, Authenticated, nil, 1116932668, noPerlBypass}, diff --git a/traffic_ops/traffic_ops_golang/server/servers_assignment.go b/traffic_ops/traffic_ops_golang/server/servers_assignment.go index 594a60a665..53b1bf908b 100644 --- a/traffic_ops/traffic_ops_golang/server/servers_assignment.go +++ b/traffic_ops/traffic_ops_golang/server/servers_assignment.go @@ -84,17 +84,27 @@ func AssignDeliveryServicesToServerHandler(w http.ResponseWriter, r *http.Reques serverInfo, ok, err := dbhelpers.GetServerInfo(server, inf.Tx.Tx) if err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server name from ID: "+err.Error())) + return } else if !ok { api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no server with that ID found"), nil) return } - if !strings.HasPrefix(serverInfo.Type, "ORG") { + if !strings.HasPrefix(serverInfo.Type, tc.OriginTypeName) { usrErr, sysErr, status := ValidateDSCapabilities(dsList, serverInfo.HostName, inf.Tx.Tx) if usrErr != nil || sysErr != nil { api.HandleErr(w, r, inf.Tx.Tx, status, usrErr, sysErr) return } + dses, sysErr := dbhelpers.GetDeliveryServicesWithTopologies(inf.Tx.Tx, dsList) + if sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, sysErr) + return + } + if len(dses) > 0 { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, fmt.Errorf("delivery services %v are already assigned to a topology", dses), nil) + return + } } // We already know the CDN exists because that's part of the serverInfo query above diff --git a/traffic_ops/v1-client/coordinate.go b/traffic_ops/v1-client/coordinate.go index ab209bd8af..4ae9a898a5 100644 --- a/traffic_ops/v1-client/coordinate.go +++ b/traffic_ops/v1-client/coordinate.go @@ -133,7 +133,7 @@ func (to *Session) DeleteCoordinateByID(id int) (tc.Alerts, ReqInf, error) { return alerts, reqInf, nil } -func (to *Session) SetCachegroupDeliveryServices(cgID int, dsIDs []int64) (tc.CacheGroupPostDSRespResponse, ReqInf, error) { +func (to *Session) SetCachegroupDeliveryServices(cgID int, dsIDs []int) (tc.CacheGroupPostDSRespResponse, ReqInf, error) { uri := apiBase + `/cachegroups/` + strconv.Itoa(cgID) + `/deliveryservices` req := tc.CachegroupPostDSReq{DeliveryServices: dsIDs} reqBody, err := json.Marshal(req) diff --git a/traffic_ops/v2-client/coordinate.go b/traffic_ops/v2-client/coordinate.go index 715917b6ad..7f1fc7623b 100644 --- a/traffic_ops/v2-client/coordinate.go +++ b/traffic_ops/v2-client/coordinate.go @@ -133,7 +133,7 @@ func (to *Session) DeleteCoordinateByID(id int) (tc.Alerts, ReqInf, error) { return alerts, reqInf, nil } -func (to *Session) SetCachegroupDeliveryServices(cgID int, dsIDs []int64) (tc.CacheGroupPostDSRespResponse, ReqInf, error) { +func (to *Session) SetCachegroupDeliveryServices(cgID int, dsIDs []int) (tc.CacheGroupPostDSRespResponse, ReqInf, error) { uri := apiBase + `/cachegroups/` + strconv.Itoa(cgID) + `/deliveryservices` req := tc.CachegroupPostDSReq{DeliveryServices: dsIDs} reqBody, err := json.Marshal(req)