blob: a28eddd45bb26c0639c92a526e201764b55270b2 [file] [log] [blame]
package servers
/*
* 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"
"encoding/json"
"errors"
"fmt"
"github.com/apache/trafficcontrol/lib/go-rfc"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
"net/http"
"strconv"
"strings"
"time"
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
"github.com/apache/trafficcontrol/lib/go-util"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
"github.com/go-ozzo/ozzo-validation"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
)
// TODeliveryServiceRequest provides a type alias to define functions on
type TODeliveryServiceServer struct {
api.APIInfoImpl `json:"-"`
tc.DeliveryServiceServer
TenantIDs pq.Int64Array `json:"-" db:"accessibleTenants"`
DeliveryServiceIDs pq.Int64Array `json:"-" db:"dsids"`
ServerIDs pq.Int64Array `json:"-" db:"serverids"`
}
func (dss TODeliveryServiceServer) GetKeyFieldsInfo() []api.KeyFieldInfo {
return []api.KeyFieldInfo{{"deliveryservice", api.GetIntKey}, {"server", api.GetIntKey}}
}
//Implementation of the Identifier, Validator interface functions
func (dss TODeliveryServiceServer) GetKeys() (map[string]interface{}, bool) {
if dss.DeliveryService == nil {
return map[string]interface{}{"deliveryservice": 0}, false
}
if dss.Server == nil {
return map[string]interface{}{"server": 0}, false
}
keys := make(map[string]interface{})
ds_id := *dss.DeliveryService
server_id := *dss.Server
keys["deliveryservice"] = ds_id
keys["server"] = server_id
return keys, true
}
func (dss *TODeliveryServiceServer) GetAuditName() string {
if dss.DeliveryService != nil {
return strconv.Itoa(*dss.DeliveryService) + "-" + strconv.Itoa(*dss.Server)
}
return "unknown"
}
func (dss *TODeliveryServiceServer) GetType() string {
return "deliveryserviceServers"
}
func (dss *TODeliveryServiceServer) SetKeys(keys map[string]interface{}) {
ds_id, _ := keys["deliveryservice"].(int) //this utilizes the non panicking type assertion, if the thrown away ok variable is false i will be the zero of the type, 0 here.
dss.DeliveryService = &ds_id
server_id, _ := keys["server"].(int) //this utilizes the non panicking type assertion, if the thrown away ok variable is false i will be the zero of the type, 0 here.
dss.Server = &server_id
}
// Validate fulfills the api.Validator interface
func (dss *TODeliveryServiceServer) Validate(tx *sql.Tx) error {
errs := validation.Errors{
"deliveryservice": validation.Validate(dss.DeliveryService, validation.Required),
"server": validation.Validate(dss.Server, validation.Required),
}
return util.JoinErrs(tovalidate.ToErrors(errs))
}
// ReadDSSHandler list all of the Deliveryservice Servers in response to requests to api/1.1/deliveryserviceserver$
func ReadDSSHandler(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"limit", "page"})
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
dss := TODeliveryServiceServer{}
dss.SetInfo(inf)
results, err, maxTime := dss.readDSS(nil, inf.Tx, inf.User, inf.Params, inf.IntParams, nil, nil, false)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
return
}
if maxTime != nil {
// RFC1123
date := maxTime.Format("Mon, 02 Jan 2006 15:04:05 MST")
w.Header().Add(rfc.LastModified, date)
}
// statusnotmodified
if err == nil && results == nil {
w.WriteHeader(http.StatusNotModified)
}
api.WriteRespRaw(w, r, results)
}
// ReadDSSHandler list all of the Deliveryservice Servers in response to requests to api/1.1/deliveryserviceserver$
func ReadDSSHandlerV14(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"limit", "page"})
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
dsIDs := []int64{}
dsIDStrs := strings.Split(inf.Params["deliveryserviceids"], ",")
for _, dsIDStr := range dsIDStrs {
dsIDStr = strings.TrimSpace(dsIDStr)
if dsIDStr == "" {
continue
}
dsID, err := strconv.Atoi(dsIDStr)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, 400, errors.New("deliveryserviceids query parameter must be a comma-delimited list of integers, got '"+inf.Params["deliveryserviceids"]+"'"), nil)
return
}
dsIDs = append(dsIDs, int64(dsID))
}
serverIDs := []int64{}
serverIDStrs := strings.Split(inf.Params["serverids"], ",")
for _, serverIDStr := range serverIDStrs {
serverIDStr = strings.TrimSpace(serverIDStr)
if serverIDStr == "" {
continue
}
serverID, err := strconv.Atoi(serverIDStr)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, 400, errors.New("serverids query parameter must be a comma-delimited list of integers, got '"+inf.Params["serverids"]+"'"), nil)
return
}
serverIDs = append(serverIDs, int64(serverID))
}
dss := TODeliveryServiceServer{}
dss.SetInfo(inf)
cfg, e := api.GetConfig(r.Context())
useIMS := false
if e == nil && cfg != nil {
useIMS = cfg.UseIMS
} else {
log.Warnf("Couldn't get config %v", e)
}
results, err, maxTime := dss.readDSS(r.Header, inf.Tx, inf.User, inf.Params, inf.IntParams, dsIDs, serverIDs, useIMS)
if maxTime != nil {
// RFC1123
date := maxTime.Format("Mon, 02 Jan 2006 15:04:05 MST")
w.Header().Add(rfc.LastModified, date)
}
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
return
}
// statusnotmodified
if err == nil && results == nil {
w.WriteHeader(http.StatusNotModified)
}
api.WriteRespRaw(w, r, results)
}
func (dss *TODeliveryServiceServer) readDSS(h http.Header, tx *sqlx.Tx, user *auth.CurrentUser, params map[string]string, intParams map[string]int, dsIDs []int64, serverIDs []int64, useIMS bool) (*tc.DeliveryServiceServerResponse, error, *time.Time) {
var maxTime time.Time
var runSecond bool
orderby := params["orderby"]
limit := 20
offset := 0
page := 0
err := error(nil)
if plimit, ok := intParams["limit"]; ok {
limit = plimit
}
if ppage, ok := intParams["page"]; ok {
page = ppage
offset = page
if offset > 0 {
offset -= 1
}
offset *= limit
}
if orderby == "" {
orderby = "deliveryService"
}
tenantIDs, err := tenant.GetUserTenantIDListTx(tx.Tx, user.TenantID)
if err != nil {
return nil, errors.New("getting user tenant ID list: " + err.Error()), nil
}
for _, id := range tenantIDs {
dss.TenantIDs = append(dss.TenantIDs, int64(id))
}
dss.ServerIDs = serverIDs
dss.DeliveryServiceIDs = dsIDs
query1, err := selectQuery(orderby, strconv.Itoa(limit), strconv.Itoa(offset), dsIDs, serverIDs, true)
if err != nil {
log.Warnf("Error getting the max last updated query %v", err)
}
if useIMS {
runSecond, maxTime = ims.TryIfModifiedSinceQuery(tx, h, map[string]interface{}{}, query1)
if !runSecond {
log.Debugln("IMS HIT")
return nil, nil, &maxTime
}
log.Debugln("IMS MISS")
} else {
log.Debugln("Non IMS request")
}
query, err := selectQuery(orderby, strconv.Itoa(limit), strconv.Itoa(offset), dsIDs, serverIDs, false)
if err != nil {
return nil, errors.New("creating query for DeliveryserviceServers: " + err.Error()), nil
}
log.Debugln("Query is ", query)
rows, err := tx.NamedQuery(query, dss)
if err != nil {
return nil, errors.New("Error querying DeliveryserviceServers: " + err.Error()), nil
}
defer rows.Close()
servers := []tc.DeliveryServiceServer{}
for rows.Next() {
s := tc.DeliveryServiceServer{}
if err = rows.StructScan(&s); err != nil {
return nil, errors.New("error parsing dss rows: " + err.Error()), nil
}
servers = append(servers, s)
}
return &tc.DeliveryServiceServerResponse{orderby, servers, page, limit}, nil, &maxTime
}
func selectQuery(orderBy string, limit string, offset string, dsIDs []int64, serverIDs []int64, getMaxQuery bool) (string, error) {
selectStmt := `SELECT
s.deliveryService,
s.server,
s.last_updated
FROM deliveryservice_server s`
if getMaxQuery {
selectStmt = `SELECT max(t) from (
SELECT max(s.last_updated) as t FROM deliveryservice_server s`
}
allowedOrderByCols := map[string]string{
"": "",
"deliveryservice": "s.deliveryService",
"server": "s.server",
"lastupdated": "s.last_updated",
"deliveryService": "s.deliveryService",
"lastUpdated": "s.last_updated",
"last_updated": "s.last_updated",
}
orderBy, ok := allowedOrderByCols[orderBy]
if !ok {
return "", errors.New("orderBy '" + orderBy + "' not permitted")
}
// TODO refactor to use dbhelpers.AddTenancyCheck
selectStmt += `
JOIN deliveryservice d on s.deliveryservice = d.id
WHERE d.tenant_id = ANY(CAST(:accessibleTenants AS bigint[]))
`
if len(dsIDs) > 0 {
selectStmt += `
AND s.deliveryservice = ANY(:dsids)
`
}
if len(serverIDs) > 0 {
selectStmt += `
AND s.server = ANY(:serverids)
`
}
if orderBy != "" {
selectStmt += ` ORDER BY ` + orderBy
}
selectStmt += ` LIMIT ` + limit + ` OFFSET ` + offset + ` ROWS`
if getMaxQuery {
return selectStmt + `UNION ALL
select max(last_updated) as t from last_deleted l where l.table_name='deliveryservice_server') as res`, nil
}
return selectStmt, nil
}
type DSServerIds struct {
DsId *int `json:"dsId" db:"deliveryservice"`
Servers []int `json:"servers"`
Replace *bool `json:"replace"`
}
type TODSServerIds DSServerIds
func GetReplaceHandler(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"limit", "page"})
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
payload := DSServerIds{}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("malformed JSON"), nil)
return
}
servers := payload.Servers
dsId := payload.DsId
if servers == nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("servers must exist in post"), nil)
return
}
if dsId == nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("dsid must exist in post"), nil)
return
}
if payload.Replace == nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("replace must exist in post"), nil)
return
}
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()))
return
}
if !ok {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("no delivery service with that ID exists"), nil)
return
}
if userErr, sysErr, errCode := tenant.Check(inf.User, ds.Name, inf.Tx.Tx); userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
serverNamesCdnIdAndTypes, err := dbhelpers.GetServerHostNamesAndTypesFromIDs(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)
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)
return
}
if *payload.Replace {
// delete existing
_, err := inf.Tx.Tx.Exec("DELETE FROM deliveryservice_server WHERE deliveryservice = $1", *dsId)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("unable to remove the existing servers assigned to the delivery service: "+err.Error()))
return
}
}
respServers := []int{}
for _, server := range servers {
dtos := map[string]interface{}{"id": dsId, "server": server}
if _, err := inf.Tx.NamedExec(insertIdsQuery(), dtos); err != nil {
usrErr, sysErr, code := api.ParseDBError(err)
api.HandleErr(w, r, inf.Tx.Tx, code, usrErr, sysErr)
return
}
respServers = append(respServers, server)
}
if err := deliveryservice.EnsureParams(inf.Tx.Tx, *dsId, ds.Name, ds.EdgeHeaderRewrite, ds.MidHeaderRewrite, ds.RegexRemap, ds.CacheURL, ds.SigningAlgorithm, ds.Type, ds.MaxOriginConnections); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("deliveryservice_server replace ensuring ds parameters: "+err.Error()))
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})
}
type TODeliveryServiceServers tc.DeliveryServiceServers
// GetCreateHandler assigns an existing Server to and existing Deliveryservice in response to api/1.1/deliveryservices/{xml_id}/servers
func GetCreateHandler(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"xml_id"}, nil)
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
dsName := inf.Params["xml_id"]
if userErr, sysErr, errCode := tenant.Check(inf.User, dsName, inf.Tx.Tx); userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
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()))
return
} else if !ok {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, errors.New("delivery service not found"))
return
}
// get list of server Ids to insert
payload := tc.DeliveryServiceServers{}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("malformed JSON"), nil)
return
}
payload.XmlId = dsName
serverNames := payload.ServerNames
serverNamesCdnIdAndTypes, err := dbhelpers.GetServerTypesCdnIdFromHostNames(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)
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)
return
}
res, err := inf.Tx.Tx.Exec(`INSERT INTO deliveryservice_server (deliveryservice, server) SELECT $1, id FROM server WHERE host_name = ANY($2::text[])`, ds.ID, pq.Array(serverNames))
if err != nil {
usrErr, sysErr, code := api.ParseDBError(err)
api.HandleErr(w, r, inf.Tx.Tx, code, usrErr, sysErr)
return
}
if rowsAffected, err := res.RowsAffected(); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("ds servers inserting for create delivery service servers: getting rows affected: "+err.Error()))
return
} else if int(rowsAffected) != len(serverNames) {
// this happens when the names they gave don't exist
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("servers not found"), nil)
return
}
if err := deliveryservice.EnsureParams(inf.Tx.Tx, ds.ID, ds.Name, ds.EdgeHeaderRewrite, ds.MidHeaderRewrite, ds.RegexRemap, ds.CacheURL, ds.SigningAlgorithm, ds.Type, ds.MaxOriginConnections); err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("deliveryservice_server replace ensuring ds parameters: "+err.Error()))
return
}
api.CreateChangeLogRawTx(api.ApiChange, "DS: "+dsName+", ID: "+strconv.Itoa(ds.ID)+", ACTION: Assigned servers "+strings.Join(serverNames, ", ")+" to delivery service", inf.User, inf.Tx.Tx)
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 {
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 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, serverNamesAndTypes []dbhelpers.ServerHostNameCDNIDAndType, tx *sql.Tx) (error, error, int) {
nonOriginServerNames := []string{}
for _, s := range serverNamesAndTypes {
if strings.HasPrefix(s.Type, tc.EdgeTypePrefix) {
nonOriginServerNames = append(nonOriginServerNames, s.HostName)
}
}
var sCaps []string
dsCaps, err := dbhelpers.GetDSRequiredCapabilitiesFromID(dsID, tx)
if err != nil {
return nil, err, http.StatusInternalServerError
}
for _, name := range nonOriginServerNames {
sCaps, err = dbhelpers.GetServerCapabilitiesFromName(name, tx)
if err != nil {
return nil, err, http.StatusInternalServerError
}
for _, dsc := range dsCaps {
if !util.ContainsStr(sCaps, dsc) {
return fmt.Errorf("Caching server cannot be assigned to this delivery service without having the required delivery service capabilities: [%v] for server %s", dsCaps, name), nil, http.StatusBadRequest
}
}
}
return nil, nil, 0
}
func insertIdsQuery() string {
query := `INSERT INTO deliveryservice_server (deliveryservice, server)
VALUES (:id, :server )`
return query
}
// GetReadAssigned retrieves lists of servers based in the filter identified in the request: api/1.1/deliveryservices/{id}/servers|unassigned_servers|eligible
func GetReadAssigned(w http.ResponseWriter, r *http.Request) {
getRead(w, r, false, tc.Alerts{})
}
// GetReadUnassigned retrieves lists of servers based in the filter identified in the request: api/1.1/deliveryservices/{id}/servers|unassigned_servers|eligible
func GetReadUnassigned(w http.ResponseWriter, r *http.Request) {
alerts := api.CreateDeprecationAlerts(nil)
getRead(w, r, true, alerts)
}
func getRead(w http.ResponseWriter, r *http.Request, unassigned bool, alerts tc.Alerts) {
inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, []string{"id"})
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
servers, err := read(inf.Tx, inf.IntParams["id"], inf.User, unassigned)
if err != nil {
alerts.AddNewAlert(tc.ErrorLevel, err.Error())
api.WriteAlerts(w, r, http.StatusInternalServerError, alerts)
return
}
if inf.Version.Major < 3 {
v11ServerList := []tc.DSServerV11{}
for _, srv := range servers {
v11server := tc.DSServerV11{}
v11server.DSServerBase = srv.DSServerBase
interfaces := *srv.ServerInterfaces
legacyInterface, err := tc.InterfaceInfoToLegacyInterfaces(interfaces)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("converting to server detail v11: "+err.Error()))
return
}
v11server.LegacyInterfaceDetails = legacyInterface
v11ServerList = append(v11ServerList, v11server)
}
api.WriteAlertsObj(w, r, http.StatusOK, alerts, v11ServerList)
return
}
api.WriteAlertsObj(w, r, http.StatusOK, alerts, servers)
}
func read(tx *sqlx.Tx, dsID int, user *auth.CurrentUser, unassigned bool) ([]tc.DSServer, error) {
where := `WHERE s.id in (select server from deliveryservice_server where deliveryservice = $1)`
if unassigned {
where = `WHERE s.id not in (select server from deliveryservice_server where deliveryservice = $1)`
}
query := dssSelectQuery() + where
log.Debugln("Query is ", query)
rows, err := tx.Queryx(query, dsID)
if err != nil {
return nil, errors.New("error querying dss rows: " + err.Error())
}
defer rows.Close()
servers := []tc.DSServer{}
for rows.Next() {
serverInterfaceInfo := []tc.ServerInterfaceInfo{}
s := tc.DSServer{}
err := rows.Scan(
&s.Cachegroup,
&s.CachegroupID,
&s.CDNID,
&s.CDNName,
&s.DomainName,
&s.GUID,
&s.HostName,
&s.HTTPSPort,
&s.ID,
&s.ILOIPAddress,
&s.ILOIPGateway,
&s.ILOIPNetmask,
&s.ILOPassword,
&s.ILOUsername,
pq.Array(&serverInterfaceInfo),
&s.LastUpdated,
&s.MgmtIPAddress,
&s.MgmtIPGateway,
&s.MgmtIPNetmask,
&s.OfflineReason,
&s.PhysLocation,
&s.PhysLocationID,
&s.Profile,
&s.ProfileDesc,
&s.ProfileID,
&s.Rack,
&s.RouterHostName,
&s.RouterPortName,
&s.Status,
&s.StatusID,
&s.TCPPort,
&s.Type,
&s.TypeID,
&s.UpdPending,
)
if err != nil {
return nil, errors.New("error scanning dss rows: " + err.Error())
}
s.ServerInterfaces = &serverInterfaceInfo
if user.PrivLevel < auth.PrivLevelAdmin {
s.ILOPassword = util.StrPtr("")
}
servers = append(servers, s)
}
return servers, nil
}
func dssSelectQuery() string {
const JumboFrameBPS = 9000
// COALESCE is needed to default values that are nil in the database
// because Go does not allow that to marshal into the struct
selectStmt := `SELECT
cg.name as cachegroup,
s.cachegroup as cachegroup_id,
s.cdn_id,
cdn.name as cdn_name,
s.domain_name,
s.guid,
s.host_name,
s.https_port,
s.id,
s.ilo_ip_address,
s.ilo_ip_gateway,
s.ilo_ip_netmask,
s.ilo_password,
s.ilo_username,
ARRAY (
SELECT ( json_build_object (
'ipAddresses', ARRAY (
SELECT ( json_build_object (
'address', ip_address.address,
'gateway', ip_address.gateway,
'serviceAddress', ip_address.service_address
))
FROM ip_address
WHERE ip_address.interface = interface.name
AND ip_address.server = s.id
),
'max_bandwidth', interface.max_bandwidth,
'monitor', interface.monitor,
'mtu', COALESCE (interface.mtu, 9000),
'name', interface.name
))
FROM interface
WHERE interface.server = s.id
) AS interfaces,
s.last_updated,
s.mgmt_ip_address,
s.mgmt_ip_gateway,
s.mgmt_ip_netmask,
s.offline_reason,
pl.name as phys_location,
s.phys_location as phys_location_id,
p.name as profile,
p.description as profile_desc,
s.profile as profile_id,
s.rack,
s.router_host_name,
s.router_port_name,
st.name as status,
s.status as status_id,
s.tcp_port,
t.name as server_type,
s.type as server_type_id,
s.upd_pending as upd_pending
FROM server s
JOIN cachegroup cg ON s.cachegroup = cg.id
JOIN cdn cdn ON s.cdn_id = cdn.id
JOIN phys_location pl ON s.phys_location = pl.id
JOIN profile p ON s.profile = p.id
JOIN status st ON s.status = st.id
JOIN type t ON s.type = t.id `
return selectStmt
}
type TODSSDeliveryService struct {
api.APIInfoImpl `json:"-"`
tc.DeliveryServiceNullable
}
// Read shows all of the delivery services associated with the specified server.
func (dss *TODSSDeliveryService) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) {
var maxTime time.Time
var runSecond bool
returnable := []interface{}{}
params := dss.APIInfo().Params
tx := dss.APIInfo().Tx.Tx
user := dss.APIInfo().User
if err := api.IsInt(params["id"]); err != nil {
return nil, err, nil, http.StatusBadRequest, nil
}
if _, ok := params["orderby"]; !ok {
params["orderby"] = "xml_id"
}
// Query Parameters to Database Query column mappings
// see the fields mapped in the SQL query
queryParamsToSQLCols := map[string]dbhelpers.WhereColumnInfo{
"xml_id": dbhelpers.WhereColumnInfo{"ds.xml_id", nil},
"xmlId": dbhelpers.WhereColumnInfo{"ds.xml_id", nil},
}
where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToSQLCols)
if len(errs) > 0 {
return nil, nil, errors.New("reading server dses: " + util.JoinErrsStr(errs)), http.StatusInternalServerError, nil
}
if where != "" {
where = where + " AND "
} else {
where = "WHERE "
}
where += "ds.id in (SELECT deliveryService FROM deliveryservice_server where server = :server)"
tenantIDs, err := tenant.GetUserTenantIDListTx(tx, user.TenantID)
if err != nil {
log.Errorln("received error querying for user's tenants: " + err.Error())
return nil, nil, err, http.StatusInternalServerError, nil
}
where, queryValues = dbhelpers.AddTenancyCheck(where, queryValues, "ds.tenant_id", tenantIDs)
query := deliveryservice.GetDSSelectQuery() + where + orderBy + pagination
queryValues["server"] = dss.APIInfo().Params["id"]
if useIMS {
runSecond, maxTime = ims.TryIfModifiedSinceQuery(dss.APIInfo().Tx, h, queryValues, selectMaxLastUpdatedQuery(where))
if !runSecond {
log.Debugln("IMS HIT")
return returnable, nil, nil, http.StatusNotModified, &maxTime
}
log.Debugln("IMS MISS")
} else {
log.Debugln("Non IMS request")
}
log.Debugln("generated deliveryServices query: " + query)
log.Debugf("executing with values: %++v\n", queryValues)
dses, userErr, sysErr, _ := deliveryservice.GetDeliveryServices(query, queryValues, dss.APIInfo().Tx)
if sysErr != nil {
sysErr = fmt.Errorf("reading server dses: %v ", sysErr)
}
if userErr != nil || sysErr != nil {
return nil, userErr, sysErr, http.StatusInternalServerError, nil
}
for _, ds := range dses {
returnable = append(returnable, ds)
}
return returnable, nil, nil, http.StatusOK, &maxTime
}
func selectMaxLastUpdatedQuery(where string) string {
return `SELECT max(t) from (
SELECT max(dss.last_updated) as t from deliveryservice_server dss JOIN deliveryservice ds ON ds.id = dss.deliveryservice ` + where +
` UNION ALL
select max(last_updated) as t from last_deleted l where l.table_name='deliveryservice_server') as res`
}
type DSInfo struct {
ID int
Name string
Type tc.DSType
EdgeHeaderRewrite *string
MidHeaderRewrite *string
RegexRemap *string
SigningAlgorithm *string
CacheURL *string
MaxOriginConnections *int
Topology *string
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 := `
SELECT
ds.xml_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.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 {
if err == sql.ErrNoRows {
return DSInfo{}, false, nil
}
return DSInfo{}, false, fmt.Errorf("querying delivery service server ds info '%v': %v", id, 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.
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
`
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
}