diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dd5d1675d..64c4a14f49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ## [unreleased] ### Added +- [#6234](https://github.com/apache/trafficcontrol/issues/6234) *Traffic Ops, Traffic Portal* Added description field to Server Capabilities - [#6033](https://github.com/apache/trafficcontrol/issues/6033) *Traffic Ops, Traffic Portal* Added ability to assign multiple servers per capability. - [#7081](https://github.com/apache/trafficcontrol/issues/7081) *Traffic Router* Added better log messages for TR connection exceptions. - [#7089](https://github.com/apache/trafficcontrol/issues/7089) *Traffic Router* Added the ability to specify HTTPS certificate attributes. diff --git a/docs/source/api/v4/server_capabilities.rst b/docs/source/api/v4/server_capabilities.rst index 2f8915ea9d..a2472f83ad 100644 --- a/docs/source/api/v4/server_capabilities.rst +++ b/docs/source/api/v4/server_capabilities.rst @@ -50,6 +50,10 @@ Request Structure Response Structure ------------------ :name: The name of this :term:`Server Capability` +:description: The description of this :term:`Server Capability` + + .. versionadded:: 4.1 + :lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format .. code-block:: http @@ -71,6 +75,7 @@ Response Structure "response": [ { "name": "RAM", + "description": "ram server capability", "lastUpdated": "2019-10-07 20:38:24+00" } ] @@ -88,6 +93,10 @@ Create a new :term:`Server Capability`. Request Structure ----------------- :name: The name of the :term:`Server Capability` +:description: The description of this :term:`Server Capability` + + .. versionadded:: 4.1 + .. code-block:: http :caption: Request Example @@ -101,12 +110,17 @@ Request Structure Content-Type: application/json { - "name": "RAM" + "name": "RAM", + "description": "ram server capability", } Response Structure ------------------ :name: The name of this :term:`Server Capability` +:description: The description of this :term:`Server Capability` + + .. versionadded:: 4.1 + :lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format .. code-block:: http @@ -133,6 +147,7 @@ Response Structure ], "response": { "name": "RAM", + "description": "ram server capability", "lastUpdated": "2019-10-07 22:10:00+00" } } @@ -149,6 +164,10 @@ Update an existing :term:`Server Capability`. Request Structure ----------------- :name: The name of the :term:`Server Capability` +:description: The description of this :term:`Server Capability` + + .. versionadded:: 4.1 + .. code-block:: http :caption: Request Example @@ -162,12 +181,17 @@ Request Structure Content-Type: application/json { - "name": "HDD" + "name": "HDD", + "description": "HDD server capability" } Response Structure ------------------ :name: The name of this :term:`Server Capability` +:description: The description of this :term:`Server Capability` + + .. versionadded:: 4.1 + :lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format .. code-block:: http @@ -194,6 +218,7 @@ Response Structure ], "response": { "name": "HDD", + "description": "HDD server capability", "lastUpdated": "2021-03-03 21:22:08+00" } } diff --git a/docs/source/api/v5/server_capabilities.rst b/docs/source/api/v5/server_capabilities.rst index a6d99a6145..4827ca8ad7 100644 --- a/docs/source/api/v5/server_capabilities.rst +++ b/docs/source/api/v5/server_capabilities.rst @@ -50,6 +50,7 @@ Request Structure Response Structure ------------------ :name: The name of this :term:`Server Capability` +:description: The description of this :term:`Server Capability` :lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format .. code-block:: http @@ -71,6 +72,7 @@ Response Structure "response": [ { "name": "RAM", + "description": "ram server capability", "lastUpdated": "2019-10-07 20:38:24+00" } ] @@ -88,6 +90,7 @@ Create a new :term:`Server Capability`. Request Structure ----------------- :name: The name of the :term:`Server Capability` +:description: The description of this :term:`Server Capability` .. code-block:: http :caption: Request Example @@ -101,12 +104,14 @@ Request Structure Content-Type: application/json { - "name": "RAM" + "name": "RAM", + "description": "ram server capability", } Response Structure ------------------ :name: The name of this :term:`Server Capability` +:description: The description of this :term:`Server Capability` :lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format .. code-block:: http @@ -133,6 +138,7 @@ Response Structure ], "response": { "name": "RAM", + "description": "ram server capability", "lastUpdated": "2019-10-07 22:10:00+00" } } @@ -149,6 +155,7 @@ Update an existing :term:`Server Capability`. Request Structure ----------------- :name: The name of the :term:`Server Capability` +:description: The description of this :term:`Server Capability` .. code-block:: http :caption: Request Example @@ -162,12 +169,14 @@ Request Structure Content-Type: application/json { - "name": "HDD" + "name": "HDD", + "description": "HDD server capability" } Response Structure ------------------ :name: The name of this :term:`Server Capability` +:description: The description of this :term:`Server Capability` :lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format .. code-block:: http @@ -194,6 +203,7 @@ Response Structure ], "response": { "name": "HDD", + "description": "HDD server capability", "lastUpdated": "2021-03-03 21:22:08+00" } } diff --git a/infrastructure/cdn-in-a-box/enroller/enroller.go b/infrastructure/cdn-in-a-box/enroller/enroller.go index 0633ab51bb..c3a031afed 100644 --- a/infrastructure/cdn-in-a-box/enroller/enroller.go +++ b/infrastructure/cdn-in-a-box/enroller/enroller.go @@ -771,10 +771,10 @@ func enrollServer(toSession *session, r io.Reader) error { return err } -// enrollServerCapability takes a json file and creates a ServerCapability object using the TO API +// enrollServerCapability takes a json file and creates a ServerCapabilityV41 object using the TO API func enrollServerCapability(toSession *session, r io.Reader) error { dec := json.NewDecoder(r) - var s tc.ServerCapability + var s tc.ServerCapabilityV41 err := dec.Decode(&s) if err != nil { err = fmt.Errorf("error decoding Server Capability: %v", err) @@ -782,7 +782,7 @@ func enrollServerCapability(toSession *session, r io.Reader) error { return err } - alerts, _, err := toSession.CreateServerCapability(s, client.RequestOptions{}) + alerts, _, err := toSession.CreateServerCapabilityV41(s, client.RequestOptions{}) if err != nil { err = fmt.Errorf("error creating Server Capability: %v - alerts: %+v", err, alerts.Alerts) log.Infoln(err) diff --git a/lib/go-tc/server_capabilities.go b/lib/go-tc/server_capabilities.go index 45ab68b2c2..328cb6933f 100644 --- a/lib/go-tc/server_capabilities.go +++ b/lib/go-tc/server_capabilities.go @@ -25,14 +25,35 @@ type ServerCapabilitiesResponse struct { Alerts } +// ServerCapabilitiesResponseV41 contains the result data from a GET(v4.1 and above) /server_capabilities request. +type ServerCapabilitiesResponseV41 struct { + Response []ServerCapabilityV41 `json:"response"` + Alerts +} + // ServerCapability contains information about a given ServerCapability in Traffic Ops. type ServerCapability struct { Name string `json:"name" db:"name"` LastUpdated *TimeNoMod `json:"lastUpdated" db:"last_updated"` } +// ServerCapabilityV4 is an alias for the latest minor version for the major version 4. +type ServerCapabilityV4 ServerCapabilityV41 + +// ServerCapabilityV41 contains information (in-addition to description) about a given ServerCapability in Traffic Ops. +type ServerCapabilityV41 struct { + ServerCapability + Description string `json:"description" db:"description"` +} + // ServerCapabilityDetailResponse contains the result data from a POST /server_capabilities request. type ServerCapabilityDetailResponse struct { Response ServerCapability `json:"response"` Alerts } + +// ServerCapabilityDetailResponseV41 contains the result data from a POST(v4.1 and above) /server_capabilities request. +type ServerCapabilityDetailResponseV41 struct { + Response ServerCapabilityV41 `json:"response"` + Alerts +} diff --git a/traffic_ops/app/db/migrations/2022121612121500_add_description_field_in_sc_table.down.sql b/traffic_ops/app/db/migrations/2022121612121500_add_description_field_in_sc_table.down.sql new file mode 100644 index 0000000000..1974712758 --- /dev/null +++ b/traffic_ops/app/db/migrations/2022121612121500_add_description_field_in_sc_table.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. + */ + +ALTER TABLE public.server_capability DROP column description; diff --git a/traffic_ops/app/db/migrations/2022121612121500_add_description_field_in_sc_table.up.sql b/traffic_ops/app/db/migrations/2022121612121500_add_description_field_in_sc_table.up.sql new file mode 100644 index 0000000000..1713df8842 --- /dev/null +++ b/traffic_ops/app/db/migrations/2022121612121500_add_description_field_in_sc_table.up.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. + */ + +ALTER TABLE public.server_capability ADD COLUMN IF NOT EXISTS description text NOT NULL DEFAULT ''; diff --git a/traffic_ops/testing/api/v4/server_capabilities_test.go b/traffic_ops/testing/api/v4/server_capabilities_test.go index c34e926387..e3c60fcd86 100644 --- a/traffic_ops/testing/api/v4/server_capabilities_test.go +++ b/traffic_ops/testing/api/v4/server_capabilities_test.go @@ -36,7 +36,7 @@ func TestServerCapabilities(t *testing.T) { currentTime := time.Now().UTC().Add(-15 * time.Second) currentTimeRFC := currentTime.Format(time.RFC1123) - methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.ServerCapability]{ + methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.ServerCapabilityV41]{ "GET": { "OK when VALID request": { ClientSession: TOSession, @@ -57,20 +57,28 @@ func TestServerCapabilities(t *testing.T) { "POST": { "BAD REQUEST when ALREADY EXISTS": { ClientSession: TOSession, - RequestBody: tc.ServerCapability{Name: "foo"}, - Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "foo"}, + Description: "foo servers", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), }, "BAD REQUEST when INVALID NAME": { ClientSession: TOSession, - RequestBody: tc.ServerCapability{Name: "b@dname"}, - Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "b@dname"}, + Description: "Server Capability", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), }, }, "PUT": { "OK when VALID request": { ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"blah"}}}, - RequestBody: tc.ServerCapability{Name: "newname"}, + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "newname"}, + Description: "Server Capability for new name"}, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServerCapabilitiesUpdateFields(map[string]interface{}{"Name": "newname"}), validateSSCFieldsOnServerCapabilityUpdate("newname", map[string]interface{}{"ServerCapability": "newname"})), @@ -78,8 +86,11 @@ func TestServerCapabilities(t *testing.T) { "BAD REQUEST when NAME DOESNT EXIST": { ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"invalid"}}}, - RequestBody: tc.ServerCapability{Name: "newname"}, - Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "newname"}, + Description: "Server Capability for new name", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), }, "PRECONDITION FAILED when updating with IMS & IUS Headers": { ClientSession: TOSession, @@ -87,7 +98,10 @@ func TestServerCapabilities(t *testing.T) { QueryParameters: url.Values{"name": {"disk"}}, Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}, }, - RequestBody: tc.ServerCapability{Name: "newname"}, + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "newname"}, + Description: "Server Capability for new name", + }, Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), }, "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { @@ -96,7 +110,10 @@ func TestServerCapabilities(t *testing.T) { QueryParameters: url.Values{"name": {"disk"}}, Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}, }, - RequestBody: tc.ServerCapability{Name: "newname"}, + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "newname"}, + Description: "Server Capability for new name", + }, Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), }, }, @@ -120,21 +137,21 @@ func TestServerCapabilities(t *testing.T) { switch method { case "GET": t.Run(name, func(t *testing.T) { - resp, reqInf, err := testCase.ClientSession.GetServerCapabilities(testCase.RequestOpts) + resp, reqInf, err := testCase.ClientSession.GetServerCapabilitiesV41(testCase.RequestOpts) for _, check := range testCase.Expectations { check(t, reqInf, resp.Response, resp.Alerts, err) } }) case "POST": t.Run(name, func(t *testing.T) { - resp, reqInf, err := testCase.ClientSession.CreateServerCapability(testCase.RequestBody, testCase.RequestOpts) + resp, reqInf, err := testCase.ClientSession.CreateServerCapabilityV41(testCase.RequestBody, testCase.RequestOpts) for _, check := range testCase.Expectations { check(t, reqInf, resp.Response, resp.Alerts, err) } }) case "PUT": t.Run(name, func(t *testing.T) { - resp, reqInf, err := testCase.ClientSession.UpdateServerCapability(testCase.RequestOpts.QueryParameters["name"][0], testCase.RequestBody, testCase.RequestOpts) + resp, reqInf, err := testCase.ClientSession.UpdateServerCapabilityV41(testCase.RequestOpts.QueryParameters["name"][0], testCase.RequestBody, testCase.RequestOpts) for _, check := range testCase.Expectations { check(t, reqInf, resp.Response, resp.Alerts, err) } @@ -156,7 +173,7 @@ func TestServerCapabilities(t *testing.T) { func validateServerCapabilitiesUpdateFields(expectedResp map[string]interface{}) utils.CkReqFunc { return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { assert.RequireNotNil(t, resp, "Expected Server Capabilities response to not be nil.") - serverCapabilitiesResp := resp.(tc.ServerCapability) + serverCapabilitiesResp := resp.(tc.ServerCapabilityV41) for field, expected := range expectedResp { switch field { case "Name": @@ -172,7 +189,7 @@ func validateServerCapabilitiesSort() utils.CkReqFunc { return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { assert.RequireNotNil(t, resp, "Expected Server Capabilities response to not be nil.") var serverCapabilityNames []string - serverCapabilitiesResp := resp.([]tc.ServerCapability) + serverCapabilitiesResp := resp.([]tc.ServerCapabilityV41) for _, serverCapability := range serverCapabilitiesResp { serverCapabilityNames = append(serverCapabilityNames, serverCapability.Name) } @@ -182,7 +199,7 @@ func validateServerCapabilitiesSort() utils.CkReqFunc { func CreateTestServerCapabilities(t *testing.T) { for _, sc := range testData.ServerCapabilities { - resp, _, err := TOSession.CreateServerCapability(sc, client.RequestOptions{}) + resp, _, err := TOSession.CreateServerCapabilityV41(sc, client.RequestOptions{}) assert.RequireNoError(t, err, "Unexpected error creating Server Capability '%s': %v - alerts: %+v", sc.Name, err, resp.Alerts) } } diff --git a/traffic_ops/testing/api/v4/traffic_control_test.go b/traffic_ops/testing/api/v4/traffic_control_test.go index ddbc8b06a0..f9b1f93c9b 100644 --- a/traffic_ops/testing/api/v4/traffic_control_test.go +++ b/traffic_ops/testing/api/v4/traffic_control_test.go @@ -47,7 +47,7 @@ type TrafficControl struct { Roles []tc.RoleV4 `json:"roles"` Servers []tc.ServerV40 `json:"servers"` ServerServerCapabilities []tc.ServerServerCapability `json:"serverServerCapabilities"` - ServerCapabilities []tc.ServerCapability `json:"serverCapabilities"` + ServerCapabilities []tc.ServerCapabilityV41 `json:"serverCapabilities"` ServiceCategories []tc.ServiceCategory `json:"serviceCategories"` Statuses []tc.StatusNullable `json:"statuses"` StaticDNSEntries []tc.StaticDNSEntry `json:"staticdnsentries"` diff --git a/traffic_ops/testing/api/v5/server_capabilities_test.go b/traffic_ops/testing/api/v5/server_capabilities_test.go index a1b9f899f3..97182eabf7 100644 --- a/traffic_ops/testing/api/v5/server_capabilities_test.go +++ b/traffic_ops/testing/api/v5/server_capabilities_test.go @@ -36,7 +36,7 @@ func TestServerCapabilities(t *testing.T) { currentTime := time.Now().UTC().Add(-15 * time.Second) currentTimeRFC := currentTime.Format(time.RFC1123) - methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.ServerCapability]{ + methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.ServerCapabilityV41]{ "GET": { "OK when VALID request": { ClientSession: TOSession, @@ -57,20 +57,29 @@ func TestServerCapabilities(t *testing.T) { "POST": { "BAD REQUEST when ALREADY EXISTS": { ClientSession: TOSession, - RequestBody: tc.ServerCapability{Name: "foo"}, - Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "foo"}, + Description: "foo servers", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), }, "BAD REQUEST when INVALID NAME": { ClientSession: TOSession, - RequestBody: tc.ServerCapability{Name: "b@dname"}, - Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "b@dname"}, + Description: "Server Capability", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), }, }, "PUT": { "OK when VALID request": { ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"blah"}}}, - RequestBody: tc.ServerCapability{Name: "newname"}, + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "newname"}, + Description: "Server Capability for new name", + }, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServerCapabilitiesUpdateFields(map[string]interface{}{"Name": "newname"}), validateSSCFieldsOnServerCapabilityUpdate("newname", map[string]interface{}{"ServerCapability": "newname"})), @@ -78,8 +87,11 @@ func TestServerCapabilities(t *testing.T) { "BAD REQUEST when NAME DOESNT EXIST": { ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"invalid"}}}, - RequestBody: tc.ServerCapability{Name: "newname"}, - Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "newname"}, + Description: "Server Capability for new name", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), }, "PRECONDITION FAILED when updating with IMS & IUS Headers": { ClientSession: TOSession, @@ -87,7 +99,10 @@ func TestServerCapabilities(t *testing.T) { QueryParameters: url.Values{"name": {"disk"}}, Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}, }, - RequestBody: tc.ServerCapability{Name: "newname"}, + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "newname"}, + Description: "Server Capability for new name", + }, Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), }, "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { @@ -96,7 +111,10 @@ func TestServerCapabilities(t *testing.T) { QueryParameters: url.Values{"name": {"disk"}}, Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}, }, - RequestBody: tc.ServerCapability{Name: "newname"}, + RequestBody: tc.ServerCapabilityV41{ + ServerCapability: tc.ServerCapability{Name: "newname"}, + Description: "Server Capability for new name", + }, Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), }, }, @@ -156,7 +174,7 @@ func TestServerCapabilities(t *testing.T) { func validateServerCapabilitiesUpdateFields(expectedResp map[string]interface{}) utils.CkReqFunc { return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { assert.RequireNotNil(t, resp, "Expected Server Capabilities response to not be nil.") - serverCapabilitiesResp := resp.(tc.ServerCapability) + serverCapabilitiesResp := resp.(tc.ServerCapabilityV41) for field, expected := range expectedResp { switch field { case "Name": @@ -172,7 +190,7 @@ func validateServerCapabilitiesSort() utils.CkReqFunc { return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { assert.RequireNotNil(t, resp, "Expected Server Capabilities response to not be nil.") var serverCapabilityNames []string - serverCapabilitiesResp := resp.([]tc.ServerCapability) + serverCapabilitiesResp := resp.([]tc.ServerCapabilityV41) for _, serverCapability := range serverCapabilitiesResp { serverCapabilityNames = append(serverCapabilityNames, serverCapability.Name) } diff --git a/traffic_ops/testing/api/v5/tc-fixtures.json b/traffic_ops/testing/api/v5/tc-fixtures.json index 18dc10c256..8df76f8d48 100644 --- a/traffic_ops/testing/api/v5/tc-fixtures.json +++ b/traffic_ops/testing/api/v5/tc-fixtures.json @@ -5431,22 +5431,28 @@ ], "serverCapabilities": [ { - "name": "foo" + "name": "foo", + "description": "server capability foo" }, { - "name": "bar" + "name": "bar", + "description": "server capability bar" }, { - "name": "ram" + "name": "ram", + "description": "server capability ram" }, { - "name": "disk" + "name": "disk", + "description": "server capability disk" }, { - "name": "asdf" + "name": "asdf", + "description": "server capability asdf" }, { - "name": "blah" + "name": "blah", + "description": "server capability blah" } ], "serverServerCapabilities": [ diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go b/traffic_ops/testing/api/v5/traffic_control_test.go index acabaac327..bb747942e6 100644 --- a/traffic_ops/testing/api/v5/traffic_control_test.go +++ b/traffic_ops/testing/api/v5/traffic_control_test.go @@ -47,7 +47,7 @@ type TrafficControl struct { Roles []tc.RoleV4 `json:"roles"` Servers []tc.ServerV4 `json:"servers"` ServerServerCapabilities []tc.ServerServerCapability `json:"serverServerCapabilities"` - ServerCapabilities []tc.ServerCapability `json:"serverCapabilities"` + ServerCapabilities []tc.ServerCapabilityV41 `json:"serverCapabilities"` ServiceCategories []tc.ServiceCategory `json:"serviceCategories"` Statuses []tc.StatusNullable `json:"statuses"` StaticDNSEntries []tc.StaticDNSEntry `json:"staticdnsentries"` diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go index 7b1dae9fd0..719d91624c 100644 --- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go +++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go @@ -2184,3 +2184,18 @@ func GetProfileIDDesc(tx *sql.Tx, name string) (id int, desc string) { } return } + +// GetSCInfo confirms whether the server capability exists, and an error (if one occurs). +func GetSCInfo(tx *sql.Tx, name string) (bool, error) { + var count int + if err := tx.QueryRow("SELECT count(name) FROM server_capability AS sc WHERE sc.name=$1", name).Scan(&count); err != nil { + return false, fmt.Errorf("error getting server capability info: %w", err) + } + if count == 0 { + return false, nil + } + if count != 1 { + return false, fmt.Errorf("getting server capability info - expected row count: 1, actual: %d", count) + } + return true, nil +} diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go index b388e8e995..282311caca 100644 --- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go +++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go @@ -21,6 +21,7 @@ package dbhelpers import ( "context" + "database/sql" "errors" "reflect" "strconv" @@ -408,3 +409,54 @@ func TestGetCDNIDFromName(t *testing.T) { } } + +func TestGetSCInfo(t *testing.T) { + var testCases = []struct { + description string + name string + expectedError error + exists bool + }{ + { + description: "Success: Get valid SC", + name: "hdd", + expectedError: nil, + exists: true, + }, + { + description: "Failure: SC not in DB", + name: "disk", + expectedError: sql.ErrNoRows, + exists: false, + }, + } + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + mockDB, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer mockDB.Close() + + db := sqlx.NewDb(mockDB, "sqlmock") + defer db.Close() + + mock.ExpectBegin() + rows := sqlmock.NewRows([]string{"count"}) + if testCase.exists { + rows = rows.AddRow(1) + } + mock.ExpectQuery("SELECT").WillReturnRows(rows) + mock.ExpectCommit() + + scExists, err := GetSCInfo(db.MustBegin().Tx, testCase.name) + if testCase.exists != scExists { + t.Errorf("Expected return exists: %t, actual %t", testCase.exists, scExists) + } + + if !errors.Is(err, testCase.expectedError) { + t.Errorf("getSCInfo expected: %s, actual: %s", testCase.expectedError, err) + } + }) + } +} diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index 87703283d0..2a3df2e1ba 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -324,10 +324,10 @@ func Routes(d ServerData) ([]Route, http.Handler, error) { {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `servers/{id}$`, Handler: server.Delete, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:DELETE", "SERVER:READ", "DELIVERY-SERVICE:READ", "CDN:READ", "PHYSICAL-LOCATION:READ", "CACHE-GROUP:READ", "TYPE:READ", "PROFILE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 49232223331}, //Server Capability - {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `server_capabilities$`, Handler: api.ReadHandler(&servercapability.TOServerCapability{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41040739131}, - {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `server_capabilities$`, Handler: api.CreateHandler(&servercapability.TOServerCapability{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER-CAPABILITY:CREATE", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 407447070831}, - {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `server_capabilities$`, Handler: api.UpdateHandler(&servercapability.TOServerCapability{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER-CAPABILITY:UPDATE", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 425437701091}, - {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `server_capabilities$`, Handler: api.DeleteHandler(&servercapability.TOServerCapability{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER-CAPABILITY:DELETE", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 43641503831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `server_capabilities$`, Handler: servercapability.GetServerCapability, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41040739131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `server_capabilities$`, Handler: servercapability.CreateServerCapability, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER-CAPABILITY:CREATE", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 407447070831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `server_capabilities$`, Handler: servercapability.UpdateServerCapability, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER-CAPABILITY:UPDATE", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 425437701091}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `server_capabilities$`, Handler: servercapability.DeleteServerCapability, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER-CAPABILITY:DELETE", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 43641503831}, {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `multiple_servers_capabilities/?$`, Handler: server.AssignMultipleServersCapabilities, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:READ", "SERVER:CREATE", "SERVER-CAPABILITY:READ", "SERVER-CAPABILITY:CREATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 407924192581}, {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `multiple_servers_capabilities/?$`, Handler: server.DeleteMultipleServersCapabilities, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:READ", "SERVER:DELETE", "SERVER-CAPABILITY:READ", "SERVER-CAPABILITY:DELETE"}, Authenticated: Authenticated, Middlewares: nil, ID: 407924192781}, @@ -535,6 +535,11 @@ func Routes(d ServerData) ([]Route, http.Handler, error) { {Version: api.Version{Major: 4, Minor: 1}, Method: http.MethodPost, Path: `multiple_servers_capabilities/?$`, Handler: server.AssignMultipleServersCapabilities, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:READ", "SERVER:CREATE", "SERVER-CAPABILITY:READ", "SERVER-CAPABILITY:CREATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 40792419258}, {Version: api.Version{Major: 4, Minor: 1}, Method: http.MethodDelete, Path: `multiple_servers_capabilities/?$`, Handler: server.DeleteMultipleServersCapabilities, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:READ", "SERVER:DELETE", "SERVER-CAPABILITY:READ", "SERVER-CAPABILITY:DELETE"}, Authenticated: Authenticated, Middlewares: nil, ID: 40792419278}, + //Server Capability + {Version: api.Version{Major: 4, Minor: 1}, Method: http.MethodGet, Path: `server_capabilities$`, Handler: servercapability.GetServerCapability, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4104073912}, + {Version: api.Version{Major: 4, Minor: 1}, Method: http.MethodPost, Path: `server_capabilities$`, Handler: servercapability.CreateServerCapability, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER-CAPABILITY:CREATE", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40744707025}, + {Version: api.Version{Major: 4, Minor: 1}, Method: http.MethodPut, Path: `server_capabilities$`, Handler: servercapability.UpdateServerCapability, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER-CAPABILITY:UPDATE", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 425437701023}, + // 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}, diff --git a/traffic_ops/traffic_ops_golang/servercapability/servercapability.go b/traffic_ops/traffic_ops_golang/servercapability/servercapability.go index cd4f122d5e..8d9d60e442 100644 --- a/traffic_ops/traffic_ops_golang/servercapability/servercapability.go +++ b/traffic_ops/traffic_ops_golang/servercapability/servercapability.go @@ -20,6 +20,9 @@ package servercapability */ import ( + "database/sql" + "encoding/json" + "errors" "fmt" "net/http" "time" @@ -135,7 +138,7 @@ func (v *TOServerCapability) Update(h http.Header) (error, error, int) { return userErr, sysErr, errCode } - // udpate server capability name + // update server capability name rows, err := v.ReqInfo.Tx.Query(v.updateQuery(), v.RequestedName, v.Name) if err != nil { return nil, fmt.Errorf("server capability update: error setting the name for server capability %v: %v", v.Name, err.Error()), http.StatusInternalServerError @@ -151,6 +154,213 @@ func (v *TOServerCapability) Update(h http.Header) (error, error, int) { return nil, nil, http.StatusOK } +func GetServerCapability(w http.ResponseWriter, r *http.Request) { + var sc tc.ServerCapabilityV4 + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil) + tx := inf.Tx + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx.Tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + + // Query Parameters to Database Query column mappings + queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{ + "name": {Column: "sc.name", Checker: nil}, + } + if _, ok := inf.Params["orderby"]; !ok { + inf.Params["orderby"] = "name" + } + where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols) + if len(errs) > 0 { + api.HandleErr(w, r, tx.Tx, http.StatusBadRequest, util.JoinErrs(errs), nil) + return + } + + selectQuery := "SELECT name, description, last_updated FROM server_capability sc" + query := selectQuery + where + orderBy + pagination + rows, err := tx.NamedQuery(query, queryValues) + if err != nil { + api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("server capability read: error getting server capability(ies): %w", err)) + return + } + defer log.Close(rows, "unable to close DB connection") + + scList := []tc.ServerCapabilityV4{} + for rows.Next() { + if err = rows.Scan(&sc.Name, &sc.Description, &sc.LastUpdated); err != nil { + api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("error getting server capability(ies): %w", err)) + return + } + scList = append(scList, sc) + } + + api.WriteResp(w, r, scList) + return +} + +func UpdateServerCapability(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() + + tx := inf.Tx.Tx + sc, readValErr := readAndValidateJsonStruct(r) + if readValErr != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil) + return + } + + requestedName := inf.Params["name"] + // check if the entity was already updated + userErr, sysErr, errCode = api.CheckIfUnModifiedByName(r.Header, inf.Tx, requestedName, "server_capability") + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + + //update name and description of a capability + query := `UPDATE server_capability sc SET + name = $1, + description = $2 + WHERE sc.name = $3 + RETURNING sc.name, sc.description, sc.last_updated` + + err := tx.QueryRow(query, sc.Name, sc.Description, requestedName).Scan(&sc.Name, &sc.Description, &sc.LastUpdated) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("server capability with name: %s not found", sc.Name), nil) + return + } + usrErr, sysErr, code := api.ParseDBError(err) + api.HandleErr(w, r, tx, code, usrErr, sysErr) + return + } + alerts := tc.CreateAlerts(tc.SuccessLevel, "server capability was updated") + api.WriteAlertsObj(w, r, http.StatusOK, alerts, sc) + return +} + +func CreateServerCapability(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() + tx := inf.Tx.Tx + + sc, readValErr := readAndValidateJsonStruct(r) + if readValErr != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil) + return + } + + // check if capability already exists + var count int + err := tx.QueryRow("SELECT count(*) from server_capability where name = $1", sc.Name).Scan(&count) + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("error: %w, when checking if server capability with name %s exists", err, sc.Name)) + return + } + if count == 1 { + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("server_capability name '%s' already exists.", sc.Name), nil) + return + } + + // create server capability + query := `INSERT INTO server_capability (name, description) VALUES ($1, $2) RETURNING last_updated` + err = tx.QueryRow(query, sc.Name, sc.Description).Scan(&sc.LastUpdated) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + api.HandleErr(w, r, tx, http.StatusInternalServerError, fmt.Errorf("error: %w in creating server capability with name: %s", err, sc.Name), nil) + return + } + usrErr, sysErr, code := api.ParseDBError(err) + api.HandleErr(w, r, tx, code, usrErr, sysErr) + return + } + alerts := tc.CreateAlerts(tc.SuccessLevel, "server capability was created.") + w.Header().Set("Location", fmt.Sprintf("/api/%d.%d/server_capabilities?name=%s", inf.Version.Major, inf.Version.Minor, sc.Name)) + api.WriteAlertsObj(w, r, http.StatusCreated, alerts, sc) + return +} + +func DeleteServerCapability(w http.ResponseWriter, r *http.Request) { + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil) + tx := inf.Tx.Tx + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + + name := inf.Params["name"] + exists, err := dbhelpers.GetSCInfo(tx, name) + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err) + return + } + if !exists { + if name != "" { + api.HandleErr(w, r, tx, http.StatusNotFound, fmt.Errorf("no server capability exists by name: %s", name), nil) + return + } else { + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("no server capability exists for empty name: %s", name), nil) + return + } + } + + assignedServer := 0 + if err := inf.Tx.Get(&assignedServer, "SELECT count(server) FROM server_server_capability ssc WHERE ssc.server_capability=$1", name); err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("server capability delete, counting assigned servers: %w", err)) + return + } else if assignedServer != 0 { + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("can not delete a server capability with %d assigned servers", assignedServer), nil) + return + } + + res, err := tx.Exec("DELETE FROM server_capability AS sc WHERE sc.name=$1", name) + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err) + return + } + rowsAffected, err := res.RowsAffected() + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("determining rows affected for delete server capability: %w", err)) + return + } + if rowsAffected == 0 { + api.HandleErr(w, r, tx, http.StatusInternalServerError, fmt.Errorf("no rows deleted for server capability"), nil) + return + } + alerts := tc.CreateAlerts(tc.SuccessLevel, "server capability was deleted.") + api.WriteAlertsObj(w, r, http.StatusOK, alerts, name) + return +} + +func readAndValidateJsonStruct(r *http.Request) (tc.ServerCapabilityV41, error) { + var sc tc.ServerCapabilityV41 + if err := json.NewDecoder(r.Body).Decode(&sc); err != nil { + userErr := fmt.Errorf("error decoding POST request body into ServerCapabilityV41 struct %w", err) + return sc, userErr + } + + // validate JSON body + rule := validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist of only alphanumeric, dash, or underscore characters") + errs := tovalidate.ToErrors(validation.Errors{ + "name": validation.Validate(sc.Name, validation.Required, rule), + }) + if len(errs) > 0 { + userErr := util.JoinErrs(errs) + return sc, userErr + } + return sc, nil +} + func (v *TOServerCapability) SelectMaxLastUpdatedQuery(where, orderBy, pagination, tableName string) string { return `SELECT max(t) from ( SELECT max(sc.last_updated) as t from server_capability sc ` + where + orderBy + pagination + diff --git a/traffic_ops/v4-client/servercapability.go b/traffic_ops/v4-client/servercapability.go index 511b42e52c..1b41e18827 100644 --- a/traffic_ops/v4-client/servercapability.go +++ b/traffic_ops/v4-client/servercapability.go @@ -27,20 +27,37 @@ import ( const apiServerCapabilities = "/server_capabilities" // CreateServerCapability creates the given Server Capability. +// Note: Call Signature changed in v5, accepts tc.ServerCapabilityV41 instead of tc.ServerCapability func (to *Session) CreateServerCapability(sc tc.ServerCapability, opts RequestOptions) (tc.ServerCapabilityDetailResponse, toclientlib.ReqInf, error) { var scResp tc.ServerCapabilityDetailResponse reqInf, err := to.post(apiServerCapabilities, opts, sc, &scResp) return scResp, reqInf, err } +// CreateServerCapabilityV41 creates the given Server Capability V41 (includes description). +func (to *Session) CreateServerCapabilityV41(sc tc.ServerCapabilityV41, opts RequestOptions) (tc.ServerCapabilityDetailResponseV41, toclientlib.ReqInf, error) { + var scResp tc.ServerCapabilityDetailResponseV41 + reqInf, err := to.post(apiServerCapabilities, opts, sc, &scResp) + return scResp, reqInf, err +} + // GetServerCapabilities returns all the Server Capabilities in Traffic Ops. +// Note: Call Signature changed in v5, accepts tc.ServerCapabilitiesResponseV41 instead of tc.ServerCapabilitiesResponse func (to *Session) GetServerCapabilities(opts RequestOptions) (tc.ServerCapabilitiesResponse, toclientlib.ReqInf, error) { var data tc.ServerCapabilitiesResponse reqInf, err := to.get(apiServerCapabilities, opts, &data) return data, reqInf, err } +// GetServerCapabilitiesV41 returns all the Server Capabilities V41 (includes description) in Traffic Ops. +func (to *Session) GetServerCapabilitiesV41(opts RequestOptions) (tc.ServerCapabilitiesResponseV41, toclientlib.ReqInf, error) { + var data tc.ServerCapabilitiesResponseV41 + reqInf, err := to.get(apiServerCapabilities, opts, &data) + return data, reqInf, err +} + // UpdateServerCapability updates a Server Capability by name. +// Note: Call Signature changed in v5, accepts tc.ServerCapabilityV41 instead of tc.ServerCapability func (to *Session) UpdateServerCapability(name string, sc tc.ServerCapability, opts RequestOptions) (tc.ServerCapabilityDetailResponse, toclientlib.ReqInf, error) { if opts.QueryParameters == nil { opts.QueryParameters = url.Values{} @@ -51,6 +68,17 @@ func (to *Session) UpdateServerCapability(name string, sc tc.ServerCapability, o return data, reqInf, err } +// UpdateServerCapabilityV41 updates a Server Capability V41 (includes description) by name. +func (to *Session) UpdateServerCapabilityV41(name string, sc tc.ServerCapabilityV41, opts RequestOptions) (tc.ServerCapabilityDetailResponseV41, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("name", name) + var data tc.ServerCapabilityDetailResponseV41 + reqInf, err := to.put(apiServerCapabilities, opts, sc, &data) + return data, reqInf, err +} + // DeleteServerCapability deletes the given server capability by name. func (to *Session) DeleteServerCapability(name string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { if opts.QueryParameters == nil { diff --git a/traffic_ops/v5-client/servercapability.go b/traffic_ops/v5-client/servercapability.go index 511b42e52c..9e9d8567c3 100644 --- a/traffic_ops/v5-client/servercapability.go +++ b/traffic_ops/v5-client/servercapability.go @@ -27,26 +27,26 @@ import ( const apiServerCapabilities = "/server_capabilities" // CreateServerCapability creates the given Server Capability. -func (to *Session) CreateServerCapability(sc tc.ServerCapability, opts RequestOptions) (tc.ServerCapabilityDetailResponse, toclientlib.ReqInf, error) { - var scResp tc.ServerCapabilityDetailResponse +func (to *Session) CreateServerCapability(sc tc.ServerCapabilityV41, opts RequestOptions) (tc.ServerCapabilityDetailResponseV41, toclientlib.ReqInf, error) { + var scResp tc.ServerCapabilityDetailResponseV41 reqInf, err := to.post(apiServerCapabilities, opts, sc, &scResp) return scResp, reqInf, err } // GetServerCapabilities returns all the Server Capabilities in Traffic Ops. -func (to *Session) GetServerCapabilities(opts RequestOptions) (tc.ServerCapabilitiesResponse, toclientlib.ReqInf, error) { - var data tc.ServerCapabilitiesResponse +func (to *Session) GetServerCapabilities(opts RequestOptions) (tc.ServerCapabilitiesResponseV41, toclientlib.ReqInf, error) { + var data tc.ServerCapabilitiesResponseV41 reqInf, err := to.get(apiServerCapabilities, opts, &data) return data, reqInf, err } // UpdateServerCapability updates a Server Capability by name. -func (to *Session) UpdateServerCapability(name string, sc tc.ServerCapability, opts RequestOptions) (tc.ServerCapabilityDetailResponse, toclientlib.ReqInf, error) { +func (to *Session) UpdateServerCapability(name string, sc tc.ServerCapabilityV41, opts RequestOptions) (tc.ServerCapabilityDetailResponseV41, toclientlib.ReqInf, error) { if opts.QueryParameters == nil { opts.QueryParameters = url.Values{} } opts.QueryParameters.Set("name", name) - var data tc.ServerCapabilityDetailResponse + var data tc.ServerCapabilityDetailResponseV41 reqInf, err := to.put(apiServerCapabilities, opts, sc, &data) return data, reqInf, err } diff --git a/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html b/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html index f2875fda32..0d8c85d548 100644 --- a/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html +++ b/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html @@ -41,6 +41,12 @@ +