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



##########
File path: traffic_ops/app/conf/cdn.conf
##########
@@ -98,5 +98,9 @@
         "organization" : "",
         "country" : "",
         "state" : ""
+    },
+    "cdni" : {

Review comment:
       These new cdn.conf options could use some documentation

##########
File path: traffic_ops/traffic_ops_golang/routing/routes.go
##########
@@ -130,6 +131,9 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
                 * 4.x API
                 */
 
+               // CDNI integration
+               {api.Version{Major: 4, Minor: 0}, http.MethodGet, 
`OC/FCI/advertisement/?$`, cdni.GetCapabilities, auth.PrivLevelReadOnly, 
[]string{"CDNI-CAPACITY:READ"}, Authenticated, nil, 541357729077},

Review comment:
       Is `/OC/FCI/advertisement` the required path per the spec? Does it 
matter that the route is prefixed with `/api/4.0`?
   
   Also, I think all the routes were updated recently to include field names, 
so it would be good to rebase and match the new convention.

##########
File path: traffic_ops/traffic_ops_golang/cdni/capacity.go
##########
@@ -0,0 +1,177 @@
+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 (
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"

Review comment:
       nit: I think these imports (and in the other added go files) are in the 
wrong order -- generally they should be stdlib, internal, external

##########
File path: traffic_ops/traffic_ops_golang/cdni/capacity.go
##########
@@ -0,0 +1,177 @@
+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 (
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const totalLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
ctl.telemetry_id, ctl.telemetry_metric, t.id, t.type, tm.name 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 WHERE 
ctl.capability_id = $1`
+const hostLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
chl.telemetry_id, chl.telemetry_metric, t.id, t.type, tm.name, host 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 WHERE 
chl.capability_id = $1 ORDER BY host DESC`
+
+func GetCapacities(inf *api.APIInfo, ucdn string) (Capabilities, error) {
+       capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciCapacityLimits, 
ucdn)
+       if err != nil {
+               return Capabilities{}, fmt.Errorf("querying capabilities: %w", 
err)
+       }
+       defer capRows.Close()

Review comment:
       nit: could use `log.Close`

##########
File path: traffic_ops/traffic_ops_golang/cdni/capacity.go
##########
@@ -0,0 +1,177 @@
+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 (
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const totalLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
ctl.telemetry_id, ctl.telemetry_metric, t.id, t.type, tm.name 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 WHERE 
ctl.capability_id = $1`
+const hostLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
chl.telemetry_id, chl.telemetry_metric, t.id, t.type, tm.name, host 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 WHERE 
chl.capability_id = $1 ORDER BY host DESC`

Review comment:
       nit: these could be easier to read if they were formatted across more 
lines

##########
File path: traffic_ops/traffic_ops_golang/cdni/capacity.go
##########
@@ -0,0 +1,177 @@
+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 (
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const totalLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
ctl.telemetry_id, ctl.telemetry_metric, t.id, t.type, tm.name 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 WHERE 
ctl.capability_id = $1`
+const hostLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
chl.telemetry_id, chl.telemetry_metric, t.id, t.type, tm.name, host 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 WHERE 
chl.capability_id = $1 ORDER BY host DESC`
+
+func GetCapacities(inf *api.APIInfo, ucdn string) (Capabilities, error) {
+       capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciCapacityLimits, 
ucdn)
+       if err != nil {
+               return Capabilities{}, fmt.Errorf("querying capabilities: %w", 
err)
+       }
+       defer capRows.Close()
+       capabilities := []CapabilityQueryResponse{}
+       for capRows.Next() {
+               var capability CapabilityQueryResponse
+               if err := capRows.Scan(&capability.Id, &capability.Type, 
&capability.UCdn); err != nil {
+                       return Capabilities{}, fmt.Errorf("scanning db rows: 
%w", err)
+               }
+               capabilities = append(capabilities, capability)
+       }
+
+       fciCaps := Capabilities{}
+
+       for _, cap := range capabilities {
+               fciCap := Capability{}
+               footRows, err := inf.Tx.Tx.Query(FootprintQuery, cap.Id)
+               if err != nil {
+                       return Capabilities{}, fmt.Errorf("querying footprints: 
%w", err)
+               }
+               defer footRows.Close()
+               footprints := []Footprint{}
+               for footRows.Next() {
+                       var footprint Footprint
+                       if err := footRows.Scan(&footprint.FootprintType, 
pq.Array(&footprint.FootprintValue)); err != nil {
+                               return Capabilities{}, fmt.Errorf("scanning db 
rows: %w", err)
+                       }
+                       footprints = append(footprints, footprint)
+               }
+
+               fciCap.Footprints = footprints
+
+               tlRows, err := inf.Tx.Tx.Query(totalLimitsQuery, cap.Id)
+               if err != nil {
+                       return Capabilities{}, fmt.Errorf("querying total 
limits: %w", err)
+               }
+
+               defer tlRows.Close()

Review comment:
       nit: could use `log.Close`

##########
File path: traffic_ops/traffic_ops_golang/cdni/capacity.go
##########
@@ -0,0 +1,177 @@
+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 (
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const totalLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
ctl.telemetry_id, ctl.telemetry_metric, t.id, t.type, tm.name 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 WHERE 
ctl.capability_id = $1`
+const hostLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
chl.telemetry_id, chl.telemetry_metric, t.id, t.type, tm.name, host 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 WHERE 
chl.capability_id = $1 ORDER BY host DESC`
+
+func GetCapacities(inf *api.APIInfo, ucdn string) (Capabilities, error) {
+       capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciCapacityLimits, 
ucdn)
+       if err != nil {
+               return Capabilities{}, fmt.Errorf("querying capabilities: %w", 
err)
+       }
+       defer capRows.Close()
+       capabilities := []CapabilityQueryResponse{}
+       for capRows.Next() {
+               var capability CapabilityQueryResponse
+               if err := capRows.Scan(&capability.Id, &capability.Type, 
&capability.UCdn); err != nil {
+                       return Capabilities{}, fmt.Errorf("scanning db rows: 
%w", err)
+               }
+               capabilities = append(capabilities, capability)
+       }
+
+       fciCaps := Capabilities{}
+
+       for _, cap := range capabilities {
+               fciCap := Capability{}
+               footRows, err := inf.Tx.Tx.Query(FootprintQuery, cap.Id)

Review comment:
       Rather than running multiple DB queries (here, L68, and L83) in a loop, 
is it possible to come up with a single query here (or a constant number of 
queries) that returns all the data we need?

##########
File path: traffic_ops/traffic_ops_golang/cdni/shared.go
##########
@@ -0,0 +1,199 @@
+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 (
+       "errors"
+       "net/http"
+       "time"
+
+       "github.com/dgrijalva/jwt-go"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const CapabilityQuery = `SELECT id, type, ucdn FROM cdni_capabilities WHERE 
type = $1 AND ucdn = $2`
+const FootprintQuery = `SELECT footprint_type, footprint_value::text[] FROM 
cdni_footprints WHERE capability_id = $1`
+
+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 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, 
errors.New("parsing claims"), 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":
+                       ucdn = val.(string)
+               case "aud":
+                       dcdn = val.(string)
+               case "exp":
+                       expirationFloat = val.(float64)

Review comment:
       Should we use non-panicking type assertions here instead? Are these 
being parsed from trusted input?
   
   Also, it's not clear what `"iss"`, `"aud"`, and `"exp"` are short for -- do 
they have constants defined in the `jwt` library?

##########
File path: traffic_ops/traffic_ops_golang/cdni/capacity.go
##########
@@ -0,0 +1,177 @@
+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 (
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const totalLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
ctl.telemetry_id, ctl.telemetry_metric, t.id, t.type, tm.name 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 WHERE 
ctl.capability_id = $1`
+const hostLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
chl.telemetry_id, chl.telemetry_metric, t.id, t.type, tm.name, host 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 WHERE 
chl.capability_id = $1 ORDER BY host DESC`
+
+func GetCapacities(inf *api.APIInfo, ucdn string) (Capabilities, error) {

Review comment:
       nit: this function appears to only be used within the `cdni` package -- 
can it be private instead?

##########
File path: traffic_ops/traffic_ops_golang/cdni/telemetry.go
##########
@@ -0,0 +1,112 @@
+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 (
+       "errors"
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+func GetTelemetries(inf *api.APIInfo, ucdn string) (Capabilities, error) {

Review comment:
       nit: this function appears to only be used within the `cdni` package -- 
can it be private instead?

##########
File path: traffic_ops/traffic_ops_golang/cdni/telemetry.go
##########
@@ -0,0 +1,112 @@
+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 (
+       "errors"
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+func GetTelemetries(inf *api.APIInfo, ucdn string) (Capabilities, error) {
+       capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciTelemetry, ucdn)
+       if err != nil {
+               return Capabilities{}, fmt.Errorf("querying capabilities: %w", 
err)
+       }
+       defer capRows.Close()
+       capabilities := []CapabilityQueryResponse{}
+       for capRows.Next() {
+               var capability CapabilityQueryResponse
+               if err := capRows.Scan(&capability.Id, &capability.Type, 
&capability.UCdn); err != nil {
+                       return Capabilities{}, fmt.Errorf("scanning db rows: 
%w", err)
+               }
+               capabilities = append(capabilities, capability)
+       }
+
+       fciCaps := Capabilities{}
+
+       for _, cap := range capabilities {
+               fciCap := Capability{}
+               footRows, err := inf.Tx.Tx.Query(FootprintQuery, cap.Id)
+               if err != nil {
+                       return Capabilities{}, fmt.Errorf("querying footprints: 
%w", err)
+               }
+               defer footRows.Close()

Review comment:
       nit: could use `log.Close`

##########
File path: traffic_ops/traffic_ops_golang/cdni/telemetry.go
##########
@@ -0,0 +1,112 @@
+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 (
+       "errors"
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+func GetTelemetries(inf *api.APIInfo, ucdn string) (Capabilities, error) {
+       capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciTelemetry, ucdn)
+       if err != nil {
+               return Capabilities{}, fmt.Errorf("querying capabilities: %w", 
err)
+       }
+       defer capRows.Close()
+       capabilities := []CapabilityQueryResponse{}
+       for capRows.Next() {
+               var capability CapabilityQueryResponse
+               if err := capRows.Scan(&capability.Id, &capability.Type, 
&capability.UCdn); err != nil {
+                       return Capabilities{}, fmt.Errorf("scanning db rows: 
%w", err)
+               }
+               capabilities = append(capabilities, capability)
+       }
+
+       fciCaps := Capabilities{}
+
+       for _, cap := range capabilities {
+               fciCap := Capability{}
+               footRows, err := inf.Tx.Tx.Query(FootprintQuery, cap.Id)
+               if err != nil {
+                       return Capabilities{}, fmt.Errorf("querying footprints: 
%w", err)
+               }
+               defer footRows.Close()
+               footprints := []Footprint{}
+               for footRows.Next() {
+                       var footprint Footprint
+                       if err := footRows.Scan(&footprint.FootprintType, 
pq.Array(&footprint.FootprintValue)); err != nil {
+                               return Capabilities{}, fmt.Errorf("scanning db 
rows: %w", err)
+                       }
+                       footprints = append(footprints, footprint)
+               }
+
+               fciCap.Footprints = footprints
+
+               rows, err := inf.Tx.Tx.Query(`SELECT id, type FROM 
cdni_telemetry WHERE capability_id = $1`, cap.Id)
+               if err != nil {
+                       return Capabilities{}, errors.New("querying cdni 
telemetry: " + err.Error())
+               }
+               defer rows.Close()
+               returnList := []Telemetry{}
+               telemetryList := []Telemetry{}
+               for rows.Next() {
+                       telemetry := Telemetry{}
+                       if err := rows.Scan(&telemetry.Id, &telemetry.Type); 
err != nil {
+                               return Capabilities{}, errors.New("scanning 
telemetry: " + err.Error())
+                       }
+                       telemetryList = append(telemetryList, telemetry)
+               }
+
+               for _, t := range telemetryList {
+                       tmRows, err := inf.Tx.Tx.Query(`SELECT name, 
time_granularity, data_percentile, latency FROM cdni_telemetry_metrics WHERE 
telemetry_id = $1`, t.Id)
+                       if err != nil {
+                               return Capabilities{}, errors.New("querying 
cdni telemetry metrics: " + err.Error())
+                       }
+                       defer tmRows.Close()

Review comment:
       nit: could use `log.Close`

##########
File path: traffic_ops/traffic_ops_golang/cdni/telemetry.go
##########
@@ -0,0 +1,112 @@
+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 (
+       "errors"
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+func GetTelemetries(inf *api.APIInfo, ucdn string) (Capabilities, error) {
+       capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciTelemetry, ucdn)
+       if err != nil {
+               return Capabilities{}, fmt.Errorf("querying capabilities: %w", 
err)
+       }
+       defer capRows.Close()
+       capabilities := []CapabilityQueryResponse{}
+       for capRows.Next() {
+               var capability CapabilityQueryResponse
+               if err := capRows.Scan(&capability.Id, &capability.Type, 
&capability.UCdn); err != nil {
+                       return Capabilities{}, fmt.Errorf("scanning db rows: 
%w", err)
+               }
+               capabilities = append(capabilities, capability)
+       }
+
+       fciCaps := Capabilities{}
+
+       for _, cap := range capabilities {
+               fciCap := Capability{}
+               footRows, err := inf.Tx.Tx.Query(FootprintQuery, cap.Id)
+               if err != nil {
+                       return Capabilities{}, fmt.Errorf("querying footprints: 
%w", err)
+               }
+               defer footRows.Close()
+               footprints := []Footprint{}
+               for footRows.Next() {
+                       var footprint Footprint
+                       if err := footRows.Scan(&footprint.FootprintType, 
pq.Array(&footprint.FootprintValue)); err != nil {
+                               return Capabilities{}, fmt.Errorf("scanning db 
rows: %w", err)
+                       }
+                       footprints = append(footprints, footprint)
+               }
+
+               fciCap.Footprints = footprints
+
+               rows, err := inf.Tx.Tx.Query(`SELECT id, type FROM 
cdni_telemetry WHERE capability_id = $1`, cap.Id)
+               if err != nil {
+                       return Capabilities{}, errors.New("querying cdni 
telemetry: " + err.Error())
+               }
+               defer rows.Close()

Review comment:
       nit: could use `log.Close`

##########
File path: traffic_ops/traffic_ops_golang/cdni/telemetry.go
##########
@@ -0,0 +1,112 @@
+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 (
+       "errors"
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+func GetTelemetries(inf *api.APIInfo, ucdn string) (Capabilities, error) {
+       capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciTelemetry, ucdn)
+       if err != nil {
+               return Capabilities{}, fmt.Errorf("querying capabilities: %w", 
err)
+       }
+       defer capRows.Close()
+       capabilities := []CapabilityQueryResponse{}
+       for capRows.Next() {
+               var capability CapabilityQueryResponse
+               if err := capRows.Scan(&capability.Id, &capability.Type, 
&capability.UCdn); err != nil {
+                       return Capabilities{}, fmt.Errorf("scanning db rows: 
%w", err)
+               }
+               capabilities = append(capabilities, capability)
+       }
+
+       fciCaps := Capabilities{}
+
+       for _, cap := range capabilities {
+               fciCap := Capability{}
+               footRows, err := inf.Tx.Tx.Query(FootprintQuery, cap.Id)

Review comment:
       Similar to a prior comment, is it possible to come up with a single 
query here (or a constant number of queries) that returns all the data we need 
instead of making a lot of small queries?

##########
File path: traffic_ops/traffic_ops_golang/cdni/shared.go
##########
@@ -0,0 +1,199 @@
+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 (
+       "errors"
+       "net/http"
+       "time"
+
+       "github.com/dgrijalva/jwt-go"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const CapabilityQuery = `SELECT id, type, ucdn FROM cdni_capabilities WHERE 
type = $1 AND ucdn = $2`
+const FootprintQuery = `SELECT footprint_type, footprint_value::text[] FROM 
cdni_footprints WHERE capability_id = $1`
+
+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 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, 
errors.New("parsing claims"), 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":
+                       ucdn = val.(string)
+               case "aud":
+                       dcdn = val.(string)
+               case "exp":
+                       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"), nil)
+               return
+       }
+       if ucdn == "" {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token"), 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 := []Capability{}

Review comment:
       nit: this could use `make([]Capability, 0, len(capacities.Capabilities) 
+ len(telemetries.Capabilities))` to reduce allocations

##########
File path: traffic_ops/traffic_ops_golang/cdni/capacity.go
##########
@@ -0,0 +1,177 @@
+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 (
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const totalLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
ctl.telemetry_id, ctl.telemetry_metric, t.id, t.type, tm.name 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 WHERE 
ctl.capability_id = $1`
+const hostLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
chl.telemetry_id, chl.telemetry_metric, t.id, t.type, tm.name, host 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 WHERE 
chl.capability_id = $1 ORDER BY host DESC`
+
+func GetCapacities(inf *api.APIInfo, ucdn string) (Capabilities, error) {
+       capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciCapacityLimits, 
ucdn)
+       if err != nil {
+               return Capabilities{}, fmt.Errorf("querying capabilities: %w", 
err)
+       }
+       defer capRows.Close()
+       capabilities := []CapabilityQueryResponse{}
+       for capRows.Next() {
+               var capability CapabilityQueryResponse
+               if err := capRows.Scan(&capability.Id, &capability.Type, 
&capability.UCdn); err != nil {
+                       return Capabilities{}, fmt.Errorf("scanning db rows: 
%w", err)
+               }
+               capabilities = append(capabilities, capability)
+       }
+
+       fciCaps := Capabilities{}
+
+       for _, cap := range capabilities {
+               fciCap := Capability{}
+               footRows, err := inf.Tx.Tx.Query(FootprintQuery, cap.Id)
+               if err != nil {
+                       return Capabilities{}, fmt.Errorf("querying footprints: 
%w", err)
+               }
+               defer footRows.Close()
+               footprints := []Footprint{}
+               for footRows.Next() {
+                       var footprint Footprint
+                       if err := footRows.Scan(&footprint.FootprintType, 
pq.Array(&footprint.FootprintValue)); err != nil {
+                               return Capabilities{}, fmt.Errorf("scanning db 
rows: %w", err)
+                       }
+                       footprints = append(footprints, footprint)
+               }
+
+               fciCap.Footprints = footprints
+
+               tlRows, err := inf.Tx.Tx.Query(totalLimitsQuery, cap.Id)
+               if err != nil {
+                       return Capabilities{}, fmt.Errorf("querying total 
limits: %w", err)
+               }
+
+               defer tlRows.Close()
+               totalLimits := []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); err != nil {
+                               return Capabilities{}, fmt.Errorf("scanning db 
rows: %w", err)
+                       }
+                       totalLimits = append(totalLimits, totalLimit)
+               }
+
+               hlRows, err := inf.Tx.Tx.Query(hostLimitsQuery, cap.Id)
+               if err != nil {
+                       return Capabilities{}, fmt.Errorf("querying host 
limits: %w", err)
+               }
+
+               defer hlRows.Close()

Review comment:
       nit: could use `log.Close`

##########
File path: traffic_ops/traffic_ops_golang/cdni/shared.go
##########
@@ -0,0 +1,199 @@
+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 (
+       "errors"
+       "net/http"
+       "time"
+
+       "github.com/dgrijalva/jwt-go"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const CapabilityQuery = `SELECT id, type, ucdn FROM cdni_capabilities WHERE 
type = $1 AND ucdn = $2`
+const FootprintQuery = `SELECT footprint_type, footprint_value::text[] FROM 
cdni_footprints WHERE capability_id = $1`
+
+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 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, 
errors.New("parsing claims"), 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":
+                       ucdn = val.(string)
+               case "aud":
+                       dcdn = val.(string)
+               case "exp":
+                       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"), nil)

Review comment:
       Do we want to hide the fact that the DCdnId didn't match from the user? 

##########
File path: traffic_ops/traffic_ops_golang/cdni/shared.go
##########
@@ -0,0 +1,199 @@
+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 (
+       "errors"
+       "net/http"
+       "time"
+
+       "github.com/dgrijalva/jwt-go"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const CapabilityQuery = `SELECT id, type, ucdn FROM cdni_capabilities WHERE 
type = $1 AND ucdn = $2`
+const FootprintQuery = `SELECT footprint_type, footprint_value::text[] FROM 
cdni_footprints WHERE capability_id = $1`
+
+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 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, 
errors.New("parsing claims"), 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":
+                       ucdn = val.(string)
+               case "aud":
+                       dcdn = val.(string)
+               case "exp":
+                       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"), nil)
+               return
+       }
+       if ucdn == "" {
+               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token"), nil)

Review comment:
       Do we want to hide the fact that the ucdn was empty from the user? 

##########
File path: traffic_ops/traffic_ops_golang/cdni/capacity.go
##########
@@ -0,0 +1,177 @@
+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 (
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const totalLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
ctl.telemetry_id, ctl.telemetry_metric, t.id, t.type, tm.name 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 WHERE 
ctl.capability_id = $1`
+const hostLimitsQuery = `SELECT limit_type, maximum_hard, maximum_soft, 
chl.telemetry_id, chl.telemetry_metric, t.id, t.type, tm.name, host 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 WHERE 
chl.capability_id = $1 ORDER BY host DESC`
+
+func GetCapacities(inf *api.APIInfo, ucdn string) (Capabilities, error) {
+       capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciCapacityLimits, 
ucdn)
+       if err != nil {
+               return Capabilities{}, fmt.Errorf("querying capabilities: %w", 
err)
+       }
+       defer capRows.Close()
+       capabilities := []CapabilityQueryResponse{}
+       for capRows.Next() {
+               var capability CapabilityQueryResponse
+               if err := capRows.Scan(&capability.Id, &capability.Type, 
&capability.UCdn); err != nil {
+                       return Capabilities{}, fmt.Errorf("scanning db rows: 
%w", err)
+               }
+               capabilities = append(capabilities, capability)
+       }
+
+       fciCaps := Capabilities{}
+
+       for _, cap := range capabilities {
+               fciCap := Capability{}
+               footRows, err := inf.Tx.Tx.Query(FootprintQuery, cap.Id)
+               if err != nil {
+                       return Capabilities{}, fmt.Errorf("querying footprints: 
%w", err)
+               }
+               defer footRows.Close()

Review comment:
       nit: could use `log.Close`

##########
File path: traffic_ops/traffic_ops_golang/cdni/shared.go
##########
@@ -0,0 +1,199 @@
+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 (
+       "errors"
+       "net/http"
+       "time"
+
+       "github.com/dgrijalva/jwt-go"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const CapabilityQuery = `SELECT id, type, ucdn FROM cdni_capabilities WHERE 
type = $1 AND ucdn = $2`
+const FootprintQuery = `SELECT footprint_type, footprint_value::text[] FROM 
cdni_footprints WHERE capability_id = $1`
+
+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")

Review comment:
       Since this header seems required, should we immediately return an error 
here if it's empty? Otherwise, I think it would return a probably uninformative 
error on L55.

##########
File path: traffic_ops/traffic_ops_golang/cdni/telemetry.go
##########
@@ -0,0 +1,112 @@
+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 (
+       "errors"
+       "fmt"
+
+       "github.com/lib/pq"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+func GetTelemetries(inf *api.APIInfo, ucdn string) (Capabilities, error) {
+       capRows, err := inf.Tx.Tx.Query(CapabilityQuery, FciTelemetry, ucdn)
+       if err != nil {
+               return Capabilities{}, fmt.Errorf("querying capabilities: %w", 
err)
+       }
+       defer capRows.Close()

Review comment:
       nit: could use `log.Close`

##########
File path: traffic_ops/traffic_ops_golang/cdni/shared.go
##########
@@ -0,0 +1,199 @@
+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 (
+       "errors"
+       "net/http"
+       "time"
+
+       "github.com/dgrijalva/jwt-go"
+
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+const CapabilityQuery = `SELECT id, type, ucdn FROM cdni_capabilities WHERE 
type = $1 AND ucdn = $2`
+const FootprintQuery = `SELECT footprint_type, footprint_value::text[] FROM 
cdni_footprints WHERE capability_id = $1`
+
+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 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, 
errors.New("parsing claims"), nil)

Review comment:
       Should this returned error include the error message from 
`jwt.ParseWithClaims`?




-- 
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