You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by sr...@apache.org on 2022/05/04 22:09:34 UTC

[trafficcontrol] branch master updated: CDNi Capacity Update With Scope (#6774)

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

srijeet0406 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 bfae577128 CDNi Capacity Update With Scope (#6774)
bfae577128 is described below

commit bfae5771285ba9dd5fdd3deede439174ec3c07df
Author: mattjackson220 <33...@users.noreply.github.com>
AuthorDate: Wed May 4 16:09:28 2022 -0600

    CDNi Capacity Update With Scope (#6774)
    
    * CDNi Capacity Update With Scope
    
    * added public schema explicitly
    
    * updates per comments
---
 docs/source/api/v4/oc_fci_advertisement.rst        |  44 +++++----
 ...412200_cdni_capacity_limit_with_scopes.down.sql |  51 +++++++++++
 ...10412200_cdni_capacity_limit_with_scopes.up.sql |  78 ++++++++++++++++
 traffic_ops/traffic_ops_golang/cdni/capacity.go    | 101 +++++++--------------
 traffic_ops/traffic_ops_golang/cdni/shared.go      |  92 +++++++------------
 5 files changed, 222 insertions(+), 144 deletions(-)

diff --git a/docs/source/api/v4/oc_fci_advertisement.rst b/docs/source/api/v4/oc_fci_advertisement.rst
index f58e3396bb..beb3bbc81f 100644
--- a/docs/source/api/v4/oc_fci_advertisement.rst
+++ b/docs/source/api/v4/oc_fci_advertisement.rst
@@ -64,32 +64,33 @@ Response Structure
 				"capability-type": "FCI.CapacityLimits",
 				"capability-value": [
 					{
-						"total-limits": [
+						"limits": [
 							{
+								"id": "host_limit_requests_requests",
+								"scope": {
+									"type": "testScope",
+									"value": [
+										"test.com"
+									]
+								},
+								"limit-type": "requests",
+								"maximum-hard": 20,
+								"maximum-soft": 15,
+								"telemetry-source": {
+									"id": "request_metrics",
+									"metric": "requests"
+								}
+							},
+							{
+								"id": "total_limit_egress_capacity",
 								"limit-type": "egress",
-								"maximum-hard": 5000,
-								"maximum-soft": 2500,
+								"maximum-hard": 202020,
+								"maximum-soft": 500,
 								"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"
-										}
-									}
-								]
-							}
 						]
 					}
 				],
@@ -116,7 +117,10 @@ Response Structure
 									"data-percentile": 50,
 									"latency": 0
 								}
-							]
+							],
+							"configuration": {
+								"url": "example.com/telemetry1"
+							}
 						}
 					]
 				},
