Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,22 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## [unreleased]
### Added
- [#2101](https://github.com/apache/trafficcontrol/issues/2101) Added the ability to tell if a Delivery Service is the target of another steering DS.
- [#6033](https://github.com/apache/trafficcontrol/issues/6033) Added ability to assign multiple server capabilities to a server.
- [#7032](https://github.com/apache/trafficcontrol/issues/7032) Add t3c-apply flag to use local ATS version for config generation rather than Server package Parameter, to allow managing the ATS OS package via external tools. See 'man t3c-apply' and 'man t3c-generate' for details.
- Traffic Ops API version 5.0

- [Traffic Monitor] Added logging for `ipv4Availability` and `ipv6Availability` in TM.
- [Traffic Ops] Added the `ASN` field in TO Server struct, which provides the ability to query servers by `ASN`.
- *Traffic Monitor* Added logging for `ipv4Availability` and `ipv6Availability` in TM.
- *Traffic Ops* Added API version 5.0
- *Traffic Ops* Added the `ASN` field in TO Server struct, which provides the ability to query servers by `ASN`.
- [#2101](https://github.com/apache/trafficcontrol/issues/2101) *Traffic Portal* Added the ability to tell if a Delivery Service is the target of another steering DS.
- [#6033](https://github.com/apache/trafficcontrol/issues/6033) *Traffic Ops, Traffic Portal* Added ability to assign multiple server capabilities to a server.
- [#7032](https://github.com/apache/trafficcontrol/issues/7032) *Cache Config* Add t3c-apply flag to use local ATS version for config generation rather than Server package Parameter, to allow managing the ATS OS package via external tools. See 'man t3c-apply' and 'man t3c-generate' for details.

### Changed
- Traffic Portal now obscures sensitive text in Delivery Service "Raw Remap" fields, private SSL keys, "Header Rewrite" rules, and ILO interface passwords by default.
- Traffic Router now uses Traffic Ops API 4.0 by default
- The Traffic Ops Python client now uses Traffic Ops API 4.1 by default.
- *Traffic Ops* Python client now uses Traffic Ops API 4.1 by default.
- *Traffic Portal* Obscures sensitive text in Delivery Service "Raw Remap" fields, private SSL keys, "Header Rewrite" rules, and ILO interface passwords by default.
- *Traffic Router* Uses Traffic Ops API 4.0 by default

### Fixed
- Traffic Stats: Reuse InfluxDB client handle to prevent potential connection leaks
- [#7021](https://github.com/apache/trafficcontrol/issues/7021) Fixed cache config for Delivery Services with IP Origins

- *Traffic Stats* Reuse InfluxDB client handle to prevent potential connection leaks.
- [#7021](https://github.com/apache/trafficcontrol/issues/7021) *Cache Config* Fixed cache config for Delivery Services with IP Origins.
- [#7047](https://github.com/apache/trafficcontrol/issues/7047) *Traffic Ops* allow `apply_time` query parameters on the `servers/{id-name}/update` when the CDN is locked.

## [7.0.0] - 2022-07-19
### Added
Expand Down
113 changes: 113 additions & 0 deletions traffic_ops/testing/api/v4/serverupdatestatus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,119 @@ import (
client "github.com/apache/trafficcontrol/traffic_ops/v4-client"
)

func TestServerUpdateApplyTimeLocked(t *testing.T) {
WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, ServiceCategories, Topologies, DeliveryServices}, func() {
opts := client.NewRequestOptions()
opts.QueryParameters.Set("hostName", "atlanta-edge-01")
resp, _, err := TOSession.GetServers(opts)
if err != nil {
t.Fatalf("cannot get server by hostname: %v", err)
}
if len(resp.Response) != 1 {
t.Fatalf("Expected a server named 'atlanta-edge-01' to exist")
}
testServer := resp.Response[0]

testVals := func(configApply, revalApply *time.Time) {
resp, _, err := TOSession.GetServers(opts)
if err != nil {
t.Errorf("cannot get Server by name '%s': %v - alerts: %+v", *testServer.HostName, err, resp.Alerts)
} else if len(resp.Response) != 1 {
t.Fatalf("GET Server expected 1, actual %v", len(resp.Response))
}

beforeServer := resp.Response[0]

// Ensure baseline
if beforeServer.UpdPending == nil {
t.Fatalf("server '%s' had nil UpdPending before update status change", *testServer.HostName)
}
if beforeServer.RevalPending == nil {
t.Fatalf("server '%s' had nil RevalPending before update status change", *testServer.HostName)
}
if beforeServer.ConfigUpdateTime == nil {
t.Fatalf("server '%s' had nil ConfigUpdateTime before update status change", *testServer.HostName)
}
if beforeServer.ConfigApplyTime == nil {
t.Fatalf("server '%s' had nil ConfigApplyTime before update status change", *testServer.HostName)
}
if beforeServer.RevalUpdateTime == nil {
t.Fatalf("server '%s' had nil RevalUpdateTime before update status change", *testServer.HostName)
}
if beforeServer.RevalApplyTime == nil {
t.Fatalf("server '%s' had nil RevalApplyTime before update status change", *testServer.HostName)
}

_, _, err = TOSession.CreateCDNLock(tc.CDNLock{CDN: *testServer.CDNName, Soft: util.BoolPtr(false)}, client.NewRequestOptions())
if err != nil {
t.Fatalf("cannont acquire lock on the Server '%s' CDN '%s: %v", *testServer.HostName, *testServer.CDNName, err)
}

opsSession, _, err := client.LoginWithAgent(Config.TrafficOps.URL, Config.TrafficOps.Users.Operations,
Config.TrafficOps.UserPassword, true, "to-api-v4-locks-ops",
true, time.Second*time.Duration(Config.Default.Session.TimeoutInSecs))
if err != nil {
t.Fatalf("cannot login as '%s' user: %v", Config.TrafficOps.Users.Operations, err)
}

badQueryOpts := client.NewRequestOptions()
badQueryOpts.QueryParameters.Set("updated", "true")
if _, _, err := opsSession.SetUpdateServerStatusTimes(*testServer.HostName, configApply, revalApply, badQueryOpts); err == nil {
t.Fatalf("expected SetUpdateServerStatusTimes to not allow updates due to `updated` field")
}

badQueryOpts = client.NewRequestOptions()
badQueryOpts.QueryParameters.Set("reval_updated", "true")
if _, _, err := opsSession.SetUpdateServerStatusTimes(*testServer.HostName, configApply, revalApply, badQueryOpts); err == nil {
t.Fatalf("expected SetUpdateServerStatusTimes to not allow updates due to `reval_updated` field")
}

if alerts, _, err := opsSession.SetUpdateServerStatusTimes(*testServer.HostName, configApply, revalApply, client.RequestOptions{}); err != nil {
t.Fatalf("SetUpdateServerStatusTimes error. expected: nil, actual: %v - alerts: %+v", err, alerts.Alerts)
}

resp, _, err = opsSession.GetServers(opts)
if err != nil {
t.Fatalf("cannot GET Server by name '%s': %v - alerts: %+v", *testServer.HostName, err, resp.Alerts)
} else if len(resp.Response) != 1 {
t.Fatalf("GET Server expected 1, actual %v", len(resp.Response))
}
afterServer := resp.Response[0]

opts := client.NewRequestOptions()
opts.QueryParameters.Set("cdn", *testServer.CDNName)
_, _, err = TOSession.DeleteCDNLocks(opts)
if err != nil {
t.Fatalf("cannont delete acquired lock on the Server '%s' CDN '%s: %v", *testServer.HostName, *testServer.CDNName, err)
}

if afterServer.UpdPending == nil {
t.Fatalf("server '%s' had nil UpdPending after update status change", *testServer.HostName)
}
if afterServer.RevalPending == nil {
t.Fatalf("Server '%s' had nil RevalPending after update status change", *testServer.HostName)
}

// Ensure values were actually set
if configApply != nil {
if afterServer.ConfigApplyTime == nil || !afterServer.ConfigApplyTime.Equal(*configApply) {
t.Fatalf("failed to set server's ConfigApplyTime. expected: %v actual: %v", *configApply, afterServer.ConfigApplyTime)
}
}
if revalApply != nil {
if afterServer.RevalApplyTime == nil || !afterServer.RevalApplyTime.Equal(*revalApply) {
t.Fatalf("failed to set server's RevalApplyTime. expected: %v actual: %v", *revalApply, afterServer.RevalApplyTime)
}
}

}

now := time.Now().Round(time.Microsecond)
testVals(util.TimePtr(now), nil)
testVals(nil, util.TimePtr(now))
})
}

func TestServerUpdateStatusLastAssigned(t *testing.T) {
WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, ServiceCategories, Topologies, DeliveryServices}, func() {
opts := client.NewRequestOptions()
Expand Down
113 changes: 113 additions & 0 deletions traffic_ops/testing/api/v5/serverupdatestatus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,119 @@ import (
client "github.com/apache/trafficcontrol/traffic_ops/v5-client"
)

func TestServerUpdateApplyTimeLocked(t *testing.T) {
WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, ServiceCategories, Topologies, DeliveryServices}, func() {
opts := client.NewRequestOptions()
opts.QueryParameters.Set("hostName", "atlanta-edge-01")
resp, _, err := TOSession.GetServers(opts)
if err != nil {
t.Fatalf("cannot get server by hostname: %v", err)
}
if len(resp.Response) != 1 {
t.Fatalf("Expected a server named 'atlanta-edge-01' to exist")
}
testServer := resp.Response[0]

testVals := func(configApply, revalApply *time.Time) {
resp, _, err := TOSession.GetServers(opts)
if err != nil {
t.Errorf("cannot get Server by name '%s': %v - alerts: %+v", *testServer.HostName, err, resp.Alerts)
} else if len(resp.Response) != 1 {
t.Fatalf("GET Server expected 1, actual %v", len(resp.Response))
}

beforeServer := resp.Response[0]

// Ensure baseline
if beforeServer.UpdPending == nil {
t.Fatalf("server '%s' had nil UpdPending before update status change", *testServer.HostName)
}
if beforeServer.RevalPending == nil {
t.Fatalf("server '%s' had nil RevalPending before update status change", *testServer.HostName)
}
if beforeServer.ConfigUpdateTime == nil {
t.Fatalf("server '%s' had nil ConfigUpdateTime before update status change", *testServer.HostName)
}
if beforeServer.ConfigApplyTime == nil {
t.Fatalf("server '%s' had nil ConfigApplyTime before update status change", *testServer.HostName)
}
if beforeServer.RevalUpdateTime == nil {
t.Fatalf("server '%s' had nil RevalUpdateTime before update status change", *testServer.HostName)
}
if beforeServer.RevalApplyTime == nil {
t.Fatalf("server '%s' had nil RevalApplyTime before update status change", *testServer.HostName)
}

_, _, err = TOSession.CreateCDNLock(tc.CDNLock{CDN: *testServer.CDNName, Soft: util.BoolPtr(false)}, client.NewRequestOptions())
if err != nil {
t.Fatalf("cannont acquire lock on the Server '%s' CDN '%s: %v", *testServer.HostName, *testServer.CDNName, err)
}

opsSession, _, err := client.LoginWithAgent(Config.TrafficOps.URL, Config.TrafficOps.Users.Operations,
Config.TrafficOps.UserPassword, true, "to-api-v4-locks-ops",
true, time.Second*time.Duration(Config.Default.Session.TimeoutInSecs))
if err != nil {
t.Fatalf("cannot login as '%s' user: %v", Config.TrafficOps.Users.Operations, err)
}

badQueryOpts := client.NewRequestOptions()
badQueryOpts.QueryParameters.Set("updated", "true")
if _, _, err := opsSession.SetUpdateServerStatusTimes(*testServer.HostName, configApply, revalApply, badQueryOpts); err == nil {
t.Fatalf("expected SetUpdateServerStatusTimes to not allow updates due to `updated` field")
}

badQueryOpts = client.NewRequestOptions()
badQueryOpts.QueryParameters.Set("reval_updated", "true")
if _, _, err := opsSession.SetUpdateServerStatusTimes(*testServer.HostName, configApply, revalApply, badQueryOpts); err == nil {
t.Fatalf("expected SetUpdateServerStatusTimes to not allow updates due to `reval_updated` field")
}

if alerts, _, err := opsSession.SetUpdateServerStatusTimes(*testServer.HostName, configApply, revalApply, client.RequestOptions{}); err != nil {
t.Fatalf("SetUpdateServerStatusTimes error. expected: nil, actual: %v - alerts: %+v", err, alerts.Alerts)
}

resp, _, err = opsSession.GetServers(opts)
if err != nil {
t.Fatalf("cannot GET Server by name '%s': %v - alerts: %+v", *testServer.HostName, err, resp.Alerts)
} else if len(resp.Response) != 1 {
t.Fatalf("GET Server expected 1, actual %v", len(resp.Response))
}
afterServer := resp.Response[0]

opts := client.NewRequestOptions()
opts.QueryParameters.Set("cdn", *testServer.CDNName)
_, _, err = TOSession.DeleteCDNLocks(opts)
if err != nil {
t.Fatalf("cannont delete acquired lock on the Server '%s' CDN '%s: %v", *testServer.HostName, *testServer.CDNName, err)
}

if afterServer.UpdPending == nil {
t.Fatalf("server '%s' had nil UpdPending after update status change", *testServer.HostName)
}
if afterServer.RevalPending == nil {
t.Fatalf("Server '%s' had nil RevalPending after update status change", *testServer.HostName)
}

// Ensure values were actually set
if configApply != nil {
if afterServer.ConfigApplyTime == nil || !afterServer.ConfigApplyTime.Equal(*configApply) {
t.Fatalf("failed to set server's ConfigApplyTime. expected: %v actual: %v", *configApply, afterServer.ConfigApplyTime)
}
}
if revalApply != nil {
if afterServer.RevalApplyTime == nil || !afterServer.RevalApplyTime.Equal(*revalApply) {
t.Fatalf("failed to set server's RevalApplyTime. expected: %v actual: %v", *revalApply, afterServer.RevalApplyTime)
}
}

}

now := time.Now().Round(time.Microsecond)
testVals(util.TimePtr(now), nil)
testVals(nil, util.TimePtr(now))
})
}

func TestServerUpdateStatusLastAssigned(t *testing.T) {
WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, ServiceCategories, Topologies, DeliveryServices}, func() {
opts := client.NewRequestOptions()
Expand Down
7 changes: 6 additions & 1 deletion traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,12 @@ WHERE tm_user.email = $1
// CheckIfCurrentUserHasCdnLock checks if the current user has the lock on the cdn that the requested operation is to be performed on.
// This will succeed if the either there is no lock by any user on the CDN, or if the current user has the lock on the CDN.
func CheckIfCurrentUserHasCdnLock(tx *sql.Tx, cdn, user string) (error, error, int) {
query := `SELECT c.username, ARRAY_REMOVE(ARRAY_AGG(u.username), NULL) AS shared_usernames FROM cdn_lock c LEFT JOIN cdn_lock_user u ON c.username = u.owner AND c.cdn = u.cdn WHERE c.cdn=$1 GROUP BY c.username`
query := `
SELECT c.username, ARRAY_REMOVE(ARRAY_AGG(u.username), NULL) AS shared_usernames
FROM cdn_lock c
LEFT JOIN cdn_lock_user u ON c.username = u.owner AND c.cdn = u.cdn
WHERE c.cdn=$1
GROUP BY c.username`
var userName string
var sharedUserNames []string
rows, err := tx.Query(query, cdn)
Expand Down
17 changes: 13 additions & 4 deletions traffic_ops/traffic_ops_golang/server/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,19 @@ func UpdateHandlerV4(w http.ResponseWriter, r *http.Request) {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
return
}
userErr, sysErr, statusCode := dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdnName), inf.User.UserName)
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr)
return

_, hasConfigUpdatedBoolParam := inf.Params["updated"]
_, hasRevalUpdatedBoolParam := inf.Params["reval_updated"]
_, hasConfigApplyTimeParam := inf.Params["config_apply_time"]
_, hasRevalidateApplyTimeParam := inf.Params["revalidate_apply_time"]
// Allow `apply_time` changes when the CDN is locked, but not `updated`
canIgnoreLock := (hasConfigApplyTimeParam || hasRevalidateApplyTimeParam) && !hasConfigUpdatedBoolParam && !hasRevalUpdatedBoolParam
if !canIgnoreLock {
userDoesntHaveLockErr, sysErr, statusCode := dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdnName), inf.User.UserName)
if sysErr != nil || userDoesntHaveLockErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, statusCode, userDoesntHaveLockErr, sysErr)
return
}
}

// TODO parse JSON body to trump Query Params?
Expand Down