diff --git a/CHANGELOG.md b/CHANGELOG.md index a435a14d61..d3482e6707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - [#7641](https://github.com/apache/trafficcontrol/pull/7641) *Traffic Router* Added further optimization to TR's algorithm of figuring out the zone for an incoming request. - [#7646](https://github.com/apache/trafficcontrol/pull/7646) *Traffic Portal* Add the ability to delete a cert. - [#7652](https://github.com/apache/trafficcontrol/pull/7652) *t3c* added rpmdb checks and use package data from t3c-apply-metadata.json if rpmdb is corrupt. +- [#7674](https://github.com/apache/trafficcontrol/issues/7674) *Traffic Ops* Add the ability to indicate if a server failed its revalidate/config update. ### Changed - [#7732](https://github.com/apache/trafficcontrol/pull/7732) *Traffic Router* Increased negative TTL value to 900 seconds. diff --git a/docs/source/api/v5/servers.rst b/docs/source/api/v5/servers.rst index cf1af30b41..1166d01146 100644 --- a/docs/source/api/v5/servers.rst +++ b/docs/source/api/v5/servers.rst @@ -108,8 +108,9 @@ Response Structure .. versionchanged:: 5.0 In earlier versions of the API, this field was known by the name ``cdnName``. It has been changed for consistency with others e.g. ``type``, ``status``, etc. -:configUpdateTime: The last time an update was requested for this server. This field defaults to standard epoch -:configApplyTime: The last time an update was applied for this server. This field defaults to standard epoch +:configUpdateTime: The last time an update was requested for this server. This field defaults to standard epoch +:configApplyTime: The last time an update was applied for this server. This field defaults to standard epoch +:configUpdateFailed: If the last update applied for this server was applied successfully. Defaults to false. :domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` :guid: An identifier used to uniquely identify the server @@ -177,10 +178,11 @@ Response Structure .. versionchanged:: 5.0 In earlier versions of the API, this field was known by the name ``profileNames`` - it has been changed because now that this is the only identifying information for a :term:Profile that exists on a server, there is no need to distinguish it from, say, an ID. -:revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch -:revalApplyTime: The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch -:rack: A string indicating "server rack" location -:status: The :term:`Status` of the server +:revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch +:revalApplyTime: The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch +:revalUpdateFailed: If the last content invalidation/revalidation applied for this server was applied successfully. Defaults to false. +:rack: A string indicating "server rack" location +:status: The :term:`Status` of the server .. seealso:: :ref:`health-proto` @@ -223,6 +225,7 @@ Response Structure "cdn": "CDN-in-a-Box", "configUpdateTime": "1969-12-31T17:00:00-07:00", "configApplyTime": "1969-12-31T17:00:00-07:00", + "configUpdateFailed": false, "domainName": "infra.ciab.test", "guid": null, "hostName": "mid", @@ -244,6 +247,7 @@ Response Structure "rack": "", "revalUpdateTime": "1969-12-31T17:00:00-07:00", "revalApplyTime": "1969-12-31T17:00:00-07:00", + "revalUpdateFailed": false, "status": "REPORTED", "statusID": 3, "tcpPort": 80, @@ -444,6 +448,7 @@ Response Structure :configUpdateTime: The last time an update was requested for this server. This field defaults to standard epoch :configApplyTime: The last time an update was applied for this server. This field defaults to standard epoch +:configUpdateFailed: If the last update applied for this server was applied successfully. Defaults to false. :domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` :guid: An identifier used to uniquely identify the server @@ -513,6 +518,7 @@ Response Structure :revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch :revalApplyTime: The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch +:revalUpdateFailed: If the last content invalidation/revalidation applied for this server was applied successfully. Defaults to false. :rack: A string indicating "server rack" location :status: The status of the server @@ -563,6 +569,7 @@ Response Structure "cdn": "CDN-in-a-Box", "configUpdateTime": "1969-12-31T17:00:00-07:00", "configApplyTime": "1969-12-31T17:00:00-07:00", + "configUpdateFailed": false, "domainName": "infra.ciab.test", "guid": null, "hostName": "test", @@ -584,6 +591,7 @@ Response Structure "rack": null, "revalUpdateTime": "1969-12-31T17:00:00-07:00", "revalApplyTime": "1969-12-31T17:00:00-07:00", + "revalUpdateFailed": false, "status": "REPORTED", "statusID": 3, "tcpPort": 80, diff --git a/docs/source/api/v5/servers_hostname_update.rst b/docs/source/api/v5/servers_hostname_update.rst index 6caf870f41..b6cbdc492c 100644 --- a/docs/source/api/v5/servers_hostname_update.rst +++ b/docs/source/api/v5/servers_hostname_update.rst @@ -49,8 +49,12 @@ Request Structure +----------------------------+----------+--------------------------------------------------------------------------------------------------------------+ | config_apply_time | no | The value to set for when a queue update is applied for this server. Must be a valid RFC333Nano timestamp. | +----------------------------+----------+--------------------------------------------------------------------------------------------------------------+ + | config_update_failed | no | The value to set for when a queue update is applied for this server. May be 'true' or 'false'. | + +----------------------------+----------+--------------------------------------------------------------------------------------------------------------+ | revalidate_apply_time | no | The value to set for when a reval update is applied for this server. Must be a valid RFC333Nano timestamp. | +----------------------------+----------+--------------------------------------------------------------------------------------------------------------+ + | revalidate_update_failed | no | The value to set for when a reval update is applied for this server. May be 'true' or 'false'. | + +----------------------------+----------+--------------------------------------------------------------------------------------------------------------+ .. note:: While none of the timestamps is required individually, at least one must be sent to the API. @@ -87,7 +91,7 @@ Response Structure "alerts" : [ { - "text" : "successfully set server 'my-edge' config_apply_time=2022-01-31T12:00:00.123456-07:00 revalidate_apply_time=2022-01-31T12:00:00.123456-07:00", + "text" : "successfully set server 'my-edge' config_apply_time=2022-01-31T12:00:00.123456-07:00 revalidate_apply_time=2022-01-31T12:00:00.123456-07:00 config_update_failed false revalidate_update_failed false", "level" : "success" } ] diff --git a/docs/source/api/v5/servers_hostname_update_status.rst b/docs/source/api/v5/servers_hostname_update_status.rst index 5e35da9d75..3248abe7f1 100644 --- a/docs/source/api/v5/servers_hostname_update_status.rst +++ b/docs/source/api/v5/servers_hostname_update_status.rst @@ -55,6 +55,7 @@ Each object in the returned array\ [#uniqueness]_ will contain the following fie :configUpdateTime: The last time an update was requested for this server. This field defaults to standard epoch :configApplyTime: The last time an update was applied for this server. This field defaults to standard epoch +:configUpdateFailed: The status of the last time an update was applied by this server. Defaults to ``false`` :host_id: The integral, unique identifier for the server for which the other fields in this object represent the pending updates and revalidation status :host_name: The (short) hostname of the server for which the other fields in this object represent the pending updates and revalidation status :parent_pending: A boolean telling whether or not any :term:`Topology` ancestor or :term:`parent` of this server has pending updates @@ -62,6 +63,7 @@ Each object in the returned array\ [#uniqueness]_ will contain the following fie :reval_pending: ``true`` if the server has pending :term:`Content Invalidation Jobs`, ``false`` otherwise :revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch :revalApplyTime: The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch +:revalUpdateFailed: The status of the last time a content invalidation/revalidation request was applied by this server. Defaults to ``false`` :status: The name of the status of this server .. seealso:: :ref:`health-proto` gives more information on how these statuses are used, and the ``GET`` method of the :ref:`to-api-statuses` endpoint can be used to retrieve information about all server statuses configured in Traffic Ops. diff --git a/lib/go-tc/servers.go b/lib/go-tc/servers.go index 55d9ff3734..6912b60f31 100644 --- a/lib/go-tc/servers.go +++ b/lib/go-tc/servers.go @@ -1320,7 +1320,9 @@ type ServerV50 struct { ConfigApplyTime *time.Time `json:"configApplyTime" db:"config_apply_time"` // The time at which configuration updates were last queued for this server. ConfigUpdateTime *time.Time `json:"configUpdateTime" db:"config_update_time"` - DomainName string `json:"domainName" db:"domain_name"` + // If the last config apply failed for this server + ConfigUpdateFailed bool `json:"configUpdateFailed" db:"config_update_failed"` + DomainName string `json:"domainName" db:"domain_name"` // Deprecated: This property has unknown purpose and should not be used so // that we can get rid of it. GUID *string `json:"guid" db:"guid"` @@ -1356,7 +1358,9 @@ type ServerV50 struct { // The time at which revalidations for this server were last updated by t3c. RevalApplyTime *time.Time `json:"revalApplyTime" db:"revalidate_apply_time"` // The time at which revalidations were last queued for this server. - RevalUpdateTime *time.Time `json:"revalUpdateTime" db:"revalidate_update_time"` + RevalUpdateTime *time.Time `json:"revalUpdateTime" db:"revalidate_update_time"` + // If the last reval apply failed for this server + RevalUpdateFailed bool `json:"revalUpdateFailed" db:"revalidate_update_failed"` Status string `json:"status" db:"status"` StatusID int `json:"statusID" db:"status_id"` StatusLastUpdated *time.Time `json:"statusLastUpdated" db:"status_last_updated"` @@ -1437,6 +1441,55 @@ func (s ServerV50) RevalidationPending() bool { // version 5 of the Traffic Ops API. type ServerV5 = ServerV50 +// ServerUpdateStatusV5 is the type of each entry in the `response` property of +// the response from Traffic Ops to GET requests made to its +// /servers/{{host name}}/update_status in the latest minor API +// v5.0 endpoint. +type ServerUpdateStatusV5 ServerUpdateStatusV50 + +// ServerUpdateStatusV50 is the type of each entry in the `response` property of +// the response from Traffic Ops to GET requests made to its +// /servers/{{host name}}/update_status in API v5.0 endpoint. +type ServerUpdateStatusV50 struct { + HostName string `json:"host_name"` + // Deprecated: In APIv5 and later, this extraneous field is not calculated + // by Traffic Ops; the information is available by comparing ConfigUpdateTime + // to ConfigApplyTime. + UpdatePending bool `json:"upd_pending"` + // Deprecated: In APIv5 and later, this extraneous field is not calculated + // by Traffic Ops; the information is available by comparing RevalUpdateTime + // to RevalApplyTime. + RevalPending bool `json:"reval_pending"` + UseRevalPending bool `json:"use_reval_pending"` + HostId int `json:"host_id"` + Status string `json:"status"` + ParentPending bool `json:"parent_pending"` + ParentRevalPending bool `json:"parent_reval_pending"` + ConfigUpdateTime *time.Time `json:"config_update_time"` + ConfigApplyTime *time.Time `json:"config_apply_time"` + ConfigUpdateFailed *bool `json:"config_update_failed"` + RevalidateUpdateTime *time.Time `json:"revalidate_update_time"` + RevalidateApplyTime *time.Time `json:"revalidate_apply_time"` + RevalidateUpdateFailed *bool `json:"revalidate_update_failed"` +} + +func (sus ServerUpdateStatusV5) Downgrade() ServerUpdateStatusV40 { + return ServerUpdateStatusV40{ + sus.HostName, + sus.UpdatePending, + sus.RevalPending, + sus.UseRevalPending, + sus.HostId, + sus.Status, + sus.ParentPending, + sus.ParentRevalPending, + sus.ConfigUpdateTime, + sus.ConfigApplyTime, + sus.RevalidateUpdateTime, + sus.RevalidateApplyTime, + } +} + // ServerUpdateStatusV4 is the type of each entry in the `response` property of // the response from Traffic Ops to GET requests made to its // /servers/{{host name}}/update_status in the latest minor API @@ -1517,6 +1570,19 @@ func (sus ServerUpdateStatus) Upgrade() ServerUpdateStatusV4 { } } +// ServerUpdateStatusResponseV50 is the type of a response from the Traffic +// Ops API to a request to its /servers/{{host name}}/update_status endpoint +// in API version 5.0. +type ServerUpdateStatusResponseV50 struct { + Response []ServerUpdateStatusV50 `json:"response"` + Alerts +} + +// ServerUpdateStatusResponseV5 is the type of a response from the Traffic +// Ops API to a request to its /servers/{{host name}}/update_status endpoint +// in the latest minor version of API version 5. +type ServerUpdateStatusResponseV5 = ServerUpdateStatusResponseV50 + // ServerUpdateStatusResponseV40 is the type of a response from the Traffic // Ops API to a request to its /servers/{{host name}}/update_status endpoint // in API version 4.0. diff --git a/lib/go-tc/servers_test.go b/lib/go-tc/servers_test.go index 286425953e..4599efa644 100644 --- a/lib/go-tc/servers_test.go +++ b/lib/go-tc/servers_test.go @@ -183,11 +183,13 @@ func TestServerV5DowngradeUpgrade(t *testing.T) { RouterPortName: "router port", }, }, - StatusLastUpdated: nil, - ConfigUpdateTime: nil, - ConfigApplyTime: nil, - RevalUpdateTime: nil, - RevalApplyTime: nil, + StatusLastUpdated: nil, + ConfigUpdateTime: nil, + ConfigApplyTime: nil, + ConfigUpdateFailed: false, + RevalUpdateTime: nil, + RevalApplyTime: nil, + RevalUpdateFailed: false, } serverV4 := serverV5.Downgrade() diff --git a/traffic_ops/app/db/migrations/2023082308560644_server_update_status.down.sql b/traffic_ops/app/db/migrations/2023082308560644_server_update_status.down.sql new file mode 100644 index 0000000000..159b55cd41 --- /dev/null +++ b/traffic_ops/app/db/migrations/2023082308560644_server_update_status.down.sql @@ -0,0 +1,20 @@ +/* + * 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 + DROP COLUMN IF EXISTS config_update_failed, + DROP COLUMN IF EXISTS revalidate_update_failed; diff --git a/traffic_ops/app/db/migrations/2023082308560644_server_update_status.up.sql b/traffic_ops/app/db/migrations/2023082308560644_server_update_status.up.sql new file mode 100644 index 0000000000..3c2a0f7079 --- /dev/null +++ b/traffic_ops/app/db/migrations/2023082308560644_server_update_status.up.sql @@ -0,0 +1,20 @@ +/* + * 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 + ADD COLUMN IF NOT EXISTS config_update_failed boolean NOT NULL DEFAULT false, + ADD COLUMN IF NOT EXISTS revalidate_update_failed boolean NOT NULL DEFAULT false; diff --git a/traffic_ops/testing/api/v5/cdn_locks_test.go b/traffic_ops/testing/api/v5/cdn_locks_test.go index 5974aad857..3f401938aa 100644 --- a/traffic_ops/testing/api/v5/cdn_locks_test.go +++ b/traffic_ops/testing/api/v5/cdn_locks_test.go @@ -123,19 +123,21 @@ func TestCDNLocks(t *testing.T) { ClientSession: opsUserWithLockSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"hostName": {"cdn2-test-edge"}}}, RequestBody: map[string]interface{}{ - "config_apply_time": util.TimePtr(now), + "config_apply_time": util.Ptr(now), + "config_update_failed": util.Ptr(true), }, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), - validateServerApplyTimes("cdn2-test-edge", map[string]interface{}{"ConfigApplyTime": now})), + validateServerApplyTimes("cdn2-test-edge", map[string]interface{}{"ConfigApplyTime": now, "ConfigUpdateFailed": true})), }, "REVALIDATE_APPLY_TIME is SET EVEN when CDN LOCKED": { ClientSession: opsUserWithLockSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"hostName": {"cdn2-test-edge"}}}, RequestBody: map[string]interface{}{ - "revalidate_apply_time": util.TimePtr(now), + "revalidate_apply_time": util.Ptr(now), + "revalidate_update_failed": util.Ptr(true), }, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), - validateServerApplyTimes("cdn2-test-edge", map[string]interface{}{"RevalApplyTime": now})), + validateServerApplyTimes("cdn2-test-edge", map[string]interface{}{"RevalApplyTime": now, "RevalUpdateFailed": true})), }, }, "TOPOLOGY QUEUE UPDATES": { @@ -544,6 +546,8 @@ func TestCDNLocks(t *testing.T) { var hostName string var configApplyTime *time.Time var revalApplyTime *time.Time + var revalUpdateFailed *bool + var configUpdateFailed *bool if hostNameParam, ok := testCase.RequestOpts.QueryParameters["hostName"]; ok { hostName = hostNameParam[0] @@ -554,7 +558,13 @@ func TestCDNLocks(t *testing.T) { if revalApplyTimeVal, ok := testCase.RequestBody["revalidate_apply_time"]; ok { revalApplyTime = revalApplyTimeVal.(*time.Time) } - alerts, reqInf, err := testCase.ClientSession.SetUpdateServerStatusTimes(hostName, configApplyTime, revalApplyTime, testCase.RequestOpts) + if val, ok := testCase.RequestBody["config_update_failed"]; ok { + configUpdateFailed = val.(*bool) + } + if val, ok := testCase.RequestBody["revalidate_update_failed"]; ok { + revalUpdateFailed = val.(*bool) + } + alerts, reqInf, err := testCase.ClientSession.SetUpdateServerStatusTimes(hostName, configApplyTime, revalApplyTime, configUpdateFailed, revalUpdateFailed, testCase.RequestOpts) for _, check := range testCase.Expectations { check(t, reqInf, nil, alerts, err) } diff --git a/traffic_ops/testing/api/v5/servers_hostname_update_test.go b/traffic_ops/testing/api/v5/servers_hostname_update_test.go index 3e7c767206..e52ccb7052 100644 --- a/traffic_ops/testing/api/v5/servers_hostname_update_test.go +++ b/traffic_ops/testing/api/v5/servers_hostname_update_test.go @@ -44,25 +44,27 @@ func TestServersHostnameUpdate(t *testing.T) { ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"hostName": {"atlanta-edge-01"}}}, RequestBody: map[string]interface{}{ - "config_apply_time": util.TimePtr(now), + "config_update_failed": util.Ptr(true), + "config_apply_time": util.Ptr(now), }, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), - validateServerApplyTimes("atlanta-edge-01", map[string]interface{}{"ConfigApplyTime": now})), + validateServerApplyTimes("atlanta-edge-01", map[string]interface{}{"ConfigApplyTime": now, "ConfigUpdateFailed": true})), }, "OK when VALID REVALIDATE_APPLY_TIME PARAMETER": { ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"hostName": {"cdn2-test-edge"}}}, RequestBody: map[string]interface{}{ - "revalidate_apply_time": util.TimePtr(now), + "revalidate_update_failed": util.Ptr(true), + "revalidate_apply_time": util.Ptr(now), }, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), - validateServerApplyTimes("cdn2-test-edge", map[string]interface{}{"RevalApplyTime": now})), + validateServerApplyTimes("cdn2-test-edge", map[string]interface{}{"RevalApplyTime": now, "RevalUpdateFailed": true})), }, "BAD REQUEST when UPDATED AND CONFIG_APPLY_TIME PARAMETER": { ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"hostName": {"atlanta-edge-01"}, "updated": {"true"}}}, RequestBody: map[string]interface{}{ - "config_apply_time": util.TimePtr(now), + "config_apply_time": util.Ptr(now), }, Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), }, @@ -70,7 +72,7 @@ func TestServersHostnameUpdate(t *testing.T) { ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"hostName": {"atlanta-edge-01"}, "reval_updated": {"true"}}}, RequestBody: map[string]interface{}{ - "revalidate_apply_time": util.TimePtr(now), + "revalidate_apply_time": util.Ptr(now), }, Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), }, @@ -83,6 +85,8 @@ func TestServersHostnameUpdate(t *testing.T) { var hostName string var configApplyTime *time.Time var revalApplyTime *time.Time + var revalUpdateFailed *bool + var configUpdateFailed *bool if hostNameParam, ok := testCase.RequestOpts.QueryParameters["hostName"]; ok { hostName = hostNameParam[0] @@ -96,10 +100,18 @@ func TestServersHostnameUpdate(t *testing.T) { revalApplyTime = revalApplyTimeVal.(*time.Time) } + if val, ok := testCase.RequestBody["config_update_failed"]; ok { + configUpdateFailed = val.(*bool) + } + + if val, ok := testCase.RequestBody["revalidate_update_failed"]; ok { + revalUpdateFailed = val.(*bool) + } + switch method { case "POST": t.Run(name, func(t *testing.T) { - alerts, reqInf, err := testCase.ClientSession.SetUpdateServerStatusTimes(hostName, configApplyTime, revalApplyTime, testCase.RequestOpts) + alerts, reqInf, err := testCase.ClientSession.SetUpdateServerStatusTimes(hostName, configApplyTime, revalApplyTime, configUpdateFailed, revalUpdateFailed, testCase.RequestOpts) for _, check := range testCase.Expectations { check(t, reqInf, nil, alerts, err) } @@ -125,9 +137,15 @@ func validateServerApplyTimes(hostName string, expectedResp map[string]interface case "ConfigApplyTime": assert.RequireNotNil(t, resp.Response[0].ConfigApplyTime, "Expected ConfigApplyTime to not be nil.") assert.Equal(t, true, server.ConfigApplyTime.Equal(expected.(time.Time)), "Expected ConfigApplyTime to be %v, but got %v", expected, server.ConfigApplyTime) + case "ConfigUpdateFailed": + assert.RequireNotNil(t, resp.Response[0].ConfigUpdateFailed, "Expected ConfigUpdateFailed to not be nil.") + assert.Equal(t, expected.(bool), resp.Response[0].ConfigUpdateFailed, "Expected ConfigUpdateFailed to be %v, but got %v", expected, server.ConfigUpdateFailed) case "RevalApplyTime": assert.RequireNotNil(t, resp.Response[0].RevalApplyTime, "Expected RevalApplyTime to not be nil.") assert.Equal(t, true, server.RevalApplyTime.Equal(expected.(time.Time)), "Expected RevalApplyTime to be %v, but got %v", expected, server.RevalApplyTime) + case "RevalUpdateFailed": + assert.RequireNotNil(t, resp.Response[0].RevalUpdateFailed, "Expected RevalUpdateFailed to not be nil.") + assert.Equal(t, expected.(bool), resp.Response[0].RevalUpdateFailed, "Expected RevalUpdateFailed to be %v, but got %v", expected, server.RevalUpdateFailed) default: t.Errorf("Expected field: %v, does not exist in response", field) } diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go index c8d6738f37..a9d388ff89 100644 --- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go +++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go @@ -1975,6 +1975,30 @@ WHERE server.id = $2;` return nil } +// SetUpdateFailedForServer sets the update failed flag for the server. +func SetUpdateFailedForServer(tx *sql.Tx, serverID int64, failed bool) error { + query := ` +UPDATE public.server +SET config_update_failed = $1 +WHERE server.id = $2` + if _, err := tx.Exec(query, failed, serverID); err != nil { + return fmt.Errorf("setting config update failed for ServerID %d with value %v: %w", serverID, failed, err) + } + return nil +} + +// SetRevalFailedForServer sets the reval failed flag for the server. +func SetRevalFailedForServer(tx *sql.Tx, serverID int64, failed bool) error { + query := ` +UPDATE public.server +SET revalidate_update_failed = $1 +WHERE server.id = $2` + if _, err := tx.Exec(query, failed, serverID); err != nil { + return fmt.Errorf("setting reval update failed for ServerID %d with value %v: %w", serverID, failed, err) + } + return nil +} + // QueueRevalForServer sets the revalidate update time for the server to now. func QueueRevalForServer(tx *sql.Tx, serverID int64) error { query := ` diff --git a/traffic_ops/traffic_ops_golang/server/servers.go b/traffic_ops/traffic_ops_golang/server/servers.go index dfee235f90..718f2d1870 100644 --- a/traffic_ops/traffic_ops_golang/server/servers.go +++ b/traffic_ops/traffic_ops_golang/server/servers.go @@ -139,6 +139,7 @@ SELECT s.rack, s.revalidate_update_time, s.revalidate_apply_time, + s.revalidate_update_failed, st.name AS status, s.status AS status_id, s.tcp_port, @@ -146,6 +147,7 @@ SELECT s.type AS server_type_id, s.config_update_time, s.config_apply_time, + s.config_update_failed, s.xmpp_id, s.xmpp_passwd, s.status_last_updated @@ -912,6 +914,7 @@ JOIN server_profile sp ON s.id = sp.server` &s.Rack, &s.RevalUpdateTime, &s.RevalApplyTime, + &s.RevalUpdateFailed, &s.Status, &s.StatusID, &s.TCPPort, @@ -919,6 +922,7 @@ JOIN server_profile sp ON s.id = sp.server` &s.TypeID, &s.ConfigUpdateTime, &s.ConfigApplyTime, + &s.ConfigUpdateFailed, &s.XMPPID, &s.XMPPPasswd, &s.StatusLastUpdated, @@ -1137,6 +1141,7 @@ func getMidServers(edgeIDs []int, servers map[int]tc.ServerV5, dsID int, cdnID i &s.Rack, &s.RevalUpdateTime, &s.RevalApplyTime, + &s.RevalUpdateFailed, &s.Status, &s.StatusID, &s.TCPPort, @@ -1144,6 +1149,7 @@ func getMidServers(edgeIDs []int, servers map[int]tc.ServerV5, dsID int, cdnID i &s.TypeID, &s.ConfigUpdateTime, &s.ConfigApplyTime, + &s.ConfigUpdateFailed, &s.XMPPID, &s.XMPPPasswd, &s.StatusLastUpdated); err != nil { @@ -1509,6 +1515,7 @@ func Update(inf *api.APIInfo) (int, error, error) { &server.Rack, &server.RevalUpdateTime, &server.RevalApplyTime, + &server.RevalUpdateFailed, &server.Status, &server.StatusID, &server.TCPPort, @@ -1516,6 +1523,7 @@ func Update(inf *api.APIInfo) (int, error, error) { &server.TypeID, &server.ConfigUpdateTime, &server.ConfigApplyTime, + &server.ConfigUpdateFailed, &server.XMPPID, &server.XMPPPasswd, &server.StatusLastUpdated, @@ -1735,6 +1743,7 @@ func createV3(inf *api.APIInfo) (int, error, error) { &s4.Rack, &s4.RevalUpdateTime, &s4.RevalApplyTime, + &s4.RevalUpdateFailed, &s4.Status, &s4.StatusID, &s4.TCPPort, @@ -1742,6 +1751,7 @@ func createV3(inf *api.APIInfo) (int, error, error) { &s4.TypeID, &s4.ConfigUpdateTime, &s4.ConfigApplyTime, + &s4.ConfigUpdateFailed, &s4.XMPPID, &s4.XMPPPasswd, &s4.StatusLastUpdated, @@ -1845,6 +1855,7 @@ func createV5(inf *api.APIInfo) (int, error, error) { &server.Rack, &server.RevalUpdateTime, &server.RevalApplyTime, + &server.RevalUpdateFailed, &server.Status, &server.StatusID, &server.TCPPort, @@ -1852,6 +1863,7 @@ func createV5(inf *api.APIInfo) (int, error, error) { &server.TypeID, &server.ConfigUpdateTime, &server.ConfigApplyTime, + &server.ConfigUpdateFailed, &server.XMPPID, &server.XMPPPasswd, &server.StatusLastUpdated, @@ -1954,6 +1966,7 @@ func createV4(inf *api.APIInfo) (int, error, error) { &srvr.Rack, &srvr.RevalUpdateTime, &srvr.RevalApplyTime, + &srvr.RevalUpdateFailed, &srvr.Status, &srvr.StatusID, &srvr.TCPPort, @@ -1961,6 +1974,7 @@ func createV4(inf *api.APIInfo) (int, error, error) { &srvr.TypeID, &srvr.ConfigUpdateTime, &srvr.ConfigApplyTime, + &srvr.ConfigUpdateFailed, &srvr.XMPPID, &srvr.XMPPPasswd, &srvr.StatusLastUpdated, diff --git a/traffic_ops/traffic_ops_golang/server/servers_test.go b/traffic_ops/traffic_ops_golang/server/servers_test.go index 3c8155dff2..34f9b28747 100644 --- a/traffic_ops/traffic_ops_golang/server/servers_test.go +++ b/traffic_ops/traffic_ops_golang/server/servers_test.go @@ -77,8 +77,10 @@ func getTestServers() []ServerAndInterfaces { StatusLastUpdated: &(time.Time{}), ConfigUpdateTime: &(time.Time{}), ConfigApplyTime: &(time.Time{}), + ConfigUpdateFailed: false, RevalUpdateTime: &(time.Time{}), RevalApplyTime: &(time.Time{}), + RevalUpdateFailed: false, } mtu := uint64(9500) @@ -175,8 +177,9 @@ func TestGetServersByCachegroup(t *testing.T) { "https_port", "id", "ilo_ip_address", "ilo_ip_gateway", "ilo_ip_netmask", "ilo_password", "ilo_username", "last_updated", "mgmt_ip_address", "mgmt_ip_gateway", "mgmt_ip_netmask", "offline_reason", "phys_location", "phys_location_id", "profile_name", "rack", "revalidate_update_time", "revalidate_apply_time", - "status", "status_id", "tcp_port", "server_type", "server_type_id", "config_update_time", - "config_apply_time", "xmpp_id", "xmpp_passwd", "status_last_updated"} + "revalidate_update_failed", "status", "status_id", "tcp_port", "server_type", "server_type_id", + "config_update_time", "config_apply_time", "config_update_failed", "xmpp_id", "xmpp_passwd", + "status_last_updated"} interfaceCols := []string{"max_bandwidth", "monitor", "mtu", "name", "server", "router_host_name", "router_port_name"} rows := sqlmock.NewRows(cols) interfaceRows := sqlmock.NewRows(interfaceCols) @@ -214,6 +217,7 @@ func TestGetServersByCachegroup(t *testing.T) { *ts.Rack, *ts.RevalUpdateTime, *ts.RevalApplyTime, + ts.RevalUpdateFailed, ts.Status, ts.StatusID, *ts.TCPPort, @@ -221,6 +225,7 @@ func TestGetServersByCachegroup(t *testing.T) { ts.TypeID, *ts.ConfigUpdateTime, *ts.ConfigApplyTime, + ts.ConfigUpdateFailed, *ts.XMPPID, *ts.XMPPPasswd, *ts.StatusLastUpdated, @@ -294,8 +299,9 @@ func TestGetMidServers(t *testing.T) { "https_port", "id", "ilo_ip_address", "ilo_ip_gateway", "ilo_ip_netmask", "ilo_password", "ilo_username", "last_updated", "mgmt_ip_address", "mgmt_ip_gateway", "mgmt_ip_netmask", "offline_reason", "phys_location", "phys_location_id", "profile_name", "rack", "revalidate_update_time", "revalidate_apply_time", - "status", "status_id", "tcp_port", "server_type", "server_type_id", "config_update_time", - "config_apply_time", "xmpp_id", "xmpp_passwd", "status_last_updated"} + "revalidate_update_failed", "status", "status_id", "tcp_port", "server_type", "server_type_id", + "config_update_time", "config_apply_time", "config_update_failed", "xmpp_id", "xmpp_passwd", + "status_last_updated"} interfaceCols := []string{"max_bandwidth", "monitor", "mtu", "name", "server", "router_host_name", "router_port_name"} rows := sqlmock.NewRows(cols) interfaceRows := sqlmock.NewRows(interfaceCols) @@ -331,6 +337,7 @@ func TestGetMidServers(t *testing.T) { *ts.Rack, *ts.RevalUpdateTime, *ts.RevalApplyTime, + ts.RevalUpdateFailed, ts.Status, ts.StatusID, *ts.TCPPort, @@ -338,6 +345,7 @@ func TestGetMidServers(t *testing.T) { ts.TypeID, *ts.ConfigUpdateTime, *ts.ConfigApplyTime, + ts.ConfigUpdateFailed, *ts.XMPPID, *ts.XMPPPasswd, *ts.StatusLastUpdated, @@ -383,8 +391,9 @@ func TestGetMidServers(t *testing.T) { "https_port", "id", "ilo_ip_address", "ilo_ip_gateway", "ilo_ip_netmask", "ilo_password", "ilo_username", "last_updated", "mgmt_ip_address", "mgmt_ip_gateway", "mgmt_ip_netmask", "offline_reason", "phys_location", "phys_location_id", "profile_name", "rack", "revalidate_update_time", "revalidate_apply_time", - "status", "status_id", "tcp_port", "server_type", "server_type_id", "config_update_time", - "config_apply_time", "xmpp_id", "xmpp_passwd", "status_last_updated"} + "revalidate_update_failed", "status", "status_id", "tcp_port", "server_type", "server_type_id", + "config_update_time", "config_apply_time", "config_update_failed", "xmpp_id", "xmpp_passwd", + "status_last_updated"} rows2 := sqlmock.NewRows(cols2) cgs := []tc.CacheGroup{} @@ -471,6 +480,7 @@ func TestGetMidServers(t *testing.T) { *ts.Rack, *ts.RevalUpdateTime, *ts.RevalApplyTime, + ts.RevalUpdateFailed, ts.Status, ts.StatusID, *ts.TCPPort, @@ -478,6 +488,7 @@ func TestGetMidServers(t *testing.T) { ts.TypeID, *ts.ConfigUpdateTime, *ts.ConfigApplyTime, + ts.ConfigUpdateFailed, *ts.XMPPID, *ts.XMPPPasswd, *ts.StatusLastUpdated, diff --git a/traffic_ops/traffic_ops_golang/server/servers_update_status.go b/traffic_ops/traffic_ops_golang/server/servers_update_status.go index 589d643f6f..738ba19bdb 100644 --- a/traffic_ops/traffic_ops_golang/server/servers_update_status.go +++ b/traffic_ops/traffic_ops_golang/server/servers_update_status.go @@ -47,23 +47,31 @@ func GetServerUpdateStatusHandler(w http.ResponseWriter, r *http.Request) { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err) return } - if inf.Version == nil || inf.Version.Major < 4 { - var downgradedStatuses []tc.ServerUpdateStatus - for _, status := range serverUpdateStatuses { - downgradedStatuses = append(downgradedStatuses, status.Downgrade()) + if inf.Version.LessThan(&api.Version{Major: 5}) { + downgradedStatusesV4 := make([]tc.ServerUpdateStatusV40, len(serverUpdateStatuses)) + for i, status := range serverUpdateStatuses { + downgradedStatusesV4[i] = status.Downgrade() + } + if inf.Version.LessThan(&api.Version{Major: 4}) { + downgradedStatuses := make([]tc.ServerUpdateStatus, len(downgradedStatusesV4)) + for i, status := range downgradedStatusesV4 { + downgradedStatuses[i] = status.Downgrade() + } + api.WriteRespRaw(w, r, downgradedStatuses) + } else { + api.WriteResp(w, r, downgradedStatusesV4) } - api.WriteRespRaw(w, r, downgradedStatuses) } else { api.WriteResp(w, r, serverUpdateStatuses) } } -func getServerUpdateStatus(tx *sql.Tx, hostName string) ([]tc.ServerUpdateStatusV40, error, error) { +func getServerUpdateStatus(tx *sql.Tx, hostName string) ([]tc.ServerUpdateStatusV5, error, error) { if serverUpdateStatusCacheIsInitialized() { return getServerUpdateStatusFromCache(hostName), nil, nil } - updateStatuses := []tc.ServerUpdateStatusV40{} + updateStatuses := []tc.ServerUpdateStatusV5{} selectQuery := ` /* topology_ancestors finds the ancestor topology nodes of the topology node for @@ -147,8 +155,10 @@ SELECT ) AS parent_reval_pending, s.config_update_time, s.config_apply_time, + s.config_update_failed, s.revalidate_update_time, - s.revalidate_apply_time + s.revalidate_apply_time, + s.revalidate_update_failed FROM use_reval_pending, server s LEFT JOIN status ON s.status = status.id @@ -171,9 +181,9 @@ ORDER BY s.id defer log.Close(rows, "getServerUpdateStatus(): unable to close db connection") for rows.Next() { - var us tc.ServerUpdateStatusV40 + var us tc.ServerUpdateStatusV5 var serverType string - if err := rows.Scan(&us.HostId, &us.HostName, &serverType, &us.RevalPending, &us.UseRevalPending, &us.UpdatePending, &us.Status, &us.ParentPending, &us.ParentRevalPending, &us.ConfigUpdateTime, &us.ConfigApplyTime, &us.RevalidateUpdateTime, &us.RevalidateApplyTime); err != nil { + if err := rows.Scan(&us.HostId, &us.HostName, &serverType, &us.RevalPending, &us.UseRevalPending, &us.UpdatePending, &us.Status, &us.ParentPending, &us.ParentRevalPending, &us.ConfigUpdateTime, &us.ConfigApplyTime, &us.ConfigUpdateFailed, &us.RevalidateUpdateTime, &us.RevalidateApplyTime, &us.RevalidateUpdateFailed); err != nil { return nil, nil, fmt.Errorf("could not scan server update status: %w", err) } updateStatuses = append(updateStatuses, us) @@ -182,7 +192,7 @@ ORDER BY s.id } type serverUpdateStatuses struct { - serverMap map[string][]tc.ServerUpdateStatusV40 + serverMap map[string][]tc.ServerUpdateStatusV5 *sync.RWMutex initialized bool enabled bool // note: enabled is only written to once at startup, before serving requests, so it doesn't need synchronized access @@ -199,7 +209,7 @@ func serverUpdateStatusCacheIsInitialized() bool { return false } -func getServerUpdateStatusFromCache(hostname string) []tc.ServerUpdateStatusV40 { +func getServerUpdateStatusFromCache(hostname string) []tc.ServerUpdateStatusV5 { serverUpdateStatusCache.RLock() defer serverUpdateStatusCache.RUnlock() return serverUpdateStatusCache.serverMap[hostname] @@ -241,16 +251,18 @@ func refreshServerUpdateStatusCache(db *sql.DB, timeout time.Duration) { } type serverInfo struct { - id int - hostName string - typeName string - cdnId int - status string - cachegroup int - configUpdateTime *time.Time - configApplyTime *time.Time - revalUpdateTime *time.Time - revalApplyTime *time.Time + id int + hostName string + typeName string + cdnId int + status string + cachegroup int + configUpdateTime *time.Time + configApplyTime *time.Time + configUpdateFailed bool + revalUpdateTime *time.Time + revalApplyTime *time.Time + revalUpdateFailed bool } const getUseRevalPendingQuery = ` @@ -270,8 +282,10 @@ const getServerInfoQuery = ` s.cachegroup, s.config_update_time, s.config_apply_time, + s.config_update_failed, s.revalidate_update_time, - s.revalidate_apply_time + s.revalidate_apply_time, + s.revalidate_update_failed FROM server s JOIN type t ON t.id = s.type JOIN status st ON st.id = s.status @@ -297,7 +311,7 @@ const getTopologyCacheGroupParentsQuery = ` GROUP BY cg_child.id ` -func getServerUpdateStatuses(db *sql.DB, timeout time.Duration) (map[string][]tc.ServerUpdateStatusV40, error) { +func getServerUpdateStatuses(db *sql.DB, timeout time.Duration) (map[string][]tc.ServerUpdateStatusV5, error) { dbCtx, dbClose := context.WithTimeout(context.Background(), timeout) defer dbClose() serversByID := make(map[int]serverInfo) @@ -326,7 +340,7 @@ func getServerUpdateStatuses(db *sql.DB, timeout time.Duration) (map[string][]tc defer log.Close(serverRows, "closing server rows") for serverRows.Next() { s := serverInfo{} - if err := serverRows.Scan(&s.id, &s.hostName, &s.typeName, &s.cdnId, &s.status, &s.cachegroup, &s.configUpdateTime, &s.configApplyTime, &s.revalUpdateTime, &s.revalApplyTime); err != nil { + if err := serverRows.Scan(&s.id, &s.hostName, &s.typeName, &s.cdnId, &s.status, &s.cachegroup, &s.configUpdateTime, &s.configApplyTime, &s.configUpdateFailed, &s.revalUpdateTime, &s.revalApplyTime, &s.revalUpdateFailed); err != nil { return nil, fmt.Errorf("scanning servers: %w", err) } serversByID[s.id] = s @@ -396,21 +410,23 @@ func getServerUpdateStatuses(db *sql.DB, timeout time.Duration) (map[string][]tc return nil, fmt.Errorf("iterating over topology cachegroup rows: %w", err) } - serverUpdateStatuses := make(map[string][]tc.ServerUpdateStatusV40, len(serversByID)) + serverUpdateStatuses := make(map[string][]tc.ServerUpdateStatusV5, len(serversByID)) for serverID, server := range serversByID { - updateStatus := tc.ServerUpdateStatusV40{ - HostName: server.hostName, - UpdatePending: server.configUpdateTime.After(*server.configApplyTime), - RevalPending: server.revalUpdateTime.After(*server.revalApplyTime), - UseRevalPending: useRevalPending, - HostId: serverID, - Status: server.status, - ParentPending: getParentPending(cacheGroupParents[server.cachegroup], updatePendingByCDNCachegroup[server.cdnId]), - ParentRevalPending: getParentPending(cacheGroupParents[server.cachegroup], revalPendingByCDNCachegroup[server.cdnId]), - ConfigUpdateTime: server.configUpdateTime, - ConfigApplyTime: server.configApplyTime, - RevalidateUpdateTime: server.revalUpdateTime, - RevalidateApplyTime: server.revalApplyTime, + updateStatus := tc.ServerUpdateStatusV5{ + HostName: server.hostName, + UpdatePending: server.configUpdateTime.After(*server.configApplyTime), + RevalPending: server.revalUpdateTime.After(*server.revalApplyTime), + UseRevalPending: useRevalPending, + HostId: serverID, + Status: server.status, + ParentPending: getParentPending(cacheGroupParents[server.cachegroup], updatePendingByCDNCachegroup[server.cdnId]), + ParentRevalPending: getParentPending(cacheGroupParents[server.cachegroup], revalPendingByCDNCachegroup[server.cdnId]), + ConfigUpdateTime: server.configUpdateTime, + ConfigApplyTime: server.configApplyTime, + ConfigUpdateFailed: &server.configUpdateFailed, + RevalidateUpdateTime: server.revalUpdateTime, + RevalidateApplyTime: server.revalApplyTime, + RevalidateUpdateFailed: &server.revalUpdateFailed, } serverUpdateStatuses[server.hostName] = append(serverUpdateStatuses[server.hostName], updateStatus) } diff --git a/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go b/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go index 0d0ce5e7ef..9300699ce2 100644 --- a/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go +++ b/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go @@ -26,6 +26,7 @@ import ( "time" "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" "github.com/jmoiron/sqlx" sqlmock "gopkg.in/DATA-DOG/go-sqlmock.v1" @@ -44,10 +45,11 @@ func TestGetServerUpdateStatus(t *testing.T) { mock.ExpectBegin() serverStatusRow := sqlmock.NewRows([]string{"id", "host_name", "type", "server_reval_pending", "use_reval_pending", "server_upd_pending", "status", "parent_upd_pending", "parent_reval_pending", - "config_update_time", "config_apply_time", "revalidate_update_time", "revalidate_apply_time"}) + "config_update_time", "config_apply_time", "config_update_failed", "revalidate_update_time", + "revalidate_apply_time", "revalidate_update_failed"}) serverStatusRow.AddRow(1, "host_name_1", "EDGE", true, true, true, "ONLINE", true, false, - time.Now(), time.Now(), time.Now(), time.Now()) + time.Now(), time.Now(), false, time.Now(), time.Now(), false) mock.ExpectQuery("SELECT").WillReturnRows(serverStatusRow) mock.ExpectCommit() @@ -89,15 +91,15 @@ func TestGetServerUpdateStatuses(t *testing.T) { mock.ExpectQuery("SELECT").WillReturnRows(revalPendingRows) serverInfoRows := sqlmock.NewRows([]string{"id", "host_name", "type", "cdn_id", "status", - "cachegroup", "config_update_time", "config_apply_time", "revalidate_update_time", - "revalidate_apply_time"}) + "cachegroup", "config_update_time", "config_apply_time", "config_update_failed", "revalidate_update_time", + "revalidate_apply_time", "revalidate_update_failed"}) tenSecAfter := time.UnixMilli(10000) epoch := time.UnixMilli(0) - serverInfoRows.AddRow(1, "edge1", tc.CacheTypeEdge.String(), 1, tc.CacheStatusReported.String(), 1, tenSecAfter, tenSecAfter, tenSecAfter, tenSecAfter) - serverInfoRows.AddRow(2, "mid1", tc.CacheTypeMid.String(), 1, tc.CacheStatusReported.String(), 2, tenSecAfter, epoch, tenSecAfter, tenSecAfter) - serverInfoRows.AddRow(3, "edge2", tc.CacheTypeEdge.String(), 2, tc.CacheStatusReported.String(), 1, tenSecAfter, tenSecAfter, tenSecAfter, tenSecAfter) - serverInfoRows.AddRow(4, "mid2", tc.CacheTypeMid.String(), 2, tc.CacheStatusReported.String(), 2, tenSecAfter, tenSecAfter, tenSecAfter, tenSecAfter) - serverInfoRows.AddRow(5, "mid3", tc.CacheTypeMid.String(), 2, tc.CacheStatusReported.String(), 3, tenSecAfter, tenSecAfter, tenSecAfter, epoch) + serverInfoRows.AddRow(1, "edge1", tc.CacheTypeEdge.String(), 1, tc.CacheStatusReported.String(), 1, tenSecAfter, tenSecAfter, false, tenSecAfter, tenSecAfter, false) + serverInfoRows.AddRow(2, "mid1", tc.CacheTypeMid.String(), 1, tc.CacheStatusReported.String(), 2, tenSecAfter, epoch, false, tenSecAfter, tenSecAfter, false) + serverInfoRows.AddRow(3, "edge2", tc.CacheTypeEdge.String(), 2, tc.CacheStatusReported.String(), 1, tenSecAfter, tenSecAfter, false, tenSecAfter, tenSecAfter, false) + serverInfoRows.AddRow(4, "mid2", tc.CacheTypeMid.String(), 2, tc.CacheStatusReported.String(), 2, tenSecAfter, tenSecAfter, false, tenSecAfter, tenSecAfter, false) + serverInfoRows.AddRow(5, "mid3", tc.CacheTypeMid.String(), 2, tc.CacheStatusReported.String(), 3, tenSecAfter, tenSecAfter, false, tenSecAfter, epoch, false) mock.ExpectQuery("SELECT").WillReturnRows(serverInfoRows) cachegroupRows := sqlmock.NewRows([]string{"id", "parent_cachegroup_id", "secondary_parent_cachegroup_id"}) @@ -112,85 +114,95 @@ func TestGetServerUpdateStatuses(t *testing.T) { mock.ExpectCommit() - expected := map[string][]tc.ServerUpdateStatusV40{ + expected := map[string][]tc.ServerUpdateStatusV5{ "edge1": { { - HostName: "edge1", - UpdatePending: false, - RevalPending: false, - UseRevalPending: true, - HostId: 1, - Status: tc.CacheStatusReported.String(), - ParentPending: true, - ParentRevalPending: false, - ConfigUpdateTime: &tenSecAfter, - ConfigApplyTime: &tenSecAfter, - RevalidateUpdateTime: &tenSecAfter, - RevalidateApplyTime: &tenSecAfter, + HostName: "edge1", + UpdatePending: false, + RevalPending: false, + UseRevalPending: true, + HostId: 1, + Status: tc.CacheStatusReported.String(), + ParentPending: true, + ParentRevalPending: false, + ConfigUpdateTime: &tenSecAfter, + ConfigApplyTime: &tenSecAfter, + ConfigUpdateFailed: util.Ptr(false), + RevalidateUpdateTime: &tenSecAfter, + RevalidateApplyTime: &tenSecAfter, + RevalidateUpdateFailed: util.Ptr(false), }, }, "mid1": { { - HostName: "mid1", - UpdatePending: true, - RevalPending: false, - UseRevalPending: true, - HostId: 2, - Status: tc.CacheStatusReported.String(), - ParentPending: false, - ParentRevalPending: false, - ConfigUpdateTime: &tenSecAfter, - ConfigApplyTime: &epoch, - RevalidateUpdateTime: &tenSecAfter, - RevalidateApplyTime: &tenSecAfter, + HostName: "mid1", + UpdatePending: true, + RevalPending: false, + UseRevalPending: true, + HostId: 2, + Status: tc.CacheStatusReported.String(), + ParentPending: false, + ParentRevalPending: false, + ConfigUpdateTime: &tenSecAfter, + ConfigApplyTime: &epoch, + ConfigUpdateFailed: util.Ptr(false), + RevalidateUpdateTime: &tenSecAfter, + RevalidateApplyTime: &tenSecAfter, + RevalidateUpdateFailed: util.Ptr(false), }, }, "edge2": { { - HostName: "edge2", - UpdatePending: false, - RevalPending: false, - UseRevalPending: true, - HostId: 3, - Status: tc.CacheStatusReported.String(), - ParentPending: false, - ParentRevalPending: true, - ConfigUpdateTime: &tenSecAfter, - ConfigApplyTime: &tenSecAfter, - RevalidateUpdateTime: &tenSecAfter, - RevalidateApplyTime: &tenSecAfter, + HostName: "edge2", + UpdatePending: false, + RevalPending: false, + UseRevalPending: true, + HostId: 3, + Status: tc.CacheStatusReported.String(), + ParentPending: false, + ParentRevalPending: true, + ConfigUpdateTime: &tenSecAfter, + ConfigApplyTime: &tenSecAfter, + ConfigUpdateFailed: util.Ptr(false), + RevalidateUpdateTime: &tenSecAfter, + RevalidateApplyTime: &tenSecAfter, + RevalidateUpdateFailed: util.Ptr(false), }, }, "mid2": { { - HostName: "mid2", - UpdatePending: false, - RevalPending: false, - UseRevalPending: true, - HostId: 4, - Status: tc.CacheStatusReported.String(), - ParentPending: false, - ParentRevalPending: false, - ConfigUpdateTime: &tenSecAfter, - ConfigApplyTime: &tenSecAfter, - RevalidateUpdateTime: &tenSecAfter, - RevalidateApplyTime: &tenSecAfter, + HostName: "mid2", + UpdatePending: false, + RevalPending: false, + UseRevalPending: true, + HostId: 4, + Status: tc.CacheStatusReported.String(), + ParentPending: false, + ParentRevalPending: false, + ConfigUpdateTime: &tenSecAfter, + ConfigApplyTime: &tenSecAfter, + ConfigUpdateFailed: util.Ptr(false), + RevalidateUpdateTime: &tenSecAfter, + RevalidateApplyTime: &tenSecAfter, + RevalidateUpdateFailed: util.Ptr(false), }, }, "mid3": { { - HostName: "mid3", - UpdatePending: false, - RevalPending: true, - UseRevalPending: true, - HostId: 5, - Status: tc.CacheStatusReported.String(), - ParentPending: false, - ParentRevalPending: false, - ConfigUpdateTime: &tenSecAfter, - ConfigApplyTime: &tenSecAfter, - RevalidateUpdateTime: &tenSecAfter, - RevalidateApplyTime: &epoch, + HostName: "mid3", + UpdatePending: false, + RevalPending: true, + UseRevalPending: true, + HostId: 5, + Status: tc.CacheStatusReported.String(), + ParentPending: false, + ParentRevalPending: false, + ConfigUpdateTime: &tenSecAfter, + ConfigApplyTime: &tenSecAfter, + ConfigUpdateFailed: util.Ptr(false), + RevalidateUpdateTime: &tenSecAfter, + RevalidateApplyTime: &epoch, + RevalidateUpdateFailed: util.Ptr(false), }, }, } diff --git a/traffic_ops/traffic_ops_golang/server/update.go b/traffic_ops/traffic_ops_golang/server/update.go index 741498397c..dfeb82a0b1 100644 --- a/traffic_ops/traffic_ops_golang/server/update.go +++ b/traffic_ops/traffic_ops_golang/server/update.go @@ -141,13 +141,15 @@ func UpdateHandler(w http.ResponseWriter, r *http.Request) { } type updateValues struct { - configUpdateBool *bool // Deprecated, prefer timestamps - revalUpdateBool *bool // Deprecated, prefer timestamps - configApplyTime *time.Time - revalApplyTime *time.Time + configUpdateBool *bool // Deprecated, prefer timestamps + revalUpdateBool *bool // Deprecated, prefer timestamps + configApplyTime *time.Time + revalApplyTime *time.Time + configUpdateFailed *bool + revalUpdateFailed *bool } -func parseQueryParams(params map[string]string) (*updateValues, error) { +func parseQueryParams(params map[string]string, version api.Version) (*updateValues, error) { var paramValues updateValues // Verify query string parameters @@ -155,20 +157,22 @@ func parseQueryParams(params map[string]string) (*updateValues, error) { revalUpdatedBoolParam, hasRevalUpdatedBoolParam := params["reval_updated"] // Deprecated, but still required for backwards compatibility configApplyTimeParam, hasConfigApplyTimeParam := params["config_apply_time"] revalidateApplyTimeParam, hasRevalidateApplyTimeParam := params["revalidate_apply_time"] + configUpdateFailedParam, hasConfigUpdateFailedParam := params["config_update_failed"] + revalidateUpdateFailedParam, hasRevalidateUpdateFailedParam := params["revalidate_update_failed"] + isAfterApi5 := version.GreaterThanOrEqualTo(&api.Version{Major: 5}) - if !hasConfigApplyTimeParam && !hasRevalidateApplyTimeParam && + if !(hasConfigApplyTimeParam || (hasConfigUpdateFailedParam && isAfterApi5)) && !(hasRevalidateApplyTimeParam || (hasRevalidateUpdateFailedParam && isAfterApi5)) && !hasConfigUpdatedBoolParam && !hasRevalUpdatedBoolParam { return nil, errors.New("must pass at least one of the following query parameters: 'config_apply_time', 'revalidate_apply_time' ,'updated', 'reval_updated'") } // Prevent collision between booleans and timestamps - if hasConfigApplyTimeParam && hasConfigUpdatedBoolParam { + if (hasConfigApplyTimeParam || (hasConfigUpdateFailedParam && isAfterApi5)) && hasConfigUpdatedBoolParam { return nil, errors.New("conflicting parameters. may not pass 'updated' along with 'config_apply_time'") } - if hasRevalidateApplyTimeParam && hasRevalUpdatedBoolParam { + if (hasRevalidateApplyTimeParam || (hasConfigUpdateFailedParam && isAfterApi5)) && hasRevalUpdatedBoolParam { return nil, errors.New("conflicting parameters. may not pass 'reval_updated' along with 'revalidate_apply_time'") - } // Validate and parse parameters before attempting to apply them (don't want to partially apply various status before an error) @@ -204,6 +208,20 @@ func parseQueryParams(params map[string]string) (*updateValues, error) { } paramValues.revalUpdateBool = &revalUpdatedBool } + if hasConfigUpdateFailedParam && isAfterApi5 { + configUpdateFailedBool, err := strconv.ParseBool(configUpdateFailedParam) + if err != nil { + return nil, errors.New("query parameter 'config_update_failed' must be a boolean") + } + paramValues.configUpdateFailed = &configUpdateFailedBool + } + if hasRevalidateUpdateFailedParam && isAfterApi5 { + revalUpdateFailedBool, err := strconv.ParseBool(revalidateUpdateFailedParam) + if err != nil { + return nil, errors.New("query parameter 'revalidate_update_failed' must be a boolean") + } + paramValues.revalUpdateFailed = &revalUpdateFailedBool + } return ¶mValues, nil } @@ -216,12 +234,24 @@ func setUpdateStatuses(tx *sql.Tx, serverID int64, values updateValues) error { } } + if values.configUpdateFailed != nil { + if err := dbhelpers.SetUpdateFailedForServer(tx, serverID, *values.configUpdateFailed); err != nil { + return fmt.Errorf("setting config update status: %v", err) + } + } + if values.revalApplyTime != nil { if err := dbhelpers.SetApplyRevalForServerWithTime(tx, serverID, *values.revalApplyTime); err != nil { return fmt.Errorf("setting reval apply time: %w", err) } } + if values.revalUpdateFailed != nil { + if err := dbhelpers.SetRevalFailedForServer(tx, serverID, *values.revalUpdateFailed); err != nil { + return fmt.Errorf("setting reval update status: %v", err) + } + } + if values.configUpdateBool != nil { if *values.configUpdateBool { if err := dbhelpers.QueueUpdateForServer(tx, serverID); err != nil { @@ -265,6 +295,14 @@ func responseMessage(idOrName string, values updateValues) string { respMsg += " revalidate_apply_time=" + (*values.revalApplyTime).Format(time.RFC3339Nano) } + if values.configUpdateFailed != nil { + respMsg += " config_update_failed=" + strconv.FormatBool(*values.configUpdateFailed) + } + + if values.revalUpdateFailed != nil { + respMsg += " revalidate_update_failed=" + strconv.FormatBool(*values.revalUpdateFailed) + } + return respMsg } @@ -301,8 +339,13 @@ func UpdateHandlerV4(w http.ResponseWriter, r *http.Request) { _, hasRevalUpdatedBoolParam := inf.Params["reval_updated"] _, hasConfigApplyTimeParam := inf.Params["config_apply_time"] _, hasRevalidateApplyTimeParam := inf.Params["revalidate_apply_time"] + hasRevalUpdateFailParam, hasConfigUpdateFailParam := false, false + if inf.Version.GreaterThanOrEqualTo(&api.Version{Major: 5}) { + _, hasRevalUpdateFailParam = inf.Params["revalidate_update_failed"] + _, hasConfigUpdateFailParam = inf.Params["config_update_failed"] + } // Allow `apply_time` changes when the CDN is locked, but not `updated` - canIgnoreLock := (hasConfigApplyTimeParam || hasRevalidateApplyTimeParam) && !hasConfigUpdatedBoolParam && !hasRevalUpdatedBoolParam + canIgnoreLock := (hasConfigApplyTimeParam || hasRevalidateApplyTimeParam) && !hasConfigUpdatedBoolParam && !hasRevalUpdatedBoolParam && !hasConfigUpdateFailParam && !hasRevalUpdateFailParam if !canIgnoreLock { userDoesntHaveLockErr, sysErr, statusCode := dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdnName), inf.User.UserName) if sysErr != nil || userDoesntHaveLockErr != nil { @@ -312,7 +355,7 @@ func UpdateHandlerV4(w http.ResponseWriter, r *http.Request) { } // TODO parse JSON body to trump Query Params? - updateValues, err := parseQueryParams(inf.Params) + updateValues, err := parseQueryParams(inf.Params, *inf.Version) if err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, err, nil) return diff --git a/traffic_ops/v5-client/server.go b/traffic_ops/v5-client/server.go index e88032bd70..da2cb96047 100644 --- a/traffic_ops/v5-client/server.go +++ b/traffic_ops/v5-client/server.go @@ -158,9 +158,9 @@ func (to *Session) GetServerIDDeliveryServices(server int, opts RequestOptions) // GetServerUpdateStatus retrieves the Server Update Status of the Server with // the given (short) hostname. -func (to *Session) GetServerUpdateStatus(hostName string, opts RequestOptions) (tc.ServerUpdateStatusResponseV4, toclientlib.ReqInf, error) { +func (to *Session) GetServerUpdateStatus(hostName string, opts RequestOptions) (tc.ServerUpdateStatusResponseV5, toclientlib.ReqInf, error) { path := apiServers + `/` + url.PathEscape(hostName) + `/update_status` - var data tc.ServerUpdateStatusResponseV4 + var data tc.ServerUpdateStatusResponseV5 reqInf, err := to.get(path, opts, &data) return data, reqInf, err } diff --git a/traffic_ops/v5-client/server_update_status.go b/traffic_ops/v5-client/server_update_status.go index b61223dd74..d5bb3e97d8 100644 --- a/traffic_ops/v5-client/server_update_status.go +++ b/traffic_ops/v5-client/server_update_status.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "net/url" + "strconv" "time" "github.com/apache/trafficcontrol/lib/go-tc" @@ -52,12 +53,12 @@ func (to *Session) SetServerQueueUpdate(serverID int, queueUpdate bool, opts Req // SetUpdateServerStatusTimes updates a server's config queue status and/or reval status. // Each argument individually is optional, however at least one argument must not be nil. -func (to *Session) SetUpdateServerStatusTimes(serverName string, configApplyTime, revalApplyTime *time.Time, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { +func (to *Session) SetUpdateServerStatusTimes(serverName string, configApplyTime, revalApplyTime *time.Time, configUpdateFailed, revalUpdateFailed *bool, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { reqInf := toclientlib.ReqInf{CacheHitStatus: toclientlib.CacheHitStatusMiss} var alerts tc.Alerts - if configApplyTime == nil && revalApplyTime == nil { - return alerts, reqInf, errors.New("one must be non-nil (configApplyTime, revalApplyTime); nothing to do") + if configApplyTime == nil && revalApplyTime == nil && configUpdateFailed == nil && revalUpdateFailed == nil { + return alerts, reqInf, errors.New("one must be non-nil (configApplyTime, configUpdateFailed, revalApplyTime); nothing to do") } if opts.QueryParameters == nil { @@ -68,10 +69,16 @@ func (to *Session) SetUpdateServerStatusTimes(serverName string, configApplyTime cat := configApplyTime.Format(time.RFC3339Nano) opts.QueryParameters.Set("config_apply_time", cat) } + if configUpdateFailed != nil { + opts.QueryParameters.Set("config_update_failed", strconv.FormatBool(*configUpdateFailed)) + } if revalApplyTime != nil { rat := revalApplyTime.Format(time.RFC3339Nano) opts.QueryParameters.Set("revalidate_apply_time", rat) } + if revalUpdateFailed != nil { + opts.QueryParameters.Set("revalidate_update_failed", strconv.FormatBool(*revalUpdateFailed)) + } path := `/servers/` + url.PathEscape(serverName) + `/update` reqInf, err := to.post(path, opts, nil, &alerts)