traffic_ops/traffic_ops_golang/server/servers_server_capability.go (779 lines of code) (raw):
package server
/*
* 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"
"net/http"
"strconv"
"strings"
"time"
"github.com/apache/trafficcontrol/v8/lib/go-log"
"github.com/apache/trafficcontrol/v8/lib/go-tc"
"github.com/apache/trafficcontrol/v8/lib/go-tc/tovalidate"
"github.com/apache/trafficcontrol/v8/lib/go-util"
"github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/api"
"github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/dbhelpers"
"github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/tenant"
validation "github.com/go-ozzo/ozzo-validation"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
)
const (
ServerCapabilityQueryParam = "serverCapability"
ServerQueryParam = "serverId"
ServerHostNameQueryParam = "serverHostName"
)
type TOServerServerCapabilityV5 struct {
api.APIInfoImpl `json:"-"`
tc.ServerServerCapabilityV5
}
func (ssc *TOServerServerCapabilityV5) SetLastUpdated(t tc.TimeNoMod) { ssc.LastUpdated = &t.Time }
func (ssc *TOServerServerCapabilityV5) NewReadObj() interface{} {
return &tc.ServerServerCapabilityV5{}
}
func (ssc *TOServerServerCapabilityV5) SelectQuery() string { return scSelectQuery() }
func (ssc *TOServerServerCapabilityV5) ParamColumns() map[string]dbhelpers.WhereColumnInfo {
return map[string]dbhelpers.WhereColumnInfo{
ServerCapabilityQueryParam: dbhelpers.WhereColumnInfo{Column: "sc.server_capability"},
ServerQueryParam: dbhelpers.WhereColumnInfo{Column: "s.id", Checker: api.IsInt},
ServerHostNameQueryParam: dbhelpers.WhereColumnInfo{Column: "s.host_name"},
}
}
func (ssc *TOServerServerCapabilityV5) DeleteQuery() string { return scDeleteQuery() }
func (ssc TOServerServerCapabilityV5) GetKeyFieldsInfo() []api.KeyFieldInfo {
return []api.KeyFieldInfo{
{Field: ServerQueryParam, Func: api.GetIntKey},
{Field: ServerCapabilityQueryParam, Func: api.GetStringKey},
}
}
// Need to satisfy Identifier interface but is a no-op as path does not have Update
func (ssc TOServerServerCapabilityV5) GetKeys() (map[string]interface{}, bool) {
if ssc.ServerID == nil {
return map[string]interface{}{ServerQueryParam: 0}, false
}
if ssc.ServerCapability == nil {
return map[string]interface{}{ServerCapabilityQueryParam: 0}, false
}
return map[string]interface{}{
ServerQueryParam: *ssc.ServerID,
ServerCapabilityQueryParam: *ssc.ServerCapability,
}, true
}
func (ssc *TOServerServerCapabilityV5) SetKeys(keys map[string]interface{}) {
sID, _ := keys[ServerQueryParam].(int)
ssc.ServerID = &sID
sc, _ := keys[ServerCapabilityQueryParam].(string)
ssc.ServerCapability = &sc
}
func (ssc *TOServerServerCapabilityV5) GetAuditName() string {
if ssc.ServerCapability != nil {
return *ssc.ServerCapability
}
return "unknown"
}
func (ssc *TOServerServerCapabilityV5) GetType() string {
return "server server_capability"
}
// Validate fulfills the api.Validator interface.
func (ssc TOServerServerCapabilityV5) Validate() (error, error) {
errs := validation.Errors{
ServerQueryParam: validation.Validate(ssc.ServerID, validation.Required),
ServerCapabilityQueryParam: validation.Validate(ssc.ServerCapability, validation.Required),
}
return util.JoinErrs(tovalidate.ToErrors(errs)), nil
}
func (ssc *TOServerServerCapabilityV5) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) {
api.DefaultSort(ssc.APIInfo(), "serverHostName")
return api.GenericRead(h, ssc, useIMS)
}
func (v *TOServerServerCapabilityV5) SelectMaxLastUpdatedQuery(where, orderBy, pagination, tableName string) string {
return `SELECT max(t) from (
SELECT max(sc.last_updated) as t from server_server_capability sc
JOIN server s ON sc.server = s.id ` + where + orderBy + pagination +
` UNION ALL
select max(last_updated) as t from last_deleted l where l.table_name='server_server_capability') as res`
}
func (ssc *TOServerServerCapabilityV5) Delete() (error, error, int) {
tenantIDs, err := tenant.GetUserTenantIDListTx(ssc.APIInfo().Tx.Tx, ssc.APIInfo().User.TenantID)
if err != nil {
return nil, fmt.Errorf("deleting servers_server_capability: %v", err), http.StatusInternalServerError
}
accessibleTenants := make(map[int]struct{}, len(tenantIDs))
for _, id := range tenantIDs {
accessibleTenants[id] = struct{}{}
}
userErr, sysErr, status := checkTopologyBasedDSRequiredCapabilitiesV5(ssc, accessibleTenants)
if userErr != nil || sysErr != nil {
return userErr, sysErr, status
}
userErr, sysErr, status = checkDSRequiredCapabilitiesV5(ssc, accessibleTenants)
if userErr != nil || sysErr != nil {
return userErr, sysErr, status
}
if ssc.ServerID != nil {
cdnName, err := dbhelpers.GetCDNNameFromServerID(ssc.APIInfo().Tx.Tx, int64(*ssc.ServerID))
if err != nil {
return nil, err, http.StatusInternalServerError
}
userErr, sysErr, errCode := dbhelpers.CheckIfCurrentUserCanModifyCDN(ssc.APIInfo().Tx.Tx, string(cdnName), ssc.APIInfo().User.UserName)
if userErr != nil || sysErr != nil {
return userErr, sysErr, errCode
}
}
return api.GenericDelete(ssc)
}
func checkTopologyBasedDSRequiredCapabilitiesV5(ssc *TOServerServerCapabilityV5, accessibleTenants map[int]struct{}) (error, error, int) {
dsRows, err := ssc.APIInfo().Tx.Tx.Query(getTopologyBasedDSesReqCapQuery(), ssc.ServerID, ssc.ServerCapability)
if err != nil {
return nil, fmt.Errorf("querying topology-based DSes with the required capability %s: %v", *ssc.ServerCapability, err), http.StatusInternalServerError
}
defer log.Close(dsRows, "closing dsRows in checkTopologyBasedDSRequiredCapabilitiesV5")
xmlidToTopology := make(map[string]string)
xmlidToTenantID := make(map[string]int)
xmlidToReqCaps := make(map[string][]string)
for dsRows.Next() {
xmlID := ""
topology := ""
tenantID := 0
reqCaps := []string{}
if err := dsRows.Scan(&xmlID, &topology, &tenantID, pq.Array(&reqCaps)); err != nil {
return nil, fmt.Errorf("scanning dsRows in checkTopologyBasedDSRequiredCapabilitiesV5: %v", err), http.StatusInternalServerError
}
xmlidToTenantID[xmlID] = tenantID
xmlidToTopology[xmlID] = topology
xmlidToReqCaps[xmlID] = reqCaps
}
if len(xmlidToTopology) == 0 {
return nil, nil, http.StatusOK
}
serverRows, err := ssc.APIInfo().Tx.Tx.Query(getServerCapabilitiesOfCachegoupQuery(), ssc.ServerID, ssc.ServerCapability)
if err != nil {
return nil, fmt.Errorf("querying server capabilitites of server %d's cachegroup: %v", *ssc.ServerID, err), http.StatusInternalServerError
}
defer log.Close(serverRows, "closing serverRows in checkTopologyBasedDSRequiredCapabilitiesV5")
serverIDToCapabilities := make(map[int]map[string]struct{})
for serverRows.Next() {
serverID := 0
capabilities := []string{}
if err := serverRows.Scan(&serverID, pq.Array(&capabilities)); err != nil {
return nil, fmt.Errorf("scanning serverRows in checkTopologyBasedDSRequiredCapabilitiesV5: %v", err), http.StatusInternalServerError
}
serverIDToCapabilities[serverID] = make(map[string]struct{})
for _, c := range capabilities {
serverIDToCapabilities[serverID][c] = struct{}{}
}
}
unsatisfiedDSes := []string{}
for ds, dsReqCaps := range xmlidToReqCaps {
dsIsSatisfied := false
for _, serverCaps := range serverIDToCapabilities {
serverHasCapabilities := true
for _, dsReqCap := range dsReqCaps {
if _, ok := serverCaps[dsReqCap]; !ok {
serverHasCapabilities = false
break
}
}
if serverHasCapabilities {
dsIsSatisfied = true
break
}
}
if !dsIsSatisfied {
unsatisfiedDSes = append(unsatisfiedDSes, ds)
}
}
if len(unsatisfiedDSes) == 0 {
return nil, nil, http.StatusOK
}
dsStrings := make([]string, 0, len(unsatisfiedDSes))
for _, ds := range unsatisfiedDSes {
if _, ok := accessibleTenants[xmlidToTenantID[ds]]; ok {
dsStrings = append(dsStrings, "(xml_id = "+ds+", topology = "+xmlidToTopology[ds]+")")
}
}
return fmt.Errorf("this capability is required by delivery services, but there are no other servers in this server's cachegroup to satisfy them %s", strings.Join(dsStrings, ", ")), nil, http.StatusBadRequest
}
func checkDSRequiredCapabilitiesV5(ssc *TOServerServerCapabilityV5, accessibleTenants map[int]struct{}) (error, error, int) {
// Ensure that the user is not removing a server capability from the server
// that is required by the delivery services the server is assigned to (if applicable)
dsIDs := []int64{}
if err := ssc.APIInfo().Tx.Tx.QueryRow(checkDSReqCapQuery(), ssc.ServerID, ssc.ServerCapability).Scan(pq.Array(&dsIDs)); err != nil {
return nil, fmt.Errorf("checking removing server server capability would still suffice delivery service requried capabilites: %v", err), http.StatusInternalServerError
}
if len(dsIDs) > 0 {
return ssc.buildDSReqCapError(dsIDs, accessibleTenants)
}
return nil, nil, http.StatusOK
}
func (ssc *TOServerServerCapabilityV5) buildDSReqCapError(dsIDs []int64, accessibleTenants map[int]struct{}) (error, error, int) {
dsTenantIDs, err := getDSTenantIDsByIDs(ssc.APIInfo().Tx, dsIDs)
if err != nil {
return nil, err, http.StatusInternalServerError
}
authDSIDs := []string{}
for _, dsTenantID := range dsTenantIDs {
if _, ok := accessibleTenants[dsTenantID.TenantID]; ok {
if ok {
authDSIDs = append(authDSIDs, strconv.Itoa(dsTenantID.ID))
}
continue
}
}
dsStr := "delivery services"
if len(authDSIDs) > 0 {
dsStr = fmt.Sprintf("the delivery services %v", strings.Join(authDSIDs, ","))
}
return fmt.Errorf("cannot remove the capability %v from the server %v as the server is assigned to %v that require it", *ssc.ServerCapability, *ssc.ServerID, dsStr), nil, http.StatusBadRequest
}
func (ssc *TOServerServerCapabilityV5) Create() (error, error, int) {
tx := ssc.APIInfo().Tx
// Check existence prior to checking type
_, exists, err := dbhelpers.GetServerNameFromID(tx.Tx, int64(*ssc.ServerID))
if err != nil {
return nil, err, http.StatusInternalServerError
}
if !exists {
return fmt.Errorf("server %v does not exist", *ssc.ServerID), nil, http.StatusNotFound
}
// Ensure type is correct
var sidList []int64
sidList = append(sidList, int64(*ssc.ServerID))
errCode, userErr, sysErr := checkServerType(tx.Tx, sidList)
if userErr != nil || sysErr != nil {
return userErr, sysErr, errCode
}
cdnName, err := dbhelpers.GetCDNNameFromServerID(tx.Tx, int64(*ssc.ServerID))
if err != nil {
return nil, err, http.StatusInternalServerError
}
userErr, sysErr, errCode = dbhelpers.CheckIfCurrentUserCanModifyCDN(tx.Tx, string(cdnName), ssc.APIInfo().User.UserName)
if userErr != nil || sysErr != nil {
return userErr, sysErr, errCode
}
resultRows, err := tx.NamedQuery(scInsertQuery(), ssc)
if err != nil {
return api.ParseDBError(err)
}
defer resultRows.Close()
rowsAffected := 0
for resultRows.Next() {
rowsAffected++
if err := resultRows.StructScan(&ssc); err != nil {
return nil, errors.New(ssc.GetType() + " create scanning: " + err.Error()), http.StatusInternalServerError
}
}
if rowsAffected == 0 {
return nil, errors.New(ssc.GetType() + " create: no " + ssc.GetType() + " was inserted, no rows was returned"), http.StatusInternalServerError
} else if rowsAffected > 1 {
return nil, errors.New("too many rows returned from " + ssc.GetType() + " insert"), http.StatusInternalServerError
}
return nil, nil, http.StatusOK
}
type (
TOServerServerCapability struct {
api.APIInfoImpl `json:"-"`
tc.ServerServerCapability
}
DSTenant struct {
TenantID int `db:"tenant_id"`
ID int `db:"id"`
}
)
func (ssc *TOServerServerCapability) SetLastUpdated(t tc.TimeNoMod) { ssc.LastUpdated = &t }
func (ssc *TOServerServerCapability) NewReadObj() interface{} {
return &tc.ServerServerCapability{}
}
func (ssc *TOServerServerCapability) SelectQuery() string { return scSelectQuery() }
func (ssc *TOServerServerCapability) ParamColumns() map[string]dbhelpers.WhereColumnInfo {
return map[string]dbhelpers.WhereColumnInfo{
ServerCapabilityQueryParam: dbhelpers.WhereColumnInfo{Column: "sc.server_capability"},
ServerQueryParam: dbhelpers.WhereColumnInfo{Column: "s.id", Checker: api.IsInt},
ServerHostNameQueryParam: dbhelpers.WhereColumnInfo{Column: "s.host_name"},
}
}
func (ssc *TOServerServerCapability) DeleteQuery() string { return scDeleteQuery() }
func (ssc TOServerServerCapability) GetKeyFieldsInfo() []api.KeyFieldInfo {
return []api.KeyFieldInfo{
{Field: ServerQueryParam, Func: api.GetIntKey},
{Field: ServerCapabilityQueryParam, Func: api.GetStringKey},
}
}
// Need to satisfy Identifier interface but is a no-op as path does not have Update
func (ssc TOServerServerCapability) GetKeys() (map[string]interface{}, bool) {
if ssc.ServerID == nil {
return map[string]interface{}{ServerQueryParam: 0}, false
}
if ssc.ServerCapability == nil {
return map[string]interface{}{ServerCapabilityQueryParam: 0}, false
}
return map[string]interface{}{
ServerQueryParam: *ssc.ServerID,
ServerCapabilityQueryParam: *ssc.ServerCapability,
}, true
}
func (ssc *TOServerServerCapability) SetKeys(keys map[string]interface{}) {
sID, _ := keys[ServerQueryParam].(int)
ssc.ServerID = &sID
sc, _ := keys[ServerCapabilityQueryParam].(string)
ssc.ServerCapability = &sc
}
func (ssc *TOServerServerCapability) GetAuditName() string {
if ssc.ServerCapability != nil {
return *ssc.ServerCapability
}
return "unknown"
}
func (ssc *TOServerServerCapability) GetType() string {
return "server server_capability"
}
// Validate fulfills the api.Validator interface.
func (ssc TOServerServerCapability) Validate() (error, error) {
errs := validation.Errors{
ServerQueryParam: validation.Validate(ssc.ServerID, validation.Required),
ServerCapabilityQueryParam: validation.Validate(ssc.ServerCapability, validation.Required),
}
return util.JoinErrs(tovalidate.ToErrors(errs)), nil
}
func (ssc *TOServerServerCapability) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) {
api.DefaultSort(ssc.APIInfo(), "serverHostName")
return api.GenericRead(h, ssc, useIMS)
}
func (v *TOServerServerCapability) SelectMaxLastUpdatedQuery(where, orderBy, pagination, tableName string) string {
return `SELECT max(t) from (
SELECT max(sc.last_updated) as t from server_server_capability sc
JOIN server s ON sc.server = s.id ` + where + orderBy + pagination +
` UNION ALL
select max(last_updated) as t from last_deleted l where l.table_name='server_server_capability') as res`
}
func (ssc *TOServerServerCapability) Delete() (error, error, int) {
tenantIDs, err := tenant.GetUserTenantIDListTx(ssc.APIInfo().Tx.Tx, ssc.APIInfo().User.TenantID)
if err != nil {
return nil, fmt.Errorf("deleting servers_server_capability: %v", err), http.StatusInternalServerError
}
accessibleTenants := make(map[int]struct{}, len(tenantIDs))
for _, id := range tenantIDs {
accessibleTenants[id] = struct{}{}
}
userErr, sysErr, status := checkTopologyBasedDSRequiredCapabilities(ssc, accessibleTenants)
if userErr != nil || sysErr != nil {
return userErr, sysErr, status
}
userErr, sysErr, status = checkDSRequiredCapabilities(ssc, accessibleTenants)
if userErr != nil || sysErr != nil {
return userErr, sysErr, status
}
if ssc.ServerID != nil {
cdnName, err := dbhelpers.GetCDNNameFromServerID(ssc.APIInfo().Tx.Tx, int64(*ssc.ServerID))
if err != nil {
return nil, err, http.StatusInternalServerError
}
userErr, sysErr, errCode := dbhelpers.CheckIfCurrentUserCanModifyCDN(ssc.APIInfo().Tx.Tx, string(cdnName), ssc.APIInfo().User.UserName)
if userErr != nil || sysErr != nil {
return userErr, sysErr, errCode
}
}
return api.GenericDelete(ssc)
}
func checkTopologyBasedDSRequiredCapabilities(ssc *TOServerServerCapability, accessibleTenants map[int]struct{}) (error, error, int) {
dsRows, err := ssc.APIInfo().Tx.Tx.Query(getTopologyBasedDSesReqCapQuery(), ssc.ServerID, ssc.ServerCapability)
if err != nil {
return nil, fmt.Errorf("querying topology-based DSes with the required capability %s: %v", *ssc.ServerCapability, err), http.StatusInternalServerError
}
defer log.Close(dsRows, "closing dsRows in checkTopologyBasedDSRequiredCapabilities")
xmlidToTopology := make(map[string]string)
xmlidToTenantID := make(map[string]int)
xmlidToReqCaps := make(map[string][]string)
for dsRows.Next() {
xmlID := ""
topology := ""
tenantID := 0
reqCaps := []string{}
if err := dsRows.Scan(&xmlID, &topology, &tenantID, pq.Array(&reqCaps)); err != nil {
return nil, fmt.Errorf("scanning dsRows in checkTopologyBasedDSRequiredCapabilities: %v", err), http.StatusInternalServerError
}
xmlidToTenantID[xmlID] = tenantID
xmlidToTopology[xmlID] = topology
xmlidToReqCaps[xmlID] = reqCaps
}
if len(xmlidToTopology) == 0 {
return nil, nil, http.StatusOK
}
serverRows, err := ssc.APIInfo().Tx.Tx.Query(getServerCapabilitiesOfCachegoupQuery(), ssc.ServerID, ssc.ServerCapability)
if err != nil {
return nil, fmt.Errorf("querying server capabilitites of server %d's cachegroup: %v", *ssc.ServerID, err), http.StatusInternalServerError
}
defer log.Close(serverRows, "closing serverRows in checkTopologyBasedDSRequiredCapabilities")
serverIDToCapabilities := make(map[int]map[string]struct{})
for serverRows.Next() {
serverID := 0
capabilities := []string{}
if err := serverRows.Scan(&serverID, pq.Array(&capabilities)); err != nil {
return nil, fmt.Errorf("scanning serverRows in checkTopologyBasedDSRequiredCapabilities: %v", err), http.StatusInternalServerError
}
serverIDToCapabilities[serverID] = make(map[string]struct{})
for _, c := range capabilities {
serverIDToCapabilities[serverID][c] = struct{}{}
}
}
unsatisfiedDSes := []string{}
for ds, dsReqCaps := range xmlidToReqCaps {
dsIsSatisfied := false
for _, serverCaps := range serverIDToCapabilities {
serverHasCapabilities := true
for _, dsReqCap := range dsReqCaps {
if _, ok := serverCaps[dsReqCap]; !ok {
serverHasCapabilities = false
break
}
}
if serverHasCapabilities {
dsIsSatisfied = true
break
}
}
if !dsIsSatisfied {
unsatisfiedDSes = append(unsatisfiedDSes, ds)
}
}
if len(unsatisfiedDSes) == 0 {
return nil, nil, http.StatusOK
}
dsStrings := make([]string, 0, len(unsatisfiedDSes))
for _, ds := range unsatisfiedDSes {
if _, ok := accessibleTenants[xmlidToTenantID[ds]]; ok {
dsStrings = append(dsStrings, "(xml_id = "+ds+", topology = "+xmlidToTopology[ds]+")")
}
}
return fmt.Errorf("this capability is required by delivery services, but there are no other servers in this server's cachegroup to satisfy them %s", strings.Join(dsStrings, ", ")), nil, http.StatusBadRequest
}
func checkDSRequiredCapabilities(ssc *TOServerServerCapability, accessibleTenants map[int]struct{}) (error, error, int) {
// Ensure that the user is not removing a server capability from the server
// that is required by the delivery services the server is assigned to (if applicable)
dsIDs := []int64{}
if err := ssc.APIInfo().Tx.Tx.QueryRow(checkDSReqCapQuery(), ssc.ServerID, ssc.ServerCapability).Scan(pq.Array(&dsIDs)); err != nil {
return nil, fmt.Errorf("checking removing server server capability would still suffice delivery service requried capabilites: %v", err), http.StatusInternalServerError
}
if len(dsIDs) > 0 {
return ssc.buildDSReqCapError(dsIDs, accessibleTenants)
}
return nil, nil, http.StatusOK
}
func (ssc *TOServerServerCapability) buildDSReqCapError(dsIDs []int64, accessibleTenants map[int]struct{}) (error, error, int) {
dsTenantIDs, err := getDSTenantIDsByIDs(ssc.APIInfo().Tx, dsIDs)
if err != nil {
return nil, err, http.StatusInternalServerError
}
authDSIDs := []string{}
for _, dsTenantID := range dsTenantIDs {
if _, ok := accessibleTenants[dsTenantID.TenantID]; ok {
if ok {
authDSIDs = append(authDSIDs, strconv.Itoa(dsTenantID.ID))
}
continue
}
}
dsStr := "delivery services"
if len(authDSIDs) > 0 {
dsStr = fmt.Sprintf("the delivery services %v", strings.Join(authDSIDs, ","))
}
return fmt.Errorf("cannot remove the capability %v from the server %v as the server is assigned to %v that require it", *ssc.ServerCapability, *ssc.ServerID, dsStr), nil, http.StatusBadRequest
}
func (ssc *TOServerServerCapability) Create() (error, error, int) {
tx := ssc.APIInfo().Tx
// Check existence prior to checking type
_, exists, err := dbhelpers.GetServerNameFromID(tx.Tx, int64(*ssc.ServerID))
if err != nil {
return nil, err, http.StatusInternalServerError
}
if !exists {
return fmt.Errorf("server %v does not exist", *ssc.ServerID), nil, http.StatusNotFound
}
// Ensure type is correct
var sidList []int64
sidList = append(sidList, int64(*ssc.ServerID))
errCode, userErr, sysErr := checkServerType(tx.Tx, sidList)
if userErr != nil || sysErr != nil {
return userErr, sysErr, errCode
}
cdnName, err := dbhelpers.GetCDNNameFromServerID(tx.Tx, int64(*ssc.ServerID))
if err != nil {
return nil, err, http.StatusInternalServerError
}
userErr, sysErr, errCode = dbhelpers.CheckIfCurrentUserCanModifyCDN(tx.Tx, string(cdnName), ssc.APIInfo().User.UserName)
if userErr != nil || sysErr != nil {
return userErr, sysErr, errCode
}
resultRows, err := tx.NamedQuery(scInsertQuery(), ssc)
if err != nil {
return api.ParseDBError(err)
}
defer resultRows.Close()
rowsAffected := 0
for resultRows.Next() {
rowsAffected++
if err := resultRows.StructScan(&ssc); err != nil {
return nil, errors.New(ssc.GetType() + " create scanning: " + err.Error()), http.StatusInternalServerError
}
}
if rowsAffected == 0 {
return nil, errors.New(ssc.GetType() + " create: no " + ssc.GetType() + " was inserted, no rows was returned"), http.StatusInternalServerError
} else if rowsAffected > 1 {
return nil, errors.New("too many rows returned from " + ssc.GetType() + " insert"), http.StatusInternalServerError
}
return nil, nil, http.StatusOK
}
func scSelectQuery() string {
return `SELECT
sc.server_capability,
sc.server,
sc.last_updated,
s.host_name as host_name
FROM server_server_capability sc
JOIN server s ON sc.server = s.id`
}
func scDeleteQuery() string {
return `DELETE FROM server_server_capability
WHERE server = :server AND server_capability = :server_capability`
}
func scInsertQuery() string {
return `INSERT INTO server_server_capability (
server_capability,
server) VALUES (
:server_capability,
:server) RETURNING server, server_capability, last_updated`
}
func checkDSReqCapQuery() string {
return `
SELECT ARRAY(
SELECT ds.id
FROM deliveryservice as ds
WHERE id IN (
SELECT deliveryservice
FROM deliveryservice_server
WHERE server = $1)
AND $2 = ANY(ds.required_capabilities))`
}
// get the topology-based DSes (with all their required capabilities) that a given
// server is assigned to, filtered by the given capability
func getTopologyBasedDSesReqCapQuery() string {
return `
SELECT
ds.xml_id,
ds.topology,
ds.tenant_id,
ds.required_capabilities AS req_caps
FROM server s
JOIN cachegroup c ON s.cachegroup = c.id
JOIN topology_cachegroup tc ON c.name = tc.cachegroup
JOIN deliveryservice ds ON ds.topology = tc.topology
WHERE s.id = $1
GROUP BY ds.xml_id, ds.tenant_id, ds.topology, ds.required_capabilities
HAVING $2 = ANY(ds.required_capabilities)
`
}
// get all the capabilities of the servers in a given server's cachegroup
// that have a given capability
func getServerCapabilitiesOfCachegoupQuery() string {
return `
SELECT s.id, ARRAY_AGG(ssc.server_capability) AS capabilities
FROM server s
JOIN cachegroup c ON c.id = s.cachegroup AND c.id = (SELECT cachegroup FROM server WHERE server.id = $1)
JOIN server_server_capability ssc ON ssc.server = s.id
WHERE
s.cdn_id = (SELECT cdn_id FROM server WHERE server.id = $1)
AND s.id != $1
GROUP BY s.id
HAVING $2 = ANY(ARRAY_AGG(ssc.server_capability));
`
}
func getDSTenantIDsByIDs(tx *sqlx.Tx, dsIDs []int64) ([]DSTenant, error) {
dsTenantIDs := []DSTenant{}
query, args, err := sqlx.In("SELECT id, tenant_id FROM deliveryservice where id IN (?);", dsIDs)
if err != nil {
return nil, fmt.Errorf("building query for getting delivery services' tenants: %v", err)
}
query = tx.Rebind(query)
resultRows, err := tx.Queryx(query, args...)
if err != nil {
return nil, fmt.Errorf("querying tenant IDs for delivery service IDs: %v", err)
}
defer log.Close(resultRows, "closing resultRows in getDSTenantIDsByIDs")
for resultRows.Next() {
dsTenantID := DSTenant{}
if err := resultRows.StructScan(&dsTenantID); err != nil {
return nil, errors.New("scanning delivery service tenant ID: " + err.Error())
}
dsTenantIDs = append(dsTenantIDs, dsTenantID)
}
return dsTenantIDs, nil
}
// AssignMultipleServersCapabilities assigns multiple servers to a capability or multiple server capabilities to a server
func AssignMultipleServersCapabilities(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
tx := inf.Tx.Tx
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
var mssc tc.MultipleServersCapabilities
if err := json.NewDecoder(r.Body).Decode(&mssc); err != nil {
api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("error decoding POST request body into MultipleServersCapabilities struct %w", err), nil)
return
}
// validate JSON body.
errs := tovalidate.ToErrors(validation.Errors{
"serverIds": validation.Validate(mssc.ServerIDs, validation.Required),
"serverCapabilities": validation.Validate(mssc.ServerCapabilities, validation.Required),
"pageType": validation.Validate(mssc.PageType, validation.Required),
})
if len(errs) > 0 {
api.HandleErr(w, r, tx, http.StatusBadRequest, util.JoinErrs(errs), nil)
return
}
if len(mssc.ServerIDs) > 1 && len(mssc.ServerCapabilities) > 1 {
api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("not allowed to have many:many association between server and server capability. "+
"Only associations allowed are; 1:1, 1:many or many:1"), nil)
return
}
if len(mssc.ServerIDs) >= 1 {
errCode, userErr, sysErr = checkExistingServer(tx, mssc.ServerIDs, inf.User.UserName)
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
}
// Ensure type is correct
errCode, userErr, sysErr = checkServerType(tx, mssc.ServerIDs)
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
// Insert rows in DB
sid := make([]int64, len(mssc.ServerCapabilities))
scs := make([]string, len(mssc.ServerIDs))
switch mssc.PageType {
case "sc":
for i := range mssc.ServerIDs {
scs[i] = mssc.ServerCapabilities[0]
}
sid = mssc.ServerIDs
case "server":
for i := range mssc.ServerCapabilities {
sid[i] = mssc.ServerIDs[0]
}
scs = mssc.ServerCapabilities
default:
api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("incorrect page type: '%s'. Should be 'sc' or 'server'", mssc.PageType), nil)
return
}
msscQuery := `INSERT INTO server_server_capability
select "server_capability", "server"
FROM UNNEST($1::text[], $2::int[]) AS tmp("server_capability", "server")`
_, err := tx.Query(msscQuery, pq.Array(scs), pq.Array(sid))
if err != nil {
useErr, sysErr, statusCode := api.ParseDBError(err)
api.HandleErr(w, r, tx, statusCode, useErr, sysErr)
return
}
var alerts tc.Alerts
if mssc.PageType == "sc" {
alerts = tc.CreateAlerts(tc.SuccessLevel, "Assign Server(s) to a capability")
} else {
alerts = tc.CreateAlerts(tc.SuccessLevel, "Assign Server Capability(ies) to a server")
}
api.WriteAlertsObj(w, r, http.StatusOK, alerts, mssc)
return
}
// DeleteMultipleServersCapabilities deletes multiple servers to a capability or multiple server capabilities to a server
func DeleteMultipleServersCapabilities(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
tx := inf.Tx.Tx
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
var mssc tc.MultipleServersCapabilities
if err := json.NewDecoder(r.Body).Decode(&mssc); err != nil {
api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("error decoding DELETE request body into MultipleServersCapabilities struct %w", err), nil)
return
}
if len(mssc.ServerIDs) >= 1 {
errCode, userErr, sysErr = checkExistingServer(tx, mssc.ServerIDs, inf.User.UserName)
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
}
//Delete existing rows from server_server_capability for a given server or for a given capability
const delQuery = `DELETE FROM server_server_capability ssc WHERE `
var dq string
var alerts tc.Alerts
var result sql.Result
var err error
switch mssc.PageType {
case "sc":
dq = delQuery + `ssc.server_capability=$1`
if len(mssc.ServerIDs) == 1 {
dq = dq + ` AND ssc.server=$2`
result, err = tx.Exec(dq, mssc.ServerCapabilities[0], mssc.ServerIDs[0])
} else {
result, err = tx.Exec(dq, mssc.ServerCapabilities[0])
}
case "server":
dq = delQuery + `ssc.server=$1`
if len(mssc.ServerCapabilities) == 1 {
dq = dq + ` AND ssc.server_capability=$2`
result, err = tx.Exec(dq, mssc.ServerIDs[0], mssc.ServerCapabilities[0])
} else {
result, err = tx.Exec(dq, mssc.ServerIDs[0])
}
default:
api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("incorrect page type:'%s'. Should be 'sc' or 'server'", mssc.PageType), nil)
return
}
if err != nil {
useErr, sysErr, statusCode := api.ParseDBError(err)
api.HandleErr(w, r, tx, statusCode, useErr, sysErr)
return
}
rowsAffected, err := result.RowsAffected()
if err != nil {
api.HandleErr(w, r, tx, http.StatusInternalServerError, fmt.Errorf("no rows were deleted from server_server_capability table: %w", err), sysErr)
return
}
if rowsAffected >= 1 {
if mssc.PageType == "sc" {
alerts = tc.CreateAlerts(tc.SuccessLevel, "Removed Server(s) associated with a capability")
} else {
alerts = tc.CreateAlerts(tc.SuccessLevel, "Removed Server Capability(ies) associated with a server")
}
}
api.WriteAlertsObj(w, r, http.StatusOK, alerts, mssc)
return
}
// checkExistingServer checks server existence
func checkExistingServer(tx *sql.Tx, sidList []int64, uName string) (int, error, error) {
for _, sid := range sidList {
_, exists, err := dbhelpers.GetServerNameFromID(tx, sid)
if err != nil {
return http.StatusInternalServerError, nil, err
}
if !exists {
userErr := fmt.Errorf("server %d does not exist", sid)
return http.StatusNotFound, userErr, nil
}
cdnName, err := dbhelpers.GetCDNNameFromServerID(tx, sid)
if err != nil {
return http.StatusInternalServerError, nil, err
}
userErr, sysErr, errCode := dbhelpers.CheckIfCurrentUserCanModifyCDN(tx, string(cdnName), uName)
if userErr != nil || sysErr != nil {
return errCode, userErr, sysErr
}
}
return http.StatusOK, nil, nil
}
// checkServerType checks if the server type is MID and/or EDGE
func checkServerType(tx *sql.Tx, sids []int64) (int, error, error) {
var servArray []int64
queryType := `SELECT array_agg(s.id)
FROM server s
JOIN type t ON s.type = t.id
WHERE s.id = any ($1)
AND t.use_in_table = 'server'
AND (t.name LIKE 'MID%' OR t.name LIKE 'EDGE%')`
if err := tx.QueryRow(queryType, pq.Array(sids)).Scan(pq.Array(&servArray)); err != nil {
return http.StatusInternalServerError, nil, fmt.Errorf("checking server type: %w", err)
}
cmp := make(map[int64]bool)
for _, item := range servArray {
cmp[item] = true
}
for _, sid := range sids {
if _, ok := cmp[sid]; !ok {
userErr := fmt.Errorf("server id: %d has an incorrect server type. Server capabilities can only be assigned to EDGE or MID servers", sid)
return http.StatusBadRequest, userErr, nil
}
}
return http.StatusOK, nil, nil
}