diff --git a/traffic_ops/app/db/migrations/2022050410412200_cdni_capacity_limit_with_scopes.down.sql b/traffic_ops/app/db/migrations/2022050410412200_cdni_capacity_limit_with_scopes.down.sql
new file mode 100644
index 0000000000..9c7577b42a
--- /dev/null
+++ b/traffic_ops/app/db/migrations/2022050410412200_cdni_capacity_limit_with_scopes.down.sql
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+/* Downgrading this will lose some data because the specification has changed.
+ * Previously, only total and host limits were set. Now with the scope field, it is not as limited so downgrading
+ * cannot put the data back in the old tables since it wont necessarily fit into those buckets.
+*/
+CREATE TABLE IF NOT EXISTS public.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 public.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
+);
+
+DROP TABLE IF EXISTS public.cdni_limits;
+
+ALTER TABLE public.cdni_telemetry DROP COLUMN IF EXISTS configuration_url;
diff --git a/traffic_ops/app/db/migrations/2022050410412200_cdni_capacity_limit_with_scopes.up.sql b/traffic_ops/app/db/migrations/2022050410412200_cdni_capacity_limit_with_scopes.up.sql
new file mode 100644
index 0000000000..17ac9f2a9a
--- /dev/null
+++ b/traffic_ops/app/db/migrations/2022050410412200_cdni_capacity_limit_with_scopes.up.sql
@@ -0,0 +1,78 @@
+/*
+ * 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 public.cdni_limits (
+                                id bigserial NOT NULL,
+                                limit_id text NOT NULL,
+                                scope_type text,
+                                scope_value text[],
+                                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_limits PRIMARY KEY (id),
+    CONSTRAINT fk_cdni_limits_telemetry FOREIGN KEY (telemetry_id) REFERENCES cdni_telemetry(id) ON UPDATE CASCADE ON DELETE CASCADE,
+    CONSTRAINT fk_cdni_limits_capabilities FOREIGN KEY (capability_id) REFERENCES cdni_capabilities(id) ON UPDATE CASCADE ON DELETE CASCADE
+);
+
+INSERT INTO public.cdni_limits (limit_id,
+                         scope_type,
+                         scope_value,
+                         limit_type,
+                         maximum_hard,
+                         maximum_soft,
+                         telemetry_id,
+                         telemetry_metric,
+                         capability_id)
+SELECT CONCAT('host_limit_', chl.limit_type, '_', chl.telemetry_metric),
+       'published-host',
+       ARRAY[chl.host],
+       chl.limit_type,
+       chl.maximum_hard,
+       chl.maximum_soft,
+       chl.telemetry_id,
+       chl.telemetry_metric,
+       chl.capability_id
+FROM public.cdni_host_limits as chl;
+
+INSERT INTO public.cdni_limits (limit_id,
+                         scope_type,
+                         scope_value,
+                         limit_type,
+                         maximum_hard,
+                         maximum_soft,
+                         telemetry_id,
+                         telemetry_metric,
+                         capability_id)
+SELECT CONCAT('total_limit_', thl.limit_type, '_', thl.telemetry_metric),
+       NULL,
+       NULL,
+       thl.limit_type,
+       thl.maximum_hard,
+       thl.maximum_soft,
+       thl.telemetry_id,
+       thl.telemetry_metric,
+       thl.capability_id
+FROM public.cdni_total_limits as thl;
+
+DROP TABLE IF EXISTS public.cdni_total_limits;
+DROP TABLE IF EXISTS public.cdni_host_limits;
+
+ALTER TABLE public.cdni_telemetry ADD COLUMN configuration_url text DEFAULT '';
diff --git a/traffic_ops/traffic_ops_golang/cdni/capacity.go b/traffic_ops/traffic_ops_golang/cdni/capacity.go
index 17f6cf0090..3afa6f62f6 100644
--- a/traffic_ops/traffic_ops_golang/cdni/capacity.go
+++ b/traffic_ops/traffic_ops_golang/cdni/capacity.go
@@ -46,12 +46,7 @@ func getCapacities(inf *api.APIInfo, ucdn string) (Capabilities, error) {
 		return Capabilities{}, err
 	}
 
-	totalLimitsMap, err := getTotalLimitsMap(inf.Tx.Tx)
-	if err != nil {
-		return Capabilities{}, err
-	}
-
-	hostLimitsMap, err := getHostLimitsMap(inf.Tx.Tx)
+	limitsMap, err := getLimitsMap(inf.Tx.Tx)
 	if err != nil {
 		return Capabilities{}, err
 	}
@@ -64,65 +59,33 @@ func getCapacities(inf *api.APIInfo, ucdn string) (Capabilities, error) {
 		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{}
+		limits := limitsMap[cap.Id]
+		if limits == nil {
+			limits = []LimitsQueryResponse{}
 		}
 
-		returnedTotalLimits := []Limit{}
-		for _, tl := range totalLimits {
+		returnedLimits := []Limit{}
+		for _, l := range limits {
 			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,
+				Id:          l.LimitId,
+				LimitType:   CapacityLimitType(l.LimitType),
+				MaximumHard: l.MaximumHard,
+				MaximumSoft: l.MaximumSoft,
 				TelemetrySource: TelemetrySource{
-					Id:     hl.TelemetryId,
-					Metric: hl.TelemetryMetic,
+					Id:     l.TelemetryId,
+					Metric: l.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
-			}
-		}
+			returnedTotalLimit.Scope = l.Scope
 
-		for h, l := range hostToLimitMap {
-			returnedHostLimit := HostLimit{
-				Host:   h,
-				Limits: l,
-			}
-			returnedHostLimits = append(returnedHostLimits, returnedHostLimit)
+			returnedLimits = append(returnedLimits, returnedTotalLimit)
 		}
 
 		fciCap.CapabilityType = FciCapacityLimits
 		fciCap.CapabilityValue = []CapacityCapabilityValue{
 			{
-				TotalLimits: returnedTotalLimits,
-				HostLimits:  returnedHostLimits,
+				Limits: returnedLimits,
 			},
 		}
 
@@ -132,25 +95,31 @@ func getCapacities(inf *api.APIInfo, ucdn string) (Capabilities, error) {
 	return fciCaps, nil
 }
 
+// CapabilityQueryResponse contains data about the capability query.
 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:"-"`
+// LimitsQueryResponse contains information about the limits query.
+type LimitsQueryResponse struct {
+	Scope          *LimitScope `json:"scope,omitempty"`
+	LimitId        string      `json:"limitId" db:"limit_id"`
+	LimitType      string      `json:"limitType" 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
+
+// LimitScope contains information for a specific limit.
+type LimitScope struct {
+	ScopeType  *string  `json:"type" db:"scope_type"`
+	ScopeValue []string `json:"value" db:"scope_value"`
 }
diff --git a/traffic_ops/traffic_ops_golang/cdni/shared.go b/traffic_ops/traffic_ops_golang/cdni/shared.go
index 305d202576..22dbe883b6 100644
--- a/traffic_ops/traffic_ops_golang/cdni/shared.go
+++ b/traffic_ops/traffic_ops_golang/cdni/shared.go
@@ -43,27 +43,19 @@ const (
 	CapabilityQuery   = `SELECT id, type, ucdn FROM cdni_capabilities WHERE type = $1 AND ucdn = $2`
 	AllFootprintQuery = `SELECT footprint_type, footprint_value::text[], capability_id FROM cdni_footprints`
 
-	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 
+	limitsQuery = `
+SELECT limit_id, scope_type, scope_value, limit_type, maximum_hard, maximum_soft, cl.telemetry_id, cl.telemetry_metric, t.id, t.type, tm.name, cl.capability_id 
+FROM cdni_limits AS cl 
 LEFT JOIN cdni_telemetry as t ON telemetry_id = t.id 
 LEFT JOIN cdni_telemetry_metrics as tm ON telemetry_metric = tm.name`
 
-	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`
-
 	InsertCapabilityUpdateQuery     = `INSERT INTO cdni_capability_updates (ucdn, data, async_status_id, request_type, host) VALUES ($1, $2, $3, $4, $5)`
 	SelectCapabilityUpdateQuery     = `SELECT ucdn, data, async_status_id, request_type, host FROM cdni_capability_updates WHERE id = $1`
 	SelectAllCapabilityUpdatesQuery = `SELECT id, ucdn, data, request_type, host FROM cdni_capability_updates`
 
-	DeleteCapabilityUpdateQuery                    = `DELETE FROM cdni_capability_updates WHERE id = $1`
-	UpdateTotalLimitsByCapabilityAndLimitTypeQuery = `UPDATE cdni_total_limits SET maximum_hard = $1 WHERE capability_id = $2 AND limit_type = $3`
-	UpdateHostLimitsByCapabilityAndLimitTypeQuery  = `UPDATE cdni_host_limits SET maximum_hard = $1 WHERE capability_id = $2 AND limit_type = $3 AND host = $4`
-	hostQuery                                      = `SELECT count(*) FROM cdni_host_limits WHERE host = $1`
+	DeleteCapabilityUpdateQuery               = `DELETE FROM cdni_capability_updates WHERE id = $1`
+	UpdateLimitsByCapabilityAndLimitTypeQuery = `UPDATE cdni_limits SET maximum_hard = $1 WHERE capability_id = $2 AND limit_type = $3`
+	hostQuery                                 = `SELECT count(*) FROM cdni_limits WHERE $1 = ANY(scope_value)`
 
 	hostConfigLabel = "hostConfigUpdate"
 )
@@ -424,10 +416,10 @@ func PutConfigurationResponse(w http.ResponseWriter, r *http.Request) {
 					return
 				}
 
-				query := UpdateTotalLimitsByCapabilityAndLimitTypeQuery
+				query := UpdateLimitsByCapabilityAndLimitTypeQuery
 				queryParams := []interface{}{capLim.LimitValue, capId, capLim.LimitType}
 				if host != "" {
-					query = UpdateHostLimitsByCapabilityAndLimitTypeQuery
+					query = query + " AND $4 = ANY(scope_value)"
 					queryParams = []interface{}{capLim.LimitValue, capId, capLim.LimitType, host}
 				}
 
@@ -603,48 +595,32 @@ func getFootprintMap(tx *sql.Tx) (map[int][]Footprint, error) {
 	return footprintMap, nil
 }
 
-func getTotalLimitsMap(tx *sql.Tx) (map[int][]TotalLimitsQueryResponse, error) {
-	tlRows, err := tx.Query(totalLimitsQuery)
+func getLimitsMap(tx *sql.Tx) (map[int][]LimitsQueryResponse, error) {
+	rows, err := tx.Query(limitsQuery)
 	if err != nil {
-		return nil, fmt.Errorf("querying total limits: %w", err)
+		return nil, fmt.Errorf("querying 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 {
+	defer log.Close(rows, "closing capacity limits query")
+	limitsMap := map[int][]LimitsQueryResponse{}
+	for rows.Next() {
+		var limit LimitsQueryResponse
+		var scope LimitScope
+		if err := rows.Scan(&limit.LimitId, &scope.ScopeType, pq.Array(&scope.ScopeValue), &limit.LimitType, &limit.MaximumHard, &limit.MaximumSoft, &limit.TelemetryId, &limit.TelemetryMetic, &limit.Id, &limit.Type, &limit.Name, &limit.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)
+		if scope.ScopeType != nil {
+			limit.Scope = &scope
 		}
 
-		hostLimitsMap[hostLimit.CapabilityId] = append(hostLimitsMap[hostLimit.CapabilityId], hostLimit)
+		limitsMap[limit.CapabilityId] = append(limitsMap[limit.CapabilityId], limit)
 	}
 
-	return hostLimitsMap, nil
+	return limitsMap, nil
 }
 
 func getTelemetriesMap(tx *sql.Tx) (map[int][]Telemetry, error) {
-	rows, err := tx.Query(`SELECT id, type, capability_id FROM cdni_telemetry`)
+	rows, err := tx.Query(`SELECT id, type, capability_id, configuration_url FROM cdni_telemetry`)
 	if err != nil {
 		return nil, errors.New("querying cdni telemetry: " + err.Error())
 	}
@@ -653,7 +629,7 @@ func getTelemetriesMap(tx *sql.Tx) (map[int][]Telemetry, error) {
 	telemetryMap := map[int][]Telemetry{}
 	for rows.Next() {
 		telemetry := Telemetry{}
-		if err := rows.Scan(&telemetry.Id, &telemetry.Type, &telemetry.CapabilityId); err != nil {
+		if err := rows.Scan(&telemetry.Id, &telemetry.Type, &telemetry.CapabilityId, &telemetry.Configuration.Url); err != nil {
 			return nil, errors.New("scanning telemetry: " + err.Error())
 		}
 
@@ -697,18 +673,13 @@ type Capability struct {
 
 // CapacityCapabilityValue contains the total and host capability limits.
 type CapacityCapabilityValue struct {
-	TotalLimits []Limit     `json:"total-limits"`
-	HostLimits  []HostLimit `json:"host-limits"`
-}
-
-// HostLimit contains the capacity limit information for a specific host.
-type HostLimit struct {
-	Host   string  `json:"host"`
 	Limits []Limit `json:"limits"`
 }
 
 // Limit contains the information for a capacity limit.
 type Limit struct {
+	Id              string            `json:"id"`
+	Scope           *LimitScope       `json:"scope,omitempty"`
 	LimitType       CapacityLimitType `json:"limit-type"`
 	MaximumHard     int64             `json:"maximum-hard"`
 	MaximumSoft     int64             `json:"maximum-soft"`
@@ -728,10 +699,15 @@ type TelemetryCapabilityValue struct {
 
 // Telemetry contains the information for a telemetry metric.
 type Telemetry struct {
-	Id           string              `json:"id"`
-	Type         TelemetrySourceType `json:"type"`
-	CapabilityId int                 `json:"-"`
-	Metrics      []Metric            `json:"metrics"`
+	Id            string                 `json:"id"`
+	Type          TelemetrySourceType    `json:"type"`
+	CapabilityId  int                    `json:"-"`
+	Metrics       []Metric               `json:"metrics"`
+	Configuration TelemetryConfiguration `json:"configuration"`
+}
+
+type TelemetryConfiguration struct {
+	Url string `json:"url"`
 }
 
 // Metric contains the metric information for a telemetry metric.