blob: e2bc60942f0ea554352a65146be91989c2a24544 [file] [log] [blame]
package tc
/*
* 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"
"fmt"
"strconv"
"strings"
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
"github.com/apache/trafficcontrol/lib/go-util"
validation "github.com/go-ozzo/ozzo-validation"
"github.com/lib/pq"
)
// These are the valid values for the Type property of a Profile. No other
// values will be accepted, and these are not configurable.
const (
CacheServerProfileType = "ATS_PROFILE"
DeliveryServiceProfileType = "DS_PROFILE"
ElasticSearchProfileType = "ES_PROFILE"
GroveProfileType = "GROVE_PROFILE"
InfluxdbProfileType = "INFLUXDB_PROFILE"
KafkaProfileType = "KAFKA_PROFILE"
LogstashProfileType = "LOGSTASH_PROFILE"
OriginProfileType = "ORG_PROFILE"
// RiakProfileType is the type of a Profile used on the legacy RiakKV system
// which used to be used as a back-end for Traffic Vault.
//
// Deprecated: Support for Riak as a Traffic Vault back-end is being dropped
// in the near future. Profiles of type UnknownProfileType should be used on
// PostgreSQL database servers instead.
RiakProfileType = "RIAK_PROFILE"
SplunkProfileType = "SPLUNK_PROFILE"
TrafficMonitorProfileType = "TM_PROFILE"
TrafficPortalProfileType = "TP_PROFILE"
TrafficRouterProfileType = "TR_PROFILE"
TrafficStatsProfileType = "TS_PROFILE"
UnkownProfileType = "UNK_PROFILE"
)
// ProfilesResponse is a list of profiles returned by GET requests.
type ProfilesResponse struct {
Response []Profile `json:"response"`
Alerts
}
// ProfileResponse is a single Profile Response for Update and Create to depict what changed
// swagger:response ProfileResponse
// in: body
type ProfileResponse struct {
// in: body
Response Profile `json:"response"`
Alerts
}
// A Profile represents a set of configuration for a server or Delivery Service
// which may be reused to allow sharing configuration across the objects to
// which it is assigned.
type Profile struct {
ID int `json:"id" db:"id"`
LastUpdated TimeNoMod `json:"lastUpdated"`
Name string `json:"name"`
Parameter string `json:"param"`
Description string `json:"description"`
CDNName string `json:"cdnName"`
CDNID int `json:"cdn"`
RoutingDisabled bool `json:"routingDisabled"`
Type string `json:"type"`
Parameters []ParameterNullable `json:"params,omitempty"`
}
// ProfileNullable is exactly the same as Profile except that its fields are
// reference values, so they may be nil.
type ProfileNullable struct {
ID *int `json:"id" db:"id"`
LastUpdated *TimeNoMod `json:"lastUpdated" db:"last_updated"`
Name *string `json:"name" db:"name"`
Description *string `json:"description" db:"description"`
CDNName *string `json:"cdnName" db:"cdn_name"`
CDNID *int `json:"cdn" db:"cdn"`
RoutingDisabled *bool `json:"routingDisabled" db:"routing_disabled"`
Type *string `json:"type" db:"type"`
Parameters []ParameterNullable `json:"params,omitempty"`
}
// ProfileCopy contains details about the profile created from an existing profile.
type ProfileCopy struct {
ID int `json:"id"`
Name string `json:"name"`
ExistingID int `json:"idCopyFrom"`
ExistingName string `json:"profileCopyFrom"`
Description string `json:"description"`
}
// ProfileCopyResponse represents the Traffic Ops API's response when a Profile
// is copied.
type ProfileCopyResponse struct {
Response ProfileCopy `json:"response"`
Alerts
}
// ProfileExportImportNullable is an object of the form used by Traffic Ops
// to represent exported and imported profiles.
type ProfileExportImportNullable struct {
Name *string `json:"name"`
Description *string `json:"description"`
CDNName *string `json:"cdn"`
Type *string `json:"type"`
}
// ProfileExportResponse is an object of the form used by Traffic Ops
// to represent exported profile response.
type ProfileExportResponse struct {
// Parameters associated to the profile
//
Profile ProfileExportImportNullable `json:"profile"`
// Parameters associated to the profile
//
Parameters []ProfileExportImportParameterNullable `json:"parameters"`
Alerts
}
// ProfileImportRequest is an object of the form used by Traffic Ops
// to represent a request to import a profile.
type ProfileImportRequest struct {
// Parameters associated to the profile
//
Profile ProfileExportImportNullable `json:"profile"`
// Parameters associated to the profile
//
Parameters []ProfileExportImportParameterNullable `json:"parameters"`
}
// ProfileImportResponse is an object of the form used by Traffic Ops
// to represent a response from importing a profile.
type ProfileImportResponse struct {
Response ProfileImportResponseObj `json:"response"`
Alerts
}
// ProfileImportResponseObj contains data about the profile being imported.
type ProfileImportResponseObj struct {
ProfileExportImportNullable
ID *int `json:"id"`
}
// Validate validates an profile import request, implementing the
// github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api.ParseValidator
// interface.
func (profileImport *ProfileImportRequest) Validate(tx *sql.Tx) error {
profile := profileImport.Profile
// Profile fields are valid
errs := tovalidate.ToErrors(validation.Errors{
"name": validation.Validate(profile.Name, validation.By(
func(value interface{}) error {
name, ok := value.(*string)
if !ok {
return fmt.Errorf("wrong type, need: string, got: %T", value)
}
if name == nil || *name == "" {
return errors.New("required and cannot be blank")
}
if strings.Contains(*name, " ") {
return errors.New("cannot contain spaces")
}
return nil
},
)),
"description": validation.Validate(profile.Description, validation.Required),
"cdnName": validation.Validate(profile.CDNName, validation.Required),
"type": validation.Validate(profile.Type, validation.Required),
})
// Validate CDN exist
if profile.CDNName != nil {
if ok, err := CDNExistsByName(*profile.CDNName, tx); err != nil {
errString := fmt.Sprintf("checking cdn name %v existence", *profile.CDNName)
log.Errorf("%v: %v", errString, err.Error())
errs = append(errs, errors.New(errString))
} else if !ok {
errs = append(errs, fmt.Errorf("%v CDN does not exist", *profile.CDNName))
}
}
// Validate profile does not already exist
if profile.Name != nil {
if ok, err := ProfileExistsByName(*profile.Name, tx); err != nil {
errString := fmt.Sprintf("checking profile name %v existence", *profile.Name)
log.Errorf("%v: %v", errString, err.Error())
errs = append(errs, errors.New(errString))
} else if ok {
errs = append(errs, fmt.Errorf("a profile with the name \"%s\" already exists", *profile.Name))
}
}
// Validate all parameters
// export/import does not include secure flag
// default value to not flag on validation
secure := 1
for i, pp := range profileImport.Parameters {
if ppErrs := validateProfileParamPostFields(pp.ConfigFile, pp.Name, pp.Value, &secure); len(ppErrs) > 0 {
for _, err := range ppErrs {
errs = append(errs, errors.New("parameter "+strconv.Itoa(i)+": "+err.Error()))
}
}
}
if len(errs) > 0 {
return util.JoinErrs(errs)
}
return nil
}
// ProfilesExistByIDs returns whether profiles exist for all the given ids, and any error.
// TODO move to helper package.
func ProfilesExistByIDs(ids []int64, tx *sql.Tx) (bool, error) {
count := 0
if err := tx.QueryRow(`SELECT count(*) from profile where id = ANY($1)`, pq.Array(ids)).Scan(&count); err != nil {
return false, errors.New("querying profiles existence from id: " + err.Error())
}
return count == len(ids), nil
}
// ProfileExistsByID returns whether a profile with the given id exists, and any error.
// TODO move to helper package.
func ProfileExistsByID(id int64, tx *sql.Tx) (bool, error) {
count := 0
if err := tx.QueryRow(`SELECT count(*) from profile where id = $1`, id).Scan(&count); err != nil {
return false, errors.New("querying profile existence from id: " + err.Error())
}
return count > 0, nil
}
// ProfileExistsByName returns whether a profile with the given name exists, and any error.
// TODO move to helper package.
func ProfileExistsByName(name string, tx *sql.Tx) (bool, error) {
count := 0
if err := tx.QueryRow(`SELECT count(*) from profile where name = $1`, name).Scan(&count); err != nil {
return false, errors.New("querying profile existence from name: " + err.Error())
}
return count > 0, nil
}