From d5b3f30163751a15d48d78aa6fe65f4947104a56 Mon Sep 17 00:00:00 2001 From: ocket8888 Date: Fri, 19 Jan 2024 10:22:47 -0700 Subject: [PATCH] APIInfo refactor (#7902) * Rename api.APIInfo -> api.Info * Move Info structures and related functionality to dedicated files I also added some tests for some Info methods/related functions that didn't have any tests, and I moved the APIInfoImpl type to the only place where it's actually used. * Fixup references to the old name for api.Info in the docs * Remove lingering references to the old name for api.Info --- docs/source/_ext/atc.py | 2 +- .../development/documentation_guidelines.rst | 2 +- docs/source/development/traffic_ops.rst | 6 +- traffic_ops/traffic_ops_golang/README.md | 2 +- traffic_ops/traffic_ops_golang/api/api.go | 371 +------- .../traffic_ops_golang/api/api_test.go | 206 ----- .../traffic_ops_golang/api/generic_crud.go | 10 +- traffic_ops/traffic_ops_golang/api/info.go | 429 ++++++++++ .../traffic_ops_golang/api/info_test.go | 804 ++++++++++++++++++ .../traffic_ops_golang/api/shared_handlers.go | 10 +- .../api/shared_handlers_test.go | 2 +- .../api/shared_interfaces.go | 27 +- .../apitenant/tenant_test.go | 10 +- .../traffic_ops_golang/asn/asns_test.go | 10 +- .../cachegroup/cachegroups_test.go | 6 +- .../cachegroupparameter/parameters_test.go | 2 +- .../traffic_ops_golang/cdn/cdns_test.go | 8 +- .../cdnfederation/cdnfederations.go | 12 +- .../cdnfederation/cdnfederations_test.go | 6 +- .../traffic_ops_golang/cdni/capacity.go | 2 +- traffic_ops/traffic_ops_golang/cdni/shared.go | 10 +- .../traffic_ops_golang/cdni/telemetry.go | 2 +- .../cdnnotification/cdnnotifications.go | 8 +- .../coordinate/coordinates_test.go | 2 +- .../deliveryservice/deliveryservices.go | 24 +- ...veryservices_required_capabilities_test.go | 12 +- .../deliveryservice/keys.go | 2 +- .../deliveryservice/request/requests.go | 16 +- .../deliveryservice/request/requests_test.go | 2 +- .../deliveryservice/servers/servers.go | 16 +- .../deliveryservice/servers/servers_test.go | 2 +- .../deliveryservice/sslkeys.go | 2 +- .../division/divisions_test.go | 2 +- .../federation_resolvers.go | 2 +- .../invalidationjobs/invalidationjobs.go | 18 +- traffic_ops/traffic_ops_golang/logs/log.go | 4 +- .../parameter/parameters_test.go | 2 +- .../physlocation/phys_locations_test.go | 2 +- .../traffic_ops_golang/profile/copy.go | 6 +- .../traffic_ops_golang/profile/copy_test.go | 4 +- .../profile/profiles_test.go | 2 +- .../profile_parameters_test.go | 2 +- .../traffic_ops_golang/region/regions_test.go | 2 +- .../traffic_ops_golang/role/roles_test.go | 2 +- .../traffic_ops_golang/routing/routing.go | 2 +- .../traffic_ops_golang/server/servers.go | 14 +- .../server/servers_server_capability_test.go | 4 +- .../servercapability/servercapability_test.go | 2 +- .../servercheck/servercheck.go | 2 +- .../staticdnsentry/staticdnsentry_test.go | 2 +- .../status/statuses_test.go | 4 +- .../steeringtargets/steeringtargets_test.go | 2 +- .../traffic_ops_golang/topology/topologies.go | 6 +- .../traffic_ops_golang/topology/validation.go | 2 +- .../traffic_ops_golang/trafficstats/cache.go | 2 +- .../trafficstats/deliveryservice.go | 4 +- .../trafficstats/stats_summary.go | 4 +- .../traffic_ops_golang/trafficstats/util.go | 2 +- .../trafficstats/util_test.go | 2 +- .../traffic_ops_golang/types/types_test.go | 4 +- .../traffic_ops_golang/user/current.go | 2 +- 61 files changed, 1413 insertions(+), 720 deletions(-) create mode 100644 traffic_ops/traffic_ops_golang/api/info.go create mode 100644 traffic_ops/traffic_ops_golang/api/info_test.go diff --git a/docs/source/_ext/atc.py b/docs/source/_ext/atc.py index 3d60ed762f..f566ec9a32 100644 --- a/docs/source/_ext/atc.py +++ b/docs/source/_ext/atc.py @@ -301,7 +301,7 @@ def to_godoc_role( Example: - API endpoints make heavy use of :to-godoc:`api#APIInfo` objects. + API endpoints make heavy use of :to-godoc:`api#Info` objects. """ if options is None: options = {} diff --git a/docs/source/development/documentation_guidelines.rst b/docs/source/development/documentation_guidelines.rst index 05b9cd3de1..d1d570cbfc 100644 --- a/docs/source/development/documentation_guidelines.rst +++ b/docs/source/development/documentation_guidelines.rst @@ -244,4 +244,4 @@ godoc atc-godoc This is provided for convenience, and is identical to ``:godoc:`` except that it is assumed to be relative to the Apache Traffic Control project. For example, ``:atc-godoc:`lib/go-rfc.MimeType.Quality``` renders as :atc-godoc:`lib/go-rfc.MimeType.Quality`. to-godoc - This is provided for convenience, and is identical to ``:godoc:`` except that it is assumed to be relative to the :atc-godoc:`traffic_ops/traffic_ops_golang` package. For example, ``:to-godoc:`api.APIInfo``` renders as :to-godoc:`api.APIInfo`. + This is provided for convenience, and is identical to ``:godoc:`` except that it is assumed to be relative to the :atc-godoc:`traffic_ops/traffic_ops_golang` package. For example, ``:to-godoc:`api.Info``` renders as :to-godoc:`api.Info`. diff --git a/docs/source/development/traffic_ops.rst b/docs/source/development/traffic_ops.rst index 850c4d1c69..2494cdea90 100644 --- a/docs/source/development/traffic_ops.rst +++ b/docs/source/development/traffic_ops.rst @@ -694,12 +694,12 @@ The "Generic 'CRUDer'", as it's known, is a pattern of API endpoint development .. seealso:: The :to-godoc:`api.GenericCreate`, :to-godoc:`api.GenericDelete`, :to-godoc:`api.GenericRead`, and :to-godoc:`api.GenericUpdate` helpers are often used to provide the default operations of creating, deleting, reading, and updating objects, respectively. When the API endpoint being written is only meant to perform these basic operations on an object or objects stored in the database, these should be totally sufficient. -This method offers a lot of functionality "out-of-the-box" as compared to the `APIInfo`_ method, but because of that is also restrictive. For example, it is not possible to write an endpoint that returns data not encoded as JSON using this method. That's an uncommon use-case, but not unheard-of. +This method offers a lot of functionality "out-of-the-box" as compared to the `API Info`_ method, but because of that is also restrictive. For example, it is not possible to write an endpoint that returns data not encoded as JSON using this method. That's an uncommon use-case, but not unheard-of. This method is best used for basic creation, reading, update, and deletion operations performed on simple objects with no structural differences across API versions. -APIInfo -""""""" +API Info +"""""""" Endpoint handlers can also be defined by simply implementing the :godoc:`net/http.HandlerFunc` interface. The :godoc:`net/http.Request` reference passed into such handlers provides identifying information for the authenticated user (where applicable) in its context. To easily obtain the information needed to identify a user and their associated permissions, as well as server configuration information and a database transaction handle, authors should use the :to-godoc:`api.NewInfo` function which will return all of that information in a single structure as well as any errors encountered during the process and an appropriate HTTP response code in case of such errors. diff --git a/traffic_ops/traffic_ops_golang/README.md b/traffic_ops/traffic_ops_golang/README.md index 5cfa6d268c..a28db5e81a 100644 --- a/traffic_ops/traffic_ops_golang/README.md +++ b/traffic_ops/traffic_ops_golang/README.md @@ -144,7 +144,7 @@ func CreateV15(w http.ResponseWriter, r *http.Request) { api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice creation was successful.", []tc.DeliveryServiceNullableV15{*res}) } -func createV15(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, reqDS tc.DeliveryServiceNullableV15) *tc.DeliveryServiceNullableV15 { +func createV15(w http.ResponseWriter, r *http.Request, inf *api.Info, reqDS tc.DeliveryServiceNullableV15) *tc.DeliveryServiceNullableV15 { ... } ``` diff --git a/traffic_ops/traffic_ops_golang/api/api.go b/traffic_ops/traffic_ops_golang/api/api.go index 51bbee515c..bc4dcf97ac 100644 --- a/traffic_ops/traffic_ops_golang/api/api.go +++ b/traffic_ops/traffic_ops_golang/api/api.go @@ -43,12 +43,10 @@ import ( "github.com/apache/trafficcontrol/v8/lib/go-tc" "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/auth" "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/config" - "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/tenant" "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/tocookie" "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/trafficvault" "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/trafficvault/backends/disabled" - influx "github.com/influxdata/influxdb/client/v2" "github.com/jmoiron/sqlx" "github.com/lestrrat-go/jwx/jwa" "github.com/lestrrat-go/jwx/jwt" @@ -61,13 +59,13 @@ func (e errorConstant) Error() string { return string(e) } -// NilRequestError is returned by APIInfo methods when the request internally -// referred to by the APIInfo cannot be found. -const NilRequestError = errorConstant("method called on APIInfo with nil request") +// NilRequestError is returned by Info methods when the request internally +// referred to by the Info cannot be found. +const NilRequestError = errorConstant("method called on Info with nil request") -// NilTransactionError is returned by APIInfo methods when the transaction -// internally referred to by the APIInfo cannot be found. -const NilTransactionError = errorConstant("method called on APIInfo with nil transaction") +// NilTransactionError is returned by Info methods when the transaction +// internally referred to by the Info cannot be found. +const NilTransactionError = errorConstant("method called on Info with nil transaction") // ResourceModifiedError is a user-safe error that indicates a precondition // failure. @@ -85,18 +83,6 @@ const ( const MojoCookie = "mojoCookie" -const influxServersQuery = ` -SELECT (host_name||'.'||domain_name) as fqdn, - tcp_port, - https_port -FROM server -WHERE type in ( SELECT id - FROM type - WHERE name='INFLUXDB' - ) -AND status=(SELECT id FROM status WHERE name='ONLINE') -` - type APIResponse struct { Response interface{} `json:"response"` } @@ -200,7 +186,7 @@ func WriteIMSHitResp(w http.ResponseWriter, r *http.Request, t time.Time) { // HandleErr handles an API error, rolling back the transaction, writing the given statusCode and userErr to the user, and logging the sysErr. If userErr is nil, the text of the HTTP statusCode is written. // -// The tx may be nil, if there is no transaction. Passing a nil tx is strongly discouraged if a transaction exists, because it will result in copy-paste errors for the common APIInfo use case. +// The tx may be nil, if there is no transaction. Passing a nil tx is strongly discouraged if a transaction exists, because it will result in copy-paste errors for the common Info use case. // // This is a helper for the common case; not using this in unusual cases is perfectly acceptable. func HandleErr(w http.ResponseWriter, r *http.Request, tx *sql.Tx, statusCode int, userErr error, sysErr error) { @@ -233,7 +219,7 @@ func HandleErrOptionalDeprecation(w http.ResponseWriter, r *http.Request, tx *sq // // The alternative may be nil if there is no alternative and the deprecation message will be selected appropriately. // -// The tx may be nil, if there is no transaction. Passing a nil tx is strongly discouraged if a transaction exists, because it will result in copy-paste errors for the common APIInfo use case. +// The tx may be nil, if there is no transaction. Passing a nil tx is strongly discouraged if a transaction exists, because it will result in copy-paste errors for the common Info use case. // // This is a helper for the common case; not using this in unusual cases is perfectly acceptable. func HandleDeprecatedErr(w http.ResponseWriter, r *http.Request, tx *sql.Tx, statusCode int, userErr error, sysErr error, alternative *string) { @@ -507,125 +493,6 @@ func Parse(r io.Reader, tx *sql.Tx, v ParseValidator) error { return nil } -type APIInfo struct { - Params map[string]string - IntParams map[string]int - User *auth.CurrentUser - ReqID uint64 - Version *Version - Tx *sqlx.Tx - CancelTx context.CancelFunc - Vault trafficvault.TrafficVault - Config *config.Config - request *http.Request - w http.ResponseWriter -} - -// NewInfo get and returns the context info needed by handlers. It also returns any user error, any system error, and the status code which should be returned to the client if an error occurred. -// -// It is encouraged to call APIInfo.Tx.Tx.Commit() manually when all queries are finished, to release database resources early, and also to return an error to the user if the commit failed. -// -// NewInfo guarantees the returned APIInfo.Tx is non-nil and APIInfo.Tx.Tx is nil or valid, even if a returned error is not nil. Hence, it is safe to pass the Tx.Tx to HandleErr when this returns errors. -// -// Close() must be called to free resources, and should be called in a defer immediately after NewInfo(), to finish the transaction. -// -// Example: -// -// func handler(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() -// -// respObj, err := finalDatabaseOperation(inf.Tx) -// if err != nil { -// api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("final db op: " + err.Error())) -// return -// } -// if err := inf.Tx.Tx.Commit(); err != nil { -// api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("committing transaction: " + err.Error())) -// return -// } -// api.WriteResp(w, r, respObj) -// } -func NewInfo(r *http.Request, requiredParams []string, intParamNames []string) (*APIInfo, error, error, int) { - db, err := GetDB(r.Context()) - if err != nil { - return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting db: " + err.Error()), nil, http.StatusInternalServerError - } - cfg, err := GetConfig(r.Context()) - if err != nil { - return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting config: " + err.Error()), nil, http.StatusInternalServerError - } - tv, err := GetTrafficVault(r.Context()) - if err != nil { - return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting TrafficVault: " + err.Error()), nil, http.StatusInternalServerError - } - reqID, err := getReqID(r.Context()) - if err != nil { - return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting reqID: " + err.Error()), nil, http.StatusInternalServerError - } - version := GetRequestedAPIVersion(r.URL.Path) - - user, err := auth.GetCurrentUser(r.Context()) - if err != nil { - return &APIInfo{Tx: &sqlx.Tx{}}, errors.New("getting user: " + err.Error()), nil, http.StatusInternalServerError - } - params, intParams, userErr, sysErr, errCode := AllParams(r, requiredParams, intParamNames) - if userErr != nil || sysErr != nil { - return &APIInfo{Tx: &sqlx.Tx{}}, userErr, sysErr, errCode - } - dbCtx, cancelTx := context.WithTimeout(r.Context(), time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second) //only place we could call cancel here is in APIInfo.Close(), which already will rollback the transaction (which is all cancel will do.) - tx, err := db.BeginTxx(dbCtx, nil) // must be last, MUST not return an error if this succeeds, without closing the tx - if err != nil { - return &APIInfo{Tx: &sqlx.Tx{}, CancelTx: cancelTx}, userErr, errors.New("could not begin transaction: " + err.Error()), http.StatusInternalServerError - } - return &APIInfo{ - Config: cfg, - ReqID: reqID, - Version: version, - Params: params, - IntParams: intParams, - User: user, - Tx: tx, - CancelTx: cancelTx, - Vault: tv, - request: r, - }, nil, nil, http.StatusOK -} - -const createChangeLogQuery = ` -INSERT INTO log ( - level, - message, - tm_user -) VALUES ( - $1, - $2, - $3 -) -` - -// CreateChangeLog creates a new changelog message at the APICHANGE level for -// the current user. -func (inf APIInfo) CreateChangeLog(msg string) { - _, err := inf.Tx.Tx.Exec(createChangeLogQuery, ApiChange, msg, inf.User.ID) - if err != nil { - log.Errorf("Inserting chage log level '%s' message '%s' for user '%s': %v", ApiChange, msg, inf.User.UserName, err) - } -} - -// UseIMS returns whether or not If-Modified-Since constraints should be used to -// service the given request. -func (inf APIInfo) UseIMS() bool { - if inf.request == nil || inf.Config == nil { - return false - } - return inf.Config.UseIMS && inf.request.Header.Get(rfc.IfModifiedSince) != "" -} - // WriteNotModifiedResponse writes a 304 Not Modified response with the given // last modification time to the provided response writer. The request must be // provided as well, so that it can be marked as handled. @@ -635,153 +502,6 @@ func WriteNotModifiedResponse(t time.Time, w http.ResponseWriter, r *http.Reques WriteResp(w, r, nil) } -// CheckPrecondition checks a request's "preconditions" - its If-Match and -// If-Unmodified-Since headers versus the last updated time of the requested -// object(s), and returns (in order), an HTTP response code appropriate for the -// precondition check results, a user-safe error that should be returned to -// clients, and a server-side error that should be logged. -// Callers must pass in a query that will return one row containing one column -// that is the representative date/time of the last update of the requested -// object(s), and optionally any values for placeholder arguments in the query. -func (inf APIInfo) CheckPrecondition(query string, args ...interface{}) (int, error, error) { - if inf.request == nil { - return http.StatusInternalServerError, nil, NilRequestError - } - - ius := inf.request.Header.Get(rfc.IfUnmodifiedSince) - etag := inf.request.Header.Get(rfc.IfMatch) - if ius == "" && etag == "" { - return http.StatusOK, nil, nil - } - - if inf.Tx == nil || inf.Tx.Tx == nil { - return http.StatusInternalServerError, nil, NilTransactionError - } - - var lastUpdated time.Time - if err := inf.Tx.Tx.QueryRow(query, args...).Scan(&lastUpdated); err != nil { - return http.StatusInternalServerError, nil, fmt.Errorf("scanning for lastUpdated: %v", err) - } - - if etag != "" { - if et, ok := rfc.ParseETags(strings.Split(etag, ",")); ok { - if lastUpdated.After(et) { - return http.StatusPreconditionFailed, ResourceModifiedError, nil - } - } - } - - if ius == "" { - return http.StatusOK, nil, nil - } - - if tm, ok := rfc.ParseHTTPDate(ius); ok { - if lastUpdated.After(tm) { - return http.StatusPreconditionFailed, ResourceModifiedError, nil - } - } - - return http.StatusOK, nil, nil -} - -// Close implements the io.Closer interface. It should be called in a defer immediately after NewInfo(). -// -// Close will commit the transaction, if it hasn't been rolled back. -func (inf *APIInfo) Close() { - defer inf.CancelTx() - if err := inf.Tx.Tx.Commit(); err != nil && err != sql.ErrTxDone { - log.Errorln("committing transaction: " + err.Error()) - } -} - -// WriteOKResponse writes a 200 OK response with the given object as the -// 'response' property of the response body. -// -// This CANNOT be used by any APIInfo that wasn't constructed for the caller by -// Wrap - ing a Handler (yet). -func (inf APIInfo) WriteOKResponse(resp any) (int, error, error) { - WriteResp(inf.w, inf.request, resp) - return http.StatusOK, nil, nil -} - -// WriteOKResponseWithSummary writes a 200 OK response with the given object as -// the 'response' property of the response body, and the given count as the -// `count` property of the response's summary. -// -// This CANNOT be used by any APIInfo that wasn't constructed for the caller by -// Wrap - ing a Handler (yet). -// -// Deprecated: Summary sections on responses were intended to cover up for a -// deficiency in jQuery-based tables on the front-end, so now that we aren't -// using those anymore it serves no purpose. -func (inf APIInfo) WriteOKResponseWithSummary(resp any, count uint64) (int, error, error) { - WriteRespWithSummary(inf.w, inf.request, resp, count) - return http.StatusOK, nil, nil -} - -// WriteNotModifiedResponse writes a 304 Not Modified response with the given -// time as the last modified time in the headers. -// -// This CANNOT be used by any APIInfo that wasn't constructed for the caller by -// Wrap - ing a Handler (yet). -func (inf APIInfo) WriteNotModifiedResponse(lastModified time.Time) (int, error, error) { - inf.w.Header().Set(rfc.LastModified, FormatLastModified(lastModified)) - inf.w.WriteHeader(http.StatusNotModified) - setRespWritten(inf.request) - return http.StatusNotModified, nil, nil -} - -// WriteSuccessResponse writes the given response object as the `response` -// property of the response body, with the accompanying message as a -// success-level Alert. -func (inf APIInfo) WriteSuccessResponse(resp any, message string) (int, error, error) { - WriteAlertsObj(inf.w, inf.request, http.StatusOK, tc.CreateAlerts(tc.SuccessLevel, message), resp) - return http.StatusOK, nil, nil -} - -// WriteCreatedResponse writes the given response object as the `response` -// property of the response body of a 201 created response, with the -// accompanying message as a success-level Alert. It also sets the Location -// header to the given path. This will be automatically prefaced with the -// correct path to the API version the client requested. -func (inf APIInfo) WriteCreatedResponse(resp any, message, path string) (int, error, error) { - inf.w.Header().Set(rfc.Location, strings.Join([]string{"/api", inf.Version.String(), strings.TrimPrefix(path, "/")}, "/")) - inf.w.WriteHeader(http.StatusCreated) - WriteAlertsObj(inf.w, inf.request, http.StatusCreated, tc.CreateAlerts(tc.SuccessLevel, message), resp) - return http.StatusCreated, nil, nil -} - -// RequestHeaders returns the headers sent by the client in the API request. -func (inf APIInfo) RequestHeaders() http.Header { - return inf.request.Header -} - -// SetLastModified sets the "last modified" header on the response writer. -// -// This CANNOT be used by any APIInfo that wasn't constructed for the caller by -// Wrap - ing a Handler (yet). -func (inf APIInfo) SetLastModified(t time.Time) { - inf.w.Header().Set(rfc.LastModified, FormatLastModified(t)) -} - -// DecodeBody reads the client request's body and attempts to decode it into the -// provided reference. -func (inf APIInfo) DecodeBody(ref any) error { - return json.NewDecoder(inf.request.Body).Decode(ref) -} - -// SendMail is a convenience method used to call SendMail using an APIInfo structure's configuration. -func (inf *APIInfo) SendMail(to rfc.EmailAddress, msg []byte) (int, error, error) { - return SendMail(to, msg, inf.Config) -} - -// IsResourceAuthorizedToCurrentUser is a convenience method used to call -// github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/tenant.IsResourceAuthorizedToUserTx -// using an APIInfo structure to provide the current user and database transaction. -func (inf *APIInfo) IsResourceAuthorizedToCurrentUser(resourceTenantID int) (bool, error) { - return tenant.IsResourceAuthorizedToUserTx(resourceTenantID, inf.User, inf.Tx.Tx) -} - // SendMail sends an email msg to the address identified by to. The msg parameter should be an // RFC822-style email with headers first, a blank line, and then the message body. The lines of msg // should be CRLF terminated. The msg headers should usually include fields such as "From", "To", @@ -807,79 +527,6 @@ func SendMail(to rfc.EmailAddress, msg []byte, cfg *config.Config) (int, error, return http.StatusOK, nil, nil } -// CreateInfluxClient constructs and returns an InfluxDB HTTP client, if enabled and when possible. -// The error this returns should not be exposed to the user; it's for logging purposes only. -// -// If Influx connections are not enabled, this will return `nil` - but also no error. It is expected -// that the caller will handle this situation appropriately. -func (inf *APIInfo) CreateInfluxClient() (*influx.Client, error) { - if !inf.Config.InfluxEnabled { - return nil, nil - } - - var fqdn string - var tcpPort uint - var httpsPort sql.NullInt64 // this is the only one that's optional - - row := inf.Tx.Tx.QueryRow(influxServersQuery) - if e := row.Scan(&fqdn, &tcpPort, &httpsPort); e != nil { - return nil, fmt.Errorf("Failed to create influx client: %v", e) - } - - host := "http%s://%s:%d" - if inf.Config.ConfigInflux != nil && *inf.Config.ConfigInflux.Secure { - if !httpsPort.Valid { - log.Warnf("INFLUXDB Server %s has no secure ports, assuming default of 8086!", fqdn) - httpsPort = sql.NullInt64{Int64: 8086, Valid: true} - } - port, err := httpsPort.Value() - if err != nil { - return nil, fmt.Errorf("Failed to create influx client: %v", err) - } - - p := port.(int64) - if p <= 0 || p > 65535 { - log.Warnf("INFLUXDB Server %s has invalid port, assuming default of 8086!", fqdn) - p = 8086 - } - - host = fmt.Sprintf(host, "s", fqdn, p) - } else if tcpPort > 0 && tcpPort <= 65535 { - host = fmt.Sprintf(host, "", fqdn, tcpPort) - } else { - log.Warnf("INFLUXDB Server %s has invalid port, assuming default of 8086!", fqdn) - host = fmt.Sprintf(host, "", fqdn, 8086) - } - - config := influx.HTTPConfig{ - Addr: host, - Username: inf.Config.ConfigInflux.User, - Password: inf.Config.ConfigInflux.Password, - UserAgent: fmt.Sprintf("TrafficOps/%s (Go)", inf.Config.Version), - Timeout: time.Duration(float64(inf.Config.ReadTimeout)/2.1) * time.Second, - } - - var client influx.Client - client, e := influx.NewHTTPClient(config) - if client == nil { - return nil, fmt.Errorf("Failed to create influx client (client was nil): %v", e) - } - return &client, e -} - -// APIInfoImpl implements APIInfo via the APIInfoer interface -type APIInfoImpl struct { - ReqInfo *APIInfo -} - -func (val *APIInfoImpl) SetInfo(inf *APIInfo) { - val.ReqInfo = inf -} - -func (val APIInfoImpl) APIInfo() *APIInfo { - return val.ReqInfo -} - // Version represents an API version. type Version struct { Major uint64 @@ -1448,7 +1095,7 @@ func AddLastModifiedHdr(w http.ResponseWriter, t time.Time) { } // DefaultSort sorts alphabetically for a given readerType (eg: TOCDN, TODeliveryService, TOOrigin etc). -func DefaultSort(readerType *APIInfo, param string) { +func DefaultSort(readerType *Info, param string) { if _, ok := readerType.Params["orderby"]; !ok { readerType.Params["orderby"] = param } diff --git a/traffic_ops/traffic_ops_golang/api/api_test.go b/traffic_ops/traffic_ops_golang/api/api_test.go index 451927aba9..4c685e4f92 100644 --- a/traffic_ops/traffic_ops_golang/api/api_test.go +++ b/traffic_ops/traffic_ops_golang/api/api_test.go @@ -26,15 +26,11 @@ import ( "errors" "fmt" "net/http" - "net/http/httptest" "net/url" - "strings" "testing" - "time" "github.com/lib/pq" - "github.com/apache/trafficcontrol/v8/lib/go-rfc" "github.com/apache/trafficcontrol/v8/lib/go-tc" ) @@ -266,205 +262,3 @@ func TestParseRestrictFKConstraint(t *testing.T) { }) } } - -func TestAPIInfo_WriteOKResponse(t *testing.T) { - w := httptest.NewRecorder() - r := httptest.NewRequest(http.MethodGet, "/", nil) - - inf := APIInfo{ - request: r, - w: w, - } - code, userErr, sysErr := inf.WriteOKResponse("test") - if code != http.StatusOK { - t.Errorf("WriteOKResponse should return a %d %s code, got: %d %s", http.StatusOK, http.StatusText(http.StatusOK), code, http.StatusText(code)) - } - if userErr != nil { - t.Errorf("Unexpected user error: %v", userErr) - } - if sysErr != nil { - t.Errorf("Unexpected system error: %v", sysErr) - } - - if w.Code != http.StatusOK { - t.Errorf("incorrect response status code; want: %d, got: %d", http.StatusOK, w.Code) - } -} -func TestAPIInfo_WriteOKResponseWithSummary(t *testing.T) { - w := httptest.NewRecorder() - r := httptest.NewRequest(http.MethodGet, "/", nil) - - inf := APIInfo{ - request: r, - w: w, - } - code, userErr, sysErr := inf.WriteOKResponseWithSummary("test", 42) - if code != http.StatusOK { - t.Errorf("WriteOKResponseWithSummary should return a %d %s code, got: %d %s", http.StatusOK, http.StatusText(http.StatusOK), code, http.StatusText(code)) - } - if userErr != nil { - t.Errorf("Unexpected user error: %v", userErr) - } - if sysErr != nil { - t.Errorf("Unexpected system error: %v", sysErr) - } - - if w.Code != http.StatusOK { - t.Errorf("incorrect response status code; want: %d, got: %d", http.StatusOK, w.Code) - } -} -func TestAPIInfo_WriteNotModifiedResponse(t *testing.T) { - w := httptest.NewRecorder() - r := httptest.NewRequest(http.MethodGet, "/", nil) - - inf := APIInfo{ - request: r, - w: w, - } - code, userErr, sysErr := inf.WriteNotModifiedResponse(time.Time{}) - if code != http.StatusNotModified { - t.Errorf("WriteNotModifiedResponse should return a %d %s code, got: %d %s", http.StatusNotModified, http.StatusText(http.StatusNotModified), code, http.StatusText(code)) - } - if userErr != nil { - t.Errorf("Unexpected user error: %v", userErr) - } - if sysErr != nil { - t.Errorf("Unexpected system error: %v", sysErr) - } - - if w.Code != http.StatusNotModified { - t.Errorf("incorrect response status code; want: %d, got: %d", http.StatusNotModified, w.Code) - } -} - -func TestAPIInfo_WriteSuccessResponse(t *testing.T) { - w := httptest.NewRecorder() - r := httptest.NewRequest(http.MethodGet, "/", nil) - - inf := APIInfo{ - request: r, - w: w, - } - code, userErr, sysErr := inf.WriteSuccessResponse("test", "quest") - if code != http.StatusOK { - t.Errorf("WriteSuccessResponse should return a %d %s code, got: %d %s", http.StatusOK, http.StatusText(http.StatusOK), code, http.StatusText(code)) - } - if userErr != nil { - t.Errorf("Unexpected user error: %v", userErr) - } - if sysErr != nil { - t.Errorf("Unexpected system error: %v", sysErr) - } - - if w.Code != http.StatusOK { - t.Errorf("incorrect response status code; want: %d, got: %d", http.StatusOK, w.Code) - } - var alerts tc.Alerts - if err := json.NewDecoder(w.Body).Decode(&alerts); err != nil { - t.Fatalf("couldn't decode response body: %v", err) - } - - if len(alerts.Alerts) != 1 { - t.Fatalf("expected exactly one alert; got: %d", len(alerts.Alerts)) - } - alert := alerts.Alerts[0] - if alert.Level != tc.SuccessLevel.String() { - t.Errorf("Incorrect alert level; want: %s, got: %s", tc.SuccessLevel, alert.Level) - } - if alert.Text != "quest" { - t.Errorf("Incorrect alert text; want: 'quest', got: '%s'", alert.Text) - } -} - -func TestAPIInfo_WriteCreatedResponse(t *testing.T) { - w := httptest.NewRecorder() - r := httptest.NewRequest(http.MethodPost, "/", nil) - - inf := APIInfo{ - request: r, - Version: &Version{Major: 420, Minor: 9001}, - w: w, - } - code, userErr, sysErr := inf.WriteCreatedResponse("test", "quest", "mypath") - if code != http.StatusCreated { - t.Errorf("WriteCreatedResponse should return a %d %s code, got: %d %s", http.StatusCreated, http.StatusText(http.StatusCreated), code, http.StatusText(code)) - } - if userErr != nil { - t.Errorf("Unexpected user error: %v", userErr) - } - if sysErr != nil { - t.Errorf("Unexpected system error: %v", sysErr) - } - - if w.Code != http.StatusCreated { - t.Errorf("incorrect response status code; want: %d, got: %d", http.StatusCreated, w.Code) - } - if locHdr := w.Header().Get(rfc.Location); locHdr != "/api/420.9001/mypath" { - t.Errorf("incorrect '%s' header value; want: '/api/420.9001/mypath', got: '%s'", rfc.Location, locHdr) - } - var alerts tc.Alerts - if err := json.NewDecoder(w.Body).Decode(&alerts); err != nil { - t.Fatalf("couldn't decode response body: %v", err) - } - - if len(alerts.Alerts) != 1 { - t.Fatalf("expected exactly one alert; got: %d", len(alerts.Alerts)) - } - alert := alerts.Alerts[0] - if alert.Level != tc.SuccessLevel.String() { - t.Errorf("Incorrect alert level; want: %s, got: %s", tc.SuccessLevel, alert.Level) - } - if alert.Text != "quest" { - t.Errorf("Incorrect alert text; want: 'quest', got: '%s'", alert.Text) - } -} - -func TestAPIInfo_RequestHeaders(t *testing.T) { - r := httptest.NewRequest(http.MethodGet, "/", nil) - r.Header.Set("test", "quest") - - inf := APIInfo{ - request: r, - } - testHdr := inf.RequestHeaders().Get("test") - if testHdr != "quest" { - t.Errorf("should have retrieved the 'test' header (expected value: 'quest'), but found that header to have value: '%s'", testHdr) - } - -} - -func TestAPIInfo_SetLastModified(t *testing.T) { - w := httptest.NewRecorder() - inf := APIInfo{w: w} - tm := time.Now().Truncate(time.Second).UTC() - inf.SetLastModified(tm) - - wLMHdr := w.Header().Get(rfc.LastModified) - lm, err := time.Parse(time.RFC1123, wLMHdr) - if err != nil { - t.Fatalf("Failed to parse the response writer's '%s' header as an RFC1123 timestamp: %v", rfc.LastModified, err) - } - - // For unknown reasons, our API always adds a second to the truncated time - // value for LastModified headers. I suspect it's a poor attempt at rounding - // - for which the `Round` method ought to be used instead. - if expected := tm.Add(time.Second); lm != expected { - t.Errorf("Incorrect time set as '%s' header; want: %s, got: %s", rfc.LastModified, expected.Format(time.RFC3339Nano), lm.Format(time.RFC3339Nano)) - } -} - -func TestAPIInfo_DecodeBody(t *testing.T) { - inf := APIInfo{ - request: httptest.NewRequest(http.MethodConnect, "/", strings.NewReader(`{"test": "quest"}`)), - } - - var out struct { - Test string `json:"test"` - } - if err := inf.DecodeBody(&out); err != nil { - t.Fatalf("failed to decode body: %v", err) - } - if out.Test != "quest" { - t.Errorf(`incorrect request body parsed; want: {"test": "quest"}, got: {"test": "%s"}`, out.Test) - } -} diff --git a/traffic_ops/traffic_ops_golang/api/generic_crud.go b/traffic_ops/traffic_ops_golang/api/generic_crud.go index 13b800df9b..f89f17349a 100644 --- a/traffic_ops/traffic_ops_golang/api/generic_crud.go +++ b/traffic_ops/traffic_ops_golang/api/generic_crud.go @@ -37,7 +37,7 @@ import ( type GenericCreator interface { GetType() string - APIInfo() *APIInfo + APIInfo() *Info SetKeys(map[string]interface{}) SetLastUpdated(tc.TimeNoMod) InsertQuery() string @@ -45,7 +45,7 @@ type GenericCreator interface { type GenericReader interface { GetType() string - APIInfo() *APIInfo + APIInfo() *Info ParamColumns() map[string]dbhelpers.WhereColumnInfo NewReadObj() interface{} SelectQuery() string @@ -54,7 +54,7 @@ type GenericReader interface { type GenericUpdater interface { GetType() string - APIInfo() *APIInfo + APIInfo() *Info SetLastUpdated(tc.TimeNoMod) UpdateQuery() string GetLastUpdated() (*time.Time, bool, error) @@ -62,14 +62,14 @@ type GenericUpdater interface { type GenericDeleter interface { GetType() string - APIInfo() *APIInfo + APIInfo() *Info DeleteQuery() string } // GenericOptionsDeleter can use any key listed in DeleteKeyOptions() to delete a resource. type GenericOptionsDeleter interface { GetType() string - APIInfo() *APIInfo + APIInfo() *Info DeleteKeyOptions() map[string]dbhelpers.WhereColumnInfo DeleteQueryBase() string } diff --git a/traffic_ops/traffic_ops_golang/api/info.go b/traffic_ops/traffic_ops_golang/api/info.go new file mode 100644 index 0000000000..f6462c672d --- /dev/null +++ b/traffic_ops/traffic_ops_golang/api/info.go @@ -0,0 +1,429 @@ +package api + +/* + * 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 ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/apache/trafficcontrol/v8/lib/go-log" + "github.com/apache/trafficcontrol/v8/lib/go-rfc" + "github.com/apache/trafficcontrol/v8/lib/go-tc" + "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/auth" + "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/config" + "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/tenant" + "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/trafficvault" + influx "github.com/influxdata/influxdb/client/v2" + "github.com/jmoiron/sqlx" +) + +const createChangeLogQuery = ` +INSERT INTO log ( + level, + message, + tm_user +) VALUES ( + $1, + $2, + $3 +) +` +const influxServersQuery = ` +SELECT (host_name||'.'||domain_name) as fqdn, + tcp_port, + https_port +FROM server +WHERE type in ( SELECT id + FROM type + WHERE name='INFLUXDB' + ) +AND status=(SELECT id FROM status WHERE name='ONLINE') +` + +// Info structures contain all of the information an API route handler needs to +// be able to service a request, including some things that are pre-parsed (e.g. +// query string parameters) for you. It also provides some methods for +// accomplishing common tasks. +type Info struct { + // Params is a mapping of all query string and path parameters to their + // respective values. The behavior of this map is not defined when any two + // query string parameters and/or path parameters share a name. For example, + // if the route is `cdns/{id}/delivery_services/{id}`, the two cannot be + // distinguished. Similarly, a request like `GET /api/5.0/cdns?id=1&id=2` + // will give either an "id" key that maps to "1", or an "id" key that maps + // to "2". Most convolutedly, for the aforementioned route definition, the + // request `GET cdns/1/deliveryservices/2?id=3&id=4` gives four possible + // values for the "id" key. Take care when constructing routes and deciding + // the parameters they will accept. + Params map[string]string + // IntParams is a mapping of all of the declared parameters that are to be + // parsed as ints to the parsed values of those parameters. No key will + // appear here that isn't also in Params. + IntParams map[string]int + // The currently authenticated user - this may be `nil` on routes that do + // not require authentication. + User *auth.CurrentUser + // A unique identifier for the request. + ReqID uint64 + // The version of the API being requested. This is a pointer for legacy + // reasons - all handlers should assume this is not nil (with the possible + // exception of plugin handlers). + Version *Version + // A transaction opened to the Traffic Ops database. + Tx *sqlx.Tx + // The cancel function for the request and transaction contexts. + CancelTx context.CancelFunc + // The Traffic Vault implementation. + Vault trafficvault.TrafficVault + // Config is the Traffic Ops server's current configuration. This is a + // pointer presumably to save memory; it should and must never be `nil`. + Config *config.Config + + request *http.Request + w http.ResponseWriter +} + +// NewInfo get and returns the context info needed by handlers. It also returns +// any user error, any system error, and the status code which should be +// returned to the client if an error occurred. +// +// It is encouraged to call Info.Tx.Tx.Commit() manually when all queries are +// finished, to release database resources early, and also to return an error to +// the user if the commit failed. In practice, though, the `Close` method +// handles this in nearly every case. +// +// NewInfo guarantees the returned Info.Tx is non-`nil` and Info.Tx.Tx is `nil` +// or valid, even if a returned error is not `nil`. Hence, it is safe to pass +// the Tx.Tx to HandleErr when this returns errors. +// +// Close() must be called to free resources, and should be called in a defer +// immediately after NewInfo(), to finish the transaction. +// +// Example: +// +// func handler(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() +// +// respObj, err := finalDatabaseOperation(inf.Tx) +// if err != nil { +// api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("final db op: %w", err)) +// return +// } +// if err := inf.Tx.Tx.Commit(); err != nil { +// api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("committing transaction: %w", err)) +// return +// } +// api.WriteResp(w, r, respObj) +// } +func NewInfo(r *http.Request, requiredParams []string, intParamNames []string) (*Info, error, error, int) { + db, err := GetDB(r.Context()) + if err != nil { + return &Info{Tx: &sqlx.Tx{}}, fmt.Errorf("getting db: %w", err), nil, http.StatusInternalServerError + } + cfg, err := GetConfig(r.Context()) + if err != nil { + return &Info{Tx: &sqlx.Tx{}}, fmt.Errorf("getting config: %w", err), nil, http.StatusInternalServerError + } + tv, err := GetTrafficVault(r.Context()) + if err != nil { + return &Info{Tx: &sqlx.Tx{}}, fmt.Errorf("getting TrafficVault: %w", err), nil, http.StatusInternalServerError + } + reqID, err := getReqID(r.Context()) + if err != nil { + return &Info{Tx: &sqlx.Tx{}}, fmt.Errorf("getting reqID: %w", err), nil, http.StatusInternalServerError + } + version := GetRequestedAPIVersion(r.URL.Path) + + user, err := auth.GetCurrentUser(r.Context()) + if err != nil { + return &Info{Tx: &sqlx.Tx{}}, fmt.Errorf("getting user: %w", err), nil, http.StatusInternalServerError + } + params, intParams, userErr, sysErr, errCode := AllParams(r, requiredParams, intParamNames) + if userErr != nil || sysErr != nil { + return &Info{Tx: &sqlx.Tx{}}, userErr, sysErr, errCode + } + + // only place we could call cancel here is in Info.Close(), which already + // will rollback the transaction (which is all cancel will do.) + // must be last, MUST not return an error if this succeeds, without closing + // the tx + dbCtx, cancelTx := context.WithTimeout(r.Context(), time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second) + tx, err := db.BeginTxx(dbCtx, nil) + if err != nil { + return &Info{Tx: &sqlx.Tx{}, CancelTx: cancelTx}, userErr, fmt.Errorf("could not begin transaction: %w", err), http.StatusInternalServerError + } + return &Info{ + Config: cfg, + ReqID: reqID, + Version: version, + Params: params, + IntParams: intParams, + User: user, + Tx: tx, + CancelTx: cancelTx, + Vault: tv, + request: r, + }, nil, nil, http.StatusOK +} + +// CheckPrecondition checks a request's "preconditions" - its If-Match and +// If-Unmodified-Since headers versus the last updated time of the requested +// object(s), and returns (in order), an HTTP response code appropriate for the +// precondition check results, a user-safe error that should be returned to +// clients, and a server-side error that should be logged. +// Callers must pass in a query that will return one row containing one column +// that is the representative date/time of the last update of the requested +// object(s), and optionally any values for placeholder arguments in the query. +func (inf Info) CheckPrecondition(query string, args ...interface{}) (int, error, error) { + if inf.request == nil { + return http.StatusInternalServerError, nil, NilRequestError + } + + ius := inf.request.Header.Get(rfc.IfUnmodifiedSince) + etag := inf.request.Header.Get(rfc.IfMatch) + if ius == "" && etag == "" { + return http.StatusOK, nil, nil + } + + if inf.Tx == nil || inf.Tx.Tx == nil { + return http.StatusInternalServerError, nil, NilTransactionError + } + + var lastUpdated time.Time + if err := inf.Tx.Tx.QueryRow(query, args...).Scan(&lastUpdated); err != nil { + return http.StatusInternalServerError, nil, fmt.Errorf("scanning for lastUpdated: %w", err) + } + + if etag != "" { + if et, ok := rfc.ParseETags(strings.Split(etag, ",")); ok { + if lastUpdated.After(et) { + return http.StatusPreconditionFailed, ResourceModifiedError, nil + } + } + } + + if ius == "" { + return http.StatusOK, nil, nil + } + + if tm, ok := rfc.ParseHTTPDate(ius); ok { + if lastUpdated.After(tm) { + return http.StatusPreconditionFailed, ResourceModifiedError, nil + } + } + + return http.StatusOK, nil, nil +} + +// Close implements the io.Closer interface. It should be called in a defer immediately after NewInfo(). +// +// Close will commit the transaction, if it hasn't been rolled back. +func (inf *Info) Close() { + defer inf.CancelTx() + if err := inf.Tx.Tx.Commit(); err != nil && !errors.Is(err, sql.ErrTxDone) { + log.Errorln("committing transaction: " + err.Error()) + } +} + +// WriteOKResponse writes a 200 OK response with the given object as the +// 'response' property of the response body. +// +// This CANNOT be used by any Info that wasn't constructed for the caller by +// Wrap - ing a Handler (yet). +func (inf Info) WriteOKResponse(resp any) (int, error, error) { + WriteResp(inf.w, inf.request, resp) + return http.StatusOK, nil, nil +} + +// WriteOKResponseWithSummary writes a 200 OK response with the given object as +// the 'response' property of the response body, and the given count as the +// `count` property of the response's summary. +// +// This CANNOT be used by any Info that wasn't constructed for the caller by +// Wrap - ing a Handler (yet). +// +// Deprecated: Summary sections on responses were intended to cover up for a +// deficiency in jQuery-based tables on the front-end, so now that we aren't +// using those anymore it serves no purpose. +func (inf Info) WriteOKResponseWithSummary(resp any, count uint64) (int, error, error) { + WriteRespWithSummary(inf.w, inf.request, resp, count) + return http.StatusOK, nil, nil +} + +// WriteNotModifiedResponse writes a 304 Not Modified response with the given +// time as the last modified time in the headers. +// +// This CANNOT be used by any Info that wasn't constructed for the caller by +// Wrap - ing a Handler (yet). +func (inf Info) WriteNotModifiedResponse(lastModified time.Time) (int, error, error) { + inf.w.Header().Set(rfc.LastModified, FormatLastModified(lastModified)) + inf.w.WriteHeader(http.StatusNotModified) + setRespWritten(inf.request) + return http.StatusNotModified, nil, nil +} + +// WriteSuccessResponse writes the given response object as the `response` +// property of the response body, with the accompanying message as a +// success-level Alert. +func (inf Info) WriteSuccessResponse(resp any, message string) (int, error, error) { + WriteAlertsObj(inf.w, inf.request, http.StatusOK, tc.CreateAlerts(tc.SuccessLevel, message), resp) + return http.StatusOK, nil, nil +} + +// WriteCreatedResponse writes the given response object as the `response` +// property of the response body of a 201 created response, with the +// accompanying message as a success-level Alert. It also sets the Location +// header to the given path. This will be automatically prefaced with the +// correct path to the API version the client requested. +func (inf Info) WriteCreatedResponse(resp any, message, path string) (int, error, error) { + inf.w.Header().Set(rfc.Location, strings.Join([]string{"/api", inf.Version.String(), strings.TrimPrefix(path, "/")}, "/")) + inf.w.WriteHeader(http.StatusCreated) + WriteAlertsObj(inf.w, inf.request, http.StatusCreated, tc.CreateAlerts(tc.SuccessLevel, message), resp) + return http.StatusCreated, nil, nil +} + +// RequestHeaders returns the headers sent by the client in the API request. +func (inf Info) RequestHeaders() http.Header { + return inf.request.Header +} + +// SetLastModified sets the "last modified" header on the response writer. +// +// This CANNOT be used by any Info that wasn't constructed for the caller by +// Wrap - ing a Handler (yet). +func (inf Info) SetLastModified(t time.Time) { + inf.w.Header().Set(rfc.LastModified, FormatLastModified(t)) +} + +// DecodeBody reads the client request's body and attempts to decode it into the +// provided reference. +func (inf Info) DecodeBody(ref any) error { + return json.NewDecoder(inf.request.Body).Decode(ref) +} + +// SendMail is a convenience method used to call SendMail using an Info +// structure's configuration. +func (inf *Info) SendMail(to rfc.EmailAddress, msg []byte) (int, error, error) { + return SendMail(to, msg, inf.Config) +} + +// IsResourceAuthorizedToCurrentUser is a convenience method used to call +// github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/tenant.IsResourceAuthorizedToUserTx +// using an Info structure to provide the current user and database transaction. +func (inf *Info) IsResourceAuthorizedToCurrentUser(resourceTenantID int) (bool, error) { + return tenant.IsResourceAuthorizedToUserTx(resourceTenantID, inf.User, inf.Tx.Tx) +} + +// CreateChangeLog creates a new changelog message at the APICHANGE level for +// the current user. +func (inf Info) CreateChangeLog(msg string) { + _, err := inf.Tx.Tx.Exec(createChangeLogQuery, ApiChange, msg, inf.User.ID) + if err != nil { + log.Errorf("Inserting chage log level '%s' message '%s' for user '%s': %v", ApiChange, msg, inf.User.UserName, err) + } +} + +// UseIMS returns whether or not If-Modified-Since constraints should be used to +// service the given request. +func (inf Info) UseIMS() bool { + if inf.request == nil || inf.Config == nil { + return false + } + return inf.Config.UseIMS && inf.request.Header.Get(rfc.IfModifiedSince) != "" +} + +// CreateInfluxClient constructs and returns an InfluxDB HTTP client, if enabled +// and when possible. The error this returns should not be exposed to the user; +// it's for logging purposes only. +// +// If Influx connections are not enabled, this will return `nil` - but also no +// error. It is expected that the caller will handle this situation +// appropriately. +func (inf *Info) CreateInfluxClient() (*influx.Client, error) { + if !inf.Config.InfluxEnabled || inf.Config.ConfigInflux == nil { + return nil, nil + } + + var fqdn string + var tcpPort uint + var httpsPort sql.NullInt64 // this is the only one that's optional + + row := inf.Tx.Tx.QueryRow(influxServersQuery) + if e := row.Scan(&fqdn, &tcpPort, &httpsPort); e != nil { + return nil, fmt.Errorf("failed to create influx client: %w", e) + } + + host := "http%s://%s:%d" + if inf.Config.ConfigInflux.Secure != nil && *inf.Config.ConfigInflux.Secure { + if !httpsPort.Valid { + log.Warnf("INFLUXDB Server %s has no secure ports, assuming default of 8086!", fqdn) + httpsPort = sql.NullInt64{Int64: 8086, Valid: true} + } + + p := httpsPort.Int64 + if p <= 0 || p > 65535 { + log.Warnf("INFLUXDB Server %s has invalid port, assuming default of 8086!", fqdn) + p = 8086 + } + + host = fmt.Sprintf(host, "s", fqdn, p) + } else if tcpPort > 0 && tcpPort <= 65535 { + host = fmt.Sprintf(host, "", fqdn, tcpPort) + } else { + log.Warnf("INFLUXDB Server %s has invalid port, assuming default of 8086!", fqdn) + host = fmt.Sprintf(host, "", fqdn, 8086) + } + + config := influx.HTTPConfig{ + Addr: host, + Username: inf.Config.ConfigInflux.User, + Password: inf.Config.ConfigInflux.Password, + UserAgent: fmt.Sprintf("TrafficOps/%s (Go)", inf.Config.Version), + Timeout: time.Duration(float64(inf.Config.ReadTimeout)/2.1) * time.Second, + } + + var client influx.Client + client, e := influx.NewHTTPClient(config) + if e != nil { + return nil, fmt.Errorf("failed to create influx client: %w", e) + } + if client == nil { + return nil, errors.New("failed to create influx client: client was nil") + } + return &client, e +} + +// DefaultSort sets the `orderby` query string parameter to the given value, as +// though the client had set it, should it be missing. +func (inf Info) DefaultSort(param string) { + if _, ok := inf.Params["orderby"]; !ok { + inf.Params["orderby"] = param + } +} diff --git a/traffic_ops/traffic_ops_golang/api/info_test.go b/traffic_ops/traffic_ops_golang/api/info_test.go new file mode 100644 index 0000000000..dafcd4b034 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/api/info_test.go @@ -0,0 +1,804 @@ +package api + +/* + * 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 ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "time" + + "github.com/apache/trafficcontrol/v8/lib/go-rfc" + "github.com/apache/trafficcontrol/v8/lib/go-tc" + "github.com/apache/trafficcontrol/v8/lib/go-util" + "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/auth" + "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/config" + "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/trafficvault/backends/disabled" + + "github.com/jmoiron/sqlx" + "gopkg.in/DATA-DOG/go-sqlmock.v1" +) + +func buildContextWithout(t *testing.T, key any, beginnable bool) context.Context { + t.Helper() + ctx := context.Background() + + if key != DBContextKey { + d, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("failed to open a stub database connection: %v", err) + } + db := sqlx.NewDb(d, "sqlmock") + if beginnable { + mock.ExpectBegin() + } + ctx = context.WithValue(ctx, DBContextKey, db) + } + if key != ConfigContextKey { + ctx = context.WithValue(ctx, ConfigContextKey, &config.Config{ConfigTrafficOpsGolang: config.ConfigTrafficOpsGolang{DBQueryTimeoutSeconds: 1}}) + } + if key != TrafficVaultContextKey { + ctx = context.WithValue(ctx, TrafficVaultContextKey, &disabled.Disabled{}) + } + if key != ReqIDContextKey { + ctx = context.WithValue(ctx, ReqIDContextKey, uint64(1)) + } + if key != auth.CurrentUserKey { + ctx = context.WithValue(ctx, auth.CurrentUserKey, auth.CurrentUser{}) + } + if key != PathParamsKey { + ctx = context.WithValue(ctx, PathParamsKey, map[string]string{}) + } + return ctx +} + +func buildRequest(t *testing.T, withoutContext any, beginnable bool, params map[string]string) *http.Request { + t.Helper() + var values url.Values = make(map[string][]string, len(params)) + for p, v := range params { + values[p] = []string{v} + } + + r := httptest.NewRequest(http.MethodConnect, "/api/5.0/ping?"+values.Encode(), nil) + + return r.WithContext(buildContextWithout(t, withoutContext, beginnable)) +} + +func testNewInfo_MissingContextKey(key any) func(*testing.T) { + return func(t *testing.T) { + r := buildRequest(t, key, false, nil) + _, sysErr, _, code := NewInfo(r, nil, nil) + if sysErr == nil { + t.Errorf("Expected non-nil system error, got: nil") + } else { + t.Log("Received expected system error:", sysErr) + } + if code != http.StatusInternalServerError { + t.Errorf("Incorrect status code for context missing '%v'; want: %d, got: %d", key, http.StatusInternalServerError, code) + } + } +} + +func TestNewInfo(t *testing.T) { + for _, key := range []any{ + DBContextKey, + ConfigContextKey, + TrafficVaultContextKey, + ReqIDContextKey, + auth.CurrentUserKey, + } { + t.Run(fmt.Sprintf("missing '%v' context key", key), testNewInfo_MissingContextKey(key)) + } + + // TODO: This really should return a system-internal error. But I'm testing + // the current behavior, soo... ¯\_(ツ)_/¯ + r := buildRequest(t, nil, false, nil) + _, sysErr, userErr, code := NewInfo(r, nil, nil) + if userErr == nil { + t.Errorf("Expected non-nil user error, got: nil") + } else { + t.Log("Received expected user error:", userErr) + } + if code != http.StatusInternalServerError { + t.Errorf("Incorrect status code for unable to start a database transaction; want: %d, got: %d", http.StatusInternalServerError, code) + } + + // TODO: shouldn't this be a user-facing error? It returns + // http.StatusBadRequest but a nil user error... why? + r = buildRequest(t, nil, false, nil) + _, sysErr, userErr, code = NewInfo(r, []string{"testquest"}, nil) + if sysErr == nil { + t.Error("Expected a system error; got: nil") + } else { + t.Log("Recieved expected system error:", sysErr) + } + if userErr != nil { + t.Errorf("Unexpected user-facing error: %v", userErr) + } + if code != http.StatusBadRequest { + t.Errorf("Incorrect status code for missing required parameter; want: %d, got: %d", http.StatusBadRequest, code) + } + + r = buildRequest(t, nil, true, nil) + _, sysErr, userErr, _ = NewInfo(r, nil, nil) + if userErr != nil { + t.Error("Unexpected user error:", userErr) + } + if sysErr != nil { + t.Error("Unexpected system error:", sysErr) + } +} + +func TestInfo_CheckPrecondition(t *testing.T) { + var inf Info + code, userErr, sysErr := inf.CheckPrecondition("anything", nil) + if code != http.StatusInternalServerError { + t.Errorf("incorrect status code for unitialized info structure; want: %d, got: %d", http.StatusInternalServerError, code) + } + if userErr != nil { + t.Errorf("Unexpected user-facing error: %v", userErr) + } + if sysErr == nil { + t.Errorf("Expected a system error; got: nil") + } else if !errors.Is(sysErr, NilRequestError) { + t.Errorf("Incorrect system error; want: %v, got: %v", NilRequestError, sysErr) + } else { + t.Log("Received expected system error:", sysErr) + } + + inf.request = httptest.NewRequest(http.MethodConnect, "/", nil) + code, userErr, sysErr = inf.CheckPrecondition("anything", nil) + if code != http.StatusOK { + t.Errorf("incorrect status code for a request with no precondition headers; want: %d, got: %d", http.StatusOK, code) + } + if userErr != nil { + t.Errorf("Unexpected user-facing error: %v", userErr) + } + if sysErr != nil { + t.Errorf("Unexpected system error: %v", sysErr) + } + + // We have to use `time.Now` because for whatever reason the ETag parser + // refuses to aknowledge timestamps more than 20 years in either direction + // from whatever `time.Now` returns at the time the parser runs. + // TODO: Fix that? + etag := rfc.ETag(time.Now()) + inf.request.Header.Add(rfc.IfMatch, etag) + code, userErr, sysErr = inf.CheckPrecondition("anything", nil) + if code != http.StatusInternalServerError { + t.Errorf("incorrect status code for nil db transaction; want: %d, got: %d", http.StatusInternalServerError, code) + } + if userErr != nil { + t.Errorf("Unexpected user-facing error: %v", userErr) + } + if sysErr == nil { + t.Errorf("Expected a system error; got: nil") + } else if !errors.Is(sysErr, NilTransactionError) { + t.Errorf("Incorrect system error; want: %v, got: %v", NilTransactionError, sysErr) + } else { + t.Log("Received expected system error:", sysErr) + } + + d, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("failed to open a stub database connection: %v", err) + } + db := sqlx.NewDb(d, "sqlmock") + mock.ExpectBegin() + inf.Tx = db.MustBegin() + + code, userErr, sysErr = inf.CheckPrecondition("anything", nil) + if code != http.StatusInternalServerError { + t.Errorf("incorrect status code for database query error; want: %d, got: %d", http.StatusInternalServerError, code) + } + if userErr != nil { + t.Errorf("Unexpected user-facing error: %v", userErr) + } + if sysErr == nil { + t.Errorf("Expected a system error; got: nil") + } else { + t.Log("Received expected system error:", sysErr) + } + + rows := sqlmock.NewRows([]string{"last_updated"}) + rows.AddRow(time.Now().Add(time.Hour)) + mock.ExpectQuery("^anything$").WillReturnRows(rows) + code, userErr, sysErr = inf.CheckPrecondition("anything", nil) + if code != http.StatusPreconditionFailed { + t.Errorf("incorrect status code for ETag match failure; want: %d, got: %d", http.StatusPreconditionFailed, code) + } + if sysErr != nil { + t.Errorf("Unexpected system error: %v", sysErr) + } + if userErr == nil { + t.Errorf("Expected a user-facing error; got: nil") + } else if !errors.Is(userErr, ResourceModifiedError) { + t.Errorf("Incorrect user-facing error; want: %v, got: %v", ResourceModifiedError, userErr) + } else { + t.Log("Received expected user-facing error:", userErr) + } + + rows = sqlmock.NewRows([]string{"last_updated"}) + rows.AddRow(time.Now().Add(-time.Hour)) + mock.ExpectQuery("^anything$").WillReturnRows(rows) + code, userErr, sysErr = inf.CheckPrecondition("anything", nil) + if code != http.StatusOK { + t.Errorf("incorrect status code for ETag match success (with no unmodified-since time); want: %d, got: %d", http.StatusOK, code) + } + if sysErr != nil { + t.Errorf("Unexpected system error: %v", sysErr) + } + if userErr != nil { + t.Errorf("Unexpected user-facing error: %v", userErr) + } + + ius := rfc.FormatHTTPDate(time.Now()) + rows = sqlmock.NewRows([]string{"last_updated"}) + rows.AddRow(time.Now().Add(time.Hour)) + mock.ExpectQuery("^anything$").WillReturnRows(rows) + inf.request.Header.Add(rfc.IfUnmodifiedSince, ius) + inf.request.Header.Del(rfc.IfMatch) + code, userErr, sysErr = inf.CheckPrecondition("anything", nil) + if code != http.StatusPreconditionFailed { + t.Errorf("incorrect status code for ETag match failure; want: %d, got: %d", http.StatusPreconditionFailed, code) + } + if sysErr != nil { + t.Errorf("Unexpected system error: %v", sysErr) + } + if userErr == nil { + t.Errorf("Expected a user-facing error; got: nil") + } else if !errors.Is(userErr, ResourceModifiedError) { + t.Errorf("Incorrect user-facing error; want: %v, got: %v", ResourceModifiedError, userErr) + } else { + t.Log("Received expected user-facing error:", userErr) + } + + rows = sqlmock.NewRows([]string{"last_updated"}) + rows.AddRow(time.Now().Add(-time.Hour)) + mock.ExpectQuery("^anything$").WillReturnRows(rows) + code, userErr, sysErr = inf.CheckPrecondition("anything", nil) + if code != http.StatusOK { + t.Errorf("incorrect status code for ETag match success (with no unmodified-since time); want: %d, got: %d", http.StatusOK, code) + } + if sysErr != nil { + t.Errorf("Unexpected system error: %v", sysErr) + } + if userErr != nil { + t.Errorf("Unexpected user-facing error: %v", userErr) + } +} + +func TestInfo_Close(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("Failed to open stub database connection: %v", err) + } + mock.ExpectBegin() + + called := false + inf := Info{ + CancelTx: func() { + called = true + }, + Tx: sqlx.NewDb(db, "sqlmock").MustBegin(), + } + + mock.ExpectCommit() + inf.Close() + if !called { + t.Errorf("Expected cancel function to be called when the Info is closed; but it wasn't") + } + + called = false + mock.ExpectBegin() + inf.Tx = sqlx.NewDb(db, "sqlmock").MustBegin() + mock.ExpectCommit().WillReturnError(errors.New("testquest")) + inf.Close() + if !called { + t.Errorf("Expected cancel function to be called when the Info is closed (even if an error occurs commiting the transaction); but it wasn't") + } +} + +func TestInfo_WriteOKResponse(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/", nil) + + inf := Info{ + request: r, + w: w, + } + code, userErr, sysErr := inf.WriteOKResponse("test") + if code != http.StatusOK { + t.Errorf("WriteOKResponse should return a %d %s code, got: %d %s", http.StatusOK, http.StatusText(http.StatusOK), code, http.StatusText(code)) + } + if userErr != nil { + t.Errorf("Unexpected user error: %v", userErr) + } + if sysErr != nil { + t.Errorf("Unexpected system error: %v", sysErr) + } + + if w.Code != http.StatusOK { + t.Errorf("incorrect response status code; want: %d, got: %d", http.StatusOK, w.Code) + } +} + +func TestInfo_WriteOKResponseWithSummary(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/", nil) + + inf := Info{ + request: r, + w: w, + } + code, userErr, sysErr := inf.WriteOKResponseWithSummary("test", 42) + if code != http.StatusOK { + t.Errorf("WriteOKResponseWithSummary should return a %d %s code, got: %d %s", http.StatusOK, http.StatusText(http.StatusOK), code, http.StatusText(code)) + } + if userErr != nil { + t.Errorf("Unexpected user error: %v", userErr) + } + if sysErr != nil { + t.Errorf("Unexpected system error: %v", sysErr) + } + + if w.Code != http.StatusOK { + t.Errorf("incorrect response status code; want: %d, got: %d", http.StatusOK, w.Code) + } +} + +func TestInfo_WriteNotModifiedResponse(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/", nil) + + inf := Info{ + request: r, + w: w, + } + code, userErr, sysErr := inf.WriteNotModifiedResponse(time.Time{}) + if code != http.StatusNotModified { + t.Errorf("WriteNotModifiedResponse should return a %d %s code, got: %d %s", http.StatusNotModified, http.StatusText(http.StatusNotModified), code, http.StatusText(code)) + } + if userErr != nil { + t.Errorf("Unexpected user error: %v", userErr) + } + if sysErr != nil { + t.Errorf("Unexpected system error: %v", sysErr) + } + + if w.Code != http.StatusNotModified { + t.Errorf("incorrect response status code; want: %d, got: %d", http.StatusNotModified, w.Code) + } +} + +func TestInfo_WriteSuccessResponse(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodGet, "/", nil) + + inf := Info{ + request: r, + w: w, + } + code, userErr, sysErr := inf.WriteSuccessResponse("test", "quest") + if code != http.StatusOK { + t.Errorf("WriteSuccessResponse should return a %d %s code, got: %d %s", http.StatusOK, http.StatusText(http.StatusOK), code, http.StatusText(code)) + } + if userErr != nil { + t.Errorf("Unexpected user error: %v", userErr) + } + if sysErr != nil { + t.Errorf("Unexpected system error: %v", sysErr) + } + + if w.Code != http.StatusOK { + t.Errorf("incorrect response status code; want: %d, got: %d", http.StatusOK, w.Code) + } + var alerts tc.Alerts + if err := json.NewDecoder(w.Body).Decode(&alerts); err != nil { + t.Fatalf("couldn't decode response body: %v", err) + } + + if len(alerts.Alerts) != 1 { + t.Fatalf("expected exactly one alert; got: %d", len(alerts.Alerts)) + } + alert := alerts.Alerts[0] + if alert.Level != tc.SuccessLevel.String() { + t.Errorf("Incorrect alert level; want: %s, got: %s", tc.SuccessLevel, alert.Level) + } + if alert.Text != "quest" { + t.Errorf("Incorrect alert text; want: 'quest', got: '%s'", alert.Text) + } +} + +func TestInfo_WriteCreatedResponse(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest(http.MethodPost, "/", nil) + + inf := Info{ + request: r, + Version: &Version{Major: 420, Minor: 9001}, + w: w, + } + code, userErr, sysErr := inf.WriteCreatedResponse("test", "quest", "mypath") + if code != http.StatusCreated { + t.Errorf("WriteCreatedResponse should return a %d %s code, got: %d %s", http.StatusCreated, http.StatusText(http.StatusCreated), code, http.StatusText(code)) + } + if userErr != nil { + t.Errorf("Unexpected user error: %v", userErr) + } + if sysErr != nil { + t.Errorf("Unexpected system error: %v", sysErr) + } + + if w.Code != http.StatusCreated { + t.Errorf("incorrect response status code; want: %d, got: %d", http.StatusCreated, w.Code) + } + if locHdr := w.Header().Get(rfc.Location); locHdr != "/api/420.9001/mypath" { + t.Errorf("incorrect '%s' header value; want: '/api/420.9001/mypath', got: '%s'", rfc.Location, locHdr) + } + var alerts tc.Alerts + if err := json.NewDecoder(w.Body).Decode(&alerts); err != nil { + t.Fatalf("couldn't decode response body: %v", err) + } + + if len(alerts.Alerts) != 1 { + t.Fatalf("expected exactly one alert; got: %d", len(alerts.Alerts)) + } + alert := alerts.Alerts[0] + if alert.Level != tc.SuccessLevel.String() { + t.Errorf("Incorrect alert level; want: %s, got: %s", tc.SuccessLevel, alert.Level) + } + if alert.Text != "quest" { + t.Errorf("Incorrect alert text; want: 'quest', got: '%s'", alert.Text) + } +} + +func TestInfo_RequestHeaders(t *testing.T) { + r := httptest.NewRequest(http.MethodGet, "/", nil) + r.Header.Set("test", "quest") + + inf := Info{ + request: r, + } + testHdr := inf.RequestHeaders().Get("test") + if testHdr != "quest" { + t.Errorf("should have retrieved the 'test' header (expected value: 'quest'), but found that header to have value: '%s'", testHdr) + } +} + +func TestInfo_SetLastModified(t *testing.T) { + w := httptest.NewRecorder() + inf := Info{w: w} + tm := time.Now().Truncate(time.Second).UTC() + inf.SetLastModified(tm) + + wLMHdr := w.Header().Get(rfc.LastModified) + lm, err := time.Parse(time.RFC1123, wLMHdr) + if err != nil { + t.Fatalf("Failed to parse the response writer's '%s' header as an RFC1123 timestamp: %v", rfc.LastModified, err) + } + + // For unknown reasons, our API always adds a second to the truncated time + // value for LastModified headers. I suspect it's a poor attempt at rounding + // - for which the `Round` method ought to be used instead. + if expected := tm.Add(time.Second); lm != expected { + t.Errorf("Incorrect time set as '%s' header; want: %s, got: %s", rfc.LastModified, expected.Format(time.RFC3339Nano), lm.Format(time.RFC3339Nano)) + } +} + +func TestInfo_DecodeBody(t *testing.T) { + inf := Info{ + request: httptest.NewRequest(http.MethodConnect, "/", strings.NewReader(`{"test": "quest"}`)), + } + + var out struct { + Test string `json:"test"` + } + if err := inf.DecodeBody(&out); err != nil { + t.Fatalf("failed to decode body: %v", err) + } + if out.Test != "quest" { + t.Errorf(`incorrect request body parsed; want: {"test": "quest"}, got: {"test": "%s"}`, out.Test) + } +} + +func ExampleInfo_SendMail() { + inf := Info{ + Config: &config.Config{ + SMTP: &config.ConfigSMTP{ + // Note that we're not actually sending an email in this + // example. In fact it's explicitly disabled! + Enabled: false, + }, + }, + } + + code, _, err := inf.SendMail(rfc.EmailAddress{}, []byte("anything")) + fmt.Println(code, "-", err.Error()) + // Output: 500 - SMTP is not enabled; mail cannot be sent +} + +func TestInfo_IsResourceAuthorizedToUser(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("Failed to open stub database connection: %v", err) + } + + mock.ExpectBegin() + inf := Info{ + Tx: sqlx.NewDb(db, "sqlmock").MustBegin(), + User: &auth.CurrentUser{}, + } + + mock.ExpectQuery(".").WillReturnError(errors.New("testquest")) + ok, err := inf.IsResourceAuthorizedToCurrentUser(-1) + if ok { + t.Errorf("Expected a query failure to report the user is not authorized") + } + if err == nil { + t.Errorf("Expected an error when a query error occurs, but didn't get one") + } else { + t.Logf("Received expected error: %v", err) + } +} + +func TestInfo_CreateChangeLog(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("Failed to open stub database connection: %v", err) + } + + mock.ExpectBegin() + inf := Info{ + Tx: sqlx.NewDb(db, "sqlmock").MustBegin(), + User: &auth.CurrentUser{ + ID: 1, + }, + } + + msg := "anything" + mock.ExpectExec("INSERT INTO log").WithArgs(ApiChange, msg, inf.User.ID) + inf.CreateChangeLog(msg) + if err = mock.ExpectationsWereMet(); err != nil { + t.Errorf("Unmet expectations: %v", err) + } + + mock.ExpectExec("INSERT INTO log").WithArgs(ApiChange, msg, inf.User.ID).WillReturnError(errors.New("testquest")) + inf.CreateChangeLog(msg) + if err = mock.ExpectationsWereMet(); err != nil { + t.Errorf("Unmet expectations: %v", err) + } +} + +func TestInfo_UseIMS(t *testing.T) { + r := httptest.NewRequest(http.MethodConnect, "", nil) + r.Header.Add(rfc.IfModifiedSince, "doesn't matter") + inf := Info{ + request: r, + } + use := inf.UseIMS() + if use { + t.Error("expected IMS to not be used if the Info structure has no Config") + } + + inf = Info{ + Config: &config.Config{ + UseIMS: true, + }, + } + use = inf.UseIMS() + if use { + t.Error("expected IMS to not be used if the Info structure has no request") + } + + inf.request = r + inf.Config.UseIMS = false + use = inf.UseIMS() + if use { + t.Error("expected IMS to not be used if it's configured not to") + } + + inf.Config.UseIMS = true + inf.request.Header.Del(rfc.IfModifiedSince) + use = inf.UseIMS() + if use { + t.Error("expected IMS to not be used if the request has no If-Modified-Since header") + } + + inf.request.Header.Add(rfc.IfModifiedSince, "literally anything") + use = inf.UseIMS() + if !use { + t.Error("expected IMS to be used when it's configured to and the request includes an If-Modified-Since header") + } +} + +func ExampleInfo_CreateInfluxClient() { + inf := Info{ + Config: &config.Config{ + InfluxEnabled: false, + ConfigInflux: &config.ConfigInflux{ + // ... + }, + }, + } + + // These are BOTH `nil` when Influx is disabled! + client, err := inf.CreateInfluxClient() + fmt.Println(client, err) + // Output: +} + +// TODO: the situation where the actual influx library would return a nil client +// but also a nil error is untestable - because the library doesn't ever do that +// under any circumstances. So... figure out how to test that, or change the +// code to not handle unnecessary circumstances. +func TestInfo_CreateInfluxClient(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("Failed to open stub database connection: %v", err) + } + + mock.ExpectBegin() + inf := Info{ + Config: &config.Config{ + ConfigTrafficOpsGolang: config.ConfigTrafficOpsGolang{ + ReadTimeout: 2, + }, + InfluxEnabled: true, + Version: "9.9.9", + }, + Tx: sqlx.NewDb(db, "sqlmock").MustBegin(), + User: &auth.CurrentUser{}, + } + client, err := inf.CreateInfluxClient() + if err != nil { + t.Errorf("Unexpected error when Influx is not configured: %v", err) + } + if client != nil { + t.Error("Client should be nil when Influx is not configured") + } + + inf.Config.ConfigInflux = &config.ConfigInflux{ + User: "user", + Password: "password", + Secure: new(bool), + } + + mock.ExpectQuery("^SELECT").WillReturnError(errors.New("testquest")) + client, err = inf.CreateInfluxClient() + if err == nil { + t.Error("Expected to get an error when the influx server query fails, but didn't") + } else { + t.Log("Received expected error:", err) + } + if client != nil { + t.Error("Client should be nil when querying for influx servers fails") + } + + rows := sqlmock.NewRows([]string{"fqdn", "tcp_port", "https_port"}) + rows.AddRow("not a valid hostname, to make the Influx library return an error", 1, 2) + mock.ExpectQuery("^SELECT").WillReturnRows(rows) + _, err = inf.CreateInfluxClient() + if err == nil { + t.Error("Expected an error trying to create a client for an invalid URL, but didn't get one") + } else { + t.Logf("Received expected error: %v", err) + } + + rows = sqlmock.NewRows([]string{"fqdn", "tcp_port", "https_port"}) + rows.AddRow("test.quest", 1, 2) + mock.ExpectQuery("^SELECT").WillReturnRows(rows) + client, err = inf.CreateInfluxClient() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if client == nil { + t.Error("client should not be nil when everything's supposed to succeed") + } + + rows = sqlmock.NewRows([]string{"fqdn", "tcp_port", "https_port"}) + rows.AddRow("test.quest", 0, 2) + mock.ExpectQuery("^SELECT").WillReturnRows(rows) + client, err = inf.CreateInfluxClient() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if client == nil { + t.Error("client should not be nil - even when the influx server's TCP port is incorrectly configured") + } + + inf.Config.ConfigInflux.Secure = nil + rows = sqlmock.NewRows([]string{"fqdn", "tcp_port", "https_port"}) + rows.AddRow("test.quest", 1, 2) + mock.ExpectQuery("^SELECT").WillReturnRows(rows) + client, err = inf.CreateInfluxClient() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if client == nil { + t.Error("client should not be nil - even influx configuration leaves 'secure' null/undefined") + } + + inf.Config.ConfigInflux.Secure = util.Ptr(true) + rows = sqlmock.NewRows([]string{"fqdn", "tcp_port", "https_port"}) + rows.AddRow("test.quest", 1, nil) + mock.ExpectQuery("^SELECT").WillReturnRows(rows) + client, err = inf.CreateInfluxClient() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if client == nil { + t.Error("client should not be nil - even when the server's HTTPS port is not configured") + } + + rows = sqlmock.NewRows([]string{"fqdn", "tcp_port", "https_port"}) + rows.AddRow("test.quest", 1, -1) + mock.ExpectQuery("^SELECT").WillReturnRows(rows) + client, err = inf.CreateInfluxClient() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if client == nil { + t.Error("client should not be nil - even when the server's HTTPS port is negative") + } + + rows = sqlmock.NewRows([]string{"fqdn", "tcp_port", "https_port"}) + rows.AddRow("test.quest", 1, 65536) + mock.ExpectQuery("^SELECT").WillReturnRows(rows) + client, err = inf.CreateInfluxClient() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if client == nil { + t.Error("client should not be nil - even when the server's HTTPS port is out of the valid port number range") + } + + rows = sqlmock.NewRows([]string{"fqdn", "tcp_port", "https_port"}) + rows.AddRow("test.quest", 1, 2) + mock.ExpectQuery("^SELECT").WillReturnRows(rows) + client, err = inf.CreateInfluxClient() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if client == nil { + t.Error("client should not be nil when everything's supposed to be working (but with HTTPS this time)") + } +} + +func ExampleInfo_DefaultSort() { + inf := Info{ + Params: map[string]string{}, + } + + inf.DefaultSort("testquest") + fmt.Println(inf.Params["orderby"]) + + inf.DefaultSort("id") + fmt.Println(inf.Params["orderby"]) + + // Output: testquest + // testquest +} diff --git a/traffic_ops/traffic_ops_golang/api/shared_handlers.go b/traffic_ops/traffic_ops_golang/api/shared_handlers.go index 5d88b75d66..44ed2e57f0 100644 --- a/traffic_ops/traffic_ops_golang/api/shared_handlers.go +++ b/traffic_ops/traffic_ops_golang/api/shared_handlers.go @@ -642,7 +642,7 @@ func DeprecatedCreateHandler(creator Creator, alternative *string) http.HandlerF ) } -func parseMultipleCreates(data []byte, desiredType reflect.Type, inf *APIInfo) ([]Creator, error) { +func parseMultipleCreates(data []byte, desiredType reflect.Type, inf *Info) ([]Creator, error) { buf := ioutil.NopCloser(bytes.NewReader(data)) var genericInt interface{} @@ -678,14 +678,14 @@ func parseMultipleCreates(data []byte, desiredType reflect.Type, inf *APIInfo) ( return creatorSlice, nil } -// A Handler is an API endpoint handlers. The take in APIInfo helper objects and +// A Handler is an API endpoint handlers. They take in Info helper objects and // return - in order - an HTTP response status code, a user-facing error (if one // occurred), and a system-only error not safe for exposure to clients (if one // occurred). -type Handler = func(*APIInfo) (int, error, error) +type Handler = func(*Info) (int, error, error) // Wrap wraps an API endpoint handler in the more generic HTTP request handler -// type from the http package. This constructs and provides the APIInfo for the +// type from the http package. This constructs and provides the Info for the // underlying Handler. If the handler requires any request path and/or query // string parameters, those should be declared in requiredParams. Likewise, if // any of those parameters are required to be integral, they should be named in @@ -693,7 +693,7 @@ type Handler = func(*APIInfo) (int, error, error) // Note that this will still require the normal routing middleware for // authentication and context setup. // Also note that handlers utilizing this need not defer closing of the provided -// APIInfo, as this will handle that for them. +// Info, as this will handle that for them. // Finally, make sure this is ONLY used on versioned endpoints; this will return // an internal error if there is no associated API version. func Wrap(h Handler, requiredParams, intParams []string) http.HandlerFunc { diff --git a/traffic_ops/traffic_ops_golang/api/shared_handlers_test.go b/traffic_ops/traffic_ops_golang/api/shared_handlers_test.go index 23fdd80d6b..98bc2f0d97 100644 --- a/traffic_ops/traffic_ops_golang/api/shared_handlers_test.go +++ b/traffic_ops/traffic_ops_golang/api/shared_handlers_test.go @@ -351,7 +351,7 @@ func TestDeleteHandler(t *testing.T) { // The constructed handler will return an error if fail is true, or nothing // special otherwise. func testingHandler(fail bool) Handler { - return func(inf *APIInfo) (int, error, error) { + return func(inf *Info) (int, error, error) { if fail { return http.StatusBadRequest, errors.New("testing user error"), errors.New("testing system error") } diff --git a/traffic_ops/traffic_ops_golang/api/shared_interfaces.go b/traffic_ops/traffic_ops_golang/api/shared_interfaces.go index c3216893f0..6b4235811f 100644 --- a/traffic_ops/traffic_ops_golang/api/shared_interfaces.go +++ b/traffic_ops/traffic_ops_golang/api/shared_interfaces.go @@ -114,9 +114,28 @@ type Tenantable interface { IsTenantAuthorized(user *auth.CurrentUser) (bool, error) } -// APIInfoer is an interface that guarantees the existance of a variable through its setters and getters. -// Every CRUD operation uses this login session context +// APIInfoer is an interface that guarantees the existence of a variable through +// its setters and getters. Every CRUD operation uses this login session +// context. type APIInfoer interface { - SetInfo(*APIInfo) - APIInfo() *APIInfo + SetInfo(*Info) + APIInfo() *Info +} + +// APIInfoImpl implements APIInfo via the APIInfoer interface. The purpose of +// this is somewhat unclear. +type APIInfoImpl struct { + ReqInfo *Info +} + +// SetInfo sets the APIInfo of the APIInfoImpl to the given Info. The purpose of +// this is somewhat unclear. +func (val *APIInfoImpl) SetInfo(inf *Info) { + val.ReqInfo = inf +} + +// APIInfo returns the APIInfoer's Info. The purpose of this is somewhat +// unclear. +func (val APIInfoImpl) APIInfo() *Info { + return val.ReqInfo } diff --git a/traffic_ops/traffic_ops_golang/apitenant/tenant_test.go b/traffic_ops/traffic_ops_golang/apitenant/tenant_test.go index 1fce2167c1..11d58e8102 100644 --- a/traffic_ops/traffic_ops_golang/apitenant/tenant_test.go +++ b/traffic_ops/traffic_ops_golang/apitenant/tenant_test.go @@ -83,7 +83,7 @@ func TestIsUpdateable(t *testing.T) { mock.ExpectBegin() mock.ExpectQuery("SELECT") - child.ReqInfo = &api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"id": strconv.Itoa(*child.ID)}} + child.ReqInfo = &api.Info{Tx: db.MustBegin(), Params: map[string]string{"id": strconv.Itoa(*child.ID)}} userErr, _, statusCode = child.isUpdatable() if userErr != nil && statusCode != http.StatusOK { t.Errorf("Should be able to update child to new parent (from Parent to Root). userErr = %s, statuscode = %d", userErr, statusCode) @@ -106,7 +106,7 @@ func TestIsUpdateable(t *testing.T) { mock.ExpectBegin() mock.ExpectQuery("SELECT").WillReturnRows(rows) - parent.ReqInfo = &api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"id": strconv.Itoa(*parent.ID)}} + parent.ReqInfo = &api.Info{Tx: db.MustBegin(), Params: map[string]string{"id": strconv.Itoa(*parent.ID)}} userErr, _, statusCode = parent.isUpdatable() if userErr == nil && statusCode != http.StatusBadRequest { t.Errorf("Should NOT be able to update parent to own child (from Parent to Child). userErr = %s, statuscode = %d", userErr, statusCode) @@ -129,7 +129,7 @@ func getValidChildTenant() *TOTenant { ParentID: &tenpid, ParentName: &tenpaname, } - ten.ReqInfo = &api.APIInfo{} + ten.ReqInfo = &api.Info{} return ten } @@ -148,7 +148,7 @@ func getValidParentTenant() *TOTenant { ParentID: &tenpid, ParentName: &tenpaname, } - ten.ReqInfo = &api.APIInfo{} + ten.ReqInfo = &api.Info{} return ten } @@ -165,6 +165,6 @@ func getRootTestTenant() *TOTenant { ParentID: nil, ParentName: nil, } - ten.ReqInfo = &api.APIInfo{} + ten.ReqInfo = &api.Info{} return ten } diff --git a/traffic_ops/traffic_ops_golang/asn/asns_test.go b/traffic_ops/traffic_ops_golang/asn/asns_test.go index c7024c0823..8fa0c62c3c 100644 --- a/traffic_ops/traffic_ops_golang/asn/asns_test.go +++ b/traffic_ops/traffic_ops_golang/asn/asns_test.go @@ -81,7 +81,7 @@ func TestGetASNs(t *testing.T) { mock.ExpectBegin() mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} obj := TOASNV11{ api.APIInfoImpl{ReqInfo: &reqInfo}, @@ -156,7 +156,7 @@ func TestCheckNumberForUpdate(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin()} + reqInfo := api.Info{Tx: db.MustBegin()} asnNum := 2 cachegroupID := 10 id := 1 @@ -194,7 +194,7 @@ func TestASNExistsForUpdateFailure(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin()} + reqInfo := api.Info{Tx: db.MustBegin()} asnNum := 2 cachegroupID := 10 id := 1 @@ -230,7 +230,7 @@ func TestASNExistsForUpdateSuccess(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin()} + reqInfo := api.Info{Tx: db.MustBegin()} asnNum := 2 cachegroupID := 10 id := 1 @@ -264,7 +264,7 @@ func TestASNExists(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin()} + reqInfo := api.Info{Tx: db.MustBegin()} asnNum := 2 cachegroupID := 10 asn := TOASNV11{ diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go index aaa4fbb5c5..5708505f15 100644 --- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go +++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go @@ -137,7 +137,7 @@ func TestReadCacheGroups(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"id": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"id": "1"}} obj := TOCacheGroup{ api.APIInfoImpl{ReqInfo: &reqInfo}, tc.CacheGroupNullable{}, @@ -205,7 +205,7 @@ func TestValidate(t *testing.T) { mock.ExpectBegin() mock.ExpectQuery("SELECT\\s+name,\\s+use_in_table").WillReturnRows(rows) tx := db.MustBegin() - reqInfo := api.APIInfo{Tx: tx} + reqInfo := api.Info{Tx: tx} // invalid name, shortname, loattude, and longitude id := 1 @@ -335,7 +335,7 @@ func TestBadTypeParamCacheGroups(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"type": "wrong"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"type": "wrong"}} obj := TOCacheGroup{ api.APIInfoImpl{ReqInfo: &reqInfo}, tc.CacheGroupNullable{}, diff --git a/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters_test.go b/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters_test.go index 226407df20..93fcb54758 100644 --- a/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters_test.go +++ b/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters_test.go @@ -194,7 +194,7 @@ func TestReadCacheGroupParameters(t *testing.T) { } mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: testCase.params} + reqInfo := api.Info{Tx: db.MustBegin(), Params: testCase.params} toParameterReader.SetInfo(&reqInfo) parameters, userErr, sysErr, returnCode, _ := toParameterReader.Read(nil, false) diff --git a/traffic_ops/traffic_ops_golang/cdn/cdns_test.go b/traffic_ops/traffic_ops_golang/cdn/cdns_test.go index 799b245112..a4d47290a6 100644 --- a/traffic_ops/traffic_ops_golang/cdn/cdns_test.go +++ b/traffic_ops/traffic_ops_golang/cdn/cdns_test.go @@ -86,7 +86,7 @@ func TestReadCDNs(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}, Version: &api.Version{Major: 5, Minor: 0}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}, Version: &api.Version{Major: 5, Minor: 0}} obj := TOCDN{ api.APIInfoImpl{ReqInfo: &reqInfo}, tc.CDNNullable{}, @@ -141,7 +141,7 @@ func TestInterfaces(t *testing.T) { func TestValidate(t *testing.T) { // invalid name, empty domainname n := "not_a_valid_cdn" - reqInfo := api.APIInfo{Tx: nil, Params: map[string]string{"dsId": "1"}, Version: &api.Version{Major: 5, Minor: 0}} + reqInfo := api.Info{Tx: nil, Params: map[string]string{"dsId": "1"}, Version: &api.Version{Major: 5, Minor: 0}} c := TOCDN{CDNNullable: tc.CDNNullable{Name: &n}, APIInfoImpl: api.APIInfoImpl{ReqInfo: &reqInfo}} err, _ := c.Validate() errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err))) @@ -210,8 +210,8 @@ func TestTOCDNUpdate(t *testing.T) { // Expect a query to select last_updated from the database with an argument of 1 mock.ExpectQuery("select last_updated").WithArgs(1).WillReturnRows(rows) - // Create a new APIInfo object with required information - reqInfo := api.APIInfo{ + // Create a new api.Info object with required information + reqInfo := api.Info{ Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}, User: &auth.CurrentUser{ diff --git a/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go index a94fb8ad11..8cf97086ae 100644 --- a/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go +++ b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go @@ -499,7 +499,7 @@ func addTenancyStmt(where string) string { return where } -func getCDNFederations(inf *api.APIInfo) ([]tc.CDNFederationV5, time.Time, int, error, error) { +func getCDNFederations(inf *api.Info) ([]tc.CDNFederationV5, time.Time, int, error, error) { tenantList, err := tenant.GetUserTenantIDListTx(inf.Tx.Tx, inf.User.TenantID) if err != nil { return nil, time.Time{}, http.StatusInternalServerError, nil, fmt.Errorf("getting tenant list for user: %w", err) @@ -562,7 +562,7 @@ func getCDNFederations(inf *api.APIInfo) ([]tc.CDNFederationV5, time.Time, int, } // Read handles GET requests to `cdns/{{name}}/federations`. -func Read(inf *api.APIInfo) (int, error, error) { +func Read(inf *api.Info) (int, error, error) { api.DefaultSort(inf, "cname") feds, max, code, userErr, sysErr := getCDNFederations(inf) if userErr != nil || sysErr != nil { @@ -575,7 +575,7 @@ func Read(inf *api.APIInfo) (int, error, error) { } // ReadID handles GET requests to `cdns/{{name}}/federations/{{ID}}`. -func ReadID(inf *api.APIInfo) (int, error, error) { +func ReadID(inf *api.Info) (int, error, error) { feds, max, code, userErr, sysErr := getCDNFederations(inf) if userErr != nil || sysErr != nil { return code, userErr, sysErr @@ -611,7 +611,7 @@ func validate(fed tc.CDNFederationV5) error { } // Create handles POST requests to `cdns/{{name}}/federations`. -func Create(inf *api.APIInfo) (int, error, error) { +func Create(inf *api.Info) (int, error, error) { var fed tc.CDNFederationV5 if err := inf.DecodeBody(&fed); err != nil { return http.StatusBadRequest, fmt.Errorf("parsing request body: %w", err), nil @@ -638,7 +638,7 @@ func Create(inf *api.APIInfo) (int, error, error) { } // Update handles PUT requests to `cdns/{{name}}/federations/{{id}}`. -func Update(inf *api.APIInfo) (int, error, error) { +func Update(inf *api.Info) (int, error, error) { var fed tc.CDNFederationV5 if err := inf.DecodeBody(&fed); err != nil { return http.StatusBadRequest, fmt.Errorf("parsing request body: %w", err), nil @@ -678,7 +678,7 @@ func Update(inf *api.APIInfo) (int, error, error) { } // Delete handles DELETE requests to `cdns/{{name}}/federations/{{id}}`. -func Delete(inf *api.APIInfo) (int, error, error) { +func Delete(inf *api.Info) (int, error, error) { id := inf.IntParams["id"] var fed tc.CDNFederationV5 diff --git a/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations_test.go b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations_test.go index 276a6fae18..27d087f259 100644 --- a/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations_test.go +++ b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations_test.go @@ -108,7 +108,7 @@ func gettingUserTenantListFails(t *testing.T) { err := errors.New("unknown failure") mock.ExpectQuery("WITH RECURSIVE").WillReturnError(err) - _, _, code, userErr, sysErr := getCDNFederations(&api.APIInfo{Tx: tx, User: &auth.CurrentUser{TenantID: 1}}) + _, _, code, userErr, sysErr := getCDNFederations(&api.Info{Tx: tx, User: &auth.CurrentUser{TenantID: 1}}) if code != http.StatusInternalServerError { t.Errorf("Incorrect response code when getting user tenants fails; want: %d, got: %d", http.StatusInternalServerError, code) @@ -141,7 +141,7 @@ func buildingQueryPartsFails(t *testing.T) { mock.ExpectQuery("WITH RECURSIVE").WillReturnRows(rows) - inf := api.APIInfo{ + inf := api.Info{ Params: map[string]string{ "dsID": "not an integer", }, @@ -193,7 +193,7 @@ func everythingWorks(t *testing.T) { fedRows.AddRow(1, fed.ID, fed.CName, fed.TTL, fed.Description, fed.LastUpdated, fed.DeliveryService.ID, fed.DeliveryService.XMLID) mock.ExpectQuery("SELECT").WillReturnRows(fedRows) - feds, _, _, userErr, sysErr := getCDNFederations(&api.APIInfo{Tx: tx, User: &auth.CurrentUser{TenantID: 1}, Version: &api.Version{Major: 5}}) + feds, _, _, userErr, sysErr := getCDNFederations(&api.Info{Tx: tx, User: &auth.CurrentUser{TenantID: 1}, Version: &api.Version{Major: 5}}) if userErr != nil { t.Errorf("Unexpected user-facing error: %v", userErr) } diff --git a/traffic_ops/traffic_ops_golang/cdni/capacity.go b/traffic_ops/traffic_ops_golang/cdni/capacity.go index 8dc891008a..e4186bad82 100644 --- a/traffic_ops/traffic_ops_golang/cdni/capacity.go +++ b/traffic_ops/traffic_ops_golang/cdni/capacity.go @@ -26,7 +26,7 @@ import ( "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/api" ) -func getCapacities(inf *api.APIInfo, ucdn string) (Capabilities, error) { +func getCapacities(inf *api.Info, ucdn string) (Capabilities, error) { capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciCapacityLimits, ucdn) if err != nil { return Capabilities{}, fmt.Errorf("querying capabilities: %w", err) diff --git a/traffic_ops/traffic_ops_golang/cdni/shared.go b/traffic_ops/traffic_ops_golang/cdni/shared.go index d6ee011cde..e65f7e677c 100644 --- a/traffic_ops/traffic_ops_golang/cdni/shared.go +++ b/traffic_ops/traffic_ops_golang/cdni/shared.go @@ -44,9 +44,9 @@ const ( AllFootprintQuery = `SELECT footprint_type, footprint_value::text[], capability_id FROM cdni_footprints` limitsQuery = ` -SELECT limit_id, scope_type, scope_value, limit_type, maximum_hard, maximum_soft, cl.telemetry_id, cl.telemetry_metric, t.id, t.type, tm.name, cl.capability_id -FROM cdni_limits AS cl -LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id +SELECT limit_id, scope_type, scope_value, limit_type, maximum_hard, maximum_soft, cl.telemetry_id, cl.telemetry_metric, t.id, t.type, tm.name, cl.capability_id +FROM cdni_limits AS cl +LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name` InsertCapabilityUpdateQuery = `INSERT INTO cdni_capability_updates (ucdn, data, async_status_id, request_type, host) VALUES ($1, $2, $3, $4, $5)` @@ -456,7 +456,7 @@ func PutConfigurationResponse(w http.ResponseWriter, r *http.Request) { api.WriteResp(w, r, msg) } -func getCapabilityIdFromFootprints(updatedData CapacityLimit, ucdn string, inf *api.APIInfo) (int, error) { +func getCapabilityIdFromFootprints(updatedData CapacityLimit, ucdn string, inf *api.Info) (int, error) { tableAbbr := "" selectClause := "" whereClause := "" @@ -534,7 +534,7 @@ func validateHostExists(host string, tx *sql.Tx) (int, error, error) { return http.StatusOK, nil, nil } -func checkBearerToken(bearerToken string, inf *api.APIInfo) (string, error) { +func checkBearerToken(bearerToken string, inf *api.Info) (string, error) { if bearerToken == "" { return "", errors.New("bearer token is required") } diff --git a/traffic_ops/traffic_ops_golang/cdni/telemetry.go b/traffic_ops/traffic_ops_golang/cdni/telemetry.go index 7d63af12c9..f3e9cb24cc 100644 --- a/traffic_ops/traffic_ops_golang/cdni/telemetry.go +++ b/traffic_ops/traffic_ops_golang/cdni/telemetry.go @@ -26,7 +26,7 @@ import ( "github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/api" ) -func getTelemetries(inf *api.APIInfo, ucdn string) (Capabilities, error) { +func getTelemetries(inf *api.Info, ucdn string) (Capabilities, error) { capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciTelemetry, ucdn) if err != nil { return Capabilities{}, fmt.Errorf("querying capabilities: %w", err) diff --git a/traffic_ops/traffic_ops_golang/cdnnotification/cdnnotifications.go b/traffic_ops/traffic_ops_golang/cdnnotification/cdnnotifications.go index 4f730a4c0a..503e83074c 100644 --- a/traffic_ops/traffic_ops_golang/cdnnotification/cdnnotifications.go +++ b/traffic_ops/traffic_ops_golang/cdnnotification/cdnnotifications.go @@ -33,10 +33,10 @@ import ( const readQuery = ` SELECT cn.id, - cn.cdn, + cn.cdn, cn.last_updated, - cn.user, - cn.notification + cn.user, + cn.notification FROM cdn_notification as cn INNER JOIN cdn ON cdn.name = cn.cdn INNER JOIN tm_user ON tm_user.username = cn.user @@ -164,7 +164,7 @@ func Delete(w http.ResponseWriter, r *http.Request) { api.WriteRespAlertObj(w, r, tc.SuccessLevel, alert.Text, respObj) } -func deleteCDNNotification(inf *api.APIInfo) (tc.Alert, tc.CDNNotification, error, error, int) { +func deleteCDNNotification(inf *api.Info) (tc.Alert, tc.CDNNotification, error, error, int) { var userErr error var sysErr error var statusCode = http.StatusOK diff --git a/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go b/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go index a6d6f129cc..3cc4fe821a 100644 --- a/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go +++ b/traffic_ops/traffic_ops_golang/coordinate/coordinates_test.go @@ -85,7 +85,7 @@ func TestReadCoordinates(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"id": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"id": "1"}} obj := TOCoordinate{ api.APIInfoImpl{ReqInfo: &reqInfo}, tc.CoordinateNullable{}, diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go index 4eee1fdd74..b3364964ec 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go @@ -85,7 +85,7 @@ func (ds *TODeliveryService) UnmarshalJSON(data []byte) error { // APIInfo implements // github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/api.APIInfoer. -func (ds *TODeliveryService) APIInfo() *api.APIInfo { return ds.ReqInfo } +func (ds *TODeliveryService) APIInfo() *api.Info { return ds.ReqInfo } // SetKeys implements part of the // github.com/apache/trafficcontrol/v8/traffic_ops/traffic_ops_golang/api.Identifier @@ -281,7 +281,7 @@ func CreateV41(w http.ResponseWriter, r *http.Request) { api.WriteAlertsObj(w, r, http.StatusCreated, alerts, []tc.DeliveryServiceV41{*res}) } -func createV30(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV30 tc.DeliveryServiceV30) (*tc.DeliveryServiceV30, int, error, error) { +func createV30(w http.ResponseWriter, r *http.Request, inf *api.Info, dsV30 tc.DeliveryServiceV30) (*tc.DeliveryServiceV30, int, error, error) { ds := tc.DeliveryServiceV31{DeliveryServiceV30: dsV30} res, status, userErr, sysErr := createV31(w, r, inf, ds) if res != nil { @@ -290,7 +290,7 @@ func createV30(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV30 t return nil, status, userErr, sysErr } -func createV31(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV31 tc.DeliveryServiceV31) (*tc.DeliveryServiceV31, int, error, error) { +func createV31(w http.ResponseWriter, r *http.Request, inf *api.Info, dsV31 tc.DeliveryServiceV31) (*tc.DeliveryServiceV31, int, error, error) { tx := inf.Tx.Tx dsNullable := tc.DeliveryServiceNullableV30(dsV31) ds := dsNullable.UpgradeToV4() @@ -349,7 +349,7 @@ func recreateTLSVersions(versions []string, dsid int, tx *sql.Tx) error { } // createV40 creates the given ds in the database, and returns the DS with its id and other fields created on insert set. On error, the HTTP status code, user error, and system error are returned. The status code SHOULD NOT be used, if both errors are nil. -func createV40(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV4 tc.DeliveryServiceV40, omitExtraLongDescFields bool) (*tc.DeliveryServiceV40, int, error, error) { +func createV40(w http.ResponseWriter, r *http.Request, inf *api.Info, dsV4 tc.DeliveryServiceV40, omitExtraLongDescFields bool) (*tc.DeliveryServiceV40, int, error, error) { ds, code, userErr, sysErr := createV41(w, r, inf, tc.DeliveryServiceV41{DeliveryServiceV40: dsV4}, omitExtraLongDescFields) if userErr != nil || sysErr != nil || ds == nil { return nil, code, userErr, sysErr @@ -363,7 +363,7 @@ func createV40(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV4 tc // created on insert set. On error, an HTTP status code, user error, and system // error are returned. The status code SHOULD NOT be used, if both errors are // nil. -func createV41(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, ds tc.DeliveryServiceV41, omitExtraLongDescFields bool) (*tc.DeliveryServiceV41, int, error, error) { +func createV41(w http.ResponseWriter, r *http.Request, inf *api.Info, ds tc.DeliveryServiceV41, omitExtraLongDescFields bool) (*tc.DeliveryServiceV41, int, error, error) { res, code, userErr, sysErr := createV50(w, r, inf, ds.Upgrade(), omitExtraLongDescFields, ds.LongDesc1, ds.LongDesc2) if res != nil { ds := res.Downgrade() @@ -377,7 +377,7 @@ func createV41(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, ds tc.D // created on insert set. On error, an HTTP status code, user error, and system // error are returned. The status code SHOULD NOT be used, if both errors are // nil. -func createV50(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, ds tc.DeliveryServiceV5, omitExtraLongDescFields bool, longDesc1, longDesc2 *string) (*tc.DeliveryServiceV5, int, error, error) { +func createV50(w http.ResponseWriter, r *http.Request, inf *api.Info, ds tc.DeliveryServiceV5, omitExtraLongDescFields bool, longDesc1, longDesc2 *string) (*tc.DeliveryServiceV5, int, error, error) { var err error tx := inf.Tx.Tx userErr, sysErr := Validate(tx, &ds) @@ -893,7 +893,7 @@ func UpdateV50(w http.ResponseWriter, r *http.Request) { api.WriteAlertsObj(w, r, http.StatusOK, alerts, *res) } -func updateV30(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV30 *tc.DeliveryServiceV30) (*tc.DeliveryServiceV30, int, error, error) { +func updateV30(w http.ResponseWriter, r *http.Request, inf *api.Info, dsV30 *tc.DeliveryServiceV30) (*tc.DeliveryServiceV30, int, error, error) { dsV31 := tc.DeliveryServiceV31{DeliveryServiceV30: *dsV30} // query the DB for existing 3.1 fields in order to "upgrade" this 3.0 request into a 3.1 request query := ` @@ -918,7 +918,7 @@ WHERE return nil, status, userErr, sysErr } -func updateV31(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV31 *tc.DeliveryServiceV31) (*tc.DeliveryServiceV31, int, error, error) { +func updateV31(w http.ResponseWriter, r *http.Request, inf *api.Info, dsV31 *tc.DeliveryServiceV31) (*tc.DeliveryServiceV31, int, error, error) { dsNull := tc.DeliveryServiceNullableV30(*dsV31) ds := dsNull.UpgradeToV4() dsV41 := ds @@ -983,7 +983,7 @@ func updateV31(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV31 * return &oldRes, http.StatusOK, nil, nil } -func updateV40(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV40 *tc.DeliveryServiceV40, omitExtraLongDescFields bool) (*tc.DeliveryServiceV40, int, error, error) { +func updateV40(w http.ResponseWriter, r *http.Request, inf *api.Info, dsV40 *tc.DeliveryServiceV40, omitExtraLongDescFields bool) (*tc.DeliveryServiceV40, int, error, error) { ds, code, userErr, sysErr := updateV41(w, r, inf, &tc.DeliveryServiceV41{DeliveryServiceV40: *dsV40}, omitExtraLongDescFields) if userErr != nil || sysErr != nil || ds == nil { return nil, code, userErr, sysErr @@ -991,7 +991,7 @@ func updateV40(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV40 * d := ds.DeliveryServiceV40 return &d, code, nil, nil } -func updateV41(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV4 *tc.DeliveryServiceV41, omitExtraLongDescFields bool) (*tc.DeliveryServiceV41, int, error, error) { +func updateV41(w http.ResponseWriter, r *http.Request, inf *api.Info, dsV4 *tc.DeliveryServiceV41, omitExtraLongDescFields bool) (*tc.DeliveryServiceV41, int, error, error) { upgraded := dsV4.Upgrade() res, code, userErr, sysErr := updateV50(w, r, inf, &upgraded, omitExtraLongDescFields, dsV4.LongDesc1, dsV4.LongDesc2) if userErr != nil || sysErr != nil || res == nil { @@ -1001,7 +1001,7 @@ func updateV41(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV4 *t return &ds, code, userErr, sysErr } -func updateV50(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, ds *tc.DeliveryServiceV5, omitExtraLongDescFields bool, longDesc1, longDesc2 *string) (*tc.DeliveryServiceV5, int, error, error) { +func updateV50(w http.ResponseWriter, r *http.Request, inf *api.Info, ds *tc.DeliveryServiceV5, omitExtraLongDescFields bool, longDesc1, longDesc2 *string) (*tc.DeliveryServiceV5, int, error, error) { tx := inf.Tx.Tx user := inf.User userErr, sysErr := Validate(tx, ds) @@ -2367,7 +2367,7 @@ func getTenantID(tx *sql.Tx, ds tc.DeliveryServiceV5) (*int, error) { return id, err } -func isTenantAuthorized(inf *api.APIInfo, ds *tc.DeliveryServiceV5) (bool, error) { +func isTenantAuthorized(inf *api.Info, ds *tc.DeliveryServiceV5) (bool, error) { tx := inf.Tx.Tx user := inf.User diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities_test.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities_test.go index 52fadd7952..356d8f9d66 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities_test.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities_test.go @@ -64,7 +64,7 @@ func TestCreateDeliveryServicesRequiredCapability(t *testing.T) { mock.ExpectBegin() rc := RequiredCapability{ api.APIInfoImpl{ - ReqInfo: &api.APIInfo{ + ReqInfo: &api.Info{ Tx: db.MustBegin(), User: &auth.CurrentUser{PrivLevel: 30}, }, @@ -127,7 +127,7 @@ func TestUnauthorizedCreateDeliveryServicesRequiredCapability(t *testing.T) { mock.ExpectBegin() rc := RequiredCapability{ api.APIInfoImpl{ - ReqInfo: &api.APIInfo{ + ReqInfo: &api.Info{ Tx: db.MustBegin(), User: &auth.CurrentUser{PrivLevel: 1}, }, @@ -179,7 +179,7 @@ func TestReadDeliveryServicesRequiredCapability(t *testing.T) { mock.ExpectBegin() rc := RequiredCapability{ api.APIInfoImpl{ - ReqInfo: &api.APIInfo{ + ReqInfo: &api.Info{ Tx: db.MustBegin(), User: &auth.CurrentUser{PrivLevel: 30}, }, @@ -251,7 +251,7 @@ func TestDeleteDeliveryServicesRequiredCapability(t *testing.T) { rc := RequiredCapability{ api.APIInfoImpl{ - ReqInfo: &api.APIInfo{ + ReqInfo: &api.Info{ Tx: db.MustBegin(), User: &auth.CurrentUser{PrivLevel: 30}, }, @@ -292,7 +292,7 @@ func TestUnauthorizedDeleteDeliveryServicesRequiredCapability(t *testing.T) { mock.ExpectBegin() rc := RequiredCapability{ api.APIInfoImpl{ - ReqInfo: &api.APIInfo{ + ReqInfo: &api.Info{ Tx: db.MustBegin(), User: &auth.CurrentUser{PrivLevel: 1}, }, @@ -353,7 +353,7 @@ func TestCreateDeliveryServicesRequiredCapabilityInvalidDSType(t *testing.T) { mock.ExpectBegin() rc := RequiredCapability{ api.APIInfoImpl{ - ReqInfo: &api.APIInfo{ + ReqInfo: &api.Info{ Tx: db.MustBegin(), User: &auth.CurrentUser{PrivLevel: 30}, }, diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/keys.go b/traffic_ops/traffic_ops_golang/deliveryservice/keys.go index c81941abf7..2f720bc95b 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/keys.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/keys.go @@ -235,7 +235,7 @@ func GetSSLKeysByXMLID(w http.ResponseWriter, r *http.Request) { } } -func getSslKeys(inf *api.APIInfo, ctx context.Context) (tc.DeliveryServiceSSLKeysV4, error) { +func getSslKeys(inf *api.Info, ctx context.Context) (tc.DeliveryServiceSSLKeysV4, error) { xmlID := inf.Params["xmlid"] version := inf.Params["version"] decode := inf.Params["decode"] diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go index ea4f14ce3b..012331803f 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go @@ -312,7 +312,7 @@ func Get(w http.ResponseWriter, r *http.Request) { // isTenantAuthorized ensures the user is authorized on the DSR's // DeliveryService's Tenant, as appropriate to the change type. -func isTenantAuthorized(dsr tc.DeliveryServiceRequestV5, inf *api.APIInfo) (bool, error) { +func isTenantAuthorized(dsr tc.DeliveryServiceRequestV5, inf *api.Info) (bool, error) { if dsr.Requested != nil && (dsr.ChangeType == tc.DSRChangeTypeUpdate || dsr.ChangeType == tc.DSRChangeTypeCreate) { ok, err := tenant.IsResourceAuthorizedToUserTx(dsr.Requested.TenantID, inf.User, inf.Tx.Tx) if err != nil { @@ -337,7 +337,7 @@ func isTenantAuthorized(dsr tc.DeliveryServiceRequestV5, inf *api.APIInfo) (bool } // Warning: this assumes inf isn't nil, and neither is dsr, inf.Tx or inf.User or inf.Tx.Tx. -func insert(dsr *tc.DeliveryServiceRequestV5, inf *api.APIInfo) (int, error, error) { +func insert(dsr *tc.DeliveryServiceRequestV5, inf *api.Info) (int, error, error) { dsr.Author = inf.User.UserName dsr.LastEditedBy = inf.User.UserName if dsr.ChangeType != tc.DSRChangeTypeDelete { @@ -411,7 +411,7 @@ func (d dsrManipulationResult) String() string { return builder.String() } -func createV5(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result dsrManipulationResult) { +func createV5(w http.ResponseWriter, r *http.Request, inf *api.Info) (result dsrManipulationResult) { tx := inf.Tx.Tx var dsr tc.DeliveryServiceRequestV5 if err := json.NewDecoder(r.Body).Decode(&dsr); err != nil { @@ -477,7 +477,7 @@ func createV5(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result return } -func createV4(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result dsrManipulationResult) { +func createV4(w http.ResponseWriter, r *http.Request, inf *api.Info) (result dsrManipulationResult) { tx := inf.Tx.Tx var dsr tc.DeliveryServiceRequestV4 if err := json.NewDecoder(r.Body).Decode(&dsr); err != nil { @@ -551,7 +551,7 @@ func createV4(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result return } -func createLegacy(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result dsrManipulationResult) { +func createLegacy(w http.ResponseWriter, r *http.Request, inf *api.Info) (result dsrManipulationResult) { tx := inf.Tx.Tx var dsr tc.DeliveryServiceRequestNullable if err := json.NewDecoder(r.Body).Decode(&dsr); err != nil { @@ -770,7 +770,7 @@ func Delete(w http.ResponseWriter, r *http.Request) { inf.CreateChangeLog(res.String()) } -func putV50(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result dsrManipulationResult) { +func putV50(w http.ResponseWriter, r *http.Request, inf *api.Info) (result dsrManipulationResult) { tx := inf.Tx.Tx var dsr tc.DeliveryServiceRequestV5 if err := json.NewDecoder(r.Body).Decode(&dsr); err != nil { @@ -870,7 +870,7 @@ func putV50(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result ds return } -func putV4(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result dsrManipulationResult) { +func putV4(w http.ResponseWriter, r *http.Request, inf *api.Info) (result dsrManipulationResult) { tx := inf.Tx.Tx var dsr tc.DeliveryServiceRequestV4 var dsrV40 tc.DeliveryServiceRequestV40 @@ -998,7 +998,7 @@ func putV4(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result dsr return } -func putLegacy(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result dsrManipulationResult) { +func putLegacy(w http.ResponseWriter, r *http.Request, inf *api.Info) (result dsrManipulationResult) { tx := inf.Tx.Tx var dsr tc.DeliveryServiceRequestNullable if err := json.NewDecoder(r.Body).Decode(&dsr); err != nil { diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go index 64ea5261c2..57df892068 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go @@ -44,7 +44,7 @@ func TestInsert(t *testing.T) { defer db.Close() mock.ExpectBegin() - inf := api.APIInfo{ + inf := api.Info{ Params: nil, IntParams: nil, User: &auth.CurrentUser{ diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go index 4db6fe2016..1b14e0a930 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go @@ -767,7 +767,7 @@ func GetReadAssigned(w http.ResponseWriter, r *http.Request) { api.WriteAlertsObj(w, r, http.StatusOK, alerts, servers) } -func read(inf *api.APIInfo) ([]tc.DSServerV4, error) { +func read(inf *api.Info) ([]tc.DSServerV4, error) { queryDataString := `, cg.name as cachegroup, @@ -959,13 +959,13 @@ func (dss *TODSSDeliveryService) Read(h http.Header, useIMS bool) ([]interface{} ))) AND d.cdn_id = (SELECT cdn_id FROM server WHERE id = :server))) AND -(( -(SELECT (t.name = 'ORG') FROM type t JOIN server s ON s.type = t.id WHERE s.id = :server) -OR -(SELECT COALESCE(ARRAY_AGG(ssc.server_capability), '{}') -FROM server_server_capability ssc -WHERE ssc."server" = :server) -@> +(( +(SELECT (t.name = 'ORG') FROM type t JOIN server s ON s.type = t.id WHERE s.id = :server) +OR +(SELECT COALESCE(ARRAY_AGG(ssc.server_capability), '{}') +FROM server_server_capability ssc +WHERE ssc."server" = :server) +@> ( SELECT COALESCE(ds.required_capabilities, '{}') ))) diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go index e2d9b670e2..f5ff767f19 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go @@ -498,7 +498,7 @@ func TestReadServers(t *testing.T) { params := make(map[string]int, 0) params["id"] = dsID - inf := api.APIInfo{ + inf := api.Info{ Version: &api.Version{ Major: 5, Minor: 0, diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go b/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go index dd7a7c0700..b1e7b13539 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/sslkeys.go @@ -117,7 +117,7 @@ func generatePutTrafficVaultSSLKeys(req tc.DeliveryServiceGenSSLKeysReq, tx *sql // GeneratePlaceholderSelfSignedCert generates a self-signed SSL certificate as a placeholder when a new HTTPS // delivery service is created or an HTTP delivery service is updated to use HTTPS. -func GeneratePlaceholderSelfSignedCert(ds tc.DeliveryServiceV5, inf *api.APIInfo, context context.Context) (error, int) { +func GeneratePlaceholderSelfSignedCert(ds tc.DeliveryServiceV5, inf *api.Info, context context.Context) (error, int) { tx := inf.Tx.Tx tv := inf.Vault _, ok, err := tv.GetDeliveryServiceSSLKeys(ds.XMLID, "", tx, context) diff --git a/traffic_ops/traffic_ops_golang/division/divisions_test.go b/traffic_ops/traffic_ops_golang/division/divisions_test.go index abfdad849e..dfde0d4d95 100644 --- a/traffic_ops/traffic_ops_golang/division/divisions_test.go +++ b/traffic_ops/traffic_ops_golang/division/divisions_test.go @@ -73,7 +73,7 @@ func TestGetDivisions(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} obj := TODivision{ api.APIInfoImpl{ReqInfo: &reqInfo}, tc.DivisionNullable{}, diff --git a/traffic_ops/traffic_ops_golang/federation_resolvers/federation_resolvers.go b/traffic_ops/traffic_ops_golang/federation_resolvers/federation_resolvers.go index 190e98fa69..69de44e142 100644 --- a/traffic_ops/traffic_ops_golang/federation_resolvers/federation_resolvers.go +++ b/traffic_ops/traffic_ops_golang/federation_resolvers/federation_resolvers.go @@ -235,7 +235,7 @@ func Delete(w http.ResponseWriter, r *http.Request) { api.WriteRespAlertObj(w, r, tc.SuccessLevel, alert.Text, respObj) } -func deleteFederationResolver(inf *api.APIInfo) (tc.Alert, tc.FederationResolver, error, error, int) { +func deleteFederationResolver(inf *api.Info) (tc.Alert, tc.FederationResolver, error, error, int) { var userErr error var sysErr error var statusCode = http.StatusOK diff --git a/traffic_ops/traffic_ops_golang/invalidationjobs/invalidationjobs.go b/traffic_ops/traffic_ops_golang/invalidationjobs/invalidationjobs.go index 938d6ebed2..e6d6dfd26e 100644 --- a/traffic_ops/traffic_ops_golang/invalidationjobs/invalidationjobs.go +++ b/traffic_ops/traffic_ops_golang/invalidationjobs/invalidationjobs.go @@ -272,7 +272,7 @@ const deleteQueryV4 = ` DELETE FROM job WHERE job.id=$1 -RETURNING +RETURNING job.id, job.asset_url, ( @@ -1562,7 +1562,7 @@ func setRevalFlags(d interface{}, tx *sql.Tx) error { return nil } -// Checks if the current user's (identified in the APIInfo) tenant has permissions to +// Checks if the current user's (identified in the api.Info) tenant has permissions to // edit a Delivery Service. `ds` is expected to be the integral, unique identifer of the // Delivery Service in question. // @@ -1573,7 +1573,7 @@ func setRevalFlags(d interface{}, tx *sql.Tx) error { // // Note: If no such delivery service exists, the return values shall indicate that the // user isn't authorized. -func IsUserAuthorizedToModifyDSID(inf *api.APIInfo, ds uint) (bool, error) { +func IsUserAuthorizedToModifyDSID(inf *api.Info, ds uint) (bool, error) { var t uint row := inf.Tx.Tx.QueryRow(`SELECT tenant_id FROM deliveryservice WHERE id=$1`, ds) if err := row.Scan(&t); err != nil { @@ -1586,7 +1586,7 @@ func IsUserAuthorizedToModifyDSID(inf *api.APIInfo, ds uint) (bool, error) { return tenant.IsResourceAuthorizedToUserTx(int(t), inf.User, inf.Tx.Tx) } -// Checks if the current user's (identified in the APIInfo) tenant has permissions to +// Checks if the current user's (identified in the api.Info) tenant has permissions to // edit a Delivery Service. `ds` is expected to be the "xml_id" of the // Delivery Service in question. // @@ -1597,7 +1597,7 @@ func IsUserAuthorizedToModifyDSID(inf *api.APIInfo, ds uint) (bool, error) { // // Note: If no such delivery service exists, the return values shall indicate that the // user isn't authorized. -func IsUserAuthorizedToModifyDSXMLID(inf *api.APIInfo, ds string) (bool, error) { +func IsUserAuthorizedToModifyDSXMLID(inf *api.Info, ds string) (bool, error) { var t uint row := inf.Tx.Tx.QueryRow(`SELECT tenant_id FROM deliveryservice WHERE xml_id=$1`, ds) if err := row.Scan(&t); err != nil { @@ -1610,7 +1610,7 @@ func IsUserAuthorizedToModifyDSXMLID(inf *api.APIInfo, ds string) (bool, error) return tenant.IsResourceAuthorizedToUserTx(int(t), inf.User, inf.Tx.Tx) } -// Checks if the current user's (identified in the APIInfo) tenant has permissions to +// Checks if the current user's (identified in the api.Info) tenant has permissions to // edit on par with the user identified by `u`. `u` is expected to be the integral, // unique identifer of the user in question (not the current, requesting user). // @@ -1621,7 +1621,7 @@ func IsUserAuthorizedToModifyDSXMLID(inf *api.APIInfo, ds string) (bool, error) // // Note: If no such delivery service exists, the return values shall indicate that the // user isn't authorized. -func IsUserAuthorizedToModifyJobsMadeByUserID(inf *api.APIInfo, u uint) (bool, error) { +func IsUserAuthorizedToModifyJobsMadeByUserID(inf *api.Info, u uint) (bool, error) { var t uint row := inf.Tx.Tx.QueryRow(`SELECT tenant_id FROM tm_user WHERE id=$1`, u) if err := row.Scan(&t); err != nil { @@ -1634,7 +1634,7 @@ func IsUserAuthorizedToModifyJobsMadeByUserID(inf *api.APIInfo, u uint) (bool, e return tenant.IsResourceAuthorizedToUserTx(int(t), inf.User, inf.Tx.Tx) } -// Checks if the current user's (identified in the APIInfo) tenant has permissions to +// Checks if the current user's (identified in the api.Info) tenant has permissions to // edit on par with the user identified by `u`. `u` is expected to be the username of // the user in question (not the current, requesting user). // @@ -1645,7 +1645,7 @@ func IsUserAuthorizedToModifyJobsMadeByUserID(inf *api.APIInfo, u uint) (bool, e // // Note: If no such delivery service exists, the return values shall indicate that the // user isn't authorized. -func IsUserAuthorizedToModifyJobsMadeByUsername(inf *api.APIInfo, u string) (bool, error) { +func IsUserAuthorizedToModifyJobsMadeByUsername(inf *api.Info, u string) (bool, error) { var t uint row := inf.Tx.Tx.QueryRow(`SELECT tenant_id FROM tm_user WHERE username=$1`, u) if err := row.Scan(&t); err != nil { diff --git a/traffic_ops/traffic_ops_golang/logs/log.go b/traffic_ops/traffic_ops_golang/logs/log.go index 851129b216..cff2045fe3 100644 --- a/traffic_ops/traffic_ops_golang/logs/log.go +++ b/traffic_ops/traffic_ops_golang/logs/log.go @@ -171,7 +171,7 @@ FROM "log" as l JOIN tm_user as u ON l.tm_user = u.id` const countQuery = `SELECT count(l.tm_user) FROM log as l` -func getLogV40(inf *api.APIInfo, days int) ([]tc.Log, uint64, error) { +func getLogV40(inf *api.Info, days int) ([]tc.Log, uint64, error) { var count = uint64(0) var whereCount string @@ -222,7 +222,7 @@ func getLogV40(inf *api.APIInfo, days int) ([]tc.Log, uint64, error) { return ls, count, nil } -func getLog(inf *api.APIInfo, days int, limit int) ([]tc.Log, uint64, error) { +func getLog(inf *api.Info, days int, limit int) ([]tc.Log, uint64, error) { var count = uint64(0) var whereCount string if _, ok := inf.Params["limit"]; !ok { diff --git a/traffic_ops/traffic_ops_golang/parameter/parameters_test.go b/traffic_ops/traffic_ops_golang/parameter/parameters_test.go index 2dee8c29fd..f806ffdb52 100644 --- a/traffic_ops/traffic_ops_golang/parameter/parameters_test.go +++ b/traffic_ops/traffic_ops_golang/parameter/parameters_test.go @@ -94,7 +94,7 @@ func TestGetParameters(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{ + reqInfo := api.Info{ Tx: db.MustBegin(), User: &auth.CurrentUser{PrivLevel: 30}, Params: map[string]string{"name": "1"}, diff --git a/traffic_ops/traffic_ops_golang/physlocation/phys_locations_test.go b/traffic_ops/traffic_ops_golang/physlocation/phys_locations_test.go index d778a089ef..7193d6a4ec 100644 --- a/traffic_ops/traffic_ops_golang/physlocation/phys_locations_test.go +++ b/traffic_ops/traffic_ops_golang/physlocation/phys_locations_test.go @@ -96,7 +96,7 @@ func TestGetPhysLocations(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} obj := TOPhysLocation{ api.APIInfoImpl{ReqInfo: &reqInfo}, tc.PhysLocationNullable{}, diff --git a/traffic_ops/traffic_ops_golang/profile/copy.go b/traffic_ops/traffic_ops_golang/profile/copy.go index 803833a138..86c4034dfc 100644 --- a/traffic_ops/traffic_ops_golang/profile/copy.go +++ b/traffic_ops/traffic_ops_golang/profile/copy.go @@ -71,10 +71,10 @@ func CopyProfileHandler(w http.ResponseWriter, r *http.Request) { api.WriteRespAlertObj(w, r, tc.SuccessLevel, successMsg, p.Response) } -func copyProfile(inf *api.APIInfo, p *tc.ProfileCopy) errorDetails { +func copyProfile(inf *api.Info, p *tc.ProfileCopy) errorDetails { if inf == nil || p == nil { return errorDetails{ - sysErr: errors.New("copyProfile received nil APIInfo or ProfileCopy reference"), + sysErr: errors.New("copyProfile received nil api.Info or ProfileCopy reference"), errCode: http.StatusInternalServerError, } } @@ -173,7 +173,7 @@ func copyProfile(inf *api.APIInfo, p *tc.ProfileCopy) errorDetails { return errorDetails{} } -func copyParameters(inf *api.APIInfo, p *tc.ProfileCopy) errorDetails { +func copyParameters(inf *api.Info, p *tc.ProfileCopy) errorDetails { // use existing ProfileParameter CRUD helpers to find parameters for the existing profile inf.Params = map[string]string{ "profileId": fmt.Sprintf("%d", p.ExistingID), diff --git a/traffic_ops/traffic_ops_golang/profile/copy_test.go b/traffic_ops/traffic_ops_golang/profile/copy_test.go index bed7f006c1..a8dbe18eee 100644 --- a/traffic_ops/traffic_ops_golang/profile/copy_test.go +++ b/traffic_ops/traffic_ops_golang/profile/copy_test.go @@ -101,7 +101,7 @@ func TestCopyProfileInvalidExistingProfile(t *testing.T) { mockFindProfile(t, mock, c.profile.Response.Name, c.mockProfileExists) mockReadProfile(t, mock, c.existingProfile, c.mockReadProfile) - inf := api.APIInfo{ + inf := api.Info{ Tx: db.MustBegin(), Version: &api.Version{ Major: 5, @@ -162,7 +162,7 @@ func TestCopyNewProfileExists(t *testing.T) { mockFindProfile(t, mock, profile.Response.Name, 1) - inf := api.APIInfo{ + inf := api.Info{ Tx: db.MustBegin(), Params: map[string]string{ "existing_profile": profile.Response.ExistingName, diff --git a/traffic_ops/traffic_ops_golang/profile/profiles_test.go b/traffic_ops/traffic_ops_golang/profile/profiles_test.go index a04928f49a..86e77218eb 100644 --- a/traffic_ops/traffic_ops_golang/profile/profiles_test.go +++ b/traffic_ops/traffic_ops_golang/profile/profiles_test.go @@ -97,7 +97,7 @@ func TestGetProfiles(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"name": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"name": "1"}} obj := TOProfile{ api.APIInfoImpl{ReqInfo: &reqInfo}, diff --git a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters_test.go b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters_test.go index e894b6b27e..5c57a3604f 100644 --- a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters_test.go +++ b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters_test.go @@ -79,7 +79,7 @@ func TestGetProfileParameters(t *testing.T) { mock.ExpectCommit() txx := db.MustBegin() - reqInfo := api.APIInfo{Tx: txx, Params: map[string]string{"profile": "1"}} + reqInfo := api.Info{Tx: txx, Params: map[string]string{"profile": "1"}} obj := TOProfileParameter{ api.APIInfoImpl{ReqInfo: &reqInfo}, tc.ProfileParameterNullable{}, diff --git a/traffic_ops/traffic_ops_golang/region/regions_test.go b/traffic_ops/traffic_ops_golang/region/regions_test.go index 592b607d67..fadd36db64 100644 --- a/traffic_ops/traffic_ops_golang/region/regions_test.go +++ b/traffic_ops/traffic_ops_golang/region/regions_test.go @@ -74,7 +74,7 @@ func TestReadRegions(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"id": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"id": "1"}} obj := TORegion{ api.APIInfoImpl{ReqInfo: &reqInfo}, tc.Region{}, diff --git a/traffic_ops/traffic_ops_golang/role/roles_test.go b/traffic_ops/traffic_ops_golang/role/roles_test.go index 0f4afe6421..32f0d5eef2 100644 --- a/traffic_ops/traffic_ops_golang/role/roles_test.go +++ b/traffic_ops/traffic_ops_golang/role/roles_test.go @@ -91,7 +91,7 @@ func TestInterfaces(t *testing.T) { func TestValidate(t *testing.T) { // invalid name, empty domainname n := "not_a_valid_role" - reqInfo := api.APIInfo{} + reqInfo := api.Info{} role := tc.Role{} role.Name = &n r := TORole{ diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go index dc6167a57a..f0512e6a67 100644 --- a/traffic_ops/traffic_ops_golang/routing/routing.go +++ b/traffic_ops/traffic_ops_golang/routing/routing.go @@ -378,7 +378,7 @@ func HandleBackendRoute(cfg *config.Config, route config.BackendRoute, w http.Re var userErr, sysErr error var errCode int var user auth.CurrentUser - var inf *api.APIInfo + var inf *api.Info user, userErr, sysErr, errCode = api.GetUserFromReq(w, r, cfg.Secrets[0]) if userErr != nil || sysErr != nil { diff --git a/traffic_ops/traffic_ops_golang/server/servers.go b/traffic_ops/traffic_ops_golang/server/servers.go index 510a58692d..120d183d92 100644 --- a/traffic_ops/traffic_ops_golang/server/servers.go +++ b/traffic_ops/traffic_ops_golang/server/servers.go @@ -669,7 +669,7 @@ and p.id = $1 } // Read is the handler for GET requests to /servers. -func Read(inf *api.APIInfo) (int, error, error) { +func Read(inf *api.Info) (int, error, error) { useIMS := inf.UseIMS() version := inf.Version @@ -1273,7 +1273,7 @@ func deleteInterfaces(id int, tx *sql.Tx) (error, error, int) { } // Update is the handler for PUT requests to /servers. -func Update(inf *api.APIInfo) (int, error, error) { +func Update(inf *api.Info) (int, error, error) { id := inf.IntParams["id"] // Get original server @@ -1641,7 +1641,7 @@ func insertServerProfile(id int, pName []string, tx *sql.Tx) (error, error, int) return nil, nil, http.StatusOK } -func createV3(inf *api.APIInfo) (int, error, error) { +func createV3(inf *api.Info) (int, error, error) { var server tc.ServerV30 if err := inf.DecodeBody(&server); err != nil { @@ -1774,7 +1774,7 @@ func createV3(inf *api.APIInfo) (int, error, error) { return http.StatusCreated, nil, nil } -func createV5(inf *api.APIInfo) (int, error, error) { +func createV5(inf *api.Info) (int, error, error) { var server tc.ServerV5 if err := inf.DecodeBody(&server); err != nil { @@ -1874,7 +1874,7 @@ func createV5(inf *api.APIInfo) (int, error, error) { return code, userErr, sysErr } -func createV4(inf *api.APIInfo) (int, error, error) { +func createV4(inf *api.Info) (int, error, error) { var server tc.ServerV40 if err := inf.DecodeBody(&server); err != nil { @@ -2162,7 +2162,7 @@ func createServerV3(tx *sqlx.Tx, server tc.ServerV30) (int64, error) { } // Create is the handler for POST requests to /servers. -func Create(inf *api.APIInfo) (int, error, error) { +func Create(inf *api.Info) (int, error, error) { switch inf.Version.Major { case 3: return createV3(inf) @@ -2226,7 +2226,7 @@ func getActiveDeliveryServicesThatOnlyHaveThisServerAssigned(id int, serverType } // Delete is the handler for DELETE requests to the /servers API endpoint. -func Delete(inf *api.APIInfo) (int, error, error) { +func Delete(inf *api.Info) (int, error, error) { id := inf.IntParams["id"] tx := inf.Tx.Tx serverInfo, exists, err := dbhelpers.GetServerInfo(id, tx) diff --git a/traffic_ops/traffic_ops_golang/server/servers_server_capability_test.go b/traffic_ops/traffic_ops_golang/server/servers_server_capability_test.go index 4827f9e026..30f63304f1 100644 --- a/traffic_ops/traffic_ops_golang/server/servers_server_capability_test.go +++ b/traffic_ops/traffic_ops_golang/server/servers_server_capability_test.go @@ -75,7 +75,7 @@ func TestReadSCsV5(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"serverId": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"serverId": "1"}} obj := TOServerServerCapabilityV5{ api.APIInfoImpl{ReqInfo: &reqInfo}, tc.ServerServerCapabilityV5{}, @@ -254,7 +254,7 @@ func TestReadSCs(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"serverId": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"serverId": "1"}} obj := TOServerServerCapability{ api.APIInfoImpl{ReqInfo: &reqInfo}, tc.ServerServerCapability{}, diff --git a/traffic_ops/traffic_ops_golang/servercapability/servercapability_test.go b/traffic_ops/traffic_ops_golang/servercapability/servercapability_test.go index 9a163bc20d..97ea578eb7 100644 --- a/traffic_ops/traffic_ops_golang/servercapability/servercapability_test.go +++ b/traffic_ops/traffic_ops_golang/servercapability/servercapability_test.go @@ -69,7 +69,7 @@ func TestReadSCs(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"name": "test"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"name": "test"}} obj := TOServerCapability{ APIInfoImpl: api.APIInfoImpl{ReqInfo: &reqInfo}, ServerCapability: tc.ServerCapability{}, diff --git a/traffic_ops/traffic_ops_golang/servercheck/servercheck.go b/traffic_ops/traffic_ops_golang/servercheck/servercheck.go index eac1466606..67df7c1772 100644 --- a/traffic_ops/traffic_ops_golang/servercheck/servercheck.go +++ b/traffic_ops/traffic_ops_golang/servercheck/servercheck.go @@ -213,7 +213,7 @@ func ReadServerCheck(w http.ResponseWriter, r *http.Request) { api.WriteResp(w, r, data) } -func handleReadServerCheck(inf *api.APIInfo, tx *sql.Tx) ([]tc.GenericServerCheck, error, error, int) { +func handleReadServerCheck(inf *api.Info, tx *sql.Tx) ([]tc.GenericServerCheck, error, error, int) { extensions := make(map[string]string) // Query Parameters to Database Query column mappings diff --git a/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry_test.go b/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry_test.go index 2d3f077db0..e63f7fe76e 100644 --- a/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry_test.go +++ b/traffic_ops/traffic_ops_golang/staticdnsentry/staticdnsentry_test.go @@ -86,7 +86,7 @@ func TestValidate(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) tx := db.MustBegin() - reqInfo := api.APIInfo{Tx: tx} + reqInfo := api.Info{Tx: tx} // invalid name, empty domainname ts := TOStaticDNSEntry{APIInfoImpl: api.APIInfoImpl{ReqInfo: &reqInfo}} err, _ = ts.Validate() diff --git a/traffic_ops/traffic_ops_golang/status/statuses_test.go b/traffic_ops/traffic_ops_golang/status/statuses_test.go index ca1486fd33..0bad376054 100644 --- a/traffic_ops/traffic_ops_golang/status/statuses_test.go +++ b/traffic_ops/traffic_ops_golang/status/statuses_test.go @@ -76,7 +76,7 @@ func TestReadStatusesV5(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} obj := TOStatusV5{ APIInfoImpl: api.APIInfoImpl{ReqInfo: &reqInfo}, @@ -157,7 +157,7 @@ func TestReadStatuses(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} obj := TOStatus{ APIInfoImpl: api.APIInfoImpl{ReqInfo: &reqInfo}, diff --git a/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets_test.go b/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets_test.go index bf22396173..f99cbe2d54 100644 --- a/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets_test.go +++ b/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets_test.go @@ -68,7 +68,7 @@ func TestInvalidSteeringTargetType(t *testing.T) { m["deliveryservice"] = "3" stObj := &TOSteeringTargetV11{ APIInfoImpl: api.APIInfoImpl{ - ReqInfo: &api.APIInfo{ + ReqInfo: &api.Info{ Params: m, IntParams: nil, User: nil, diff --git a/traffic_ops/traffic_ops_golang/topology/topologies.go b/traffic_ops/traffic_ops_golang/topology/topologies.go index b6c9eabbb8..e20ce4cc4f 100644 --- a/traffic_ops/traffic_ops_golang/topology/topologies.go +++ b/traffic_ops/traffic_ops_golang/topology/topologies.go @@ -110,7 +110,7 @@ func DowngradeTopologyNodes(nodes []tc.TopologyNodeV5) []tc.TopologyNode { } // ValidateTopology validates a v5 topology to make sure that the supplied fields are valid. -func ValidateTopology(topology tc.TopologyV5, reqInfo *api.APIInfo) (tc.Alerts, error, error) { +func ValidateTopology(topology tc.TopologyV5, reqInfo *api.Info) (tc.Alerts, error, error) { var alertsObject tc.Alerts currentTopoName := reqInfo.Params["name"] nameRule := validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist of only alphanumeric, dash, or underscore characters.") @@ -275,7 +275,7 @@ func (topology *TOTopology) Validate() (error, error) { return util.JoinErrs(tovalidate.ToErrors(rules)), nil } -func nodesInOtherTopologies(info *api.APIInfo, topologyNodes []tc.TopologyNode) ([]tc.TopologyNode, map[string][]string, error) { +func nodesInOtherTopologies(info *api.Info, topologyNodes []tc.TopologyNode) ([]tc.TopologyNode, map[string][]string, error) { currentTopoName := info.Params["name"] baseError := errors.New("unable to verify that there are no cycles across all topologies") where := `WHERE name != :topology_name` @@ -1333,7 +1333,7 @@ func selectMaxLastUpdatedQuery(where string) string { select max(last_updated) as ti from last_deleted l where l.table_name='topology') as res` } -func checkIfTopologyCanBeAlteredByCurrentUser(info *api.APIInfo, nodes []tc.TopologyNode) (error, error, int) { +func checkIfTopologyCanBeAlteredByCurrentUser(info *api.Info, nodes []tc.TopologyNode) (error, error, int) { cachegroups := getCachegroupNames(nodes) serverIDs, err := dbhelpers.GetServerIDsFromCachegroupNames(info.Tx.Tx, cachegroups) if err != nil { diff --git a/traffic_ops/traffic_ops_golang/topology/validation.go b/traffic_ops/traffic_ops_golang/topology/validation.go index 60b45eb872..32b0d0eb17 100644 --- a/traffic_ops/traffic_ops_golang/topology/validation.go +++ b/traffic_ops/traffic_ops_golang/topology/validation.go @@ -186,7 +186,7 @@ func checkForCycles(nodes []tc.TopologyNode) ([]string, error) { return cacheGroups, util.JoinErrs(errs) } -func checkForCyclesAcrossTopologies(info *api.APIInfo, topologyNodes []tc.TopologyNode, name string) error { +func checkForCyclesAcrossTopologies(info *api.Info, topologyNodes []tc.TopologyNode, name string) error { var ( nodes []tc.TopologyNode topologiesByCacheGroup map[string][]string diff --git a/traffic_ops/traffic_ops_golang/trafficstats/cache.go b/traffic_ops/traffic_ops_golang/trafficstats/cache.go index b1b5a53b90..f8d42484cf 100644 --- a/traffic_ops/traffic_ops_golang/trafficstats/cache.go +++ b/traffic_ops/traffic_ops_golang/trafficstats/cache.go @@ -64,7 +64,7 @@ AND time < $end GROUP BY time(%s, %s), cdn%s` ) -func cacheConfigFromRequest(r *http.Request, i *api.APIInfo) (tc.TrafficCacheStatsConfig, int, error) { +func cacheConfigFromRequest(r *http.Request, i *api.Info) (tc.TrafficCacheStatsConfig, int, error) { c := tc.TrafficCacheStatsConfig{} statsConfig, rc, e := tsConfigFromRequest(r, i) if e != nil { diff --git a/traffic_ops/traffic_ops_golang/trafficstats/deliveryservice.go b/traffic_ops/traffic_ops_golang/trafficstats/deliveryservice.go index 4b1256904e..e4e9f20ef5 100644 --- a/traffic_ops/traffic_ops_golang/trafficstats/deliveryservice.go +++ b/traffic_ops/traffic_ops_golang/trafficstats/deliveryservice.go @@ -75,7 +75,7 @@ const ( GROUP BY time(%s, %s), cachegroup%s` ) -func dsConfigFromRequest(r *http.Request, i *api.APIInfo) (tc.TrafficDSStatsConfig, int, error) { +func dsConfigFromRequest(r *http.Request, i *api.Info) (tc.TrafficDSStatsConfig, int, error) { c := tc.TrafficDSStatsConfig{} statsConfig, rc, e := tsConfigFromRequest(r, i) if e != nil { @@ -178,7 +178,7 @@ func GetDSStats(w http.ResponseWriter, r *http.Request) { handleRequest(w, r, client, c, inf) } -func handleRequest(w http.ResponseWriter, r *http.Request, client *influx.Client, cfg tc.TrafficDSStatsConfig, inf *api.APIInfo) { +func handleRequest(w http.ResponseWriter, r *http.Request, client *influx.Client, cfg tc.TrafficDSStatsConfig, inf *api.Info) { // TODO: as above, this could be done on TO itself, thus sending only one synchronous request // per hit on this endpoint, rather than the current two. Not sure if that's worth it for large // data sets, though. diff --git a/traffic_ops/traffic_ops_golang/trafficstats/stats_summary.go b/traffic_ops/traffic_ops_golang/trafficstats/stats_summary.go index 9676d77e34..e8daeb2f5e 100644 --- a/traffic_ops/traffic_ops_golang/trafficstats/stats_summary.go +++ b/traffic_ops/traffic_ops_golang/trafficstats/stats_summary.go @@ -49,7 +49,7 @@ func GetStatsSummary(w http.ResponseWriter, r *http.Request) { return } -func getLastSummaryDate(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) { +func getLastSummaryDate(w http.ResponseWriter, r *http.Request, inf *api.Info) { queryParamsToSQLCols := map[string]dbhelpers.WhereColumnInfo{ "statName": dbhelpers.WhereColumnInfo{Column: "stat_name"}, } @@ -86,7 +86,7 @@ func getLastSummaryDate(w http.ResponseWriter, r *http.Request, inf *api.APIInfo } -func getStatsSummary(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) { +func getStatsSummary(w http.ResponseWriter, r *http.Request, inf *api.Info) { queryParamsToSQLCols := map[string]dbhelpers.WhereColumnInfo{ "statName": dbhelpers.WhereColumnInfo{Column: "stat_name"}, "cdnName": dbhelpers.WhereColumnInfo{Column: "cdn_name"}, diff --git a/traffic_ops/traffic_ops_golang/trafficstats/util.go b/traffic_ops/traffic_ops_golang/trafficstats/util.go index 06e8c1bb56..d9bca991d9 100644 --- a/traffic_ops/traffic_ops_golang/trafficstats/util.go +++ b/traffic_ops/traffic_ops_golang/trafficstats/util.go @@ -50,7 +50,7 @@ const ( defaultInterval = "1m" ) -func tsConfigFromRequest(r *http.Request, i *api.APIInfo) (tc.TrafficStatsConfig, int, error) { +func tsConfigFromRequest(r *http.Request, i *api.Info) (tc.TrafficStatsConfig, int, error) { c := tc.TrafficStatsConfig{} var e error if accept := r.Header.Get("Accept"); accept != "" { diff --git a/traffic_ops/traffic_ops_golang/trafficstats/util_test.go b/traffic_ops/traffic_ops_golang/trafficstats/util_test.go index 9de39edf71..dcf6a8b87f 100644 --- a/traffic_ops/traffic_ops_golang/trafficstats/util_test.go +++ b/traffic_ops/traffic_ops_golang/trafficstats/util_test.go @@ -41,7 +41,7 @@ func TestTSConfigFromRequest(t *testing.T) { t.Fatalf("Failed to parse test end time: %v", err) } - inf := api.APIInfo{ + inf := api.Info{ Params: map[string]string{ "limit": "10", "offset": "0", diff --git a/traffic_ops/traffic_ops_golang/types/types_test.go b/traffic_ops/traffic_ops_golang/types/types_test.go index bd8feee4b3..84f81a381a 100644 --- a/traffic_ops/traffic_ops_golang/types/types_test.go +++ b/traffic_ops/traffic_ops_golang/types/types_test.go @@ -86,7 +86,7 @@ func TestGetType(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(rows) mock.ExpectCommit() - reqInfo := api.APIInfo{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} + reqInfo := api.Info{Tx: db.MustBegin(), Params: map[string]string{"dsId": "1"}} obj := TOType{ api.APIInfoImpl{ReqInfo: &reqInfo}, @@ -129,7 +129,7 @@ func createDummyType(field string) *TOType { Major: 2, Minor: 0, } - apiInfo := api.APIInfo{ + apiInfo := api.Info{ Version: &version, } return &TOType{ diff --git a/traffic_ops/traffic_ops_golang/user/current.go b/traffic_ops/traffic_ops_golang/user/current.go index 87abbb4ca0..4dab6889bb 100644 --- a/traffic_ops/traffic_ops_golang/user/current.go +++ b/traffic_ops/traffic_ops_golang/user/current.go @@ -488,7 +488,7 @@ func ReplaceCurrent(w http.ResponseWriter, r *http.Request) { api.WriteRespAlertObj(w, r, tc.SuccessLevel, "User profile was successfully updated", user) } -func validateV4(user tc.UserV4, inf *api.APIInfo) (error, error) { +func validateV4(user tc.UserV4, inf *api.Info) (error, error) { validateErrs := validation.Errors{ "email": validation.Validate(user.Email, validation.Required, is.Email), "fullName": validation.Validate(user.FullName, validation.Required),