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 661cc1e  Added CDNi ability to negotiate higher capacities (#6557)
661cc1e is described below

commit 661cc1e9f2a2fc41b4d296ff016b71c6c7fa5f0b
Author: mattjackson220 <[email protected]>
AuthorDate: Mon Feb 14 16:33:58 2022 -0700

    Added CDNi ability to negotiate higher capacities (#6557)
    
    * Added CDNi ability to negotiate higher capacities
    
    * updated per comments
    
    * rewrote confusing code to find capability id from array of footprints
---
 CHANGELOG.md                                       |   1 +
 docs/source/admin/cdni.rst                         |  24 +
 docs/source/api/v4/oc_ci_configuration.rst         | 101 ++++
 docs/source/api/v4/oc_ci_configuration_host.rst    | 107 +++++
 .../v4/oc_ci_configuration_request_id_approved.rst |  60 +++
 .../traffic_router_golang/httpsrvr/httpsrvr.go     |   3 +-
 lib/go-rfc/http.go                                 |   1 +
 ...022020114365100_capacity_updates_queue.down.sql |  18 +
 .../2022020114365100_capacity_updates_queue.up.sql |  28 ++
 .../traffic_ops_golang/cdn/dnssecrefresh.go        |   3 +-
 traffic_ops/traffic_ops_golang/cdni/shared.go      | 512 +++++++++++++++++++--
 .../traffic_ops_golang/deliveryservice/acme.go     |   5 +-
 .../deliveryservice/autorenewcerts.go              |   3 +-
 traffic_ops/traffic_ops_golang/routing/routes.go   |   3 +
 14 files changed, 820 insertions(+), 49 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3634127..2e40b4f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ 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.
+- Added a Traffic Ops endpoints to `PUT` a requested configuration change for 
a full configuration or per host and an endpoint to approve or deny the request.
 
 ### Fixed
 - Update traffic_portal dependencies to mitigate `npm audit` issues.
diff --git a/docs/source/admin/cdni.rst b/docs/source/admin/cdni.rst
index 306ae47..b18db1e 100644
--- a/docs/source/admin/cdni.rst
+++ b/docs/source/admin/cdni.rst
@@ -34,3 +34,27 @@ For our use case, it is assumed that :abbr:`ATC (Apache 
Traffic Control)` is the
 .. 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 [...]
+
+/OC/CI/configuration
+====================
+.. seealso:: :ref:`to-api-oc-fci-configuration`
+
+An endpoint that is used to push (``PUT``), fetch (``GET``), or delete 
(``DELETE``) the entire metadata set for a given :abbr:`uCDN (Upstream Content 
Delivery Network)` from a :abbr:`JWT (JSON Web Token)`. This puts the requested 
change into a queue to be reviewed later and returns an endpoint to view the 
asynchronous status updates.
+
+.. Note:: This is under construction. Currently only ``PUT`` is supported and 
in a very limited sense.
+
+/OC/CI/configuration/{{host}}
+=============================
+.. seealso:: :ref:`to-api-oc-fci-configuration-host`
+
+An endpoint that is used to push (``PUT``), fetch (``GET``), or delete 
(``DELETE``) the metadata set that is attached to host name for a given 
:abbr:`uCDN (Upstream Content Delivery Network)` from a :abbr:`JWT (JSON Web 
Token)`. This puts the requested change into a queue to be reviewed later and 
returns an endpoint to view the asynchronous status updates.
+
+.. Note:: This is under construction. Currently only ``PUT`` is supported and 
in a very limited sense.
+
+/OC/CI/configuration/request/{{id}}/{{approved}}
+================================================
+.. seealso:: :ref:`to-api-oc-fci-configuration-request-id-approved`
+
+This endpoint allows a user to approve or deny a queued update request from 
the previous endpoints. A denial will result in the removal from the queue and 
a ``FAILED`` status update. An approval will result in the changes being made 
to the configuration and a ``SUCCEEDED`` status update.
+
+.. Note:: This is under construction and only supports very limited metadata 
field and limited configuration updates.
diff --git a/docs/source/api/v4/oc_ci_configuration.rst 
b/docs/source/api/v4/oc_ci_configuration.rst
new file mode 100644
index 0000000..25d9dfc
--- /dev/null
+++ b/docs/source/api/v4/oc_ci_configuration.rst
@@ -0,0 +1,101 @@
+..
+..
+.. 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-configuration:
+
+***********************
+``OC/CI/configuration``
+***********************
+
+``PUT``
+=======
+Triggers an asynchronous task to update the configuration for the :abbr:`uCDN 
(Upstream Content Delivery Network)` by adding the request to a queue to be 
reviewed later. This returns a 202 Accepted status and an endpoint to be used 
for status updates.
+
+:Auth. Required: Yes
+:Roles Required: "admin" or "operations"
+:Permissions Required: CDNI:UPDATE
+:Response Type:  Object
+: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)`    
                                                                         |
+       
+-----------------+------------------------------------------------------------------------------------------------------------------------------+
+
+:type: A string of the type of metadata to follow. See :rfc:`8006` for 
possible values. Only a selection of these are supported.
+:host: A string of the domain that the requested updates will change.
+:metadata: An array of generic metadata objects that conform to :rfc:`8006`.
+:generic-metadata-type: A string of the type of metadata to follow conforming 
to :rfc:`8006`.
+:generic-metadata-value: An array of generic metadata value objects conforming 
to :rfc:`8006` and :abbr:`SVA (Streaming Video Alliance)` specifications.
+
+.. code-block:: http
+       :caption: Example /OC/CI/configuration Request
+
+       PUT /api/4.0/oc/ci/configuration HTTP/1.1
+       Host: trafficops.infra.ciab.test
+       User-Agent: curl/7.47.0
+       Accept: */*
+       Cookie: mojolicious=...
+       Content-Length: 181
+       Content-Type: application/json
+
+       {
+               "type": "MI.HostMetadata",
+               "host": "example.com",
+               "metadata": [
+                       {
+                               "generic-metadata-type": 
"MI.RequestedCapacityLimits",
+                               "generic-metadata-value": {
+                                       "requested-limits": [
+                                               {
+                                                       "limit-type": "egress",
+                                                       "limit-value": 20000,
+                                                       "footprints": [
+                                                               {
+                                                                       
"footprint-type": "ipv4cidr",
+                                                                       
"footprint-value": [
+                                                                               
"127.0.0.1",
+                                                                               
"127.0.0.2"
+                                                                       ]
+                                                               }
+                                                       ]
+                                               }
+                                       ]
+                               }
+                       }
+               ]
+       }
+
+Response Structure
+------------------
+
+.. code-block:: http
+       :caption: Response Example
+
+       HTTP/1.1 202 Accepted
+       Content-Type: application/json
+
+       { "alerts": [
+               {
+                       "text": "CDNi configuration update request received. 
Status updates can be found here: /api/4.0/async_status/1",
+                       "level": "success"
+               }
+       ]}
diff --git a/docs/source/api/v4/oc_ci_configuration_host.rst 
b/docs/source/api/v4/oc_ci_configuration_host.rst
new file mode 100644
index 0000000..a44ae961
--- /dev/null
+++ b/docs/source/api/v4/oc_ci_configuration_host.rst
@@ -0,0 +1,107 @@
+..
+..
+.. 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-configuration-host:
+
+********************************
+``OC/CI/configuration/{{host}}``
+********************************
+
+``PUT``
+=======
+Triggers an asynchronous task to update the configuration for the :abbr:`uCDN 
(Upstream Content Delivery Network)` and the specified host by adding the 
request to a queue to be reviewed later. This returns a 202 Accepted status and 
an endpoint to be used for status updates.
+
+:Auth. Required: Yes
+:Roles Required: "admin" or "operations"
+:Permissions Required: CDNI:UPDATE
+:Response Type:  Object
+: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)`    
                                                                         |
+       
+-----------------+------------------------------------------------------------------------------------------------------------------------------+
+
+.. table:: Request Path Parameters
+
+       
+-------+-----------------------------------------------------------------------------------+
+       | Name  |                 Description                                   
                    |
+       
+=======+===================================================================================+
+       |  host | The text identifier for the host domain to be updated with 
the new configuration. |
+       
+-------+-----------------------------------------------------------------------------------+
+
+:type: A string of the type of metadata to follow. See :rfc:`8006` for 
possible values. Only a selection of these are supported.
+:host-metadata: An array of generic metadata objects that conform to 
:rfc:`8006`.
+:generic-metadata-type: A string of the type of metadata to follow conforming 
to :rfc:`8006`.
+:generic-metadata-value: An array of generic metadata value objects conforming 
to :rfc:`8006` and :abbr:`SVA (Streaming Video Alliance)` specifications.
+
+.. code-block:: http
+       :caption: Example /OC/CI/configuration Request
+
+       PUT /api/4.0/oc/ci/configuration/example.com HTTP/1.1
+       Host: trafficops.infra.ciab.test
+       User-Agent: curl/7.47.0
+       Accept: */*
+       Cookie: mojolicious=...
+       Content-Length: 181
+       Content-Type: application/json
+
+       {
+               "type": "MI.HostMetadata",
+               "host-metadata": [
+                       {
+                               "generic-metadata-type": 
"MI.RequestedCapacityLimits",
+                               "generic-metadata-value": {
+                                       "requested-limits": [
+                                               {
+                                                       "limit-type": "egress",
+                                                       "limit-value": 20000,
+                                                       "footprints": [
+                                                               {
+                                                                       
"footprint-type": "ipv4cidr",
+                                                                       
"footprint-value": [
+                                                                               
"127.0.0.1",
+                                                                               
"127.0.0.2"
+                                                                       ]
+                                                               }
+                                                       ]
+                                               }
+                                       ]
+                               }
+                       }
+               ]
+       }
+
+Response Structure
+------------------
+
+.. code-block:: http
+       :caption: Response Example
+
+       HTTP/1.1 202 Accepted
+       Content-Type: application/json
+
+       { "alerts": [
+               {
+                       "text": "CDNi configuration update request received. 
Status updates can be found here: /api/4.0/async_status/1",
+                       "level": "success"
+               }
+       ]}
diff --git a/docs/source/api/v4/oc_ci_configuration_request_id_approved.rst 
b/docs/source/api/v4/oc_ci_configuration_request_id_approved.rst
new file mode 100644
index 0000000..c709871
--- /dev/null
+++ b/docs/source/api/v4/oc_ci_configuration_request_id_approved.rst
@@ -0,0 +1,60 @@
+..
+..
+.. 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-configuration-request-id-approved:
+
+***************************************************
+``OC/CI/configuration/request/{{id}}/{{approved}}``
+***************************************************
+
+``PUT``
+=======
+Triggers an asynchronous task to update the configuration for the :abbr:`uCDN 
(Upstream Content Delivery Network)` and the specified host by adding the 
request to a queue to be reviewed later. This returns a 202 Accepted status and 
an endpoint to be used for status updates.
+
+:Auth. Required: Yes
+:Roles Required: "admin"
+:Permissions Required: CDNI-CAPACITY:ADMIN
+:Response Type:  Object
+
+Request Structure
+-----------------
+.. table:: Request Path Parameters
+
+       
+-----------+----------------------------------------------------------------------------------------+
+       | Name      |                 Description                               
                             |
+       
+===========+========================================================================================+
+       |  id       | The integral identifier for the configuration update 
request to be approved or denied. |
+       
+-----------+----------------------------------------------------------------------------------------+
+       |  approved | A boolean for whether to approve a configuration change 
request or not.                |
+       
+-----------+----------------------------------------------------------------------------------------+
+
+Response Structure
+------------------
+
+.. code-block:: http
+       :caption: Response Example For Approved Change
+
+       HTTP/1.1 200 OK
+       Content-Type: application/json
+
+       { "response": "Successfully updated configuration." }
+
+.. code-block:: http
+       :caption: Response Example For Denied Change
+
+       HTTP/1.1 200 OK
+       Content-Type: application/json
+
+       { "response": "Successfully denied configuration update request." }
diff --git a/experimental/traffic_router_golang/httpsrvr/httpsrvr.go 
b/experimental/traffic_router_golang/httpsrvr/httpsrvr.go
index a535211..785ce2d 100644
--- a/experimental/traffic_router_golang/httpsrvr/httpsrvr.go
+++ b/experimental/traffic_router_golang/httpsrvr/httpsrvr.go
@@ -34,6 +34,7 @@ import (
        
"github.com/apache/trafficcontrol/experimental/traffic_router_golang/nextcache"
 
        "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/lib/go-rfc"
        "github.com/apache/trafficcontrol/lib/go-tc"
 )
 
@@ -146,7 +147,7 @@ func getHandler(
                        newURL += "?" + r.URL.RawQuery
                }
 
-               w.Header().Add("Location", newURL)
+               w.Header().Add(rfc.Location, newURL)
                w.WriteHeader(http.StatusFound)
        }
 }
diff --git a/lib/go-rfc/http.go b/lib/go-rfc/http.go
index 2ae1065..08fa281 100644
--- a/lib/go-rfc/http.go
+++ b/lib/go-rfc/http.go
@@ -41,6 +41,7 @@ const (
        UserAgent          = "User-Agent"          // RFC7231§5.5.3
        Vary               = "Vary"                // RFC7231§7.1.4
        Age                = "Age"                 // RFC7234§5.1
+       Location           = "Location"            // RFC7231§7.1.2
 )
 
 // These are (some) valid values for content encoding and MIME types, for
diff --git 
a/traffic_ops/app/db/migrations/2022020114365100_capacity_updates_queue.down.sql
 
b/traffic_ops/app/db/migrations/2022020114365100_capacity_updates_queue.down.sql
new file mode 100644
index 0000000..381453a
--- /dev/null
+++ 
b/traffic_ops/app/db/migrations/2022020114365100_capacity_updates_queue.down.sql
@@ -0,0 +1,18 @@
+/*
+ * 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_capability_updates;
diff --git 
a/traffic_ops/app/db/migrations/2022020114365100_capacity_updates_queue.up.sql 
b/traffic_ops/app/db/migrations/2022020114365100_capacity_updates_queue.up.sql
new file mode 100644
index 0000000..7ea094f
--- /dev/null
+++ 
b/traffic_ops/app/db/migrations/2022020114365100_capacity_updates_queue.up.sql
@@ -0,0 +1,28 @@
+/*
+ * 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_capability_updates (
+                                                 id bigserial NOT NULL,
+                                                 request_type text NOT NULL,
+                                                 ucdn text NOT NULL,
+                                                 host text,
+                                                 data json NOT NULL,
+                                                 async_status_id bigint NOT 
NULL,
+                                                 last_updated timestamp with 
time zone DEFAULT now() NOT NULL,
+    CONSTRAINT pk_cdni_capability_updates PRIMARY KEY (id),
+    CONSTRAINT fk_cdni_capability_updates_async FOREIGN KEY (async_status_id) 
REFERENCES async_status(id) ON UPDATE CASCADE ON DELETE CASCADE
+);
diff --git a/traffic_ops/traffic_ops_golang/cdn/dnssecrefresh.go 
b/traffic_ops/traffic_ops_golang/cdn/dnssecrefresh.go
index 4db4e24..25c10ff 100644
--- a/traffic_ops/traffic_ops_golang/cdn/dnssecrefresh.go
+++ b/traffic_ops/traffic_ops_golang/cdn/dnssecrefresh.go
@@ -30,6 +30,7 @@ import (
        "time"
 
        "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/lib/go-rfc"
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-util"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
@@ -63,7 +64,7 @@ func RefreshDNSSECKeysV4(w http.ResponseWriter, r 
*http.Request) {
        if started {
                message = "Starting DNSSEC key refresh in the background. This 
may take a few minutes. Status updates can be found here: " + 
api.CurrentAsyncEndpoint + strconv.Itoa(asyncStatusID)
        }
-       w.Header().Add("Location", 
api.CurrentAsyncEndpoint+strconv.Itoa(asyncStatusID))
+       w.Header().Add(rfc.Location, 
api.CurrentAsyncEndpoint+strconv.Itoa(asyncStatusID))
        api.WriteAlerts(w, r, http.StatusAccepted, 
tc.CreateAlerts(tc.SuccessLevel, message))
 }
 
diff --git a/traffic_ops/traffic_ops_golang/cdni/shared.go 
b/traffic_ops/traffic_ops_golang/cdni/shared.go
index 83dee5f..bcf3161 100644
--- a/traffic_ops/traffic_ops_golang/cdni/shared.go
+++ b/traffic_ops/traffic_ops_golang/cdni/shared.go
@@ -21,34 +21,50 @@ package cdni
 
 import (
        "database/sql"
+       "encoding/json"
        "errors"
        "fmt"
-       "github.com/lib/pq"
        "net/http"
+       "strconv"
+       "strings"
        "time"
 
        "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/lib/go-rfc"
+       "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 
        "github.com/dgrijalva/jwt-go"
+       "github.com/lib/pq"
 )
 
-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 (
+       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`
 
-const totalLimitsQuery = `
+       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 = `
+       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`
+       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`
+
+       hostConfigLabel = "hostConfigUpdate"
+)
+
 func GetCapabilities(w http.ResponseWriter, r *http.Request) {
        inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
        if userErr != nil || sysErr != nil {
@@ -57,9 +73,50 @@ func GetCapabilities(w http.ResponseWriter, r *http.Request) 
{
        }
        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)
+       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
+       }
+
+       ucdn, err := checkBearerToken(r.Header.Get("Authorization"), inf)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, err)
+               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 PutHostConfiguration(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"host"}, nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       host := inf.Params["host"]
+       if errCode, userErr, sysErr := validateHostExists(host, inf.Tx.Tx); 
userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
                return
        }
 
@@ -68,17 +125,365 @@ func GetCapabilities(w http.ResponseWriter, r 
*http.Request) {
                return
        }
 
+       ucdn, err := checkBearerToken(r.Header.Get("Authorization"), inf)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, err)
+               return
+       }
+
+       var genericHostRequest GenericHostMetadata
+       err = json.NewDecoder(r.Body).Decode(&genericHostRequest)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("decoding host json request: %w", err))
+               return
+       }
+
+       db, err := api.GetDB(r.Context())
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("getting async db: %w", err))
+               return
+       }
+       asyncTx, err := db.Begin()
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("getting async tx: %w", err))
+               return
+       }
+       logTx, err := db.Begin()
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("getting log tx: %w", err))
+               return
+       }
+       defer logTx.Commit()
+
+       asyncStatusId, errCode, userErr, sysErr := 
api.InsertAsyncStatus(asyncTx, "CDNi host configuration update request 
received.")
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+
+       data := genericHostRequest.HostMetadata.Metadata
+
+       _, err = inf.Tx.Tx.Query(InsertCapabilityUpdateQuery, ucdn, data, 
asyncStatusId, hostConfigLabel, host)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("inserting capability update request into queue: %w", err))
+               return
+       }
+
+       msg := "CDNi configuration update request received. Status updates can 
be found here: " + api.CurrentAsyncEndpoint + strconv.Itoa(asyncStatusId)
+       api.CreateChangeLogRawTx(api.ApiChange, msg, inf.User, logTx)
+
+       var alerts tc.Alerts
+       alerts.AddAlert(tc.Alert{
+               Text:  msg,
+               Level: tc.SuccessLevel.String(),
+       })
+
+       w.Header().Add(rfc.Location, 
api.CurrentAsyncEndpoint+strconv.Itoa(asyncStatusId))
+       api.WriteAlerts(w, r, http.StatusAccepted, alerts)
+}
+
+func PutConfiguration(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()
+
+       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
+       }
+
+       ucdn, err := checkBearerToken(r.Header.Get("Authorization"), inf)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, err)
+               return
+       }
+
+       var genericRequest GenericRequestMetadata
+       err = json.NewDecoder(r.Body).Decode(&genericRequest)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("decoding json request: %w", err))
+               return
+       }
+
+       db, err := api.GetDB(r.Context())
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("getting async db: %w", err))
+               return
+       }
+       asyncTx, err := db.Begin()
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("getting async tx: %w", err))
+               return
+       }
+       logTx, err := db.Begin()
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("getting log tx: %w", err))
+               return
+       }
+       defer logTx.Commit()
+
+       asyncStatusId, errCode, userErr, sysErr := 
api.InsertAsyncStatus(asyncTx, "CDNi configuration update request received.")
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+
+       data := genericRequest.Metadata
+
+       _, err = inf.Tx.Tx.Query(InsertCapabilityUpdateQuery, ucdn, data, 
asyncStatusId, SupportedGenericMetadataType(genericRequest.Type), 
genericRequest.Host)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("inserting capability update request into queue: %w", err))
+               return
+       }
+
+       msg := "CDNi configuration update request received. Status updates can 
be found here: " + api.CurrentAsyncEndpoint + strconv.Itoa(asyncStatusId)
+       api.CreateChangeLogRawTx(api.ApiChange, msg, inf.User, logTx)
+       var alerts tc.Alerts
+       alerts.AddAlert(tc.Alert{
+               Text:  msg,
+               Level: tc.SuccessLevel.String(),
+       })
+
+       w.Header().Add(rfc.Location, 
api.CurrentAsyncEndpoint+strconv.Itoa(asyncStatusId))
+       api.WriteAlerts(w, r, http.StatusAccepted, alerts)
+}
+
+func PutConfigurationResponse(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"approved", 
"id"}, []string{"id"})
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       reqId := inf.IntParams["id"]
+       approvedString := inf.Params["approved"]
+       approved, err := strconv.ParseBool(approvedString)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("approved parameter must be a boolean"), nil)
+               return
+       }
+
+       db, err := api.GetDB(r.Context())
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("getting async db: %w", err))
+               return
+       }
+
+       logTx, err := db.Begin()
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("getting log tx: %w", err))
+               return
+       }
+       defer logTx.Commit()
+
+       rows, err := inf.Tx.Tx.Query(SelectCapabilityUpdateQuery, reqId)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("querying for capability update request: %w", err))
+               return
+       }
+       defer log.Close(rows, "closing capabilities update query")
+       var ucdn string
+       var data json.RawMessage
+       var host string
+       var asyncId int
+       var requestType string
+       count := 0
+       for rows.Next() {
+               if err := rows.Scan(&ucdn, &data, &asyncId, &requestType, 
&host); err != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("scanning db rows: %w", err))
+                       return
+               }
+               count++
+       }
+       if count == 0 {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, 
fmt.Errorf("no configuration request for that id"), nil)
+               return
+       }
+
+       if !approved {
+               if asyncErr := api.UpdateAsyncStatus(db, api.AsyncFailed, 
"Requested configuration update has been denied.", asyncId, true); asyncErr != 
nil {
+                       log.Errorf("updating async status for id %d: %s", 
asyncId, asyncErr.Error())
+               }
+               status, err := deleteCapabilityRequest(reqId, inf.Tx.Tx)
+               if err != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, status, nil, 
fmt.Errorf("deleting configuration request from queue: %w", err))
+                       return
+               }
+               msg := "Successfully denied configuration update request."
+               api.CreateChangeLogRawTx(api.ApiChange, msg, inf.User, 
inf.Tx.Tx)
+               api.WriteResp(w, r, msg)
+               return
+       }
+
+       var updatedDataList []GenericMetadata
+       if err = json.Unmarshal(data, &updatedDataList); err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, fmt.Errorf("unmarshalling data for configuration update: %w", err))
+               return
+       }
+
+       var unsupportedTypes []string
+       for _, updatedData := range updatedDataList {
+               if !updatedData.Type.isValid() {
+                       unsupportedTypes = append(unsupportedTypes, 
string(updatedData.Type))
+               }
+       }
+
+       if len(unsupportedTypes) != 0 {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
fmt.Errorf("unsupported generic metadata types found: %v", 
strings.Join(unsupportedTypes, ", ")), nil)
+               return
+       }
+
+       for _, updatedData := range updatedDataList {
+               switch updatedData.Type {
+               case MiRequestedCapacityLimits:
+                       var capacityRequestedLimits CapacityRequestedLimits
+                       if err = json.Unmarshal(updatedData.Value, 
&capacityRequestedLimits); err != nil {
+                               api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("unmarshalling data for 
configuration update: %w", err))
+                               return
+                       }
+                       for _, capLim := range 
capacityRequestedLimits.RequestedLimits {
+                               capId, err := 
getCapabilityIdFromFootprints(capLim, ucdn, inf)
+                               if err != nil {
+                                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("finding capability for given 
information: %w", err))
+                                       return
+                               }
+
+                               query := 
UpdateTotalLimitsByCapabilityAndLimitTypeQuery
+                               queryParams := []interface{}{capLim.LimitValue, 
capId, capLim.LimitType}
+                               if host != "" {
+                                       query = 
UpdateHostLimitsByCapabilityAndLimitTypeQuery
+                                       queryParams = 
[]interface{}{capLim.LimitValue, capId, capLim.LimitType, host}
+                               }
+
+                               result, err := inf.Tx.Tx.Exec(query, 
queryParams...)
+                               if err != nil {
+                                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("updating capacity: %w", err))
+                                       return
+                               }
+
+                               if rowsAffected, err := result.RowsAffected(); 
err != nil {
+                                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("updating capacity: getting 
rows affected: %w", err))
+                                       return
+                               } else if rowsAffected < 1 {
+                                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusNotFound, fmt.Errorf("no capacity found for update: host: %s, type: 
%s, limit: %v", host, updatedData.Type, capLim), nil)
+                                       return
+                               } else if rowsAffected > 1 {
+                                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("capacity update affected too 
many rows: %d", rowsAffected))
+                                       return
+                               }
+                       }
+               }
+       }
+
+       if asyncErr := api.UpdateAsyncStatus(db, api.AsyncSucceeded, "Capacity 
requested update has been completed.", asyncId, true); asyncErr != nil {
+               log.Errorf("updating async status for id %v: %v", asyncId, 
asyncErr)
+       }
+       status, err := deleteCapabilityRequest(reqId, inf.Tx.Tx)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, status, nil, 
fmt.Errorf("deleting capacity request from queue: %w", err))
+               return
+       }
+       msg := "Successfully updated configuration."
+       api.CreateChangeLogRawTx(api.ApiChange, msg, inf.User, logTx)
+       api.WriteResp(w, r, msg)
+}
+
+func getCapabilityIdFromFootprints(updatedData CapacityLimit, ucdn string, inf 
*api.APIInfo) (int, error) {
+       tableAbbr := ""
+       selectClause := ""
+       whereClause := ""
+       var queryParams []interface{}
+       paramCount := 1
+
+       for i, footprint := range updatedData.Footprints {
+               if i == 0 {
+                       tableAbbr = "f"
+                       selectClause = "SELECT " + tableAbbr + ".capability_id 
FROM cdni_footprints as " + tableAbbr
+                       whereClause = " WHERE " + tableAbbr + ".ucdn = $" + 
strconv.Itoa(paramCount) + " AND " + tableAbbr + ".footprint_type = $" + 
strconv.Itoa(paramCount+1) + " AND " + tableAbbr + ".footprint_value = $" + 
strconv.Itoa(paramCount+2) + "::text[]"
+               } else {
+                       oldTableAbbr := tableAbbr
+                       tableAbbr = tableAbbr + "f"
+                       selectClause = selectClause + " JOIN cdni_footprints as 
" + tableAbbr + " on " + tableAbbr + ".capability_id = " + oldTableAbbr + 
".capability_id"
+                       whereClause = whereClause + " AND " + tableAbbr + 
".ucdn = $" + strconv.Itoa(paramCount) + " AND " + tableAbbr + ".footprint_type 
= $" + strconv.Itoa(paramCount+1) + " AND " + tableAbbr + ".footprint_value = 
$" + strconv.Itoa(paramCount+2) + "::text[]"
+               }
+               paramCount = paramCount + 3
+               queryParams = append(queryParams, ucdn)
+               queryParams = append(queryParams, footprint.FootprintType)
+               queryParams = append(queryParams, 
pq.Array(footprint.FootprintValue))
+       }
+
+       selectQuery := selectClause + whereClause + " AND (SELECT count(*) from 
cdni_footprints as c where c.capability_id = f.capability_id) = " + 
strconv.Itoa(len(updatedData.Footprints))
+       rows, err := inf.Tx.Tx.Query(selectQuery, queryParams...)
+       if err != nil {
+               return 0, fmt.Errorf("querying for capacity update request: 
%w", err)
+       }
+       defer log.Close(rows, "closing footprints query")
+       var capabilityIds []int
+       rowCount := 0
+       for rows.Next() {
+               var capabilityId int
+               if err := rows.Scan(&capabilityId); err != nil {
+                       return 0, fmt.Errorf("scanning db rows: %w", err)
+               }
+               rowCount++
+               capabilityIds = append(capabilityIds, capabilityId)
+       }
+
+       if len(capabilityIds) == 0 {
+               return 0, fmt.Errorf("no capabilities found that match all 
footprints: %v", updatedData.Footprints)
+       }
+       if len(capabilityIds) > 1 {
+               return 0, fmt.Errorf("more than 1 capability found that match 
all footprints: %v", updatedData.Footprints)
+       }
+       return capabilityIds[0], nil
+}
+
+func deleteCapabilityRequest(reqId int, tx *sql.Tx) (int, error) {
+       result, err := tx.Exec(DeleteCapabilityUpdateQuery, reqId)
+       if err != nil {
+               return http.StatusInternalServerError, fmt.Errorf("deleting 
configuration update: %w", err)
+       }
+
+       if rowsAffected, err := result.RowsAffected(); err != nil {
+               return http.StatusInternalServerError, fmt.Errorf("deleting 
configuration update: getting rows affected: %w", err)
+       } else if rowsAffected < 1 {
+               return http.StatusNotFound, errors.New("no configuration update 
with that key found")
+       } else if rowsAffected > 1 {
+               return http.StatusInternalServerError, fmt.Errorf("delete 
affected too many rows: %d", rowsAffected)
+       }
+
+       return http.StatusOK, nil
+}
+
+func validateHostExists(host string, tx *sql.Tx) (int, error, error) {
+       count := 0
+       if err := tx.QueryRow(hostQuery, host).Scan(&count); err != nil {
+               return http.StatusInternalServerError, nil, 
fmt.Errorf("querying if host %s exists: %w", host, err)
+       }
+       if count == 0 {
+               return http.StatusBadRequest, fmt.Errorf("No data found for 
host: %s", host), nil
+       }
+       return http.StatusOK, nil, nil
+}
+
+func checkBearerToken(bearerToken string, inf *api.APIInfo) (string, error) {
+       if bearerToken == "" {
+               return "", errors.New("bearer token header is required")
+       }
+
        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
+               return "", fmt.Errorf("parsing claims: %w", err)
        }
        if !token.Valid {
-               api.HandleErr(w, r, nil, http.StatusInternalServerError, 
errors.New("invalid token"), nil)
-               return
+               return "", errors.New("invalid token")
        }
 
        var expirationFloat float64
@@ -88,20 +493,17 @@ func GetCapabilities(w http.ResponseWriter, r 
*http.Request) {
                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
+                               return "", errors.New("invalid token - iss 
(Issuer) must be a string")
                        }
                        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
+                               return "", errors.New("invalid token - aud 
(Audience) must be a string")
                        }
                        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
+                               return "", errors.New("invalid token - exp 
(Expiration) must be a float64")
                        }
                        expirationFloat = val.(float64)
                }
@@ -110,38 +512,16 @@ func GetCapabilities(w http.ResponseWriter, r 
*http.Request) {
        expiration := int64(expirationFloat)
 
        if expiration < time.Now().Unix() {
-               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("token is expired"), nil)
-               return
+               return "", errors.New("token is expired")
        }
        if dcdn != inf.Config.Cdni.DCdnId {
-               api.HandleErr(w, r, nil, http.StatusForbidden, 
errors.New("invalid token - incorrect dcdn"), nil)
-               return
+               return "", errors.New("invalid token - incorrect dcdn")
        }
        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
+               return "", errors.New("invalid token - empty ucdn field")
        }
 
-       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)
+       return ucdn, nil
 }
 
 func getFootprintMap(tx *sql.Tx) (map[int][]Footprint, error) {
@@ -318,6 +698,20 @@ const (
        FciCapacityLimits                       = "FCI.CapacityLimits"
 )
 
+type SupportedGenericMetadataType string
+
+const (
+       MiRequestedCapacityLimits SupportedGenericMetadataType = 
"MI.RequestedCapacityLimits"
+)
+
+func (s SupportedGenericMetadataType) isValid() bool {
+       switch s {
+       case MiRequestedCapacityLimits:
+               return true
+       }
+       return false
+}
+
 type TelemetrySourceType string
 
 const (
@@ -332,3 +726,33 @@ const (
        Asn                       = "asn"
        CountryCode               = "countrycode"
 )
+
+type GenericHostMetadata struct {
+       Host         string           `json:"host"`
+       HostMetadata HostMetadataList `json:"host-metadata"`
+}
+
+type GenericRequestMetadata struct {
+       Type     string          `json:"type"`
+       Metadata json.RawMessage `json:"metadata"`
+       Host     string          `json:"host,omitempty"`
+}
+
+type HostMetadataList struct {
+       Metadata json.RawMessage `json:"metadata"`
+}
+
+type GenericMetadata struct {
+       Type  SupportedGenericMetadataType `json:"generic-metadata-type"`
+       Value json.RawMessage              `json:"generic-metadata-value"`
+}
+
+type CapacityRequestedLimits struct {
+       RequestedLimits []CapacityLimit `json:"requested-limits"`
+}
+
+type CapacityLimit struct {
+       LimitType  string      `json:"limit-type"`
+       LimitValue int64       `json:"limit-value"`
+       Footprints []Footprint `json:"footprints"`
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/acme.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/acme.go
index e89b8e6..4483248 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/acme.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/acme.go
@@ -36,6 +36,7 @@ import (
        "time"
 
        "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/lib/go-rfc"
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-util"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
@@ -228,7 +229,7 @@ func GenerateAcmeCertificates(w http.ResponseWriter, r 
*http.Request) {
                Level: tc.SuccessLevel.String(),
        })
 
-       w.Header().Add("Location", 
api.CurrentAsyncEndpoint+strconv.Itoa(asyncStatusId))
+       w.Header().Add(rfc.Location, 
api.CurrentAsyncEndpoint+strconv.Itoa(asyncStatusId))
        api.WriteAlerts(w, r, http.StatusAccepted, alerts)
 }
 
@@ -321,7 +322,7 @@ func GenerateLetsEncryptCertificates(w http.ResponseWriter, 
r *http.Request) {
                Level: tc.SuccessLevel.String(),
        })
 
-       w.Header().Add("Location", 
api.CurrentAsyncEndpoint+strconv.Itoa(asyncStatusId))
+       w.Header().Add(rfc.Location, 
api.CurrentAsyncEndpoint+strconv.Itoa(asyncStatusId))
        api.WriteAlerts(w, r, http.StatusAccepted, alerts)
 }
 
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go
index 85aa3cf..ea6108a 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/autorenewcerts.go
@@ -28,6 +28,7 @@ import (
        "time"
 
        "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/lib/go-rfc"
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-util"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
@@ -135,7 +136,7 @@ func renewCertificates(w http.ResponseWriter, r 
*http.Request, deprecated bool)
                Level: tc.SuccessLevel.String(),
        })
 
-       w.Header().Add("Location", 
api.CurrentAsyncEndpoint+strconv.Itoa(asyncStatusId))
+       w.Header().Add(rfc.Location, 
api.CurrentAsyncEndpoint+strconv.Itoa(asyncStatusId))
        api.WriteAlerts(w, r, http.StatusAccepted, alerts)
 
 }
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 8252faa..7915524 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -133,6 +133,9 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
 
                // 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},
+               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodPut, Path: `OC/CI/configuration/?$`, Handler: cdni.PutConfiguration, 
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: 
[]string{"CDNI-CAPACITY:UPDATE"}, Authenticated: Authenticated, Middlewares: 
nil, ID: 541357729078},
+               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodPut, Path: `OC/CI/configuration/{host}?$`, Handler: 
cdni.PutHostConfiguration, RequiredPrivLevel: auth.PrivLevelReadOnly, 
RequiredPermissions: []string{"CDNI-CAPACITY:UPDATE"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 541357729079},
+               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodPut, Path: `OC/CI/configuration/request/{id}/{approved}?$`, Handler: 
cdni.PutConfigurationResponse, RequiredPrivLevel: auth.PrivLevelAdmin, 
RequiredPermissions: []string{"CDNI-CAPACITY:ADMIN"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 541357729080},
 
                // 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},

Reply via email to