blob: fc22ff420750dbf35c00ccc16af66f4d88721a1d [file] [log] [blame]
package steering
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import (
"database/sql"
"errors"
"net/http"
"sort"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-util"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/lib/pq"
)
func Get(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
steering, err := findSteering(inf.Tx.Tx)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("steering.Get finding: "+err.Error()))
return
}
api.WriteResp(w, r, steering)
}
func findSteering(tx *sql.Tx) ([]tc.Steering, error) {
steeringData, err := getSteeringData(tx)
if err != nil {
return nil, err
}
targetIDs := steeringDataTargetIDs(steeringData)
steeringFilters, err := getSteeringFilters(tx, targetIDs)
if err != nil {
return nil, err
}
primaryOriginCoords, err := getPrimaryOriginCoords(tx, targetIDs)
if err != nil {
return nil, err
}
steerings := map[tc.DeliveryServiceName]tc.Steering{}
for _, data := range steeringData {
if _, ok := steerings[data.DeliveryService]; !ok {
steerings[data.DeliveryService] = tc.Steering{
DeliveryService: data.DeliveryService,
ClientSteering: data.DSType == tc.DSTypeClientSteering,
Filters: []tc.SteeringFilter{}, // Initialize, so JSON produces `[]` not `null` if there are no filters.
Targets: []tc.SteeringSteeringTarget{}, // Initialize, so JSON produces `[]` not `null` if there are no targets.
}
}
steering := steerings[data.DeliveryService]
if filters, ok := steeringFilters[data.TargetID]; ok {
steering.Filters = append(steering.Filters, filters...)
}
target := tc.SteeringSteeringTarget{DeliveryService: data.TargetName}
switch data.Type {
case tc.SteeringTypeOrder:
target.Order = int32(data.Value)
case tc.SteeringTypeWeight:
target.Weight = int32(data.Value)
case tc.SteeringTypeGeoOrder:
target.GeoOrder = util.IntPtr(data.Value)
target.Latitude = util.FloatPtr(primaryOriginCoords[data.TargetID].Lat)
target.Longitude = util.FloatPtr(primaryOriginCoords[data.TargetID].Lon)
case tc.SteeringTypeGeoWeight:
target.Weight = int32(data.Value)
target.GeoOrder = util.IntPtr(0)
target.Latitude = util.FloatPtr(primaryOriginCoords[data.TargetID].Lat)
target.Longitude = util.FloatPtr(primaryOriginCoords[data.TargetID].Lon)
}
steering.Targets = append(steering.Targets, target)
steerings[data.DeliveryService] = steering
}
arr := []tc.Steering{}
for _, steering := range steerings {
arr = append(arr, steering)
}
sort.Slice(arr, func(i, j int) bool {
return arr[i].DeliveryService < arr[j].DeliveryService
})
return arr, nil
}
type SteeringData struct {
DeliveryService tc.DeliveryServiceName
SteeringID int
TargetName tc.DeliveryServiceName
TargetID int
Value int
Type tc.SteeringType
DSType tc.DSType
}
func steeringDataTargetIDs(data []SteeringData) []int {
ids := []int{}
for _, d := range data {
ids = append(ids, d.TargetID)
}
return ids
}
func getSteeringData(tx *sql.Tx) ([]SteeringData, error) {
qry := `
SELECT
ds.xml_id as steering_xml_id,
ds.id as steering_id,
t.xml_id as target_xml_id,
t.id as target_id,
st.value,
tp.name as steering_type,
dt.name as ds_type
FROM
steering_target st
JOIN deliveryservice ds on ds.id = st.deliveryservice
JOIN deliveryservice t on t.id = st.target
JOIN type tp on tp.id = st.type
JOIN type dt on dt.id = ds.type
ORDER BY
steering_xml_id,
target_xml_id
`
rows, err := tx.Query(qry)
if err != nil {
return nil, errors.New("querying steering: " + err.Error())
}
defer rows.Close()
data := []SteeringData{}
for rows.Next() {
sd := SteeringData{}
if err := rows.Scan(&sd.DeliveryService, &sd.SteeringID, &sd.TargetName, &sd.TargetID, &sd.Value, &sd.Type, &sd.DSType); err != nil {
return nil, errors.New("get steering data scanning: " + err.Error())
}
data = append(data, sd)
}
return data, nil
}
// getSteeringFilters takes a slice of ds ids, and returns a map of delivery service ids to patterns and delivery service names.
func getSteeringFilters(tx *sql.Tx, dsIDs []int) (map[int][]tc.SteeringFilter, error) {
qry := `
SELECT
ds.id,
ds.xml_id,
r.pattern
FROM
deliveryservice ds
JOIN deliveryservice_regex dsr ON dsr.deliveryservice = ds.id
JOIN regex r ON dsr.regex = r.id
JOIN type t ON r.type = t.id
WHERE
ds.id = ANY($1)
AND t.name = $2
ORDER BY
r.pattern,
ds.type,
dsr.set_number
`
rows, err := tx.Query(qry, pq.Array(dsIDs), tc.DSMatchTypeSteeringRegex)
if err != nil {
return nil, errors.New("querying steering regexes: " + err.Error())
}
defer rows.Close()
filters := map[int][]tc.SteeringFilter{}
for rows.Next() {
dsID := 0
f := tc.SteeringFilter{}
if err := rows.Scan(&dsID, &f.DeliveryService, &f.Pattern); err != nil {
return nil, errors.New("scanning steering filters: " + err.Error())
}
filters[dsID] = append(filters[dsID], f)
}
return filters, nil
}
type Coord struct {
Lat float64
Lon float64
}
// getPrimaryOriginCoords takes a slice of ds ids, and returns a map of delivery service ids to their primary origin coordinates.
func getPrimaryOriginCoords(tx *sql.Tx, dsIDs []int) (map[int]Coord, error) {
qry := `
SELECT
o.deliveryservice,
c.latitude,
c.longitude
FROM
origin o
JOIN coordinate c ON c.id = o.coordinate
WHERE
o.deliveryservice = ANY($1)
AND o.is_primary
`
rows, err := tx.Query(qry, pq.Array(dsIDs))
if err != nil {
return nil, errors.New("querying steering primary origin coords: " + err.Error())
}
defer rows.Close()
coords := map[int]Coord{}
for rows.Next() {
dsID := 0
c := Coord{}
if err := rows.Scan(&dsID, &c.Lat, &c.Lon); err != nil {
return nil, errors.New("scanning steering primary origin coords: " + err.Error())
}
coords[dsID] = c
}
return coords, nil
}