You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ra...@apache.org on 2022/02/10 23:13:05 UTC

[trafficcontrol] branch master updated: Added CDNi Capacity and Telemetry (#6524)

This is an automated email from the ASF dual-hosted git repository.

rawlin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new d417c69  Added CDNi Capacity and Telemetry (#6524)
d417c69 is described below

commit d417c69e3aa4d9d7637b6dbcc7cc8132427952ba
Author: mattjackson220 <33...@users.noreply.github.com>
AuthorDate: Thu Feb 10 16:12:42 2022 -0700

    Added CDNi Capacity and Telemetry (#6524)
    
    * Added CDNi Capacity and Telemetry
    
    * updated changelog
    
    * added cdni doc to toc
    
    * added config check
    
    * updated per comments
    
    * updated per comments
    
    * got the one i missed
---
 CHANGELOG.md                                       |   3 +
 docs/source/admin/cdni.rst                         |  36 +++
 docs/source/admin/index.rst                        |   1 +
 docs/source/admin/traffic_ops.rst                  |   7 +
 docs/source/api/v4/oc_fci_advertisement.rst        | 132 ++++++++
 traffic_ops/app/conf/cdn.conf                      |   4 +
 ...011112591400_added_cdni_capacity_table.down.sql |  23 ++
 ...22011112591400_added_cdni_capacity_table.up.sql |  82 +++++
 traffic_ops/traffic_ops_golang/cdni/capacity.go    | 156 ++++++++++
 traffic_ops/traffic_ops_golang/cdni/shared.go      | 334 +++++++++++++++++++++
 traffic_ops/traffic_ops_golang/cdni/telemetry.go   |  94 ++++++
 traffic_ops/traffic_ops_golang/config/config.go    |   6 +
 traffic_ops/traffic_ops_golang/routing/routes.go   |   4 +
 13 files changed, 882 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1dd790a..0d5c3ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 
 ## [unreleased]
+### Added
+- Added a new Traffic Ops endpoint to `GET` capacity and telemetry data for CDNi integration.
+
 ### Fixed
 - Update traffic_portal dependencies to mitigate `npm audit` issues.
 - Fixed a cdn-in-a-box build issue when using `RHEL_VERSION=7`
diff --git a/docs/source/admin/cdni.rst b/docs/source/admin/cdni.rst
new file mode 100644
index 0000000..306ae47
--- /dev/null
+++ b/docs/source/admin/cdni.rst
@@ -0,0 +1,36 @@
+..
+..
+.. Licensed 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.
+..
+
+.. _cdni_admin:
+
+****************************
+CDNi Administration
+****************************
+
+:abbr:`CDNi (Content Delivery Network Interconnect)` specifications define the standards for interoperability within the :abbr:`CDN (Content Delivery Network)` and open caching ecosystems set forth by the :abbr:`IETF (Internet Engineering Task Force)`. This integration utilizes the :abbr:`APIs (Application Programming Interfaces)` defined by the :abbr:`SVA (Streaming Video Alliance)`.
+
+.. seealso:: For complete details on CDNi and the related API specifications see :rfc:`8006`, :rfc:`8007`, :rfc:`8008`, and the :abbr:`SVA (Streaming Video Alliance)` documents titled `Footprint and Capabilities Interface: Open Caching API`, `Open Caching API Implementation Guidelines`, `Configuration Interface: Part 1 Specification - Overview & Architecture`, `Configuration Interface: Part 2 Specification – CDNi Metadata Model Extensions`, and `Configuration Interface: Part 3 Specificat [...]
+
+In short, these documents describe the :abbr:`CDNi (Content Delivery Network Interconnect)` metadata interface that enables interconnected :abbr:`CDNs (Content Delivery Networks)` to exchange content distribution metadata to enable content acquisition and delivery. These define the interfaces through which a :abbr:`uCDN (Upstream Content Delivery Network)` and a :abbr:`dCDN (Downstream Content Delivery Network)` can communicate configuration and capacity information.
+
+For our use case, it is assumed that :abbr:`ATC (Apache Traffic Control)` is the :abbr:`dCDN (Downstream Content Delivery Network)`.
+
+	..  Note:: This is currently under construction and will be for a while. This document will be updated as new features are supported.
+
+/OC/FCI/advertisement
+=====================
+.. seealso:: :ref:`to-api-oc-fci-advertisement`
+
+The advertisement response is unique for the :abbr:`uCDN (Upstream Content Delivery Network)` and contains the complete footprint and capabilities information structure the :abbr:`dCDN (Downstream Content Delivery Network)` wants to expose. This endpoint will return an array of generic :abbr:`FCI (Footprint and Capabilities Advertisement Interface)` base objects, including type, value, and footprint for each. Currently supported base object types are `FCI.Capacitiy` and `FCI.Telemetry` b [...]
diff --git a/docs/source/admin/index.rst b/docs/source/admin/index.rst
index a039b1b..bc9fde5 100644
--- a/docs/source/admin/index.rst
+++ b/docs/source/admin/index.rst
@@ -52,3 +52,4 @@ Once everything is installed, you will need to configure the servers to talk to
 	t3c/index.rst
 	traffic_vault.rst
 	quick_howto/index.rst
+	cdni.rst
diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst
index 9fe862c..c95ad83 100644
--- a/docs/source/admin/traffic_ops.rst
+++ b/docs/source/admin/traffic_ops.rst
@@ -494,6 +494,13 @@ This file deals with the configuration parameters of running Traffic Ops itself.
 
 	.. versionadded:: 6.1
 
+:cdni: This is an optional section of configurations for :abbr:`CDNi (Content Delivery Network Interconnect)` operations.
+
+	.. versionadded:: 6.2
+
+	:dcdn_id: A string representing this :abbr:`CDN (Content Delivery Network)` to be used in the :abbr:`JWT (JSON Web Token)` and subsequently in :abbr:`CDNi (Content Delivery Network Interconnect)` operations.
+	:jwt_decoding_secret: A string used to decode the :abbr:`JWT (JSON Web Token)` to get information for :abbr:`CDNi (Content Delivery Network Interconnect)` operations.
+
 
 Example cdn.conf
 ''''''''''''''''
diff --git a/docs/source/api/v4/oc_fci_advertisement.rst b/docs/source/api/v4/oc_fci_advertisement.rst
new file mode 100644
index 0000000..319c7a9
--- /dev/null
+++ b/docs/source/api/v4/oc_fci_advertisement.rst
@@ -0,0 +1,132 @@
+..
+..
+.. Licensed 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.
+..
+
+.. _to-api-oc-fci-advertisement:
+
+************************
+``OC/FCI/advertisement``
+************************
+
+``GET``
+=======
+Returns the complete footprint and capabilities information structure the :abbr:`dCDN (Downstream Content Delivery Network)` wants to expose to a given :abbr:`uCDN (Upstream Content Delivery Network)`.
+
+:Auth. Required: No
+:Roles Required: "admin" or "operations"
+:Permissions Required: CDNI:READ
+:Response Type:  Array
+:Headers Required: "Authorization"
+
+Request Structure
+-----------------
+.. table:: Request Required Headers
+
+	+-----------------+------------------------------------------------------------------------------------------------------------------------------+
+	|    Name         | Description                                                                                                                  |
+	+=================+==============================================================================================================================+
+	|  Authorization  | A :abbr:`JWT (JSON Web Token)` provided by the :abbr:`dCDN (Downstream Content Delivery Network)` to identify the            |
+	|                 | :abbr:`uCDN (Upstream Content Delivery Network)`. This token must include the following claims:                              |
+	|                 |                                                                                                                              |
+	|                 | - ``iss`` Issuer claim as a string key for the :abbr:`uCDN (Upstream Content Delivery Network)`                              |
+	|                 | - ``aud`` Audience claim as a string key for the :abbr:`dCDN (Downstream Content Delivery Network)`                          |
+	|                 | - ``exp`` Expiration claim as the expiration date as a Unix epoch timestamp (in seconds)                                     |
+	+-----------------+------------------------------------------------------------------------------------------------------------------------------+
+
+Response Structure
+------------------
+:capabilities:     An array of generic :abbr:`FCI (Footprint and Capabilities Advertisement Interface)` base objects.
+:capability-type:  A string of the type of base object.
+:capability-value: An array of the value for the base object.
+:footprints:       An array of footprints impacted by this generic base object.
+
+	.. note:: These are meant to be generic and therefore there is not much information in these documents. For further information please see :rfc:`8006`, :rfc:`8007`, :rfc:`8008`, and the :abbr:`SVA (Streaming Video Alliance)` documents titled `Footprint and Capabilities Interface: Open Caching API`, `Open Caching API Implementation Guidelines`, `Configuration Interface: Part 1 Specification - Overview & Architecture`, `Configuration Interface: Part 2 Specification – CDNi Metadata Model E [...]
+
+.. code-block:: json
+	:caption: Example /OC/FCI/advertisement Response
+
+	{
+		"capabilities": [
+			{
+				"capability-type": "FCI.CapacityLimits",
+				"capability-value": [
+					{
+						"total-limits": [
+							{
+								"limit-type": "egress",
+								"maximum-hard": 5000,
+								"maximum-soft": 2500,
+								"telemetry-source": {
+									"id": "capacity_metrics",
+									"metric": "capacity"
+								}
+							}
+						],
+						"host-limits": [
+							{
+								"host": "example.com",
+								"limits": [
+									{
+										"limit-type": "requests",
+										"maximum-hard": 100,
+										"maximum-soft": 50,
+										"telemetry-source": {
+											"id": "request_metrics",
+											"metric": "requests"
+										}
+									}
+								]
+							}
+						]
+					}
+				],
+				"footprints": [
+					{
+						"footprint-type": "countrycode",
+						"footprint-value": [
+							"us"
+						]
+					}
+				]
+			},
+			{
+				"capability-type": "FCI.Telemetry",
+				"capability-value": {
+					"sources": [
+						{
+							"id": "capacity_metrics",
+							"type": "generic",
+							"metrics": [
+								{
+									"name": "capacity",
+									"time-granularity": 0,
+									"data-percentile": 50,
+									"latency": 0
+								}
+							]
+						}
+					]
+				},
+				"footprints": [
+					{
+						"footprint-type": "countrycode",
+						"footprint-value": [
+							"us"
+						]
+					}
+				]
+			}
+		]
+	}
+
diff --git a/traffic_ops/app/conf/cdn.conf b/traffic_ops/app/conf/cdn.conf
index 6a00e20..08fc3e3 100644
--- a/traffic_ops/app/conf/cdn.conf
+++ b/traffic_ops/app/conf/cdn.conf
@@ -98,5 +98,9 @@
         "organization" : "",
         "country" : "",
         "state" : ""
+    },
+    "cdni" : {
+        "dcdn_id" : "",
+        "jwt_decoding_secret" : ""
     }
 }
diff --git a/traffic_ops/app/db/migrations/2022011112591400_added_cdni_capacity_table.down.sql b/traffic_ops/app/db/migrations/2022011112591400_added_cdni_capacity_table.down.sql
new file mode 100644
index 0000000..5d3837e
--- /dev/null
+++ b/traffic_ops/app/db/migrations/2022011112591400_added_cdni_capacity_table.down.sql
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+DROP TABLE IF EXISTS cdni_total_limits;
+DROP TABLE IF EXISTS cdni_host_limits;
+DROP TABLE IF EXISTS cdni_telemetry_metrics;
+DROP TABLE IF EXISTS cdni_telemetry;
+DROP TABLE IF EXISTS cdni_footprints;
+DROP TABLE IF EXISTS cdni_capabilities;
diff --git a/traffic_ops/app/db/migrations/2022011112591400_added_cdni_capacity_table.up.sql b/traffic_ops/app/db/migrations/2022011112591400_added_cdni_capacity_table.up.sql
new file mode 100644
index 0000000..7c542d0
--- /dev/null
+++ b/traffic_ops/app/db/migrations/2022011112591400_added_cdni_capacity_table.up.sql
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+CREATE TABLE IF NOT EXISTS cdni_capabilities (
+                                                 id bigserial NOT NULL,
+                                                 type text NOT NULL,
+                                                 ucdn text NOT NULL,
+                                                 last_updated timestamp with time zone DEFAULT now() NOT NULL,
+    CONSTRAINT pk_cdni_capabilities PRIMARY KEY (id)
+);
+
+CREATE TABLE IF NOT EXISTS cdni_footprints (
+                                               id bigserial NOT NULL,
+                                               footprint_type text NOT NULL,
+                                               footprint_value text[] NOT NULL,
+                                               ucdn text NOT NULL,
+                                               capability_id bigint NOT NULL,
+                                               last_updated timestamp with time zone DEFAULT now() NOT NULL,
+    CONSTRAINT pk_cdni_footprints PRIMARY KEY (id),
+    CONSTRAINT fk_cdni_footprint_capabilities FOREIGN KEY (capability_id) REFERENCES cdni_capabilities(id) ON UPDATE CASCADE ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS cdni_telemetry (
+                                        id text NOT NULL,
+                                        type text NOT NULL,
+                                        capability_id bigint NOT NULL,
+                                        last_updated timestamp with time zone DEFAULT now() NOT NULL,
+    CONSTRAINT pk_cdni_telemetry PRIMARY KEY (id),
+    CONSTRAINT fk_cdni_telemetry_capabilities FOREIGN KEY (capability_id) REFERENCES cdni_capabilities(id) ON UPDATE CASCADE ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS cdni_telemetry_metrics (
+                                        name text NOT NULL,
+                                        time_granularity bigint NOT NULL,
+                                        data_percentile bigint NOT NULL,
+                                        latency int NOT NULL,
+                                        telemetry_id text NOT NULL,
+                                        last_updated timestamp with time zone DEFAULT now() NOT NULL,
+    CONSTRAINT pk_cdni_telemetry_metrics PRIMARY KEY (name),
+    CONSTRAINT fk_cdni_telemetry_metrics_telemetry FOREIGN KEY (telemetry_id) REFERENCES cdni_telemetry(id) ON UPDATE CASCADE ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS cdni_total_limits (
+                                        limit_type text NOT NULL,
+                                        maximum_hard bigint NOT NULL,
+                                        maximum_soft bigint NOT NULL,
+                                        telemetry_id text NOT NULL,
+                                        telemetry_metric text NOT NULL,
+                                        capability_id bigint NOT NULL,
+                                        last_updated timestamp with time zone DEFAULT now() NOT NULL,
+    CONSTRAINT pk_cdni_total_limits PRIMARY KEY (capability_id, telemetry_id),
+    CONSTRAINT fk_cdni_total_limits_telemetry FOREIGN KEY (telemetry_id) REFERENCES cdni_telemetry(id) ON UPDATE CASCADE ON DELETE CASCADE,
+    CONSTRAINT fk_cdni_total_limits_capabilities FOREIGN KEY (capability_id) REFERENCES cdni_capabilities(id) ON UPDATE CASCADE ON DELETE CASCADE
+);
+
+CREATE TABLE IF NOT EXISTS cdni_host_limits (
+                                        limit_type text NOT NULL,
+                                        maximum_hard bigint NOT NULL,
+                                        maximum_soft bigint NOT NULL,
+                                        telemetry_id text NOT NULL,
+                                        telemetry_metric text NOT NULL,
+                                        capability_id bigint NOT NULL,
+                                        host text NOT NULL,
+                                        last_updated timestamp with time zone DEFAULT now() NOT NULL,
+    CONSTRAINT pk_cdni_host_limits PRIMARY KEY (capability_id, telemetry_id, host),
+    CONSTRAINT fk_cdni_host_limits_telemetry FOREIGN KEY (telemetry_id) REFERENCES cdni_telemetry(id) ON UPDATE CASCADE ON DELETE CASCADE,
+    CONSTRAINT fk_cdni_total_limits_capabilities FOREIGN KEY (capability_id) REFERENCES cdni_capabilities(id) ON UPDATE CASCADE ON DELETE CASCADE
+);
diff --git a/traffic_ops/traffic_ops_golang/cdni/capacity.go b/traffic_ops/traffic_ops_golang/cdni/capacity.go
new file mode 100644
index 0000000..17f6cf0
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/cdni/capacity.go
@@ -0,0 +1,156 @@
+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/apache/trafficcontrol/lib/go-log"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+)
+
+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 log.Close(capRows, "closing capabilities query")
+	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)
+	}
+
+	footprintMap, err := getFootprintMap(inf.Tx.Tx)
+	if err != nil {
+		return Capabilities{}, err
+	}
+
+	totalLimitsMap, err := getTotalLimitsMap(inf.Tx.Tx)
+	if err != nil {
+		return Capabilities{}, err
+	}
+
+	hostLimitsMap, err := getHostLimitsMap(inf.Tx.Tx)
+	if err != nil {
+		return Capabilities{}, err
+	}
+
+	fciCaps := Capabilities{}
+
+	for _, cap := range capabilities {
+		fciCap := Capability{}
+		fciCap.Footprints = footprintMap[cap.Id]
+		if fciCap.Footprints == nil {
+			fciCap.Footprints = []Footprint{}
+		}
+		totalLimits := totalLimitsMap[cap.Id]
+		if totalLimits == nil {
+			totalLimits = []TotalLimitsQueryResponse{}
+		}
+		hostLimits := hostLimitsMap[cap.Id]
+		if hostLimits == nil {
+			hostLimits = []HostLimitsResponse{}
+		}
+
+		returnedTotalLimits := []Limit{}
+		for _, tl := range totalLimits {
+			returnedTotalLimit := Limit{
+				LimitType:   CapacityLimitType(tl.LimitType),
+				MaximumHard: tl.MaximumHard,
+				MaximumSoft: tl.MaximumSoft,
+				TelemetrySource: TelemetrySource{
+					Id:     tl.TelemetryId,
+					Metric: tl.TelemetryMetic,
+				},
+			}
+			returnedTotalLimits = append(returnedTotalLimits, returnedTotalLimit)
+		}
+
+		returnedHostLimits := []HostLimit{}
+		hostToLimitMap := map[string][]Limit{}
+		for _, hl := range hostLimits {
+			limit := Limit{
+				LimitType:   CapacityLimitType(hl.LimitType),
+				MaximumHard: hl.MaximumHard,
+				MaximumSoft: hl.MaximumSoft,
+				TelemetrySource: TelemetrySource{
+					Id:     hl.TelemetryId,
+					Metric: hl.TelemetryMetic,
+				},
+			}
+
+			if val, ok := hostToLimitMap[hl.Host]; ok {
+				val = append(val, limit)
+				hostToLimitMap[hl.Host] = val
+			} else {
+				hlList := []Limit{}
+				hlList = append(hlList, limit)
+				hostToLimitMap[hl.Host] = hlList
+			}
+		}
+
+		for h, l := range hostToLimitMap {
+			returnedHostLimit := HostLimit{
+				Host:   h,
+				Limits: l,
+			}
+			returnedHostLimits = append(returnedHostLimits, returnedHostLimit)
+		}
+
+		fciCap.CapabilityType = FciCapacityLimits
+		fciCap.CapabilityValue = []CapacityCapabilityValue{
+			{
+				TotalLimits: returnedTotalLimits,
+				HostLimits:  returnedHostLimits,
+			},
+		}
+
+		fciCaps.Capabilities = append(fciCaps.Capabilities, fciCap)
+	}
+
+	return fciCaps, nil
+}
+
+type CapabilityQueryResponse struct {
+	Id   int    `json:"id" db:"id"`
+	Type string `json:"type" db:"type"`
+	UCdn string `json:"ucdn" db:"ucdn"`
+}
+
+type TotalLimitsQueryResponse struct {
+	LimitType      string `json:"limit_type" db:"limit_type"`
+	MaximumHard    int64  `json:"maximum_hard" db:"maximum_hard"`
+	MaximumSoft    int64  `json:"maximum_soft" db:"maximum_soft"`
+	TelemetryId    string `json:"telemetry_id" db:"telemetry_id"`
+	TelemetryMetic string `json:"telemetry_metric" db:"telemetry_metric"`
+	UCdn           string `json:"ucdn" db:"ucdn"`
+	Id             string `json:"id" db:"id"`
+	Type           string `json:"type" db:"type"`
+	Name           string `json:"name" db:"name"`
+	CapabilityId   int    `json:"-"`
+}
+type HostLimitsResponse struct {
+	Host string `json:"host" db:"host"`
+	TotalLimitsQueryResponse
+}
diff --git a/traffic_ops/traffic_ops_golang/cdni/shared.go b/traffic_ops/traffic_ops_golang/cdni/shared.go
new file mode 100644
index 0000000..83dee5f
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/cdni/shared.go
@@ -0,0 +1,334 @@
+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)
+		}
+
+		footprintMap[footprint.CapabilityId] = append(footprintMap[footprint.CapabilityId], footprint)
+	}
+
+	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)
+		}
+
+		totalLimitsMap[totalLimit.CapabilityId] = append(totalLimitsMap[totalLimit.CapabilityId], totalLimit)
+	}
+
+	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)
+		}
+
+		hostLimitsMap[hostLimit.CapabilityId] = append(hostLimitsMap[hostLimit.CapabilityId], hostLimit)
+	}
+
+	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())
+		}
+
+		telemetryMap[telemetry.CapabilityId] = append(telemetryMap[telemetry.CapabilityId], telemetry)
+	}
+
+	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())
+		}
+
+		telemetryMetricMap[metric.TelemetryId] = append(telemetryMetricMap[metric.TelemetryId], metric)
+	}
+
+	return telemetryMetricMap, nil
+}
+
+type Capabilities struct {
+	Capabilities []Capability `json:"capabilities"`
+}
+
+type Capability struct {
+	CapabilityType  SupportedCapabilities `json:"capability-type"`
+	CapabilityValue interface{}           `json:"capability-value"`
+	Footprints      []Footprint           `json:"footprints"`
+}
+
+type CapacityCapabilityValue struct {
+	TotalLimits []Limit     `json:"total-limits"`
+	HostLimits  []HostLimit `json:"host-limits"`
+}
+
+type HostLimit struct {
+	Host   string  `json:"host"`
+	Limits []Limit `json:"limits"`
+}
+
+type Limit struct {
+	LimitType       CapacityLimitType `json:"limit-type"`
+	MaximumHard     int64             `json:"maximum-hard"`
+	MaximumSoft     int64             `json:"maximum-soft"`
+	TelemetrySource TelemetrySource   `json:"telemetry-source"`
+}
+
+type TelemetrySource struct {
+	Id     string `json:"id"`
+	Metric string `json:"metric"`
+}
+
+type TelemetryCapabilityValue struct {
+	Sources []Telemetry `json:"sources"`
+}
+
+type Telemetry struct {
+	Id           string              `json:"id"`
+	Type         TelemetrySourceType `json:"type"`
+	CapabilityId int                 `json:"-"`
+	Metrics      []Metric            `json:"metrics"`
+}
+
+type Metric struct {
+	Name            string `json:"name"`
+	TimeGranularity int    `json:"time-granularity"`
+	DataPercentile  int    `json:"data-percentile"`
+	Latency         int    `json:"latency"`
+	TelemetryId     string `json:"-"`
+}
+
+type Footprint struct {
+	FootprintType  FootprintType `json:"footprint-type" db:"footprint_type"`
+	FootprintValue []string      `json:"footprint-value" db:"footprint_value"`
+	CapabilityId   int           `json:"-"`
+}
+
+type CapacityLimitType string
+
+const (
+	Egress         CapacityLimitType = "egress"
+	Requests                         = "requests"
+	StorageSize                      = "storage-size"
+	StorageObjects                   = "storage-objects"
+	Sessions                         = "sessions"
+	CacheSize                        = "cache-size"
+)
+
+type SupportedCapabilities string
+
+const (
+	FciTelemetry      SupportedCapabilities = "FCI.Telemetry"
+	FciCapacityLimits                       = "FCI.CapacityLimits"
+)
+
+type TelemetrySourceType string
+
+const (
+	Generic TelemetrySourceType = "generic"
+)
+
+type FootprintType string
+
+const (
+	Ipv4Cidr    FootprintType = "ipv4cidr"
+	Ipv6Cidr                  = "ipv6cidr"
+	Asn                       = "asn"
+	CountryCode               = "countrycode"
+)
diff --git a/traffic_ops/traffic_ops_golang/cdni/telemetry.go b/traffic_ops/traffic_ops_golang/cdni/telemetry.go
new file mode 100644
index 0000000..6fd718c
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/cdni/telemetry.go
@@ -0,0 +1,94 @@
+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/apache/trafficcontrol/lib/go-log"
+	"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 log.Close(capRows, "closing capabilities query")
+	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)
+	}
+
+	footprintMap, err := getFootprintMap(inf.Tx.Tx)
+	if err != nil {
+		return Capabilities{}, err
+	}
+
+	telemetryMap, err := getTelemetriesMap(inf.Tx.Tx)
+	if err != nil {
+		return Capabilities{}, err
+	}
+
+	telemetryMetricMap, err := getTelemetryMetricsMap(inf.Tx.Tx)
+	if err != nil {
+		return Capabilities{}, err
+	}
+
+	fciCaps := Capabilities{}
+
+	for _, cap := range capabilities {
+		fciCap := Capability{}
+		fciCap.Footprints = footprintMap[cap.Id]
+		if fciCap.Footprints == nil {
+			fciCap.Footprints = []Footprint{}
+		}
+		returnList := []Telemetry{}
+		telemetryList := telemetryMap[cap.Id]
+		if telemetryList == nil {
+			telemetryList = []Telemetry{}
+		}
+
+		for _, t := range telemetryList {
+			telemetryMetricsList := telemetryMetricMap[t.Id]
+			if telemetryMetricsList == nil {
+				telemetryMetricsList = []Metric{}
+			}
+			t.Metrics = telemetryMetricsList
+			returnList = append(returnList, t)
+		}
+
+		telemetry := Capability{
+			CapabilityType: FciTelemetry,
+			CapabilityValue: TelemetryCapabilityValue{
+				Sources: returnList,
+			},
+			Footprints: fciCap.Footprints,
+		}
+
+		fciCaps.Capabilities = append(fciCaps.Capabilities, telemetry)
+	}
+
+	return fciCaps, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go
index 48914e8..3d7f108 100644
--- a/traffic_ops/traffic_ops_golang/config/config.go
+++ b/traffic_ops/traffic_ops_golang/config/config.go
@@ -62,6 +62,7 @@ type Config struct {
 	UseIMS                  bool                    `json:"use_ims"`
 	RoleBasedPermissions    bool                    `json:"role_based_permissions"`
 	DefaultCertificateInfo  *DefaultCertificateInfo `json:"default_certificate_info"`
+	Cdni                    *CdniConf               `json:"cdni"`
 }
 
 // ConfigHypnotoad carries http setting for hypnotoad (mojolicious) server
@@ -237,6 +238,11 @@ type ConfigInflux struct {
 	Secure      *bool  `json:"secure"`
 }
 
+type CdniConf struct {
+	DCdnId            string `json:"dcdn_id"`
+	JwtDecodingSecret string `json:"jwt_decoding_secret"`
+}
+
 // NewFakeConfig returns a fake Config struct with just enough data to view Routes.
 func NewFakeConfig() Config {
 	c := Config{}
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go
index 6a5e362..8252faa 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -44,6 +44,7 @@ import (
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/cdn"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/cdn_lock"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/cdnfederation"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/cdni"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/cdnnotification"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/coordinate"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/crconfig"
@@ -130,6 +131,9 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
 		 * 4.x API
 		 */
 
+		// CDNI integration
+		{Version: api.Version{Major: 4, Minor: 0}, Method: http.MethodGet, Path: `OC/FCI/advertisement/?$`, Handler: cdni.GetCapabilities, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDNI-CAPACITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 541357729077},
+
 		// SSL Keys
 		{Version: api.Version{Major: 4, Minor: 0}, Method: http.MethodGet, Path: `sslkey_expirations/?$`, Handler: deliveryservice.GetSSlKeyExpirationInformation, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"SSL-KEY-EXPIRATION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41357729075},