rawlinp commented on a change in pull request #6524:
URL: https://github.com/apache/trafficcontrol/pull/6524#discussion_r804068000



##########
File path: traffic_ops/traffic_ops_golang/cdni/shared.go
##########
@@ -0,0 +1,360 @@
+package cdni
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "database/sql"
+       "errors"
+       "fmt"
+       "github.com/lib/pq"
+       "net/http"
+       "time"
+
+       "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+
+       "github.com/dgrijalva/jwt-go"
+)
+
+const CapabilityQuery = `SELECT id, type, ucdn FROM cdni_capabilities WHERE 
type = $1 AND ucdn = $2`
+const AllFootprintQuery = `SELECT footprint_type, footprint_value::text[], 
capability_id FROM cdni_footprints`
+
+const totalLimitsQuery = `
+SELECT limit_type, maximum_hard, maximum_soft, ctl.telemetry_id, 
ctl.telemetry_metric, t.id, t.type, tm.name, ctl.capability_id 
+FROM cdni_total_limits AS ctl 
+LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id 
+LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name`
+
+const hostLimitsQuery = `
+SELECT limit_type, maximum_hard, maximum_soft, chl.telemetry_id, 
chl.telemetry_metric, t.id, t.type, tm.name, host, chl.capability_id 
+FROM cdni_host_limits AS chl 
+LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id 
+LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name 
+ORDER BY host DESC`
+
+func GetCapabilities(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()
+
+       bearerToken := r.Header.Get("Authorization")
+       if bearerToken == "" {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("bearer token header is required"), nil)
+               return
+       }
+
+       if inf.Config.Cdni == nil || inf.Config.Cdni.JwtDecodingSecret == "" || 
inf.Config.Cdni.DCdnId == "" {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("cdn.conf does not contain CDNi information"))
+               return
+       }
+
+       claims := jwt.MapClaims{}
+       token, err := jwt.ParseWithClaims(bearerToken, claims, func(token 
*jwt.Token) (interface{}, error) {
+               return []byte(inf.Config.Cdni.JwtDecodingSecret), nil
+       })
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, 
fmt.Errorf("parsing claims: %w", err), nil)
+               return
+       }
+       if !token.Valid {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, 
errors.New("invalid token"), nil)
+               return
+       }
+
+       var expirationFloat float64
+       var ucdn string
+       var dcdn string
+       for key, val := range claims {
+               switch key {
+               case "iss":
+                       if _, ok := val.(string); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - iss (Issuer) must be a string"), nil)
+                               return
+                       }
+                       ucdn = val.(string)
+               case "aud":
+                       if _, ok := val.(string); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - aud (Audience) must be a string"), nil)
+                               return
+                       }
+                       dcdn = val.(string)
+               case "exp":
+                       if _, ok := val.(float64); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - exp (Expiration) must be a float64"), nil)
+                               return
+                       }
+                       expirationFloat = val.(float64)
+               }
+       }
+
+       expiration := int64(expirationFloat)
+
+       if expiration < time.Now().Unix() {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("token is expired"), nil)
+               return
+       }
+       if dcdn != inf.Config.Cdni.DCdnId {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token - incorrect dcdn"), nil)
+               return
+       }
+       if ucdn == "" {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token - empty ucdn field"), nil)
+               return
+       }
+
+       capacities, err := getCapacities(inf, ucdn)
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, err, 
nil)
+               return
+       }
+
+       telemetries, err := getTelemetries(inf, ucdn)
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, err, 
nil)
+               return
+       }
+
+       fciCaps := Capabilities{}
+       capsList := make([]Capability, 0, 
len(capacities.Capabilities)+len(telemetries.Capabilities))
+       capsList = append(capsList, capacities.Capabilities...)
+       capsList = append(capsList, telemetries.Capabilities...)
+
+       fciCaps.Capabilities = capsList
+
+       api.WriteRespRaw(w, r, fciCaps)
+}
+
+func getFootprintMap(tx *sql.Tx) (map[int][]Footprint, error) {
+       footRows, err := tx.Query(AllFootprintQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying footprints: %w", err)
+       }
+       defer log.Close(footRows, "closing foorpint query")
+       footprintMap := map[int][]Footprint{}
+       for footRows.Next() {
+               var footprint Footprint
+               if err := footRows.Scan(&footprint.FootprintType, 
pq.Array(&footprint.FootprintValue), &footprint.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+               val, ok := footprintMap[footprint.CapabilityId]
+               if !ok {
+                       footprintMap[footprint.CapabilityId] = 
[]Footprint{footprint}
+               } else {
+                       val = append(val, footprint)
+                       footprintMap[footprint.CapabilityId] = val
+               }
+       }
+
+       return footprintMap, nil
+}
+
+func getTotalLimitsMap(tx *sql.Tx) (map[int][]TotalLimitsQueryResponse, error) 
{
+       tlRows, err := tx.Query(totalLimitsQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying total limits: %w", err)
+       }
+
+       defer log.Close(tlRows, "closing total capacity limits query")
+       totalLimitsMap := map[int][]TotalLimitsQueryResponse{}
+       for tlRows.Next() {
+               var totalLimit TotalLimitsQueryResponse
+               if err := tlRows.Scan(&totalLimit.LimitType, 
&totalLimit.MaximumHard, &totalLimit.MaximumSoft, &totalLimit.TelemetryId, 
&totalLimit.TelemetryMetic, &totalLimit.Id, &totalLimit.Type, &totalLimit.Name, 
&totalLimit.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+
+               val, ok := totalLimitsMap[totalLimit.CapabilityId]
+               if !ok {
+                       totalLimitsMap[totalLimit.CapabilityId] = 
[]TotalLimitsQueryResponse{totalLimit}
+               } else {
+                       val = append(val, totalLimit)
+                       totalLimitsMap[totalLimit.CapabilityId] = val

Review comment:
       I believe my prior comment applies here as well

##########
File path: traffic_ops/traffic_ops_golang/cdni/shared.go
##########
@@ -0,0 +1,360 @@
+package cdni
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "database/sql"
+       "errors"
+       "fmt"
+       "github.com/lib/pq"
+       "net/http"
+       "time"
+
+       "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+
+       "github.com/dgrijalva/jwt-go"
+)
+
+const CapabilityQuery = `SELECT id, type, ucdn FROM cdni_capabilities WHERE 
type = $1 AND ucdn = $2`
+const AllFootprintQuery = `SELECT footprint_type, footprint_value::text[], 
capability_id FROM cdni_footprints`
+
+const totalLimitsQuery = `
+SELECT limit_type, maximum_hard, maximum_soft, ctl.telemetry_id, 
ctl.telemetry_metric, t.id, t.type, tm.name, ctl.capability_id 
+FROM cdni_total_limits AS ctl 
+LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id 
+LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name`
+
+const hostLimitsQuery = `
+SELECT limit_type, maximum_hard, maximum_soft, chl.telemetry_id, 
chl.telemetry_metric, t.id, t.type, tm.name, host, chl.capability_id 
+FROM cdni_host_limits AS chl 
+LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id 
+LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name 
+ORDER BY host DESC`
+
+func GetCapabilities(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()
+
+       bearerToken := r.Header.Get("Authorization")
+       if bearerToken == "" {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("bearer token header is required"), nil)
+               return
+       }
+
+       if inf.Config.Cdni == nil || inf.Config.Cdni.JwtDecodingSecret == "" || 
inf.Config.Cdni.DCdnId == "" {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("cdn.conf does not contain CDNi information"))
+               return
+       }
+
+       claims := jwt.MapClaims{}
+       token, err := jwt.ParseWithClaims(bearerToken, claims, func(token 
*jwt.Token) (interface{}, error) {
+               return []byte(inf.Config.Cdni.JwtDecodingSecret), nil
+       })
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, 
fmt.Errorf("parsing claims: %w", err), nil)
+               return
+       }
+       if !token.Valid {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, 
errors.New("invalid token"), nil)
+               return
+       }
+
+       var expirationFloat float64
+       var ucdn string
+       var dcdn string
+       for key, val := range claims {
+               switch key {
+               case "iss":
+                       if _, ok := val.(string); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - iss (Issuer) must be a string"), nil)
+                               return
+                       }
+                       ucdn = val.(string)
+               case "aud":
+                       if _, ok := val.(string); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - aud (Audience) must be a string"), nil)
+                               return
+                       }
+                       dcdn = val.(string)
+               case "exp":
+                       if _, ok := val.(float64); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - exp (Expiration) must be a float64"), nil)
+                               return
+                       }
+                       expirationFloat = val.(float64)
+               }
+       }
+
+       expiration := int64(expirationFloat)
+
+       if expiration < time.Now().Unix() {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("token is expired"), nil)
+               return
+       }
+       if dcdn != inf.Config.Cdni.DCdnId {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token - incorrect dcdn"), nil)
+               return
+       }
+       if ucdn == "" {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token - empty ucdn field"), nil)
+               return
+       }
+
+       capacities, err := getCapacities(inf, ucdn)
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, err, 
nil)
+               return
+       }
+
+       telemetries, err := getTelemetries(inf, ucdn)
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, err, 
nil)
+               return
+       }
+
+       fciCaps := Capabilities{}
+       capsList := make([]Capability, 0, 
len(capacities.Capabilities)+len(telemetries.Capabilities))
+       capsList = append(capsList, capacities.Capabilities...)
+       capsList = append(capsList, telemetries.Capabilities...)
+
+       fciCaps.Capabilities = capsList
+
+       api.WriteRespRaw(w, r, fciCaps)
+}
+
+func getFootprintMap(tx *sql.Tx) (map[int][]Footprint, error) {
+       footRows, err := tx.Query(AllFootprintQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying footprints: %w", err)
+       }
+       defer log.Close(footRows, "closing foorpint query")
+       footprintMap := map[int][]Footprint{}
+       for footRows.Next() {
+               var footprint Footprint
+               if err := footRows.Scan(&footprint.FootprintType, 
pq.Array(&footprint.FootprintValue), &footprint.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+               val, ok := footprintMap[footprint.CapabilityId]
+               if !ok {
+                       footprintMap[footprint.CapabilityId] = 
[]Footprint{footprint}
+               } else {
+                       val = append(val, footprint)
+                       footprintMap[footprint.CapabilityId] = val

Review comment:
       I believe you might be able to replace this with this:
   ```
   footPrintMap[footprint.CapabilityId] = 
append(footPrintMap[footprint.CapabilityId], footprint)
   ```
   because `append` can handle null/empty slices properly (no need to 
initialize them manually first): https://go.dev/play/p/7SUhd7bDEus

##########
File path: traffic_ops/traffic_ops_golang/cdni/shared.go
##########
@@ -0,0 +1,360 @@
+package cdni
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "database/sql"
+       "errors"
+       "fmt"
+       "github.com/lib/pq"
+       "net/http"
+       "time"
+
+       "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+
+       "github.com/dgrijalva/jwt-go"
+)
+
+const CapabilityQuery = `SELECT id, type, ucdn FROM cdni_capabilities WHERE 
type = $1 AND ucdn = $2`
+const AllFootprintQuery = `SELECT footprint_type, footprint_value::text[], 
capability_id FROM cdni_footprints`
+
+const totalLimitsQuery = `
+SELECT limit_type, maximum_hard, maximum_soft, ctl.telemetry_id, 
ctl.telemetry_metric, t.id, t.type, tm.name, ctl.capability_id 
+FROM cdni_total_limits AS ctl 
+LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id 
+LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name`
+
+const hostLimitsQuery = `
+SELECT limit_type, maximum_hard, maximum_soft, chl.telemetry_id, 
chl.telemetry_metric, t.id, t.type, tm.name, host, chl.capability_id 
+FROM cdni_host_limits AS chl 
+LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id 
+LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name 
+ORDER BY host DESC`
+
+func GetCapabilities(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()
+
+       bearerToken := r.Header.Get("Authorization")
+       if bearerToken == "" {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("bearer token header is required"), nil)
+               return
+       }
+
+       if inf.Config.Cdni == nil || inf.Config.Cdni.JwtDecodingSecret == "" || 
inf.Config.Cdni.DCdnId == "" {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("cdn.conf does not contain CDNi information"))
+               return
+       }
+
+       claims := jwt.MapClaims{}
+       token, err := jwt.ParseWithClaims(bearerToken, claims, func(token 
*jwt.Token) (interface{}, error) {
+               return []byte(inf.Config.Cdni.JwtDecodingSecret), nil
+       })
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, 
fmt.Errorf("parsing claims: %w", err), nil)
+               return
+       }
+       if !token.Valid {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, 
errors.New("invalid token"), nil)
+               return
+       }
+
+       var expirationFloat float64
+       var ucdn string
+       var dcdn string
+       for key, val := range claims {
+               switch key {
+               case "iss":
+                       if _, ok := val.(string); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - iss (Issuer) must be a string"), nil)
+                               return
+                       }
+                       ucdn = val.(string)
+               case "aud":
+                       if _, ok := val.(string); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - aud (Audience) must be a string"), nil)
+                               return
+                       }
+                       dcdn = val.(string)
+               case "exp":
+                       if _, ok := val.(float64); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - exp (Expiration) must be a float64"), nil)
+                               return
+                       }
+                       expirationFloat = val.(float64)
+               }
+       }
+
+       expiration := int64(expirationFloat)
+
+       if expiration < time.Now().Unix() {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("token is expired"), nil)
+               return
+       }
+       if dcdn != inf.Config.Cdni.DCdnId {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token - incorrect dcdn"), nil)
+               return
+       }
+       if ucdn == "" {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token - empty ucdn field"), nil)
+               return
+       }
+
+       capacities, err := getCapacities(inf, ucdn)
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, err, 
nil)
+               return
+       }
+
+       telemetries, err := getTelemetries(inf, ucdn)
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, err, 
nil)
+               return
+       }
+
+       fciCaps := Capabilities{}
+       capsList := make([]Capability, 0, 
len(capacities.Capabilities)+len(telemetries.Capabilities))
+       capsList = append(capsList, capacities.Capabilities...)
+       capsList = append(capsList, telemetries.Capabilities...)
+
+       fciCaps.Capabilities = capsList
+
+       api.WriteRespRaw(w, r, fciCaps)
+}
+
+func getFootprintMap(tx *sql.Tx) (map[int][]Footprint, error) {
+       footRows, err := tx.Query(AllFootprintQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying footprints: %w", err)
+       }
+       defer log.Close(footRows, "closing foorpint query")
+       footprintMap := map[int][]Footprint{}
+       for footRows.Next() {
+               var footprint Footprint
+               if err := footRows.Scan(&footprint.FootprintType, 
pq.Array(&footprint.FootprintValue), &footprint.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+               val, ok := footprintMap[footprint.CapabilityId]
+               if !ok {
+                       footprintMap[footprint.CapabilityId] = 
[]Footprint{footprint}
+               } else {
+                       val = append(val, footprint)
+                       footprintMap[footprint.CapabilityId] = val
+               }
+       }
+
+       return footprintMap, nil
+}
+
+func getTotalLimitsMap(tx *sql.Tx) (map[int][]TotalLimitsQueryResponse, error) 
{
+       tlRows, err := tx.Query(totalLimitsQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying total limits: %w", err)
+       }
+
+       defer log.Close(tlRows, "closing total capacity limits query")
+       totalLimitsMap := map[int][]TotalLimitsQueryResponse{}
+       for tlRows.Next() {
+               var totalLimit TotalLimitsQueryResponse
+               if err := tlRows.Scan(&totalLimit.LimitType, 
&totalLimit.MaximumHard, &totalLimit.MaximumSoft, &totalLimit.TelemetryId, 
&totalLimit.TelemetryMetic, &totalLimit.Id, &totalLimit.Type, &totalLimit.Name, 
&totalLimit.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+
+               val, ok := totalLimitsMap[totalLimit.CapabilityId]
+               if !ok {
+                       totalLimitsMap[totalLimit.CapabilityId] = 
[]TotalLimitsQueryResponse{totalLimit}
+               } else {
+                       val = append(val, totalLimit)
+                       totalLimitsMap[totalLimit.CapabilityId] = val
+               }
+       }
+
+       return totalLimitsMap, nil
+}
+
+func getHostLimitsMap(tx *sql.Tx) (map[int][]HostLimitsResponse, error) {
+       hlRows, err := tx.Query(hostLimitsQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying host limits: %w", err)
+       }
+
+       defer log.Close(hlRows, "closing host capacity limits query")
+       hostLimitsMap := map[int][]HostLimitsResponse{}
+       for hlRows.Next() {
+               var hostLimit HostLimitsResponse
+               if err := hlRows.Scan(&hostLimit.LimitType, 
&hostLimit.MaximumHard, &hostLimit.MaximumSoft, &hostLimit.TelemetryId, 
&hostLimit.TelemetryMetic, &hostLimit.Id, &hostLimit.Type, &hostLimit.Name, 
&hostLimit.Host, &hostLimit.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+               val, ok := hostLimitsMap[hostLimit.CapabilityId]
+               if !ok {
+                       hostLimitsMap[hostLimit.CapabilityId] = 
[]HostLimitsResponse{hostLimit}
+               } else {
+                       val = append(val, hostLimit)
+                       hostLimitsMap[hostLimit.CapabilityId] = val

Review comment:
       I believe my prior comment applies here as well

##########
File path: traffic_ops/traffic_ops_golang/cdni/shared.go
##########
@@ -0,0 +1,360 @@
+package cdni
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "database/sql"
+       "errors"
+       "fmt"
+       "github.com/lib/pq"
+       "net/http"
+       "time"
+
+       "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+
+       "github.com/dgrijalva/jwt-go"
+)
+
+const CapabilityQuery = `SELECT id, type, ucdn FROM cdni_capabilities WHERE 
type = $1 AND ucdn = $2`
+const AllFootprintQuery = `SELECT footprint_type, footprint_value::text[], 
capability_id FROM cdni_footprints`
+
+const totalLimitsQuery = `
+SELECT limit_type, maximum_hard, maximum_soft, ctl.telemetry_id, 
ctl.telemetry_metric, t.id, t.type, tm.name, ctl.capability_id 
+FROM cdni_total_limits AS ctl 
+LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id 
+LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name`
+
+const hostLimitsQuery = `
+SELECT limit_type, maximum_hard, maximum_soft, chl.telemetry_id, 
chl.telemetry_metric, t.id, t.type, tm.name, host, chl.capability_id 
+FROM cdni_host_limits AS chl 
+LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id 
+LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name 
+ORDER BY host DESC`
+
+func GetCapabilities(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()
+
+       bearerToken := r.Header.Get("Authorization")
+       if bearerToken == "" {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("bearer token header is required"), nil)
+               return
+       }
+
+       if inf.Config.Cdni == nil || inf.Config.Cdni.JwtDecodingSecret == "" || 
inf.Config.Cdni.DCdnId == "" {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("cdn.conf does not contain CDNi information"))
+               return
+       }
+
+       claims := jwt.MapClaims{}
+       token, err := jwt.ParseWithClaims(bearerToken, claims, func(token 
*jwt.Token) (interface{}, error) {
+               return []byte(inf.Config.Cdni.JwtDecodingSecret), nil
+       })
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, 
fmt.Errorf("parsing claims: %w", err), nil)
+               return
+       }
+       if !token.Valid {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, 
errors.New("invalid token"), nil)
+               return
+       }
+
+       var expirationFloat float64
+       var ucdn string
+       var dcdn string
+       for key, val := range claims {
+               switch key {
+               case "iss":
+                       if _, ok := val.(string); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - iss (Issuer) must be a string"), nil)
+                               return
+                       }
+                       ucdn = val.(string)
+               case "aud":
+                       if _, ok := val.(string); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - aud (Audience) must be a string"), nil)
+                               return
+                       }
+                       dcdn = val.(string)
+               case "exp":
+                       if _, ok := val.(float64); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - exp (Expiration) must be a float64"), nil)
+                               return
+                       }
+                       expirationFloat = val.(float64)
+               }
+       }
+
+       expiration := int64(expirationFloat)
+
+       if expiration < time.Now().Unix() {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("token is expired"), nil)
+               return
+       }
+       if dcdn != inf.Config.Cdni.DCdnId {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token - incorrect dcdn"), nil)
+               return
+       }
+       if ucdn == "" {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token - empty ucdn field"), nil)
+               return
+       }
+
+       capacities, err := getCapacities(inf, ucdn)
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, err, 
nil)
+               return
+       }
+
+       telemetries, err := getTelemetries(inf, ucdn)
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, err, 
nil)
+               return
+       }
+
+       fciCaps := Capabilities{}
+       capsList := make([]Capability, 0, 
len(capacities.Capabilities)+len(telemetries.Capabilities))
+       capsList = append(capsList, capacities.Capabilities...)
+       capsList = append(capsList, telemetries.Capabilities...)
+
+       fciCaps.Capabilities = capsList
+
+       api.WriteRespRaw(w, r, fciCaps)
+}
+
+func getFootprintMap(tx *sql.Tx) (map[int][]Footprint, error) {
+       footRows, err := tx.Query(AllFootprintQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying footprints: %w", err)
+       }
+       defer log.Close(footRows, "closing foorpint query")
+       footprintMap := map[int][]Footprint{}
+       for footRows.Next() {
+               var footprint Footprint
+               if err := footRows.Scan(&footprint.FootprintType, 
pq.Array(&footprint.FootprintValue), &footprint.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+               val, ok := footprintMap[footprint.CapabilityId]
+               if !ok {
+                       footprintMap[footprint.CapabilityId] = 
[]Footprint{footprint}
+               } else {
+                       val = append(val, footprint)
+                       footprintMap[footprint.CapabilityId] = val
+               }
+       }
+
+       return footprintMap, nil
+}
+
+func getTotalLimitsMap(tx *sql.Tx) (map[int][]TotalLimitsQueryResponse, error) 
{
+       tlRows, err := tx.Query(totalLimitsQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying total limits: %w", err)
+       }
+
+       defer log.Close(tlRows, "closing total capacity limits query")
+       totalLimitsMap := map[int][]TotalLimitsQueryResponse{}
+       for tlRows.Next() {
+               var totalLimit TotalLimitsQueryResponse
+               if err := tlRows.Scan(&totalLimit.LimitType, 
&totalLimit.MaximumHard, &totalLimit.MaximumSoft, &totalLimit.TelemetryId, 
&totalLimit.TelemetryMetic, &totalLimit.Id, &totalLimit.Type, &totalLimit.Name, 
&totalLimit.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+
+               val, ok := totalLimitsMap[totalLimit.CapabilityId]
+               if !ok {
+                       totalLimitsMap[totalLimit.CapabilityId] = 
[]TotalLimitsQueryResponse{totalLimit}
+               } else {
+                       val = append(val, totalLimit)
+                       totalLimitsMap[totalLimit.CapabilityId] = val
+               }
+       }
+
+       return totalLimitsMap, nil
+}
+
+func getHostLimitsMap(tx *sql.Tx) (map[int][]HostLimitsResponse, error) {
+       hlRows, err := tx.Query(hostLimitsQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying host limits: %w", err)
+       }
+
+       defer log.Close(hlRows, "closing host capacity limits query")
+       hostLimitsMap := map[int][]HostLimitsResponse{}
+       for hlRows.Next() {
+               var hostLimit HostLimitsResponse
+               if err := hlRows.Scan(&hostLimit.LimitType, 
&hostLimit.MaximumHard, &hostLimit.MaximumSoft, &hostLimit.TelemetryId, 
&hostLimit.TelemetryMetic, &hostLimit.Id, &hostLimit.Type, &hostLimit.Name, 
&hostLimit.Host, &hostLimit.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+               val, ok := hostLimitsMap[hostLimit.CapabilityId]
+               if !ok {
+                       hostLimitsMap[hostLimit.CapabilityId] = 
[]HostLimitsResponse{hostLimit}
+               } else {
+                       val = append(val, hostLimit)
+                       hostLimitsMap[hostLimit.CapabilityId] = val
+               }
+       }
+
+       return hostLimitsMap, nil
+}
+
+func getTelemetriesMap(tx *sql.Tx) (map[int][]Telemetry, error) {
+       rows, err := tx.Query(`SELECT id, type, capability_id FROM 
cdni_telemetry`)
+       if err != nil {
+               return nil, errors.New("querying cdni telemetry: " + 
err.Error())
+       }
+       defer log.Close(rows, "closing telemetry query")
+
+       telemetryMap := map[int][]Telemetry{}
+       for rows.Next() {
+               telemetry := Telemetry{}
+               if err := rows.Scan(&telemetry.Id, &telemetry.Type, 
&telemetry.CapabilityId); err != nil {
+                       return nil, errors.New("scanning telemetry: " + 
err.Error())
+               }
+               val, ok := telemetryMap[telemetry.CapabilityId]
+               if !ok {
+                       telemetryMap[telemetry.CapabilityId] = 
[]Telemetry{telemetry}
+               } else {
+                       val = append(val, telemetry)
+                       telemetryMap[telemetry.CapabilityId] = val

Review comment:
       I believe my prior comment applies here as well

##########
File path: traffic_ops/traffic_ops_golang/cdni/shared.go
##########
@@ -0,0 +1,360 @@
+package cdni
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "database/sql"
+       "errors"
+       "fmt"
+       "github.com/lib/pq"
+       "net/http"
+       "time"
+
+       "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+
+       "github.com/dgrijalva/jwt-go"
+)
+
+const CapabilityQuery = `SELECT id, type, ucdn FROM cdni_capabilities WHERE 
type = $1 AND ucdn = $2`
+const AllFootprintQuery = `SELECT footprint_type, footprint_value::text[], 
capability_id FROM cdni_footprints`
+
+const totalLimitsQuery = `
+SELECT limit_type, maximum_hard, maximum_soft, ctl.telemetry_id, 
ctl.telemetry_metric, t.id, t.type, tm.name, ctl.capability_id 
+FROM cdni_total_limits AS ctl 
+LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id 
+LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name`
+
+const hostLimitsQuery = `
+SELECT limit_type, maximum_hard, maximum_soft, chl.telemetry_id, 
chl.telemetry_metric, t.id, t.type, tm.name, host, chl.capability_id 
+FROM cdni_host_limits AS chl 
+LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id 
+LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name 
+ORDER BY host DESC`
+
+func GetCapabilities(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()
+
+       bearerToken := r.Header.Get("Authorization")
+       if bearerToken == "" {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("bearer token header is required"), nil)
+               return
+       }
+
+       if inf.Config.Cdni == nil || inf.Config.Cdni.JwtDecodingSecret == "" || 
inf.Config.Cdni.DCdnId == "" {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("cdn.conf does not contain CDNi information"))
+               return
+       }
+
+       claims := jwt.MapClaims{}
+       token, err := jwt.ParseWithClaims(bearerToken, claims, func(token 
*jwt.Token) (interface{}, error) {
+               return []byte(inf.Config.Cdni.JwtDecodingSecret), nil
+       })
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, 
fmt.Errorf("parsing claims: %w", err), nil)
+               return
+       }
+       if !token.Valid {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, 
errors.New("invalid token"), nil)
+               return
+       }
+
+       var expirationFloat float64
+       var ucdn string
+       var dcdn string
+       for key, val := range claims {
+               switch key {
+               case "iss":
+                       if _, ok := val.(string); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - iss (Issuer) must be a string"), nil)
+                               return
+                       }
+                       ucdn = val.(string)
+               case "aud":
+                       if _, ok := val.(string); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - aud (Audience) must be a string"), nil)
+                               return
+                       }
+                       dcdn = val.(string)
+               case "exp":
+                       if _, ok := val.(float64); !ok {
+                               api.HandleErr(w, r, nil, http.StatusBadRequest, 
errors.New("invalid token - exp (Expiration) must be a float64"), nil)
+                               return
+                       }
+                       expirationFloat = val.(float64)
+               }
+       }
+
+       expiration := int64(expirationFloat)
+
+       if expiration < time.Now().Unix() {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("token is expired"), nil)
+               return
+       }
+       if dcdn != inf.Config.Cdni.DCdnId {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token - incorrect dcdn"), nil)
+               return
+       }
+       if ucdn == "" {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token - empty ucdn field"), nil)
+               return
+       }
+
+       capacities, err := getCapacities(inf, ucdn)
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, err, 
nil)
+               return
+       }
+
+       telemetries, err := getTelemetries(inf, ucdn)
+       if err != nil {
+               api.HandleErr(w, r, nil, http.StatusInternalServerError, err, 
nil)
+               return
+       }
+
+       fciCaps := Capabilities{}
+       capsList := make([]Capability, 0, 
len(capacities.Capabilities)+len(telemetries.Capabilities))
+       capsList = append(capsList, capacities.Capabilities...)
+       capsList = append(capsList, telemetries.Capabilities...)
+
+       fciCaps.Capabilities = capsList
+
+       api.WriteRespRaw(w, r, fciCaps)
+}
+
+func getFootprintMap(tx *sql.Tx) (map[int][]Footprint, error) {
+       footRows, err := tx.Query(AllFootprintQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying footprints: %w", err)
+       }
+       defer log.Close(footRows, "closing foorpint query")
+       footprintMap := map[int][]Footprint{}
+       for footRows.Next() {
+               var footprint Footprint
+               if err := footRows.Scan(&footprint.FootprintType, 
pq.Array(&footprint.FootprintValue), &footprint.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+               val, ok := footprintMap[footprint.CapabilityId]
+               if !ok {
+                       footprintMap[footprint.CapabilityId] = 
[]Footprint{footprint}
+               } else {
+                       val = append(val, footprint)
+                       footprintMap[footprint.CapabilityId] = val
+               }
+       }
+
+       return footprintMap, nil
+}
+
+func getTotalLimitsMap(tx *sql.Tx) (map[int][]TotalLimitsQueryResponse, error) 
{
+       tlRows, err := tx.Query(totalLimitsQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying total limits: %w", err)
+       }
+
+       defer log.Close(tlRows, "closing total capacity limits query")
+       totalLimitsMap := map[int][]TotalLimitsQueryResponse{}
+       for tlRows.Next() {
+               var totalLimit TotalLimitsQueryResponse
+               if err := tlRows.Scan(&totalLimit.LimitType, 
&totalLimit.MaximumHard, &totalLimit.MaximumSoft, &totalLimit.TelemetryId, 
&totalLimit.TelemetryMetic, &totalLimit.Id, &totalLimit.Type, &totalLimit.Name, 
&totalLimit.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+
+               val, ok := totalLimitsMap[totalLimit.CapabilityId]
+               if !ok {
+                       totalLimitsMap[totalLimit.CapabilityId] = 
[]TotalLimitsQueryResponse{totalLimit}
+               } else {
+                       val = append(val, totalLimit)
+                       totalLimitsMap[totalLimit.CapabilityId] = val
+               }
+       }
+
+       return totalLimitsMap, nil
+}
+
+func getHostLimitsMap(tx *sql.Tx) (map[int][]HostLimitsResponse, error) {
+       hlRows, err := tx.Query(hostLimitsQuery)
+       if err != nil {
+               return nil, fmt.Errorf("querying host limits: %w", err)
+       }
+
+       defer log.Close(hlRows, "closing host capacity limits query")
+       hostLimitsMap := map[int][]HostLimitsResponse{}
+       for hlRows.Next() {
+               var hostLimit HostLimitsResponse
+               if err := hlRows.Scan(&hostLimit.LimitType, 
&hostLimit.MaximumHard, &hostLimit.MaximumSoft, &hostLimit.TelemetryId, 
&hostLimit.TelemetryMetic, &hostLimit.Id, &hostLimit.Type, &hostLimit.Name, 
&hostLimit.Host, &hostLimit.CapabilityId); err != nil {
+                       return nil, fmt.Errorf("scanning db rows: %w", err)
+               }
+               val, ok := hostLimitsMap[hostLimit.CapabilityId]
+               if !ok {
+                       hostLimitsMap[hostLimit.CapabilityId] = 
[]HostLimitsResponse{hostLimit}
+               } else {
+                       val = append(val, hostLimit)
+                       hostLimitsMap[hostLimit.CapabilityId] = val
+               }
+       }
+
+       return hostLimitsMap, nil
+}
+
+func getTelemetriesMap(tx *sql.Tx) (map[int][]Telemetry, error) {
+       rows, err := tx.Query(`SELECT id, type, capability_id FROM 
cdni_telemetry`)
+       if err != nil {
+               return nil, errors.New("querying cdni telemetry: " + 
err.Error())
+       }
+       defer log.Close(rows, "closing telemetry query")
+
+       telemetryMap := map[int][]Telemetry{}
+       for rows.Next() {
+               telemetry := Telemetry{}
+               if err := rows.Scan(&telemetry.Id, &telemetry.Type, 
&telemetry.CapabilityId); err != nil {
+                       return nil, errors.New("scanning telemetry: " + 
err.Error())
+               }
+               val, ok := telemetryMap[telemetry.CapabilityId]
+               if !ok {
+                       telemetryMap[telemetry.CapabilityId] = 
[]Telemetry{telemetry}
+               } else {
+                       val = append(val, telemetry)
+                       telemetryMap[telemetry.CapabilityId] = val
+               }
+       }
+
+       return telemetryMap, nil
+}
+
+func getTelemetryMetricsMap(tx *sql.Tx) (map[string][]Metric, error) {
+       tmRows, err := tx.Query(`SELECT name, time_granularity, 
data_percentile, latency, telemetry_id FROM cdni_telemetry_metrics`)
+       if err != nil {
+               return nil, errors.New("querying cdni telemetry metrics: " + 
err.Error())
+       }
+       defer log.Close(tmRows, "closing telemetry metrics query")
+
+       telemetryMetricMap := map[string][]Metric{}
+       for tmRows.Next() {
+               metric := Metric{}
+               if err := tmRows.Scan(&metric.Name, &metric.TimeGranularity, 
&metric.DataPercentile, &metric.Latency, &metric.TelemetryId); err != nil {
+                       return nil, errors.New("scanning telemetry metric: " + 
err.Error())
+               }
+               val, ok := telemetryMetricMap[metric.TelemetryId]
+               if !ok {
+                       telemetryMetricMap[metric.TelemetryId] = 
[]Metric{metric}
+               } else {
+                       val = append(val, metric)
+                       telemetryMetricMap[metric.TelemetryId] = val
+               }

Review comment:
       I believe my prior comment applies here as well




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to