diff --git a/CHANGELOG.md b/CHANGELOG.md index a6c302f728..638c5cee9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Added layered profile feature to 4.0 for `GET` /deliveryservices/{id}/servers/ and /deliveryservices/{id}/servers/eligible. ### Fixed +- [#6291](https://github.com/apache/trafficcontrol/issues/6291) Prevent Traffic Ops from modifying and/or deleting reserved statuses. - Update traffic\_portal dependencies to mitigate `npm audit` issues. - Fixed a cdn-in-a-box build issue when using `RHEL_VERSION=7` - `dequeueing` server updates should not require checking for cdn locks. diff --git a/cache-config/testing/ort-tests/t3c-apply-diff_test.go b/cache-config/testing/ort-tests/t3c-apply-diff_test.go index b35e3da372..f7613bc68e 100644 --- a/cache-config/testing/ort-tests/t3c-apply-diff_test.go +++ b/cache-config/testing/ort-tests/t3c-apply-diff_test.go @@ -28,7 +28,7 @@ import ( func TestApplyDiff(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-apply-unset-update_test.go b/cache-config/testing/ort-tests/t3c-apply-unset-update_test.go index 0d3ae688f0..55b95c36f4 100644 --- a/cache-config/testing/ort-tests/t3c-apply-unset-update_test.go +++ b/cache-config/testing/ort-tests/t3c-apply-unset-update_test.go @@ -73,7 +73,7 @@ func verifyUpdateStatusIsTrue() error { func TestT3cUnsetsUpdateFlag(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-apply-wait-for-parents_test.go b/cache-config/testing/ort-tests/t3c-apply-wait-for-parents_test.go index 94df96e26a..4b86042d4d 100644 --- a/cache-config/testing/ort-tests/t3c-apply-wait-for-parents_test.go +++ b/cache-config/testing/ort-tests/t3c-apply-wait-for-parents_test.go @@ -36,7 +36,7 @@ const childCacheHostName = DefaultCacheHostName func TestWaitForParentsTrue(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { @@ -147,7 +147,7 @@ func TestWaitForParentsTrue(t *testing.T) { func TestWaitForParentsDefaultReval(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { @@ -216,7 +216,7 @@ func TestWaitForParentsDefaultReval(t *testing.T) { func TestWaitForParentsFalse(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-create-empty-file_test.go b/cache-config/testing/ort-tests/t3c-create-empty-file_test.go index 9389597c79..48781faf4b 100644 --- a/cache-config/testing/ort-tests/t3c-create-empty-file_test.go +++ b/cache-config/testing/ort-tests/t3c-create-empty-file_test.go @@ -31,7 +31,7 @@ func TestT3cCreateEmptyFile(t *testing.T) { // t3c must create semantically blank files. Failing to do so will cause other config files that reference them to fail. tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-dns-local-bind_test.go b/cache-config/testing/ort-tests/t3c-dns-local-bind_test.go index 247fc53391..be158d885e 100644 --- a/cache-config/testing/ort-tests/t3c-dns-local-bind_test.go +++ b/cache-config/testing/ort-tests/t3c-dns-local-bind_test.go @@ -29,7 +29,7 @@ import ( func TestT3CDNSLocalBind(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-fail-log_test.go b/cache-config/testing/ort-tests/t3c-fail-log_test.go index 98e310412e..47a6a3014c 100644 --- a/cache-config/testing/ort-tests/t3c-fail-log_test.go +++ b/cache-config/testing/ort-tests/t3c-fail-log_test.go @@ -24,7 +24,7 @@ import ( func TestT3cApplyFailMsg(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-git_test.go b/cache-config/testing/ort-tests/t3c-git_test.go index 2250595d83..7f58773dc5 100644 --- a/cache-config/testing/ort-tests/t3c-git_test.go +++ b/cache-config/testing/ort-tests/t3c-git_test.go @@ -31,7 +31,7 @@ import ( func TestT3cGit(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-ims_test.go b/cache-config/testing/ort-tests/t3c-ims_test.go index 7772a539ab..50e27c119a 100644 --- a/cache-config/testing/ort-tests/t3c-ims_test.go +++ b/cache-config/testing/ort-tests/t3c-ims_test.go @@ -34,7 +34,7 @@ import ( func TestIMS(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-jobs_test.go b/cache-config/testing/ort-tests/t3c-jobs_test.go index e3fff5905b..980ee67ce3 100644 --- a/cache-config/testing/ort-tests/t3c-jobs_test.go +++ b/cache-config/testing/ort-tests/t3c-jobs_test.go @@ -29,7 +29,7 @@ import ( func TestT3CJobs(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-lockfile_test.go b/cache-config/testing/ort-tests/t3c-lockfile_test.go index 677d5b2900..6f06a8ff2b 100644 --- a/cache-config/testing/ort-tests/t3c-lockfile_test.go +++ b/cache-config/testing/ort-tests/t3c-lockfile_test.go @@ -27,7 +27,7 @@ import ( func TestLockfile(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-no-outgoing-ip-flag_test.go b/cache-config/testing/ort-tests/t3c-no-outgoing-ip-flag_test.go index 2c74bb92c6..8e14dc8068 100644 --- a/cache-config/testing/ort-tests/t3c-no-outgoing-ip-flag_test.go +++ b/cache-config/testing/ort-tests/t3c-no-outgoing-ip-flag_test.go @@ -55,7 +55,7 @@ func testNoOutgoingIPAfterUpdate(t *testing.T, noOutgoingIP *bool) { func TestT3CNoOutgoingIP(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-os-hostname-short_test.go b/cache-config/testing/ort-tests/t3c-os-hostname-short_test.go index 49d42136f8..5eaace94dc 100644 --- a/cache-config/testing/ort-tests/t3c-os-hostname-short_test.go +++ b/cache-config/testing/ort-tests/t3c-os-hostname-short_test.go @@ -25,7 +25,7 @@ import ( func TestT3cApplyOSHostnameShort(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c-reload_test.go b/cache-config/testing/ort-tests/t3c-reload_test.go index a9705b66c7..5dd390d5a5 100644 --- a/cache-config/testing/ort-tests/t3c-reload_test.go +++ b/cache-config/testing/ort-tests/t3c-reload_test.go @@ -28,7 +28,7 @@ import ( func TestT3cReload(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices, tcdata.InvalidationJobs}, func() { diff --git a/cache-config/testing/ort-tests/t3c_mode_test.go b/cache-config/testing/ort-tests/t3c_mode_test.go index 12cb8d667c..702e14e8e9 100644 --- a/cache-config/testing/ort-tests/t3c_mode_test.go +++ b/cache-config/testing/ort-tests/t3c_mode_test.go @@ -106,7 +106,7 @@ func checkDiff(fName, atsUid, atsGid string, t *testing.T) { func TestT3cBadassAndSyncDs(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/t3c_update_to_flags_test.go b/cache-config/testing/ort-tests/t3c_update_to_flags_test.go index 35ae74f980..39c9dec0ce 100644 --- a/cache-config/testing/ort-tests/t3c_update_to_flags_test.go +++ b/cache-config/testing/ort-tests/t3c_update_to_flags_test.go @@ -25,7 +25,7 @@ import ( func TestT3cTOUpdates(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices, tcdata.InvalidationJobs}, func() { diff --git a/cache-config/testing/ort-tests/tc-fixtures.json b/cache-config/testing/ort-tests/tc-fixtures.json index 4f7098aeb0..54a3677132 100644 --- a/cache-config/testing/ort-tests/tc-fixtures.json +++ b/cache-config/testing/ort-tests/tc-fixtures.json @@ -4535,35 +4535,6 @@ "type": "AAAA_RECORD" } ], - "statuses": [ - { - "description": "Edge: Puts server in CCR config file in this state, but CCR will never route traffic to it. Mid: Server will not be included in parent.config files for its edge caches", - "name": "OFFLINE" - }, - { - "description": "Edge: Puts server in CCR config file in this state, and CCR will always route traffic to it. Mid: Server will be included in parent.config files for its edges", - "name": "ONLINE" - }, - { - "description": "Edge: Puts server in CCR config file in this state, and CCR will adhere to the health protocol. Mid: N/A for now", - "name": "REPORTED" - }, - { - "description": "Temporary down. Edge: XMPP client will send status OFFLINE to CCR, otherwise similar to REPORTED. Mid: Server will not be included in parent.config files for its edge caches", - "name": "ADMIN_DOWN" - }, - { - "description": "Edge: 12M will not include caches in this state in CCR config files. Mid: N/A for now", - "name": "CCR_IGNORE" - }, - { - "description": "Pre Production. Not active in any configuration.", - "name": "PRE_PROD" - }, - { - "name": "TEST_NULL_DESCRIPTION" - } - ], "tenants": [ { "active": true, diff --git a/cache-config/testing/ort-tests/tcdata/statuses.go b/cache-config/testing/ort-tests/tcdata/statuses.go deleted file mode 100644 index 81d82b288b..0000000000 --- a/cache-config/testing/ort-tests/tcdata/statuses.go +++ /dev/null @@ -1,62 +0,0 @@ -package tcdata - -/* - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -import ( - "testing" -) - -func (r *TCData) CreateTestStatuses(t *testing.T) { - - for _, status := range r.TestData.Statuses { - resp, _, err := TOSession.CreateStatusNullable(status) - t.Log("Response: ", resp) - if err != nil { - t.Errorf("could not CREATE types: %v", err) - } - } - -} - -func (r *TCData) DeleteTestStatuses(t *testing.T) { - - for _, status := range r.TestData.Statuses { - if status.Name == nil { - t.Fatal("cannot get ftest statuses: test data statuses must have names") - } - - // Retrieve the Status by name so we can get the id for the Update - resp, _, err := TOSession.GetStatusByName(*status.Name) - if err != nil { - t.Errorf("cannot GET Status by name: %s - %v", *status.Name, err) - } - respStatus := resp[0] - - delResp, _, err := TOSession.DeleteStatusByID(respStatus.ID) - if err != nil { - t.Errorf("cannot DELETE Status by name: %v - %v", err, delResp) - } - - // Retrieve the Status to see if it got deleted - types, _, err := TOSession.GetStatusByName(*status.Name) - if err != nil { - t.Errorf("error deleting Status name: %v", err) - } - if len(types) > 0 { - t.Errorf("expected Status name: %s to be deleted", *status.Name) - } - } -} diff --git a/cache-config/testing/ort-tests/tcdata/todb.go b/cache-config/testing/ort-tests/tcdata/todb.go index d1df501774..0bbb27b76c 100644 --- a/cache-config/testing/ort-tests/tcdata/todb.go +++ b/cache-config/testing/ort-tests/tcdata/todb.go @@ -279,7 +279,7 @@ func (r *TCData) Teardown(db *sql.DB) error { DELETE FROM cachegroup; DELETE FROM coordinate; DELETE FROM type; - DELETE FROM status; + DELETE FROM status s WHERE s.name NOT IN ('OFFLINE', 'ONLINE', 'PRE_PROD', 'ADMIN_DOWN', 'REPORTED'); DELETE FROM snapshot; DELETE FROM cdn; DELETE FROM service_category; diff --git a/cache-config/testing/ort-tests/tcdata/withobjs.go b/cache-config/testing/ort-tests/tcdata/withobjs.go index b3bf3337d1..d2460bea3e 100644 --- a/cache-config/testing/ort-tests/tcdata/withobjs.go +++ b/cache-config/testing/ort-tests/tcdata/withobjs.go @@ -49,7 +49,6 @@ const ( ServerServerCapabilities Servers ServiceCategories - Statuses StaticDNSEntries SteeringTargets Tenants @@ -99,7 +98,6 @@ func (r *TCData) WithObjs(t *testing.T, objs []TCObj, f func()) { ServerServerCapabilities: {r.CreateTestServerServerCapabilities, r.DeleteTestServerServerCapabilities}, Servers: {r.CreateTestServers, r.DeleteTestServers}, ServiceCategories: {r.CreateTestServiceCategories, r.DeleteTestServiceCategories}, - Statuses: {r.CreateTestStatuses, r.DeleteTestStatuses}, StaticDNSEntries: {r.CreateTestStaticDNSEntries, r.DeleteTestStaticDNSEntries}, SteeringTargets: {r.SetupSteeringTargets, r.DeleteTestSteeringTargets}, Tenants: {r.CreateTestTenants, r.DeleteTestTenants}, diff --git a/cache-config/testing/ort-tests/to_requester_test.go b/cache-config/testing/ort-tests/to_requester_test.go index cbc1e521f1..982ba47923 100644 --- a/cache-config/testing/ort-tests/to_requester_test.go +++ b/cache-config/testing/ort-tests/to_requester_test.go @@ -35,7 +35,7 @@ type Package struct { func TestTORequester(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices}, func() { diff --git a/cache-config/testing/ort-tests/to_updater_test.go b/cache-config/testing/ort-tests/to_updater_test.go index 5384818e86..efc97cab4e 100644 --- a/cache-config/testing/ort-tests/to_updater_test.go +++ b/cache-config/testing/ort-tests/to_updater_test.go @@ -31,7 +31,7 @@ import ( func TestTOUpdater(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, tcdata.DeliveryServices, tcdata.InvalidationJobs}, func() { diff --git a/lib/go-tc/enum.go b/lib/go-tc/enum.go index 014a624485..8d100fa683 100644 --- a/lib/go-tc/enum.go +++ b/lib/go-tc/enum.go @@ -374,6 +374,9 @@ const ( // by Traffic Monitor. The vast majority of cache servers should have this // Status. CacheStatusReported = CacheStatus("REPORTED") + // CacheStatusPreProd represents a cache server that is not deployed to "production", + // but is ready for it. + CacheStatusPreProd = CacheStatus("PRE_PROD") // CacheStatusInvalid represents an unrecognized Status value. Note that // this is not actually "invalid", because Statuses may have any unique // name, not just those captured as CacheStatus values in this package. diff --git a/lib/go-tc/statuses.go b/lib/go-tc/statuses.go index eafaf1a4e5..74c847b4a3 100644 --- a/lib/go-tc/statuses.go +++ b/lib/go-tc/statuses.go @@ -70,3 +70,21 @@ type StatusNullable struct { LastUpdated *TimeNoMod `json:"lastUpdated" db:"last_updated"` Name *string `json:"name" db:"name"` } + +// IsReservedStatus returns true if the passed in status name is reserved, and false if it isn't. +// Currently, the reserved statuses are OFFLINE, ONLINE, REPORTED, PRE_PROD and ADMIN_DOWN. +func IsReservedStatus(status string) bool { + switch CacheStatus(status) { + case CacheStatusOffline: + fallthrough + case CacheStatusReported: + fallthrough + case CacheStatusOnline: + fallthrough + case CacheStatusPreProd: + fallthrough + case CacheStatusAdminDown: + return true + } + return false +} diff --git a/tc-health-client/testing/tests/health-client-startup_test.go b/tc-health-client/testing/tests/health-client-startup_test.go index 60c198bbe8..6d03fa45f3 100644 --- a/tc-health-client/testing/tests/health-client-startup_test.go +++ b/tc-health-client/testing/tests/health-client-startup_test.go @@ -39,7 +39,7 @@ func startHealthClient() { func TestHealthClientStartup(t *testing.T) { tcd.WithObjs(t, []tcdata.TCObj{ tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters, - tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses, + tcdata.Profiles, tcdata.ProfileParameters, tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations, tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies, }, func() { diff --git a/traffic_ops/app/db/migrations/2022050916074300_add_reserved_statuses.down.sql b/traffic_ops/app/db/migrations/2022050916074300_add_reserved_statuses.down.sql new file mode 100644 index 0000000000..6b6a6e9174 --- /dev/null +++ b/traffic_ops/app/db/migrations/2022050916074300_add_reserved_statuses.down.sql @@ -0,0 +1,17 @@ +/* + * 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. + */ + diff --git a/traffic_ops/app/db/migrations/2022050916074300_add_reserved_statuses.up.sql b/traffic_ops/app/db/migrations/2022050916074300_add_reserved_statuses.up.sql new file mode 100644 index 0000000000..ddd9a128d3 --- /dev/null +++ b/traffic_ops/app/db/migrations/2022050916074300_add_reserved_statuses.up.sql @@ -0,0 +1,23 @@ +/* + * 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. + */ + +INSERT INTO public.status ("name", description) VALUES ('ONLINE', 'Server is online.') ON CONFLICT ("name") DO NOTHING; +INSERT INTO public.status ("name", description) VALUES ('OFFLINE', 'Server is Offline. Not active in any configuration.') ON CONFLICT ("name") DO NOTHING; +INSERT INTO public.status ("name", description) VALUES ('REPORTED', 'Server is online and reported in the health protocol.') ON CONFLICT ("name") DO NOTHING; +INSERT INTO public.status ("name", description) VALUES ('ADMIN_DOWN', 'Sever is administrative down and does not receive traffic.') ON CONFLICT ("name") DO NOTHING; +INSERT INTO public.status ("name", description) VALUES ('PRE_PROD', 'Pre Production. Not active in any configuration.') ON CONFLICT ("name") DO NOTHING; + diff --git a/traffic_ops/testing/api/v2/statuses_test.go b/traffic_ops/testing/api/v2/statuses_test.go index 9a15714723..9a11866f20 100644 --- a/traffic_ops/testing/api/v2/statuses_test.go +++ b/traffic_ops/testing/api/v2/statuses_test.go @@ -29,48 +29,55 @@ func TestStatuses(t *testing.T) { } func CreateTestStatuses(t *testing.T) { - for _, status := range testData.Statuses { resp, _, err := TOSession.CreateStatusNullable(status) t.Log("Response: ", resp) if err != nil { - t.Errorf("could not CREATE types: %v", err) + t.Errorf("could not CREATE status: %v", err) } } - } func UpdateTestStatuses(t *testing.T) { - firstStatus := testData.Statuses[0] - if firstStatus.Name == nil { - t.Fatal("cannot update test statuses: first test data status must have a name") - } - - // Retrieve the Status by name so we can get the id for the Update - resp, _, err := TOSession.GetStatusByName(*firstStatus.Name) - if err != nil { - t.Errorf("cannot GET Status by name: %v - %v", firstStatus.Name, err) + if len(testData.Statuses) < 1 { + t.Fatal("Need at least one Status to test updating a Status") } - remoteStatus := resp[0] - expectedStatusDesc := "new description" - remoteStatus.Description = expectedStatusDesc - var alert tc.Alerts - alert, _, err = TOSession.UpdateStatusByID(remoteStatus.ID, remoteStatus) - if err != nil { - t.Errorf("cannot UPDATE Status by id: %v - %v", err, alert) - } - - // Retrieve the Status to check Status name got updated - resp, _, err = TOSession.GetStatusByID(remoteStatus.ID) - if err != nil { - t.Errorf("cannot GET Status by ID: %v - %v", firstStatus.Description, err) - } - respStatus := resp[0] - if respStatus.Description != expectedStatusDesc { - t.Errorf("results do not match actual: %s, expected: %s", respStatus.Name, expectedStatusDesc) + for _, status := range testData.Statuses { + if status.Name == nil { + t.Fatal("cannot update test statuses: test data status must have a name") + } + // Retrieve the Status by name so we can get the id for the Update + resp, _, err := TOSession.GetStatusByName(*status.Name) + if err != nil { + t.Errorf("cannot GET Status by name: %s - %v", *status.Name, err) + } + remoteStatus := resp[0] + expectedStatusDesc := "new description" + remoteStatus.Description = expectedStatusDesc + var alert tc.Alerts + alert, _, err = TOSession.UpdateStatusByID(remoteStatus.ID, remoteStatus) + + if tc.IsReservedStatus(*status.Name) { + if err == nil { + t.Errorf("expected an error about while updating a reserved status, but got nothing") + } + } else { + if err != nil { + t.Errorf("cannot UPDATE Status by id: %d, err: %v - %v", remoteStatus.ID, err, alert) + } + + // Retrieve the Status to check Status name got updated + resp, _, err = TOSession.GetStatusByID(remoteStatus.ID) + if err != nil { + t.Errorf("cannot GET Status by ID: %d - %v", remoteStatus.ID, err) + } + respStatus := resp[0] + if respStatus.Description != expectedStatusDesc { + t.Errorf("results do not match actual: %s, expected: %s", respStatus.Name, expectedStatusDesc) + } + } } - } func GetTestStatuses(t *testing.T) { @@ -97,7 +104,7 @@ func DeleteTestStatuses(t *testing.T) { // Retrieve the Status by name so we can get the id for the Update resp, _, err := TOSession.GetStatusByName(*status.Name) if err != nil { - t.Errorf("cannot GET Status by name: %v - %v", status.Name, err) + t.Errorf("cannot GET Status by name: %s - %v", *status.Name, err) } if len(resp) != 1 { t.Errorf("Expected exactly one Status to exist with name '%s', found: %d", *status.Name, len(resp)) @@ -107,15 +114,15 @@ func DeleteTestStatuses(t *testing.T) { delResp, _, err := TOSession.DeleteStatusByID(respStatus.ID) if err != nil { - t.Errorf("cannot DELETE Status by name: %v - %v", err, delResp) + t.Errorf("cannot DELETE Status by ID: %v - %v", err, delResp) } // Retrieve the Status to see if it got deleted - types, _, err := TOSession.GetStatusByName(*status.Name) + statuses, _, err := TOSession.GetStatusByName(*status.Name) if err != nil { - t.Errorf("error deleting Status name: %s", err.Error()) + t.Errorf("error getting status by name: %s, err: %v", *status.Name, err) } - if len(types) > 0 { + if len(statuses) > 0 { t.Errorf("expected Status name: %s to be deleted", *status.Name) } } diff --git a/traffic_ops/testing/api/v2/tc-fixtures.json b/traffic_ops/testing/api/v2/tc-fixtures.json index cdb6a0cad1..77467ff78c 100644 --- a/traffic_ops/testing/api/v2/tc-fixtures.json +++ b/traffic_ops/testing/api/v2/tc-fixtures.json @@ -2224,30 +2224,10 @@ } ], "statuses": [ - { - "description": "Edge: Puts server in CCR config file in this state, but CCR will never route traffic to it. Mid: Server will not be included in parent.config files for its edge caches", - "name": "OFFLINE" - }, - { - "description": "Edge: Puts server in CCR config file in this state, and CCR will always route traffic to it. Mid: Server will be included in parent.config files for its edges", - "name": "ONLINE" - }, - { - "description": "Edge: Puts server in CCR config file in this state, and CCR will adhere to the health protocol. Mid: N/A for now", - "name": "REPORTED" - }, - { - "description": "Temporary down. Edge: XMPP client will send status OFFLINE to CCR, otherwise similar to REPORTED. Mid: Server will not be included in parent.config files for its edge caches", - "name": "ADMIN_DOWN" - }, { "description": "Edge: 12M will not include caches in this state in CCR config files. Mid: N/A for now", "name": "CCR_IGNORE" }, - { - "description": "Pre Production. Not active in any configuration.", - "name": "PRE_PROD" - }, { "name": "TEST_NULL_DESCRIPTION" } diff --git a/traffic_ops/testing/api/v2/todb_test.go b/traffic_ops/testing/api/v2/todb_test.go index d8ea4870b0..887bbcdb78 100644 --- a/traffic_ops/testing/api/v2/todb_test.go +++ b/traffic_ops/testing/api/v2/todb_test.go @@ -283,7 +283,7 @@ func Teardown(db *sql.DB) error { DELETE FROM cachegroup; DELETE FROM coordinate; DELETE FROM type; - DELETE FROM status; + DELETE FROM status s WHERE s.name NOT IN ('OFFLINE', 'ONLINE', 'PRE_PROD', 'ADMIN_DOWN', 'REPORTED'); DELETE FROM snapshot; DELETE FROM cdn; DELETE FROM service_category; diff --git a/traffic_ops/testing/api/v3/statuses_test.go b/traffic_ops/testing/api/v3/statuses_test.go index db095c6c83..b5ed2b1c7d 100644 --- a/traffic_ops/testing/api/v3/statuses_test.go +++ b/traffic_ops/testing/api/v3/statuses_test.go @@ -47,27 +47,31 @@ func TestStatuses(t *testing.T) { } func UpdateTestStatusesWithHeaders(t *testing.T, header http.Header) { - if len(testData.Statuses) > 0 { - firstStatus := testData.Statuses[0] - if firstStatus.Name == nil { - t.Fatal("cannot update test statuses: first test data status must have a name") - } + if len(testData.Statuses) < 1 { + t.Fatal("Need at least one Status to test updating a status with an HTTP header") + } - // Retrieve the Status by name so we can get the id for the Update - resp, _, err := TOSession.GetStatusByNameWithHdr(*firstStatus.Name, header) - if err != nil { - t.Errorf("cannot GET Status by name: %v - %v", firstStatus.Name, err) + for _, status := range testData.Statuses { + if status.Name == nil { + t.Fatal("cannot update test statuses: test data status must have a name") } - if len(resp) > 0 { - remoteStatus := resp[0] - expectedStatusDesc := "new description" - remoteStatus.Description = expectedStatusDesc - _, reqInf, err := TOSession.UpdateStatusByIDWithHdr(remoteStatus.ID, remoteStatus, header) - if err == nil { - t.Errorf("Expected error about precondition failed, but got none") + if !tc.IsReservedStatus(*status.Name) { + // Retrieve the Status by name so we can get the id for the Update + resp, _, err := TOSession.GetStatusByNameWithHdr(*status.Name, header) + if err != nil { + t.Errorf("cannot GET Status by name: %s - %v", *status.Name, err) } - if reqInf.StatusCode != http.StatusPreconditionFailed { - t.Errorf("Expected status code 412, got %v", reqInf.StatusCode) + if len(resp) > 0 { + remoteStatus := resp[0] + expectedStatusDesc := "new description" + remoteStatus.Description = expectedStatusDesc + _, reqInf, err := TOSession.UpdateStatusByIDWithHdr(remoteStatus.ID, remoteStatus, header) + if err == nil { + t.Errorf("Expected error about precondition failed, but got none") + } + if reqInf.StatusCode != http.StatusPreconditionFailed { + t.Errorf("Expected status code 412, got %v", reqInf.StatusCode) + } } } } @@ -125,15 +129,13 @@ func GetTestStatusesIMS(t *testing.T) { } func CreateTestStatuses(t *testing.T) { - for _, status := range testData.Statuses { resp, _, err := TOSession.CreateStatusNullable(status) t.Log("Response: ", resp) if err != nil { - t.Errorf("could not CREATE types: %v", err) + t.Errorf("could not CREATE status: %v", err) } } - } func SortTestStatuses(t *testing.T) { @@ -156,36 +158,41 @@ func SortTestStatuses(t *testing.T) { } func UpdateTestStatuses(t *testing.T) { + for _, status := range testData.Statuses { + if status.Name == nil { + t.Fatal("cannot update test statuses: test data status must have a name") + } + // Retrieve the Status by name so we can get the id for the Update + resp, _, err := TOSession.GetStatusByName(*status.Name) + if err != nil { + t.Errorf("cannot GET Status by name: %s - %v", *status.Name, err) + } + remoteStatus := resp[0] + expectedStatusDesc := "new description" + remoteStatus.Description = expectedStatusDesc + var alert tc.Alerts + alert, _, err = TOSession.UpdateStatusByID(remoteStatus.ID, remoteStatus) - firstStatus := testData.Statuses[0] - if firstStatus.Name == nil { - t.Fatal("cannot update test statuses: first test data status must have a name") - } - - // Retrieve the Status by name so we can get the id for the Update - resp, _, err := TOSession.GetStatusByName(*firstStatus.Name) - if err != nil { - t.Errorf("cannot GET Status by name: %v - %v", firstStatus.Name, err) - } - remoteStatus := resp[0] - expectedStatusDesc := "new description" - remoteStatus.Description = expectedStatusDesc - var alert tc.Alerts - alert, _, err = TOSession.UpdateStatusByID(remoteStatus.ID, remoteStatus) - if err != nil { - t.Errorf("cannot UPDATE Status by id: %v - %v", err, alert) - } + if tc.IsReservedStatus(*status.Name) { + if err == nil { + t.Errorf("expected an error about while updating a reserved status, but got nothing") + } + } else { + if err != nil { + t.Errorf("cannot UPDATE Status by id: %d, err: %v - %v", remoteStatus.ID, err, alert) + } - // Retrieve the Status to check Status name got updated - resp, _, err = TOSession.GetStatusByID(remoteStatus.ID) - if err != nil { - t.Errorf("cannot GET Status by ID: %v - %v", firstStatus.Description, err) - } - respStatus := resp[0] - if respStatus.Description != expectedStatusDesc { - t.Errorf("results do not match actual: %s, expected: %s", respStatus.Name, expectedStatusDesc) + // Retrieve the Status to check Status name got updated + resp, _, err = TOSession.GetStatusByID(remoteStatus.ID) + if err != nil { + t.Errorf("cannot GET Status by ID: %d - %v", remoteStatus.ID, err) + } + respStatus := resp[0] + if respStatus.Description != expectedStatusDesc { + t.Errorf("results do not match actual: %s, expected: %s", respStatus.Name, expectedStatusDesc) + } + } } - } func GetTestStatuses(t *testing.T) { @@ -205,27 +212,27 @@ func DeleteTestStatuses(t *testing.T) { for _, status := range testData.Statuses { if status.Name == nil { - t.Fatal("cannot get ftest statuses: test data statuses must have names") + t.Fatal("cannot get test statuses: test data statuses must have names") } // Retrieve the Status by name so we can get the id for the Update resp, _, err := TOSession.GetStatusByName(*status.Name) if err != nil { - t.Errorf("cannot GET Status by name: %v - %v", status.Name, err) + t.Errorf("cannot GET Status by name: %s - %v", *status.Name, err) } respStatus := resp[0] delResp, _, err := TOSession.DeleteStatusByID(respStatus.ID) if err != nil { - t.Errorf("cannot DELETE Status by name: %v - %v", err, delResp) + t.Errorf("cannot DELETE Status by ID: %v - %v", err, delResp) } // Retrieve the Status to see if it got deleted - types, _, err := TOSession.GetStatusByName(*status.Name) + statuses, _, err := TOSession.GetStatusByName(*status.Name) if err != nil { - t.Errorf("error deleting Status name: %s", err.Error()) + t.Errorf("error getting status by name: %s, err: %v", *status.Name, err) } - if len(types) > 0 { + if len(statuses) > 0 { t.Errorf("expected Status name: %s to be deleted", *status.Name) } } diff --git a/traffic_ops/testing/api/v3/tc-fixtures.json b/traffic_ops/testing/api/v3/tc-fixtures.json index d9b962a512..39671def48 100644 --- a/traffic_ops/testing/api/v3/tc-fixtures.json +++ b/traffic_ops/testing/api/v3/tc-fixtures.json @@ -4785,30 +4785,10 @@ } ], "statuses": [ - { - "description": "Edge: Puts server in CCR config file in this state, but CCR will never route traffic to it. Mid: Server will not be included in parent.config files for its edge caches", - "name": "OFFLINE" - }, - { - "description": "Edge: Puts server in CCR config file in this state, and CCR will always route traffic to it. Mid: Server will be included in parent.config files for its edges", - "name": "ONLINE" - }, - { - "description": "Edge: Puts server in CCR config file in this state, and CCR will adhere to the health protocol. Mid: N/A for now", - "name": "REPORTED" - }, - { - "description": "Temporary down. Edge: XMPP client will send status OFFLINE to CCR, otherwise similar to REPORTED. Mid: Server will not be included in parent.config files for its edge caches", - "name": "ADMIN_DOWN" - }, { "description": "Edge: 12M will not include caches in this state in CCR config files. Mid: N/A for now", "name": "CCR_IGNORE" }, - { - "description": "Pre Production. Not active in any configuration.", - "name": "PRE_PROD" - }, { "name": "TEST_NULL_DESCRIPTION" } diff --git a/traffic_ops/testing/api/v3/todb_test.go b/traffic_ops/testing/api/v3/todb_test.go index 26a7539cc4..f42ae00ad4 100644 --- a/traffic_ops/testing/api/v3/todb_test.go +++ b/traffic_ops/testing/api/v3/todb_test.go @@ -285,7 +285,7 @@ func Teardown(db *sql.DB) error { DELETE FROM cachegroup; DELETE FROM coordinate; DELETE FROM type; - DELETE FROM status; + DELETE FROM status s WHERE s.name NOT IN ('OFFLINE', 'ONLINE', 'PRE_PROD', 'ADMIN_DOWN', 'REPORTED'); DELETE FROM snapshot; DELETE FROM cdn; DELETE FROM service_category; diff --git a/traffic_ops/testing/api/v4/statuses_test.go b/traffic_ops/testing/api/v4/statuses_test.go index 106d5ca112..90958e94e5 100644 --- a/traffic_ops/testing/api/v4/statuses_test.go +++ b/traffic_ops/testing/api/v4/statuses_test.go @@ -23,6 +23,7 @@ import ( "time" "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" client "github.com/apache/trafficcontrol/traffic_ops/v4-client" ) @@ -52,31 +53,33 @@ func UpdateTestStatusesWithHeaders(t *testing.T, header http.Header) { t.Fatal("Need at least one Status to test updating a status with an HTTP header") } - firstStatus := testData.Statuses[0] - if firstStatus.Name == nil { - t.Fatal("cannot update test statuses: first test data status must have a name") - } - - // Retrieve the Status by name so we can get the id for the Update - opts := client.NewRequestOptions() - opts.Header = header - opts.QueryParameters.Set("name", *firstStatus.Name) - resp, _, err := TOSession.GetStatuses(opts) - if err != nil { - t.Errorf("cannot get Status by name '%s': %v - alerts %+v", *firstStatus.Name, err, resp.Alerts) - } - if len(resp.Response) > 0 { - remoteStatus := resp.Response[0] - expectedStatusDesc := "new description" - remoteStatus.Description = expectedStatusDesc - - opts.QueryParameters.Del("name") - _, reqInf, err := TOSession.UpdateStatus(remoteStatus.ID, remoteStatus, opts) - if err == nil { - t.Errorf("Expected error about precondition failed, but got none") + for _, status := range testData.Statuses { + if status.Name == nil { + t.Fatal("cannot update test statuses: test data status must have a name") } - if reqInf.StatusCode != http.StatusPreconditionFailed { - t.Errorf("Expected status code 412, got %d", reqInf.StatusCode) + if !tc.IsReservedStatus(*status.Name) { + // Retrieve the Status by name so we can get the id for the Update + opts := client.NewRequestOptions() + opts.Header = header + opts.QueryParameters.Set("name", *status.Name) + resp, _, err := TOSession.GetStatuses(opts) + if err != nil { + t.Errorf("cannot get Status by name '%s': %v - alerts %+v", *status.Name, err, resp.Alerts) + } + if len(resp.Response) > 0 { + remoteStatus := resp.Response[0] + expectedStatusDesc := "new description" + remoteStatus.Description = expectedStatusDesc + + opts.QueryParameters.Del("name") + _, reqInf, err := TOSession.UpdateStatus(remoteStatus.ID, remoteStatus, opts) + if err == nil { + t.Errorf("Expected error about precondition failed, but got none") + } + if reqInf.StatusCode != http.StatusPreconditionFailed { + t.Errorf("Expected status code 412, got %d", reqInf.StatusCode) + } + } } } } @@ -148,7 +151,6 @@ func CreateTestStatuses(t *testing.T) { t.Errorf("could not create Status: %v - alerts: %+v", err, resp.Alerts) } } - } func SortTestStatuses(t *testing.T) { @@ -171,45 +173,50 @@ func UpdateTestStatuses(t *testing.T) { if len(testData.Statuses) < 1 { t.Fatal("Need at least one Status to test updating a Status") } - firstStatus := testData.Statuses[0] - if firstStatus.Name == nil { - t.Fatal("cannot update test statuses: first test data status must have a name") - } - - // Retrieve the Status by name so we can get the id for the Update - opts := client.NewRequestOptions() - opts.QueryParameters.Set("name", *firstStatus.Name) - resp, _, err := TOSession.GetStatuses(opts) - if err != nil { - t.Errorf("cannot get Status by name '%s': %v - alerts: %+v", *firstStatus.Name, err, resp.Alerts) - } - if len(resp.Response) != 1 { - t.Fatalf("Expected exactly one Status to exist with name '%s', found: %d", *firstStatus.Name, len(resp.Response)) - } - remoteStatus := resp.Response[0] - expectedStatusDesc := "new description" - remoteStatus.Description = expectedStatusDesc - - alert, _, err := TOSession.UpdateStatus(remoteStatus.ID, remoteStatus, client.RequestOptions{}) - if err != nil { - t.Errorf("cannot update Status: %v - alerts: %+v", err, alert.Alerts) - } - - // Retrieve the Status to check Status name got updated - opts.QueryParameters.Del("name") - opts.QueryParameters.Set("id", strconv.Itoa(remoteStatus.ID)) - resp, _, err = TOSession.GetStatuses(opts) - if err != nil { - t.Errorf("cannot get Status '%s' by ID %d: %v - alerts: %+v", *firstStatus.Name, remoteStatus.ID, err, resp.Alerts) - } - if len(resp.Response) != 1 { - t.Fatalf("Expected exactly one Status to exist with ID %d, found: %d", remoteStatus.ID, len(resp.Response)) - } - respStatus := resp.Response[0] - if respStatus.Description != expectedStatusDesc { - t.Errorf("results do not match actual: %s, expected: %s", respStatus.Name, expectedStatusDesc) + for _, status := range testData.Statuses { + if status.Name == nil { + t.Fatal("cannot update test statuses: test data status must have a name") + } + // Retrieve the Status by name so we can get the id for the Update + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", *status.Name) + resp, _, err := TOSession.GetStatuses(opts) + if err != nil { + t.Errorf("cannot get Status by name '%s': %v - alerts: %+v", *status.Name, err, resp.Alerts) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected exactly one Status to exist with name '%s', found: %d", *status.Name, len(resp.Response)) + } + remoteStatus := resp.Response[0] + expectedStatusDesc := "new description" + remoteStatus.Description = expectedStatusDesc + alert, _, err := TOSession.UpdateStatus(remoteStatus.ID, remoteStatus, client.RequestOptions{}) + + if tc.IsReservedStatus(*status.Name) { + if err == nil { + t.Errorf("expected an error about while updating a reserved status, but got nothing") + } + } else { + if err != nil { + t.Errorf("cannot update Status: %v - alerts: %+v", err, alert.Alerts) + } + + // Retrieve the Status to check Status name got updated + opts.QueryParameters.Del("name") + opts.QueryParameters.Set("id", strconv.Itoa(remoteStatus.ID)) + resp, _, err = TOSession.GetStatuses(opts) + if err != nil { + t.Errorf("cannot get Status '%s' by ID %d: %v - alerts: %+v", *status.Name, remoteStatus.ID, err, resp.Alerts) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected exactly one Status to exist with ID %d, found: %d", remoteStatus.ID, len(resp.Response)) + } + respStatus := resp.Response[0] + if respStatus.Description != expectedStatusDesc { + t.Errorf("results do not match actual: %s, expected: %s", respStatus.Name, expectedStatusDesc) + } + } } - } func GetTestStatuses(t *testing.T) { @@ -232,7 +239,6 @@ func DeleteTestStatuses(t *testing.T) { if status.Name == nil { t.Fatal("cannot get test statuses: test data statuses must have names") } - // Retrieve the Status by name so we can get the id for the Update opts.QueryParameters.Set("name", *status.Name) resp, _, err := TOSession.GetStatuses(opts) diff --git a/traffic_ops/testing/api/v4/tc-fixtures.json b/traffic_ops/testing/api/v4/tc-fixtures.json index 632153f725..2956a60cf1 100644 --- a/traffic_ops/testing/api/v4/tc-fixtures.json +++ b/traffic_ops/testing/api/v4/tc-fixtures.json @@ -5437,30 +5437,10 @@ } ], "statuses": [ - { - "description": "Edge: Puts server in CCR config file in this state, but CCR will never route traffic to it. Mid: Server will not be included in parent.config files for its edge caches", - "name": "OFFLINE" - }, - { - "description": "Edge: Puts server in CCR config file in this state, and CCR will always route traffic to it. Mid: Server will be included in parent.config files for its edges", - "name": "ONLINE" - }, - { - "description": "Edge: Puts server in CCR config file in this state, and CCR will adhere to the health protocol. Mid: N/A for now", - "name": "REPORTED" - }, - { - "description": "Temporary down. Edge: XMPP client will send status OFFLINE to CCR, otherwise similar to REPORTED. Mid: Server will not be included in parent.config files for its edge caches", - "name": "ADMIN_DOWN" - }, { "description": "Edge: 12M will not include caches in this state in CCR config files. Mid: N/A for now", "name": "CCR_IGNORE" }, - { - "description": "Pre Production. Not active in any configuration.", - "name": "PRE_PROD" - }, { "name": "TEST_NULL_DESCRIPTION" } diff --git a/traffic_ops/testing/api/v4/todb_test.go b/traffic_ops/testing/api/v4/todb_test.go index 697f35df36..7f9104a8a3 100644 --- a/traffic_ops/testing/api/v4/todb_test.go +++ b/traffic_ops/testing/api/v4/todb_test.go @@ -417,7 +417,7 @@ func Teardown(db *sql.DB) error { DELETE FROM cachegroup; DELETE FROM coordinate; DELETE FROM type; - DELETE FROM status; + DELETE FROM status s WHERE s.name NOT IN ('OFFLINE', 'ONLINE', 'PRE_PROD', 'ADMIN_DOWN', 'REPORTED'); DELETE FROM snapshot; DELETE FROM cdn; DELETE FROM service_category; diff --git a/traffic_ops/traffic_ops_golang/status/statuses.go b/traffic_ops/traffic_ops_golang/status/statuses.go index f5f75d5940..875edce434 100644 --- a/traffic_ops/traffic_ops_golang/status/statuses.go +++ b/traffic_ops/traffic_ops_golang/status/statuses.go @@ -119,9 +119,29 @@ func (st *TOStatus) Read(h http.Header, useIMS bool) ([]interface{}, error, erro return readVals, nil, nil, errCode, maxTime } -func (st *TOStatus) Update(h http.Header) (error, error, int) { return api.GenericUpdate(h, st) } -func (st *TOStatus) Create() (error, error, int) { return api.GenericCreate(st) } -func (st *TOStatus) Delete() (error, error, int) { return api.GenericDelete(st) } +func (st *TOStatus) Update(h http.Header) (error, error, int) { + var statusName string + err := st.APIInfo().Tx.QueryRow(`SELECT name from status WHERE id = $1`, *st.ID).Scan(&statusName) + if err != nil { + return nil, fmt.Errorf("error querying status name from ID: %w", err), http.StatusInternalServerError + } + if tc.IsReservedStatus(statusName) { + return fmt.Errorf("cannot modify %s status", statusName), nil, http.StatusForbidden + } + return api.GenericUpdate(h, st) +} +func (st *TOStatus) Create() (error, error, int) { return api.GenericCreate(st) } +func (st *TOStatus) Delete() (error, error, int) { + var statusName string + err := st.APIInfo().Tx.QueryRow(`SELECT name from status WHERE id = $1`, *st.ID).Scan(&statusName) + if err != nil { + return nil, fmt.Errorf("error querying status name from ID: %w", err), http.StatusInternalServerError + } + if tc.IsReservedStatus(statusName) { + return fmt.Errorf("cannot delete %s status", statusName), nil, http.StatusForbidden + } + return api.GenericDelete(st) +} func selectQuery() string { return `