diff --git a/docs/source/api/v3/deliveryservices.rst b/docs/source/api/v3/deliveryservices.rst index 6bee9cf55f..5e68fbd536 100644 --- a/docs/source/api/v3/deliveryservices.rst +++ b/docs/source/api/v3/deliveryservices.rst @@ -31,39 +31,41 @@ Request Structure ----------------- .. table:: Request Query Parameters - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | Name | Required | Description | - +==============+==========+=========================================================================================================================================+ - | cdn | no | Show only the :term:`Delivery Services` belonging to the :ref:`ds-cdn` identified by this integral, unique identifier | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | id | no | Show only the :term:`Delivery Service` that has this integral, unique identifier | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | logsEnabled | no | Show only the :term:`Delivery Services` that have :ref:`ds-logs-enabled` set or not based on this boolean | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | profile | no | Return only :term:`Delivery Services` using the :term:`Profile` that has this :ref:`profile-id` | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | tenant | no | Show only the :term:`Delivery Services` belonging to the :term:`Tenant` identified by this integral, unique identifier | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | topology | no | Show only the :term:`Delivery Services` assigned to the :term:`Topology` identified by this unique name | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | type | no | Return only :term:`Delivery Services` of the :term:`Delivery Service` :ref:`ds-types` identified by this integral, unique identifier | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | accessibleTo | no | Return the :term:`Delivery Services` accessible from a :term:`Tenant` *or it's children* identified by this integral, unique identifier | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | xmlId | no | Show only the :term:`Delivery Service` that has this text-based, unique identifier | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | - | | | array | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | limit | no | Choose the maximum number of results to return | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ - | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first page is 1. | - | | | If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | - +--------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===================+==========+=========================================================================================================================================+ + | cdn | no | Show only the :term:`Delivery Services` belonging to the :ref:`ds-cdn` identified by this integral, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | id | no | Show only the :term:`Delivery Service` that has this integral, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | logsEnabled | no | Show only the :term:`Delivery Services` that have :ref:`ds-logs-enabled` set or not based on this boolean | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | profile | no | Return only :term:`Delivery Services` using the :term:`Profile` that has this :ref:`profile-id` | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | tenant | no | Show only the :term:`Delivery Services` belonging to the :term:`Tenant` identified by this integral, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | topology | no | Show only the :term:`Delivery Services` assigned to the :term:`Topology` identified by this unique name | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | type | no | Return only :term:`Delivery Services` of the :term:`Delivery Service` :ref:`ds-types` identified by this integral, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | accessibleTo | no | Return the :term:`Delivery Services` accessible from a :term:`Tenant` *or it's children* identified by this integral, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | serviceCategory | no | Show only the :term:`Delivery Services` belonging to the :term:`Service Category` that has this name | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | xmlId | no | Show only the :term:`Delivery Service` that has this text-based, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first page is 1. | + | | | If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ Response Structure ------------------ @@ -133,6 +135,7 @@ Response Structure :regexRemap: A :ref:`ds-regex-remap` :regionalGeoBlocking: A boolean defining the :ref:`ds-regionalgeo` setting on this :term:`Delivery Service` :remapText: :ref:`ds-raw-remap` +:serviceCategory: The name of the :ref:`ds-service-category` with which the :term:`Delivery Service` is associated :signed: ``true`` if and only if ``signingAlgorithm`` is not ``null``, ``false`` otherwise :signingAlgorithm: Either a :ref:`ds-signing-algorithm` or ``null`` to indicate URL/URI signing is not implemented on this :term:`Delivery Service` :rangeSliceBlockSize: An integer that defines the byte block size for the ATS Slice Plugin. It can only and must be set if ``rangeRequestHandling`` is set to 3. @@ -245,6 +248,7 @@ Response Structure "ecsEnabled": false, "rangeSliceBlockSize": null, "topology": null + "serviceCategory": null }]} @@ -312,6 +316,7 @@ Request Structure :regexRemap: A :ref:`ds-regex-remap` :regionalGeoBlocking: A boolean defining the :ref:`ds-regionalgeo` setting on this :term:`Delivery Service` :remapText: :ref:`ds-raw-remap` +:serviceCategory: The name of the :ref:`ds-service-category` with which the :term:`Delivery Service` is associated - or ``null`` if there is to be no such category :signed: ``true`` if and only if ``signingAlgorithm`` is not ``null``, ``false`` otherwise :signingAlgorithm: Either a :ref:`ds-signing-algorithm` or ``null`` to indicate URL/URI signing is not implemented on this :term:`Delivery Service` :rangeSliceBlockSize: An integer that defines the byte block size for the ATS Slice Plugin. It can only and must be set if ``rangeRequestHandling`` is set to 3. It can only be between (inclusive) 262144 (256KB) - 33554432 (32MB). @@ -359,6 +364,7 @@ Request Structure "rangeRequestHandling": 0, "regionalGeoBlocking": false, "routingName": "test", + "serviceCategory": null, "signed": false, "tenant": "root", "tenantId": 1, @@ -435,6 +441,7 @@ Response Structure :regexRemap: A :ref:`ds-regex-remap` :regionalGeoBlocking: A boolean defining the :ref:`ds-regionalgeo` setting on this :term:`Delivery Service` :remapText: :ref:`ds-raw-remap` +:serviceCategory: The name of the :ref:`ds-service-category` with which the :term:`Delivery Service` is associated :signed: ``true`` if and only if ``signingAlgorithm`` is not ``null``, ``false`` otherwise :signingAlgorithm: Either a :ref:`ds-signing-algorithm` or ``null`` to indicate URL/URI signing is not implemented on this :term:`Delivery Service` :rangeSliceBlockSize: An integer that defines the byte block size for the ATS Slice Plugin. It can only and must be set if ``rangeRequestHandling`` is set to 3. @@ -528,6 +535,7 @@ Response Structure "regionalGeoBlocking": false, "remapText": null, "routingName": "test", + "serviceCategory": null, "signed": false, "sslKeyVersion": null, "tenantId": 1, diff --git a/docs/source/api/v3/servicecategories.rst b/docs/source/api/v3/servicecategories.rst new file mode 100644 index 0000000000..3ec970807a --- /dev/null +++ b/docs/source/api/v3/servicecategories.rst @@ -0,0 +1,162 @@ +.. +.. +.. 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-service-categories: + +********************** +``service_categories`` +********************** + + +``GET`` +======= +Get all requested :term:`Service Categories`. + +:Auth. Required: Yes +:Roles Required: None +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +===========+===============================================================================================================+ + | name | Filter for :term:`Service Categories` with this name | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | tenant | Return only :term:`Service Categories` belonging to the tenant identified by this integral, unique identifier | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | orderby | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | array | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | limit | Choose the maximum number of results to return | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | offset | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | page | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | defined to make use of ``page``. | + +-----------+---------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/3.0/service_categories?name=SERVICE_CATEGORY_NAME HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:name: This :term:`Service Category`'s name +:lastUpdated: The date and time at which this :term:`Service Category` was last modified, in ISO format +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Service Category` +:tenant: The name of the :term:`Tenant` that owns this :term:`Service Category` + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Yzr6TfhxgpZ3pbbrr4TRG4wC3PlnHDDzgs2igtz/1ppLSy2MzugqaGW4y5yzwzl5T3+7q6HWej7GQZt1XIVeZQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 11 Mar 2020 20:02:47 GMT + Content-Length: 102 + + { + "response": [ + { + "lastUpdated": "2020-03-04 15:46:20-07", + "name": "SERVICE_CATEGORY_NAME", + "tenantId": 1, + "tenant": "TENANT_NAME" + } + ] + } + +``POST`` +======== +Create a new service category. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Response Type: Object + +Request Structure +----------------- +:name: This :term:`Service Category`'s name +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Service Category` + +.. code-block:: http + :caption: Request Example + + POST /api/3.0/service_categories HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 48 + Content-Type: application/json + + { + "name": "SERVICE_CATEGORY_NAME", + "tenantId": 1, + } + +Response Structure +------------------ +:name: This :term:`Service Category`'s name +:lastUpdated: The date and time at which this :term:`Service Category` was last modified, in ISO format +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Service Category` +:tenant: The name of the :term:`Tenant` that owns this :term:`Service Category` + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: +pJm4c3O+JTaSXNt+LP+u240Ba/SsvSSDOQ4rDc6hcyZ0FIL+iY/WWrMHhpLulRGKGY88bM4YPCMaxGn3FZ9yQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 11 Mar 2020 20:12:20 GMT + Content-Length: 154 + + { + "alerts": [ + { + "text": "serviceCategory was created.", + "level": "success" + } + ], + "response": { + "lastUpdated": "2020-03-11 14:12:20-06", + "name": "SERVICE_CATEGORY_NAME", + "tenantId": 1, + "tenant": null + } + } diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index 0c163f54b1..0cf04935a0 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -402,6 +402,10 @@ Glossary Server Capabilities A :dfn:`Server Capability` (not to be confused with a "Capability") expresses the capacity of a :term:`cache server` to serve a particular kind of traffic. For example, a :dfn:`Server Capability` could be created named "RAM" to be assigned to :term:`cache servers` that have RAM-disks allocated for content caching. :dfn:`Server Capabilities` can also be required by :term:`Delivery Services`, which will prevent :term:`cache servers` without that :dfn:`Server Capability` from being assigned to them. It also prevents :term:`Mid-tier cache servers` without said :term:`Server Capability` from being selected to serve upstream requests from those :term:`Edge-tier cache servers` assigned to the requiring :term:`Delivery Services`. + Service Category + Service Categories + A :dfn:`Service Category` defines the type of content being delivered by a :dfn:`Delivery Service`. For example, a :dfn:`Service Category` could be created named "linear" and assigned to a :dfn:`Delivery Service` that delivers linear content. + Snapshot Snapshots CDN Snapshot diff --git a/docs/source/overview/delivery_services.rst b/docs/source/overview/delivery_services.rst index 558ac4f217..28afcce78c 100644 --- a/docs/source/overview/delivery_services.rst +++ b/docs/source/overview/delivery_services.rst @@ -749,6 +749,12 @@ Servers ------- Servers can be assigned to Delivery Services using the :ref:`tp-configure-servers` and :ref:`tp-services-delivery-service` Traffic Portal sections, or by directly using the :ref:`to-api-deliveryserviceserver` endpoint. Only :term:`Edge-tier cache servers` can be assigned to a Delivery Service, and once they are so assigned they will begin to serve content for the Delivery Service (after updates are queued and then applied). Any servers assigned to a Delivery Service must also belong to the same CDN_ as the Delivery Service itself. At least one server must be assigned to a Delivery Service in order for it to serve any content. +.. _ds-service-category: + +Service Category +---------------- +A service category is a tag that describes the type of content being delivered by the Delivery Service. Some example values are: "Linear" and "VOD" + .. _ds-signing-algorithm: Signing Algorithm diff --git a/lib/go-atscfg/headerrewritedotconfig.go b/lib/go-atscfg/headerrewritedotconfig.go index 34a96aeb14..d1d3fd874a 100644 --- a/lib/go-atscfg/headerrewritedotconfig.go +++ b/lib/go-atscfg/headerrewritedotconfig.go @@ -21,9 +21,11 @@ package atscfg import ( "errors" + "fmt" "math" "regexp" "strconv" + "strings" "github.com/apache/trafficcontrol/lib/go-tc" "github.com/apache/trafficcontrol/lib/go-util" @@ -33,6 +35,8 @@ const HeaderRewritePrefix = "hdr_rw_" const ContentTypeHeaderRewriteDotConfig = ContentTypeTextASCII const LineCommentHeaderRewriteDotConfig = LineCommentHash +const ServiceCategoryHeader = "CDN-SVC" + const MaxOriginConnectionsNoMax = 0 // 0 indicates no limit on origin connections type HeaderRewriteDS struct { @@ -41,6 +45,7 @@ type HeaderRewriteDS struct { MaxOriginConnections int MidHeaderRewrite string Type tc.DSType + ServiceCategory string } type HeaderRewriteServer struct { @@ -120,6 +125,9 @@ func HeaderRewriteDSFromDS(ds *tc.DeliveryServiceNullable) (HeaderRewriteDS, err if ds.MidHeaderRewrite == nil { ds.MidHeaderRewrite = util.StrPtr("") } + if ds.ServiceCategory == nil { + ds.ServiceCategory = new(string) + } return HeaderRewriteDS{ EdgeHeaderRewrite: *ds.EdgeHeaderRewrite, @@ -127,6 +135,7 @@ func HeaderRewriteDSFromDS(ds *tc.DeliveryServiceNullable) (HeaderRewriteDS, err MaxOriginConnections: *ds.MaxOriginConnections, MidHeaderRewrite: *ds.MidHeaderRewrite, Type: *ds.Type, + ServiceCategory: *ds.ServiceCategory, }, nil } @@ -136,6 +145,7 @@ func MakeHeaderRewriteDotConfig( toURL string, // tm.url global parameter (TODO: cache itself?) ds HeaderRewriteDS, assignedEdges []HeaderRewriteServer, // the edges assigned to ds + dsXmlId string, ) string { text := GenericHeaderComment(string(cdnName), toToolName, toURL) @@ -165,6 +175,10 @@ func MakeHeaderRewriteDotConfig( text += re.ReplaceAllString(ds.EdgeHeaderRewrite, "\n") } + if !strings.Contains(text, ServiceCategoryHeader) && ds.ServiceCategory != "" { + text += fmt.Sprintf("\nset-header %s \"%s|%s\"", ServiceCategoryHeader, dsXmlId, ds.ServiceCategory) + } + text += "\n" return text } diff --git a/lib/go-atscfg/headerrewritedotconfig_test.go b/lib/go-atscfg/headerrewritedotconfig_test.go index 8921d236b8..99366a1415 100644 --- a/lib/go-atscfg/headerrewritedotconfig_test.go +++ b/lib/go-atscfg/headerrewritedotconfig_test.go @@ -30,6 +30,7 @@ func TestMakeHeaderRewriteDotConfig(t *testing.T) { cdnName := tc.CDNName("mycdn") toToolName := "my-to" toURL := "my-to.example.net" + xmlID := "xml-id" ds := HeaderRewriteDS{ EdgeHeaderRewrite: "edgerewrite", @@ -37,6 +38,7 @@ func TestMakeHeaderRewriteDotConfig(t *testing.T) { MaxOriginConnections: 42, MidHeaderRewrite: "midrewrite", Type: tc.DSTypeHTTPLive, + ServiceCategory: "servicecategory", } assignedEdges := []HeaderRewriteServer{ HeaderRewriteServer{ @@ -50,7 +52,7 @@ func TestMakeHeaderRewriteDotConfig(t *testing.T) { }, } - txt := MakeHeaderRewriteDotConfig(cdnName, toToolName, toURL, ds, assignedEdges) + txt := MakeHeaderRewriteDotConfig(cdnName, toToolName, toURL, ds, assignedEdges, xmlID) if !strings.Contains(txt, "edgerewrite") { t.Errorf("expected 'edgerewrite' actual '%v'\n", txt) @@ -67,12 +69,17 @@ func TestMakeHeaderRewriteDotConfig(t *testing.T) { if !strings.Contains(txt, "21") { // 21, because max is 42, and there are 2 not-offline mids, so 42/2=21 t.Errorf("expected origin_max_connections of 21, actual '%v'\n", txt) } + + if !strings.Contains(txt, "xml-id|servicecategory") { + t.Errorf("expected 'xml-id|servicecategory' actual '%v'\n", txt) + } } func TestMakeHeaderRewriteDotConfigNoMaxOriginConnections(t *testing.T) { cdnName := tc.CDNName("mycdn") toToolName := "my-to" toURL := "my-to.example.net" + xmlID := "xml-id" ds := HeaderRewriteDS{ EdgeHeaderRewrite: "edgerewrite", @@ -80,6 +87,7 @@ func TestMakeHeaderRewriteDotConfigNoMaxOriginConnections(t *testing.T) { MaxOriginConnections: 42, MidHeaderRewrite: "midrewrite", Type: tc.DSTypeHTTP, + ServiceCategory: "servicecategory", } assignedEdges := []HeaderRewriteServer{ HeaderRewriteServer{ @@ -93,7 +101,7 @@ func TestMakeHeaderRewriteDotConfigNoMaxOriginConnections(t *testing.T) { }, } - txt := MakeHeaderRewriteDotConfig(cdnName, toToolName, toURL, ds, assignedEdges) + txt := MakeHeaderRewriteDotConfig(cdnName, toToolName, toURL, ds, assignedEdges, xmlID) if strings.Contains(txt, "origin_max_connections") { t.Errorf("expected no origin_max_connections on DS that uses the mid, actual '%v'\n", txt) diff --git a/lib/go-atscfg/headerrewritemiddotconfig_test.go b/lib/go-atscfg/headerrewritemiddotconfig_test.go index 62773f065f..0d75e41fd9 100644 --- a/lib/go-atscfg/headerrewritemiddotconfig_test.go +++ b/lib/go-atscfg/headerrewritemiddotconfig_test.go @@ -37,6 +37,7 @@ func TestMakeHeaderRewriteMidDotConfig(t *testing.T) { MaxOriginConnections: 42, MidHeaderRewrite: "midrewrite", Type: tc.DSTypeHTTP, + ServiceCategory: "servicecategory", } assignedMids := []HeaderRewriteServer{ HeaderRewriteServer{ @@ -80,6 +81,7 @@ func TestMakeHeaderRewriteMidDotConfigNoMaxConns(t *testing.T) { MaxOriginConnections: 42, MidHeaderRewrite: "midrewrite", Type: tc.DSTypeHTTPLive, + ServiceCategory: "servicecategory", } assignedMids := []HeaderRewriteServer{ HeaderRewriteServer{ diff --git a/lib/go-tc/deliveryservices.go b/lib/go-tc/deliveryservices.go index 3a75e4b99e..8cf11dd6af 100644 --- a/lib/go-tc/deliveryservices.go +++ b/lib/go-tc/deliveryservices.go @@ -167,6 +167,7 @@ type DeliveryServiceNullable struct { FirstHeaderRewrite *string `json:"firstHeaderRewrite" db:"first_header_rewrite"` InnerHeaderRewrite *string `json:"innerHeaderRewrite" db:"inner_header_rewrite"` LastHeaderRewrite *string `json:"lastHeaderRewrite" db:"last_header_rewrite"` + ServiceCategory *string `json:"serviceCategory" db:"service_category"` } type DeliveryServiceNullableV15 struct { diff --git a/lib/go-tc/service_category.go b/lib/go-tc/service_category.go new file mode 100644 index 0000000000..eaf99dd1cc --- /dev/null +++ b/lib/go-tc/service_category.go @@ -0,0 +1,39 @@ +package tc + +/* + * 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. + */ + +// ServiceCategoriesResponse is a list of Service Categories as a response. +type ServiceCategoriesResponse struct { + Response []ServiceCategory `json:"response"` +} + +// ServiceCategoryResponse is a single Service Category response for Update and Create to +// depict what changed. +type ServiceCategoryResponse struct { + Response ServiceCategory `json:"response"` +} + +// ServiceCategory holds the name, id and associated tenant that comprise a service category. +type ServiceCategory struct { + LastUpdated TimeNoMod `json:"lastUpdated" db:"last_updated"` + Name string `json:"name" db:"name"` + TenantID int `json:"tenantId" db:"tenant_id"` + TenantName string `json:"tenant" db:"tenant"` +} diff --git a/traffic_ops/app/db/migrations/2020081000000000_add_deliveryservice_service_category.sql b/traffic_ops/app/db/migrations/2020081000000000_add_deliveryservice_service_category.sql new file mode 100644 index 0000000000..d47bbe801a --- /dev/null +++ b/traffic_ops/app/db/migrations/2020081000000000_add_deliveryservice_service_category.sql @@ -0,0 +1,25 @@ +/* + 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. +*/ + +-- +goose Up +-- SQL in section 'Up' is executed when this migration is applied +CREATE TABLE IF NOT EXISTS service_category ( + name TEXT PRIMARY KEY CHECK (name <> ''), + tenant_id BIGINT NOT NULL REFERENCES tenant(id), + last_updated TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL +); +ALTER TABLE deliveryservice ADD COLUMN service_category TEXT REFERENCES service_category(name) ON UPDATE CASCADE; + +-- +goose Down +-- SQL section 'Down' is executed when this migration is rolled back +ALTER TABLE deliveryservice DROP COLUMN service_category; +DROP TABLE service_category; diff --git a/traffic_ops/app/db/seeds.sql b/traffic_ops/app/db/seeds.sql index c301417456..06aafa38ec 100644 --- a/traffic_ops/app/db/seeds.sql +++ b/traffic_ops/app/db/seeds.sql @@ -175,6 +175,9 @@ insert into capability (name, description) values ('server-capabilities-write', -- servers insert into capability (name, description) values ('servers-read', 'Ability to view servers') ON CONFLICT (name) DO NOTHING; insert into capability (name, description) values ('servers-write', 'Ability to edit servers') ON CONFLICT (name) DO NOTHING; +-- service categories +insert into capability (name, description) values ('service-categories-read', 'Ability to view service categories') ON CONFLICT (name) DO NOTHING; +insert into capability (name, description) values ('service-categories-write', 'Ability to edit service categories') ON CONFLICT (name) DO NOTHING; -- stats insert into capability (name, description) values ('stats-read', 'Ability to view cache stats') ON CONFLICT (name) DO NOTHING; insert into capability (name, description) values ('stats-write', 'Ability to edit cache stats') ON CONFLICT (name) DO NOTHING; @@ -262,6 +265,8 @@ insert into role_capability (role_id, cap_name) values ((select id from role whe insert into role_capability (role_id, cap_name) values ((select id from role where name='admin'), 'server-capabilities-write') ON CONFLICT (role_id, cap_name) DO NOTHING; insert into role_capability (role_id, cap_name) values ((select id from role where name='admin'), 'servers-read') ON CONFLICT (role_id, cap_name) DO NOTHING; insert into role_capability (role_id, cap_name) values ((select id from role where name='admin'), 'servers-write') ON CONFLICT (role_id, cap_name) DO NOTHING; +insert into role_capability (role_id, cap_name) values ((select id from role where name='admin'), 'service-categories-read') ON CONFLICT (role_id, cap_name) DO NOTHING; +insert into role_capability (role_id, cap_name) values ((select id from role where name='admin'), 'service-categories-write') ON CONFLICT (role_id, cap_name) DO NOTHING; insert into role_capability (role_id, cap_name) values ((select id from role where name='admin'), 'stats-read') ON CONFLICT (role_id, cap_name) DO NOTHING; insert into role_capability (role_id, cap_name) values ((select id from role where name='admin'), 'stats-write') ON CONFLICT (role_id, cap_name) DO NOTHING; insert into role_capability (role_id, cap_name) values ((select id from role where name='admin'), 'statuses-read') ON CONFLICT (role_id, cap_name) DO NOTHING; @@ -313,6 +318,7 @@ INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHER INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'read-only'), 'roles-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'read-only') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'read-only'), 'server-capabilities-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'read-only') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'read-only'), 'servers-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'read-only') ON CONFLICT DO NOTHING; +INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'read-only'), 'service-categories-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'read-only'), 'stats-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'read-only') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'read-only'), 'statuses-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'read-only') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'read-only'), 'static-dns-entries-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'read-only') ON CONFLICT DO NOTHING; @@ -355,6 +361,7 @@ INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHER INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'roles-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'server-capabilities-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'servers-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; +INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'service-categories-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'stats-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'statuses-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'static-dns-entries-read' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; @@ -384,6 +391,7 @@ INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHER INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'regions-write' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'roles-write' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'servers-write' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; +INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'service-categories-write' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'stats-write' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'statuses-write' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; INSERT INTO role_capability (role_id, cap_name) SELECT (SELECT id FROM role WHERE name = 'operations'), 'tenants-write' WHERE EXISTS (SELECT id FROM role WHERE name = 'operations') ON CONFLICT DO NOTHING; @@ -684,6 +692,11 @@ insert into api_capability (http_method, route, capability) values ('POST', 'ser insert into api_capability (http_method, route, capability) values ('GET', 'server_server_capabilities', 'servers-read') ON CONFLICT (http_method, route, capability) DO NOTHING; insert into api_capability (http_method, route, capability) values ('POST', 'server_server_capabilities', 'servers-write') ON CONFLICT (http_method, route, capability) DO NOTHING; insert into api_capability (http_method, route, capability) values ('DELETE', 'server_server_capabilities', 'servers-write') ON CONFLICT (http_method, route, capability) DO NOTHING; +-- service categories +insert into api_capability (http_method, route, capability) values ('GET', 'service_categories', 'service-categories-read') ON CONFLICT (http_method, route, capability) DO NOTHING; +insert into api_capability (http_method, route, capability) values ('POST', 'service_categories', 'service-categories-write') ON CONFLICT (http_method, route, capability) DO NOTHING; +insert into api_capability (http_method, route, capability) values ('PUT', 'service_categories/*', 'service-categories-write') ON CONFLICT (http_method, route, capability) DO NOTHING; +insert into api_capability (http_method, route, capability) values ('DELETE', 'service_categories/*', 'service-categories-write') ON CONFLICT (http_method, route, capability) DO NOTHING; -- stats insert into api_capability (http_method, route, capability) values ('GET', 'caches/stats', 'stats-read') ON CONFLICT (http_method, route, capability) DO NOTHING; insert into api_capability (http_method, route, capability) values ('GET', 'stats_summary', 'stats-read') ON CONFLICT (http_method, route, capability) DO NOTHING; diff --git a/traffic_ops/client/serviceCategory.go b/traffic_ops/client/serviceCategory.go new file mode 100644 index 0000000000..408df1b9b8 --- /dev/null +++ b/traffic_ops/client/serviceCategory.go @@ -0,0 +1,108 @@ +/* + + 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. +*/ + +package client + +import ( + "encoding/json" + "fmt" + "net" + "net/http" + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" +) + +const ( + API_SERVICE_CATEGORIES = apiBase + "/service_categories" +) + +// CreateServiceCategory performs a post to create a service category. +func (to *Session) CreateServiceCategory(serviceCategory tc.ServiceCategory) (tc.Alerts, ReqInf, error) { + + var remoteAddr net.Addr + reqBody, err := json.Marshal(serviceCategory) + reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} + if err != nil { + return tc.Alerts{}, reqInf, err + } + resp, remoteAddr, err := to.request(http.MethodPost, API_SERVICE_CATEGORIES, reqBody, nil) + if err != nil { + return tc.Alerts{}, reqInf, err + } + defer resp.Body.Close() + var alerts tc.Alerts + if err := json.NewDecoder(resp.Body).Decode(&alerts); err != nil { + return tc.Alerts{}, reqInf, err + } + return alerts, reqInf, nil +} + +// UpdateServiceCategoryByName updates a service category by its unique name. +func (to *Session) UpdateServiceCategoryByName(name string, serviceCategory tc.ServiceCategory) (tc.Alerts, ReqInf, error) { + + var remoteAddr net.Addr + reqBody, err := json.Marshal(serviceCategory) + reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} + if err != nil { + return tc.Alerts{}, reqInf, err + } + route := fmt.Sprintf("%s/%s", API_SERVICE_CATEGORIES, name) + resp, remoteAddr, err := to.request(http.MethodPut, route, reqBody, nil) + if err != nil { + return tc.Alerts{}, reqInf, err + } + defer resp.Body.Close() + var alerts tc.Alerts + if err := json.NewDecoder(resp.Body).Decode(&alerts); err != nil { + return tc.Alerts{}, reqInf, err + } + return alerts, reqInf, nil +} + +// GetServiceCategories gets a list of service categories by the passed in url values. +func (to *Session) GetServiceCategories(values *url.Values) ([]tc.ServiceCategory, ReqInf, error) { + url := fmt.Sprintf("%s?%s", API_SERVICE_CATEGORIES, values.Encode()) + resp, remoteAddr, err := to.request(http.MethodGet, url, nil, nil) + reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} + if err != nil { + return nil, reqInf, err + } + defer resp.Body.Close() + + var data tc.ServiceCategoriesResponse + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + return nil, reqInf, err + } + + return data.Response, reqInf, nil +} + +// DeleteServiceCategoryByName deletes a service category by the service +// category's unique name. +func (to *Session) DeleteServiceCategoryByName(name string) (tc.Alerts, ReqInf, error) { + route := fmt.Sprintf("%s/%s", API_SERVICE_CATEGORIES, name) + resp, remoteAddr, err := to.request(http.MethodDelete, route, nil, nil) + reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} + if err != nil { + return tc.Alerts{}, reqInf, err + } + defer resp.Body.Close() + var alerts tc.Alerts + if err := json.NewDecoder(resp.Body).Decode(&alerts); err != nil { + return tc.Alerts{}, reqInf, err + } + return alerts, reqInf, nil +} diff --git a/traffic_ops/testing/api/v1/todb_test.go b/traffic_ops/testing/api/v1/todb_test.go index 19e9d3638e..cba904d7b0 100644 --- a/traffic_ops/testing/api/v1/todb_test.go +++ b/traffic_ops/testing/api/v1/todb_test.go @@ -339,6 +339,7 @@ func Teardown(db *sql.DB) error { DELETE FROM status; DELETE FROM snapshot; DELETE FROM cdn; + DELETE FROM service_category; DELETE FROM tenant; ALTER SEQUENCE tenant_id_seq RESTART WITH 1; ` diff --git a/traffic_ops/testing/api/v2/todb_test.go b/traffic_ops/testing/api/v2/todb_test.go index b8e7246385..95e322866f 100644 --- a/traffic_ops/testing/api/v2/todb_test.go +++ b/traffic_ops/testing/api/v2/todb_test.go @@ -339,6 +339,7 @@ func Teardown(db *sql.DB) error { DELETE FROM status; DELETE FROM snapshot; DELETE FROM cdn; + DELETE FROM service_category; DELETE FROM tenant; ALTER SEQUENCE tenant_id_seq RESTART WITH 1; ` diff --git a/traffic_ops/testing/api/v3/servicecategories_test.go b/traffic_ops/testing/api/v3/servicecategories_test.go new file mode 100644 index 0000000000..beacdd20f3 --- /dev/null +++ b/traffic_ops/testing/api/v3/servicecategories_test.go @@ -0,0 +1,198 @@ +package v3 + +/* + + 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. +*/ + +import ( + "net/url" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-tc" + toclient "github.com/apache/trafficcontrol/traffic_ops/client" +) + +func TestServiceCategories(t *testing.T) { + WithObjs(t, []TCObj{Tenants, ServiceCategories, Users}, func() { + UpdateTestServiceCategories(t) + GetTestServiceCategories(t) + ServiceCategoryTenancyTest(t) + }) +} + +func CreateTestServiceCategories(t *testing.T) { + // loop through service categories, assign FKs and create + for _, sc := range testData.ServiceCategories { + tenant, _, err := TOSession.TenantByName(sc.TenantName, nil) + sc.TenantID = tenant.ID + resp, _, err := TOSession.CreateServiceCategory(sc) + if err != nil { + t.Errorf("could not CREATE service category: %v", err) + } + t.Log("Response: ", resp.Alerts) + } +} + +func GetTestServiceCategories(t *testing.T) { + params := url.Values{} + for _, sc := range testData.ServiceCategories { + params.Add("name", sc.Name) + resp, _, err := TOSession.GetServiceCategories(¶ms) + if err != nil { + t.Errorf("cannot GET Service Category by name: %v - %v", err, resp) + } + } +} + +func UpdateTestServiceCategories(t *testing.T) { + firstServiceCategory := tc.ServiceCategory{} + if len(testData.ServiceCategories) > 0 { + firstServiceCategory = testData.ServiceCategories[0] + } else { + t.Fatalf("cannot UPDATE Service Category, test data does not have service categories") + } + + tenants, _, err := TOSession.Tenants(nil) + if err != nil { + t.Fatalf("Failed to get tenants: %v", err) + } + if len(tenants) < 2 { + t.Fatalf("Need at least two tenants to test changing tenant; got: %d", len(tenants)) + } + + // Retrieve the Service Category by service category so we can get the id for the Update + params := url.Values{} + params.Add("name", firstServiceCategory.Name) + resp, _, err := TOSession.GetServiceCategories(¶ms) + if err != nil { + t.Errorf("cannot GET Service Category by name: %v - %v", firstServiceCategory.Name, err) + } + if len(resp) > 0 { + remoteServiceCategory := resp[0] + + originalTenant := remoteServiceCategory.TenantID + found := false + for _, tenant := range tenants { + if tenant.ID != originalTenant { + remoteServiceCategory.TenantID = tenant.ID + found = true + break + } + } + if !found { + t.Fatal("Could not find tenant that isn't the same as the remote service category's tenant") + } + + var alert tc.Alerts + alert, _, err = TOSession.UpdateServiceCategoryByName(remoteServiceCategory.Name, remoteServiceCategory) + if err != nil { + t.Errorf("cannot UPDATE Service Category by name: %v - %v", err, alert) + } + t.Logf("alerts: %v", alert) + + // Retrieve the Service Category to check service category got updated + resp, _, err = TOSession.GetServiceCategories(¶ms) + if err != nil { + t.Errorf("cannot GET Service Category by service category: %v - %v", firstServiceCategory.Name, err) + } + if len(resp) < 1 { + t.Fatal("empty response getting Service Category after update") + } else if len(resp) > 1 { + t.Errorf("expected a name to uniquely identify exactly one Service Category, got: %d", len(resp)) + } + + respServiceCategory := resp[0] + if respServiceCategory.TenantID != remoteServiceCategory.TenantID { + t.Errorf("results do not match; want: %d, got: %d", remoteServiceCategory.TenantID, respServiceCategory.TenantID) + } + + // Set the name back to the fixture value so we can delete it after + remoteServiceCategory.TenantID = originalTenant + alert, _, err = TOSession.UpdateServiceCategoryByName(remoteServiceCategory.Name, remoteServiceCategory) + if err != nil { + t.Errorf("cannot UPDATE Service Category by name: %v - %v", err, alert) + } + } +} + +func ServiceCategoryTenancyTest(t *testing.T) { + var alert tc.Alerts + tenant3, _, err := TOSession.TenantByName("tenant3", nil) + if err != nil { + t.Errorf("cannot GET Tenant3: %v", err) + } + + params := url.Values{} + serviceCategories, _, err := TOSession.GetServiceCategories(¶ms) + if err != nil { + t.Errorf("cannot GET Service Categories: %v", err) + } + for _, sc := range serviceCategories { + if sc.Name == "serviceCategory1" { + alert, _, err = TOSession.UpdateServiceCategoryByName(sc.Name, sc) + if err != nil { + t.Errorf("cannot UPDATE Service Category by name: %v - %v", err, alert) + } + sc.TenantID = tenant3.ID + } + } + + toReqTimeout := time.Second * time.Duration(Config.Default.Session.TimeoutInSecs) + tenant4TOClient, _, err := toclient.LoginWithAgent(TOSession.URL, "tenant4user", "pa$$word", true, "to-api-v3-client-tests/tenant4user", true, toReqTimeout) + if err != nil { + t.Fatalf("failed to log in with tenant4user: %v", err.Error()) + } + + serviceCategoriesReadableByTenant4, _, err := tenant4TOClient.GetServiceCategories(¶ms) + if err != nil { + t.Error("tenant4user cannot GET service categories") + } + + // assert that tenant4user cannot read service categories outside of its tenant + for _, sc := range serviceCategoriesReadableByTenant4 { + if sc.Name == "serviceCategory1" { + t.Error("expected tenant4 to be unable to read service categories from tenant 3") + } + } +} + +func DeleteTestServiceCategories(t *testing.T) { + for _, sc := range testData.ServiceCategories { + // Retrieve the Service Category by name so we can get the id + params := url.Values{} + params.Add("name", sc.Name) + resp, _, err := TOSession.GetServiceCategories(¶ms) + if err != nil { + t.Errorf("cannot GET Service Category by name: %v - %v", sc.Name, err) + } + if len(resp) > 0 { + respServiceCategory := resp[0] + + delResp, _, err := TOSession.DeleteServiceCategoryByName(respServiceCategory.Name) + if err != nil { + t.Errorf("cannot DELETE Service Category by service category: %v - %v", err, delResp) + } + + // Retrieve the Service Category to see if it got deleted + respDelServiceCategory, _, err := TOSession.GetServiceCategories(¶ms) + if err != nil { + t.Errorf("error deleting Service Category: %s", err.Error()) + } + if len(respDelServiceCategory) > 0 { + t.Errorf("expected Service Category : %s to be deleted", sc.Name) + } + } + } +} diff --git a/traffic_ops/testing/api/v3/tc-fixtures.json b/traffic_ops/testing/api/v3/tc-fixtures.json index 8ee3edd953..b1f492d085 100644 --- a/traffic_ops/testing/api/v3/tc-fixtures.json +++ b/traffic_ops/testing/api/v3/tc-fixtures.json @@ -2662,6 +2662,16 @@ "serverCapability": "bar" } ], + "serviceCategories": [ + { + "name": "serviceCategory1", + "tenant": "tenant3" + }, + { + "name": "serviceCategory2", + "tenant": "tenant4" + } + ], "staticdnsentries": [ { "address": "192.168.0.1", diff --git a/traffic_ops/testing/api/v3/todb_test.go b/traffic_ops/testing/api/v3/todb_test.go index 7599d9e11c..c0ab6a66da 100644 --- a/traffic_ops/testing/api/v3/todb_test.go +++ b/traffic_ops/testing/api/v3/todb_test.go @@ -342,6 +342,7 @@ func Teardown(db *sql.DB) error { DELETE FROM status; DELETE FROM snapshot; DELETE FROM cdn; + DELETE FROM service_category; DELETE FROM tenant; ALTER SEQUENCE tenant_id_seq RESTART WITH 1; ` diff --git a/traffic_ops/testing/api/v3/traffic_control_test.go b/traffic_ops/testing/api/v3/traffic_control_test.go index e090021943..15953e4da7 100644 --- a/traffic_ops/testing/api/v3/traffic_control_test.go +++ b/traffic_ops/testing/api/v3/traffic_control_test.go @@ -45,6 +45,7 @@ type TrafficControl struct { Servers []tc.ServerNullable `json:"servers"` ServerServerCapabilities []tc.ServerServerCapability `json:"serverServerCapabilities"` ServerCapabilities []tc.ServerCapability `json:"serverCapabilities"` + ServiceCategories []tc.ServiceCategory `json:"serviceCategories"` Statuses []tc.StatusNullable `json:"statuses"` StaticDNSEntries []tc.StaticDNSEntry `json:"staticdnsentries"` StatsSummaries []tc.StatsSummary `json:"statsSummaries"` diff --git a/traffic_ops/testing/api/v3/withobjs_test.go b/traffic_ops/testing/api/v3/withobjs_test.go index 0f7996e240..27c891780c 100644 --- a/traffic_ops/testing/api/v3/withobjs_test.go +++ b/traffic_ops/testing/api/v3/withobjs_test.go @@ -61,6 +61,7 @@ const ( ServerChecks ServerServerCapabilities Servers + ServiceCategories Statuses StaticDNSEntries SteeringTargets @@ -102,6 +103,7 @@ var withFuncs = map[TCObj]TCObjFuncs{ ServerChecks: {CreateTestServerChecks, DeleteTestServerChecks}, ServerServerCapabilities: {CreateTestServerServerCapabilities, DeleteTestServerServerCapabilities}, Servers: {CreateTestServers, DeleteTestServers}, + ServiceCategories: {CreateTestServiceCategories, DeleteTestServiceCategories}, Statuses: {CreateTestStatuses, DeleteTestStatuses}, StaticDNSEntries: {CreateTestStaticDNSEntries, DeleteTestStaticDNSEntries}, SteeringTargets: {SetupSteeringTargets, DeleteTestSteeringTargets}, diff --git a/traffic_ops/traffic_ops_golang/api/generic_crud.go b/traffic_ops/traffic_ops_golang/api/generic_crud.go index 8feadca97a..51a03919f3 100644 --- a/traffic_ops/traffic_ops_golang/api/generic_crud.go +++ b/traffic_ops/traffic_ops_golang/api/generic_crud.go @@ -80,7 +80,7 @@ func GenericCreate(val GenericCreator) (error, error, int) { } defer resultRows.Close() - id := 0 + var id interface{} lastUpdated := tc.TimeNoMod{} rowsAffected := 0 for resultRows.Next() { @@ -89,6 +89,16 @@ func GenericCreate(val GenericCreator) (error, error, int) { return nil, errors.New(val.GetType() + " create scanning: " + err.Error()), http.StatusInternalServerError } } + + switch id.(type) { + case int64: + // ahhh generics in a language without generics + // sql.Driver values return int64s from integral database data + // but our objects all use ambiguous-width ints for their IDs + // so naturally this suffers from overflow issues, but c'est la vie + id = int(id.(int64)) + default: + } if rowsAffected == 0 { return nil, errors.New(val.GetType() + " create: no " + val.GetType() + " was inserted, no id was returned"), http.StatusInternalServerError } else if rowsAffected > 1 { diff --git a/traffic_ops/traffic_ops_golang/ats/atscdn/headerrewritedotconfig.go b/traffic_ops/traffic_ops_golang/ats/atscdn/headerrewritedotconfig.go new file mode 100644 index 0000000000..e69de29bb2 diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go index 4ecba43fd4..c31b9240e1 100644 --- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go +++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go @@ -326,6 +326,7 @@ func createV30(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, reqDS t &ds.FirstHeaderRewrite, &ds.InnerHeaderRewrite, &ds.LastHeaderRewrite, + &ds.ServiceCategory, ) if err != nil { @@ -709,7 +710,8 @@ SELECT ds.topology, ds.first_header_rewrite, ds.inner_header_rewrite, - ds.last_header_rewrite + ds.last_header_rewrite, + ds.service_category FROM deliveryservice ds WHERE @@ -719,6 +721,7 @@ WHERE &dsV30.FirstHeaderRewrite, &dsV30.InnerHeaderRewrite, &dsV30.LastHeaderRewrite, + &dsV30.ServiceCategory, ); err != nil { if err == sql.ErrNoRows { return nil, http.StatusNotFound, fmt.Errorf("delivery service ID %d not found", *dsV30.ID), nil @@ -838,6 +841,7 @@ func updateV30(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, reqDS * &ds.FirstHeaderRewrite, &ds.InnerHeaderRewrite, &ds.LastHeaderRewrite, + &ds.ServiceCategory, &ds.ID) if err != nil { @@ -1009,6 +1013,7 @@ func readGetDeliveryServices(h http.Header, params map[string]string, tx *sqlx.T "tenant": {"ds.tenant_id", api.IsInt}, "signingAlgorithm": {"ds.signing_algorithm", nil}, "topology": {"ds.topology", nil}, + "serviceCategory": {"ds.service_category", nil}, } where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToSQLCols) @@ -1256,6 +1261,7 @@ func GetDeliveryServices(query string, queryValues map[string]interface{}, tx *s &ds.RegionalGeoBlocking, &ds.RemapText, &ds.RoutingName, + &ds.ServiceCategory, &ds.SigningAlgorithm, &ds.RangeSliceBlockSize, &ds.SSLKeyVersion, @@ -1780,6 +1786,7 @@ ds.regex_remap, ds.regional_geo_blocking, ds.remap_text, ds.routing_name, +ds.service_category, ds.signing_algorithm, ds.range_slice_block_size, ds.ssl_key_version, @@ -1861,8 +1868,9 @@ range_slice_block_size=$54, topology=$55, first_header_rewrite=$56, inner_header_rewrite=$57, -last_header_rewrite=$58 -WHERE id=$59 +last_header_rewrite=$58, +service_category=$59 +WHERE id=$60 RETURNING last_updated ` } @@ -1927,9 +1935,10 @@ ecs_enabled, range_slice_block_size, first_header_rewrite, inner_header_rewrite, -last_header_rewrite +last_header_rewrite, +service_category ) -VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58) +VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59) RETURNING id, last_updated ` } diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index a5e1ba03ee..d79e3bb272 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -80,6 +80,7 @@ import ( "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/servercapability" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/servercheck" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/servercheck/extensions" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/servicecategory" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/staticdnsentry" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/status" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/steering" @@ -415,6 +416,12 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {api.Version{3, 0}, http.MethodPut, `deliveryservices/{dsid}/regexes/{regexid}?$`, deliveryservicesregexes.Put, auth.PrivLevelOperations, Authenticated, nil, 22483396913, noPerlBypass}, {api.Version{3, 0}, http.MethodDelete, `deliveryservices/{dsid}/regexes/{regexid}?$`, deliveryservicesregexes.Delete, auth.PrivLevelOperations, Authenticated, nil, 22467316633, noPerlBypass}, + //ServiceCategories + {api.Version{3, 0}, http.MethodGet, `service_categories/?$`, api.ReadHandler(&servicecategory.TOServiceCategory{}), auth.PrivLevelReadOnly, Authenticated, nil, 1085181543, noPerlBypass}, + {api.Version{3, 0}, http.MethodPut, `service_categories/{name}/?$`, api.UpdateHandler(&servicecategory.TOServiceCategory{}), auth.PrivLevelOperations, Authenticated, nil, 306369141, noPerlBypass}, + {api.Version{3, 0}, http.MethodPost, `service_categories/?$`, api.CreateHandler(&servicecategory.TOServiceCategory{}), auth.PrivLevelOperations, Authenticated, nil, 553713801, noPerlBypass}, + {api.Version{3, 0}, http.MethodDelete, `service_categories/{name}$`, api.DeleteHandler(&servicecategory.TOServiceCategory{}), auth.PrivLevelOperations, Authenticated, nil, 1325382238, noPerlBypass}, + //StaticDNSEntries {api.Version{3, 0}, http.MethodGet, `staticdnsentries/?$`, api.ReadHandler(&staticdnsentry.TOStaticDNSEntry{}), auth.PrivLevelReadOnly, Authenticated, nil, 2289394773, noPerlBypass}, {api.Version{3, 0}, http.MethodPut, `staticdnsentries/?$`, api.UpdateHandler(&staticdnsentry.TOStaticDNSEntry{}), auth.PrivLevelOperations, Authenticated, nil, 2424571113, noPerlBypass}, diff --git a/traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go b/traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go new file mode 100644 index 0000000000..1e40eace88 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/servicecategory/servicecategories.go @@ -0,0 +1,169 @@ +package servicecategory + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import ( + "errors" + "net/http" + "strconv" + "time" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-tc/tovalidate" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant" + "github.com/go-ozzo/ozzo-validation" +) + +type TOServiceCategory struct { + api.APIInfoImpl `json:"-"` + tc.ServiceCategory +} + +func (v *TOServiceCategory) SetLastUpdated(t tc.TimeNoMod) { v.LastUpdated = t } +func (v *TOServiceCategory) InsertQuery() string { return insertQuery() } +func (v *TOServiceCategory) NewReadObj() interface{} { return &tc.ServiceCategory{} } +func (v *TOServiceCategory) SelectQuery() string { return selectQuery() } +func (v *TOServiceCategory) UpdateQuery() string { return updateQuery() } +func (v *TOServiceCategory) DeleteQuery() string { return deleteQuery() } + +func (serviceCategory TOServiceCategory) GetAuditName() string { + if serviceCategory.Name != "" { + return serviceCategory.Name + } + if serviceCategory.TenantID != 0 { + return strconv.Itoa(serviceCategory.TenantID) + } + return "unknown" +} + +func (serviceCategory TOServiceCategory) GetKeyFieldsInfo() []api.KeyFieldInfo { + return []api.KeyFieldInfo{{"name", api.GetStringKey}} +} + +//Implementation of the Identifier, Validator interface functions +func (serviceCategory TOServiceCategory) GetKeys() (map[string]interface{}, bool) { + if serviceCategory.Name == "" { + return map[string]interface{}{"name": ""}, false + } + return map[string]interface{}{"name": serviceCategory.Name}, true +} + +func (serviceCategory *TOServiceCategory) SetKeys(keys map[string]interface{}) { + n, _ := keys["name"].(string) + serviceCategory.Name = n +} + +func (serviceCategory TOServiceCategory) GetType() string { + return "serviceCategory" +} + +func (serviceCategory *TOServiceCategory) ParamColumns() map[string]dbhelpers.WhereColumnInfo { + return map[string]dbhelpers.WhereColumnInfo{ + "name": dbhelpers.WhereColumnInfo{"sc.name", nil}, + "tenantId": dbhelpers.WhereColumnInfo{"sc.tenant_id", api.IsInt}, + "tenantName": dbhelpers.WhereColumnInfo{"sc.tenant", nil}, + } +} + +func (serviceCategory *TOServiceCategory) SelectMaxLastUpdatedQuery(where, orderBy, pagination, tableName string) string { + return `SELECT max(t) from ( + SELECT max(last_updated) as t from ` + tableName + ` sc ` + where + orderBy + pagination + + ` UNION ALL + select max(last_updated) as t from last_deleted l where l.table_name='` + tableName + `') as res` +} + +func (serviceCategory TOServiceCategory) Validate() error { + errs := validation.Errors{ + "name": validation.Validate(serviceCategory.Name, validation.NotNil, validation.Required), + "tenantId": validation.Validate(serviceCategory.TenantID, validation.Min(1)), + } + return util.JoinErrs(tovalidate.ToErrors(errs)) +} + +func (serviceCategory *TOServiceCategory) Create() (error, error, int) { + return api.GenericCreate(serviceCategory) +} + +func (serviceCategory *TOServiceCategory) Read(h http.Header, useIMS bool) ([]interface{}, error, error, int, *time.Time) { + tenantIDs, err := tenant.GetUserTenantIDListTx(serviceCategory.APIInfo().Tx.Tx, serviceCategory.APIInfo().User.TenantID) + if err != nil { + return nil, nil, errors.New("getting tenant list for user: " + err.Error()), http.StatusInternalServerError, nil + } + + serviceCategories, userErr, sysErr, errCode, maxTime := api.GenericRead(h, serviceCategory, useIMS) + if userErr != nil || sysErr != nil { + return nil, userErr, sysErr, errCode, nil + } + + filteredServiceCategories := []interface{}{} + for _, sc := range serviceCategories { + sc1 := sc.(*tc.ServiceCategory) + if checkTenancy(sc1, tenantIDs) { + filteredServiceCategories = append(filteredServiceCategories, sc1) + } + } + + return filteredServiceCategories, nil, nil, errCode, maxTime +} + +func checkTenancy(category *tc.ServiceCategory, tenantIDs []int) bool { + for _, tenantID := range tenantIDs { + if tenantID == category.TenantID { + return true + } + } + return false +} + +func (serviceCategory *TOServiceCategory) Update() (error, error, int) { + return api.GenericUpdate(serviceCategory) +} +func (serviceCategory *TOServiceCategory) Delete() (error, error, int) { + return api.GenericDelete(serviceCategory) +} + +func insertQuery() string { + return `INSERT INTO service_category (name, tenant_id) VALUES (:name, :tenant_id) RETURNING name, last_updated` +} + +func selectQuery() string { + return `SELECT +sc.tenant_id, +t.name as tenant, +sc.last_updated, +sc.name +FROM service_category as sc +LEFT JOIN tenant t ON sc.tenant_id = t.id` +} + +func updateQuery() string { + return `UPDATE +service_category SET +name=:name, +tenant_id=:tenant_id +WHERE name=:name RETURNING last_updated` +} + +func deleteQuery() string { + return `DELETE FROM service_category WHERE name=:name` +} diff --git a/traffic_ops_ort/atstccfg/cfgfile/headerrewritedotconfig.go b/traffic_ops_ort/atstccfg/cfgfile/headerrewritedotconfig.go index c691538608..7b27540e9e 100644 --- a/traffic_ops_ort/atstccfg/cfgfile/headerrewritedotconfig.go +++ b/traffic_ops_ort/atstccfg/cfgfile/headerrewritedotconfig.go @@ -80,5 +80,5 @@ func GetConfigFileCDNHeaderRewrite(toData *config.TOData, fileName string) (stri assignedEdges = append(assignedEdges, cfgServer) } - return atscfg.MakeHeaderRewriteDotConfig(tc.CDNName(toData.Server.CDNName), toData.TOToolName, toData.TOURL, cfgDS, assignedEdges), atscfg.ContentTypeHeaderRewriteDotConfig, atscfg.LineCommentHeaderRewriteDotConfig, nil + return atscfg.MakeHeaderRewriteDotConfig(tc.CDNName(toData.Server.CDNName), toData.TOToolName, toData.TOURL, cfgDS, assignedEdges, dsName), atscfg.ContentTypeHeaderRewriteDotConfig, atscfg.LineCommentHeaderRewriteDotConfig, nil } diff --git a/traffic_portal/app/src/app.js b/traffic_portal/app/src/app.js index 76627fc7aa..5b39530d5e 100644 --- a/traffic_portal/app/src/app.js +++ b/traffic_portal/app/src/app.js @@ -193,6 +193,11 @@ var trafficPortal = angular.module('trafficPortal', [ require('./modules/private/servers/edit').name, require('./modules/private/servers/new').name, require('./modules/private/servers/list').name, + require('./modules/private/serviceCategories').name, + require('./modules/private/serviceCategories/deliveryServices').name, + require('./modules/private/serviceCategories/edit').name, + require('./modules/private/serviceCategories/list').name, + require('./modules/private/serviceCategories/new').name, require('./modules/private/statuses').name, require('./modules/private/statuses/edit').name, require('./modules/private/statuses/list').name, @@ -321,6 +326,9 @@ var trafficPortal = angular.module('trafficPortal', [ require('./common/modules/form/server').name, require('./common/modules/form/server/edit').name, require('./common/modules/form/server/new').name, + require('./common/modules/form/serviceCategory').name, + require('./common/modules/form/serviceCategory/edit').name, + require('./common/modules/form/serviceCategory/new').name, require('./common/modules/form/status').name, require('./common/modules/form/status/edit').name, require('./common/modules/form/status/new').name, @@ -394,6 +402,8 @@ var trafficPortal = angular.module('trafficPortal', [ require('./common/modules/table/serverServerCapabilities').name, require('./common/modules/table/servers').name, require('./common/modules/table/serverDeliveryServices').name, + require('./common/modules/table/serviceCategories').name, + require('./common/modules/table/serviceCategoryDeliveryServices').name, require('./common/modules/table/statuses').name, require('./common/modules/table/statusServers').name, require('./common/modules/table/tenants').name, diff --git a/traffic_portal/app/src/common/api/DeliveryServiceService.js b/traffic_portal/app/src/common/api/DeliveryServiceService.js index 7af8ae2a25..81c0f27474 100644 --- a/traffic_portal/app/src/common/api/DeliveryServiceService.js +++ b/traffic_portal/app/src/common/api/DeliveryServiceService.js @@ -55,7 +55,6 @@ var DeliveryServiceService = function($http, locationUtils, messageModel, ENV) { ); }; - // todo: change to use query param when it is supported this.updateDeliveryService = function(ds) { // strip out any falsy values or duplicates from consistentHashQueryParams ds.consistentHashQueryParams = Array.from(new Set(ds.consistentHashQueryParams)).filter(function(i){return i;}); diff --git a/traffic_portal/app/src/common/api/ServiceCategoryService.js b/traffic_portal/app/src/common/api/ServiceCategoryService.js new file mode 100644 index 0000000000..01b7263a5b --- /dev/null +++ b/traffic_portal/app/src/common/api/ServiceCategoryService.js @@ -0,0 +1,86 @@ +/* + * 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. + */ + +var ServiceCategoryService = function($http, ENV, locationUtils, messageModel) { + + this.getServiceCategories = function(queryParams) { + return $http.get(ENV.api['root'] + 'service_categories', {params: queryParams}).then( + function(result) { + return result.data.response; + }, + function(err) { + throw err; + } + ); + }; + + this.getServiceCategory = function(name) { + return $http.get(ENV.api['root'] + 'service_categories', {params: {name: name}}).then( + function(result) { + return result.data.response[0]; + }, + function(err) { + throw err; + } + ); + }; + + this.createServiceCategory = function(serviceCategory) { + return $http.post(ENV.api['root'] + 'service_categories', serviceCategory).then( + function(result) { + messageModel.setMessages(result.data.alerts, true); + locationUtils.navigateToPath('/service-categories'); + return result; + }, + function(err) { + messageModel.setMessages(err.data.alerts, false); + throw err; + } + ); + }; + + this.updateServiceCategory = function(serviceCategory) { + return $http.put(ENV.api['root'] + 'service_categories/' + encodeURIComponent(serviceCategory.name), serviceCategory).then( + function(result) { + messageModel.setMessages(result.data.alerts, false); + return result; }, + function(err) { + messageModel.setMessages(err.data.alerts, false); + throw err; + } + ); + }; + + this.deleteServiceCategory = function(name) { + return $http.delete(ENV.api['root'] + 'service_categories/' + encodeURIComponent(name)).then( + function(result) { + messageModel.setMessages(result.data.alerts, true); + return result; + }, + function(err) { + messageModel.setMessages(err.data.alerts, false); + throw err; + } + ); + }; + +}; + +ServiceCategoryService.$inject = ['$http', 'ENV', 'locationUtils', 'messageModel']; +module.exports = ServiceCategoryService; diff --git a/traffic_portal/app/src/common/api/index.js b/traffic_portal/app/src/common/api/index.js index 079b6d9001..2108739a17 100644 --- a/traffic_portal/app/src/common/api/index.js +++ b/traffic_portal/app/src/common/api/index.js @@ -49,6 +49,7 @@ module.exports = angular.module('trafficPortal.api', []) .service('regionService', require('./RegionService')) .service('serverService', require('./ServerService')) .service('serverCapabilityService', require('./ServerCapabilityService')) + .service('serviceCategoryService', require('./ServiceCategoryService')) .service('staticDnsEntryService', require('./StaticDnsEntryService')) .service('statusService', require('./StatusService')) .service('tenantService', require('./TenantService')) diff --git a/traffic_portal/app/src/common/modules/form/deliveryService/FormDeliveryServiceController.js b/traffic_portal/app/src/common/modules/form/deliveryService/FormDeliveryServiceController.js index 4ccff2412a..be362357de 100644 --- a/traffic_portal/app/src/common/modules/form/deliveryService/FormDeliveryServiceController.js +++ b/traffic_portal/app/src/common/modules/form/deliveryService/FormDeliveryServiceController.js @@ -17,7 +17,7 @@ * under the License. */ -var FormDeliveryServiceController = function(deliveryService, dsCurrent, origin, topologies, type, types, $scope, $location, $uibModal, $window, formUtils, locationUtils, tenantUtils, deliveryServiceUtils, cdnService, profileService, tenantService, propertiesModel, userModel) { +var FormDeliveryServiceController = function(deliveryService, dsCurrent, origin, topologies, type, types, $scope, $location, $uibModal, $window, formUtils, locationUtils, tenantUtils, deliveryServiceUtils, cdnService, profileService, tenantService, propertiesModel, userModel, serviceCategoryService) { var getCDNs = function() { cdnService.getCDNs() @@ -46,6 +46,13 @@ var FormDeliveryServiceController = function(deliveryService, dsCurrent, origin, }); }; + var getServiceCategories = function() { + serviceCategoryService.getServiceCategories() + .then(function(result) { + $scope.serviceCategories = result; + }); + }; + $scope.deliveryService = deliveryService; $scope.showGeneralConfig = true; @@ -306,6 +313,7 @@ var FormDeliveryServiceController = function(deliveryService, dsCurrent, origin, getCDNs(); getProfiles(); getTenants(); + getServiceCategories(); if (!deliveryService.consistentHashQueryParams || deliveryService.consistentHashQueryParams.length < 1) { // add an empty one so the dynamic form widget is visible. empty strings get stripped out on save anyhow. $scope.deliveryService.consistentHashQueryParams = [ '' ]; @@ -315,5 +323,5 @@ var FormDeliveryServiceController = function(deliveryService, dsCurrent, origin, }; -FormDeliveryServiceController.$inject = ['deliveryService', 'dsCurrent', 'origin', 'topologies', 'type', 'types', '$scope', '$location', '$uibModal', '$window', 'formUtils', 'locationUtils', 'tenantUtils', 'deliveryServiceUtils', 'cdnService', 'profileService', 'tenantService', 'propertiesModel', 'userModel']; +FormDeliveryServiceController.$inject = ['deliveryService', 'dsCurrent', 'origin', 'topologies', 'type', 'types', '$scope', '$location', '$uibModal', '$window', 'formUtils', 'locationUtils', 'tenantUtils', 'deliveryServiceUtils', 'cdnService', 'profileService', 'tenantService', 'propertiesModel', 'userModel', 'serviceCategoryService']; module.exports = FormDeliveryServiceController; diff --git a/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html b/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html index 38d17e82cb..bc3e2ece8d 100644 --- a/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html +++ b/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html @@ -244,6 +244,22 @@
| Regional Geoblocking | Raw Remap Text | Routing Name | +Service Category | Signed | Signing Algorithm | Range Slice Block Size | @@ -162,6 +163,7 @@{{::ds.regionalGeoBlocking}} | {{::ds.remapText}} | {{::ds.routingName}} | +{{::ds.serviceCategory}} | {{::ds.signed}} | {{::ds.signingAlgorithm}} | {{::ds.rangeSliceBlockSize}} | diff --git a/traffic_portal/app/src/common/modules/table/typeDeliveryServices/table.typeDeliveryServices.tpl.html b/traffic_portal/app/src/common/modules/table/typeDeliveryServices/table.typeDeliveryServices.tpl.html index 24c2139d52..a6e93c74e5 100644 --- a/traffic_portal/app/src/common/modules/table/typeDeliveryServices/table.typeDeliveryServices.tpl.html +++ b/traffic_portal/app/src/common/modules/table/typeDeliveryServices/table.typeDeliveryServices.tpl.html @@ -98,6 +98,7 @@Regional Geoblocking | Raw Remap Text | Routing Name | +Service Category | Signed | Signing Algorithm | Range Slice Block Size | @@ -162,6 +163,7 @@{{::ds.regionalGeoBlocking}} | {{::ds.remapText}} | {{::ds.routingName}} | +{{::ds.serviceCategory}} | {{::ds.signed}} | {{::ds.signingAlgorithm}} | {{::ds.rangeSliceBlockSize}} | diff --git a/traffic_portal/app/src/modules/private/serviceCategories/deliveryServices/index.js b/traffic_portal/app/src/modules/private/serviceCategories/deliveryServices/index.js new file mode 100644 index 0000000000..ac937942d1 --- /dev/null +++ b/traffic_portal/app/src/modules/private/serviceCategories/deliveryServices/index.js @@ -0,0 +1,42 @@ +/* + * 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. + */ + +module.exports = angular.module('trafficPortal.private.serviceCategories.deliveryServices', []) + .config(function($stateProvider, $urlRouterProvider) { + $stateProvider + .state('trafficPortal.private.serviceCategories.deliveryServices', { + url: '/{serviceCategory}/delivery-services', + views: { + serviceCategoriesContent: { + templateUrl: 'common/modules/table/serviceCategoryDeliveryServices/table.serviceCategoryDeliveryServices.tpl.html', + controller: 'TableServiceCategoryDeliveryServicesController', + resolve: { + serviceCategory: function($stateParams, serviceCategoryService) { + return serviceCategoryService.getServiceCategory($stateParams.serviceCategory); + }, + deliveryServices: function(serviceCategory, deliveryServiceService) { + return deliveryServiceService.getDeliveryServices({ serviceCategory: serviceCategory.name }); + } + } + } + } + }) + ; + $urlRouterProvider.otherwise('/'); + }); diff --git a/traffic_portal/app/src/modules/private/serviceCategories/edit/index.js b/traffic_portal/app/src/modules/private/serviceCategories/edit/index.js new file mode 100644 index 0000000000..2fd9486025 --- /dev/null +++ b/traffic_portal/app/src/modules/private/serviceCategories/edit/index.js @@ -0,0 +1,39 @@ +/* + * 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. + */ + +module.exports = angular.module('trafficPortal.private.serviceCategories.edit', []) + .config(function($stateProvider, $urlRouterProvider) { + $stateProvider + .state('trafficPortal.private.serviceCategories.edit', { + url: '/edit?name', + views: { + serviceCategoriesContent: { + templateUrl: 'common/modules/form/serviceCategory/form.serviceCategory.tpl.html', + controller: 'FormEditServiceCategoryController', + resolve: { + serviceCategory: function($stateParams, serviceCategoryService) { + return serviceCategoryService.getServiceCategory($stateParams.name); + } + } + } + } + }) + ; + $urlRouterProvider.otherwise('/'); + }); diff --git a/traffic_portal/app/src/modules/private/serviceCategories/index.js b/traffic_portal/app/src/modules/private/serviceCategories/index.js new file mode 100644 index 0000000000..d8d3f7c3bb --- /dev/null +++ b/traffic_portal/app/src/modules/private/serviceCategories/index.js @@ -0,0 +1,34 @@ +/* + * 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. + */ + +module.exports = angular.module('trafficPortal.private.serviceCategories', []) + .config(function($stateProvider, $urlRouterProvider) { + $stateProvider + .state('trafficPortal.private.serviceCategories', { + url: 'service-categories', + abstract: true, + views: { + privateContent: { + templateUrl: 'modules/private/serviceCategories/serviceCategories.tpl.html' + } + } + }) + ; + $urlRouterProvider.otherwise('/'); + }); diff --git a/traffic_portal/app/src/modules/private/serviceCategories/list/index.js b/traffic_portal/app/src/modules/private/serviceCategories/list/index.js new file mode 100644 index 0000000000..8d17947532 --- /dev/null +++ b/traffic_portal/app/src/modules/private/serviceCategories/list/index.js @@ -0,0 +1,39 @@ +/* + * 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. + */ + +module.exports = angular.module('trafficPortal.private.serviceCategories.list', []) + .config(function($stateProvider, $urlRouterProvider) { + $stateProvider + .state('trafficPortal.private.serviceCategories.list', { + url: '', + views: { + serviceCategoriesContent: { + templateUrl: 'common/modules/table/serviceCategories/table.serviceCategories.tpl.html', + controller: 'TableServiceCategoriesController', + resolve: { + serviceCategories: function(serviceCategoryService) { + return serviceCategoryService.getServiceCategories(); + } + } + } + } + }) + ; + $urlRouterProvider.otherwise('/'); + }); diff --git a/traffic_portal/app/src/modules/private/serviceCategories/new/index.js b/traffic_portal/app/src/modules/private/serviceCategories/new/index.js new file mode 100644 index 0000000000..290ff2ed70 --- /dev/null +++ b/traffic_portal/app/src/modules/private/serviceCategories/new/index.js @@ -0,0 +1,39 @@ +/* + * 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. + */ + +module.exports = angular.module('trafficPortal.private.serviceCategories.new', []) + .config(function($stateProvider, $urlRouterProvider) { + $stateProvider + .state('trafficPortal.private.serviceCategories.new', { + url: '/new', + views: { + serviceCategoriesContent: { + templateUrl: 'common/modules/form/serviceCategory/form.serviceCategory.tpl.html', + controller: 'FormNewServiceCategoryController', + resolve: { + serviceCategory: function() { + return {}; + } + } + } + } + }) + ; + $urlRouterProvider.otherwise('/'); + }); diff --git a/traffic_portal/app/src/modules/private/serviceCategories/serviceCategories.tpl.html b/traffic_portal/app/src/modules/private/serviceCategories/serviceCategories.tpl.html new file mode 100644 index 0000000000..d5bb90bd83 --- /dev/null +++ b/traffic_portal/app/src/modules/private/serviceCategories/serviceCategories.tpl.html @@ -0,0 +1,22 @@ + + +
|---|