You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by oc...@apache.org on 2021/04/26 23:43:51 UTC

[trafficcontrol] 01/05: Return legacy Monitoring Config from legacy Monitoring Config handler (#5755)

This is an automated email from the ASF dual-hosted git repository.

ocket8888 pushed a commit to branch 5.1.x
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit 00916fb690c5aee9a946f3bd7928c8feabe5abbe
Author: Zach Hoffman <zr...@apache.org>
AuthorDate: Fri Apr 23 12:43:02 2021 -0600

    Return legacy Monitoring Config from legacy Monitoring Config handler (#5755)
    
    * Set Health Threshold Parameters when converting to legacy Traffic Monitor Config
    
    * Make tc.strToThreshold() visibile
    
    * Return legacy Monitoring Config from legacy Monitoring Config handler
    
    * Remove duplicate parameters
    
    * Remove SnapshotGetMonitoringLegacyHandler()
    
    * Remove "Legacy" from logging
    
    * Copy new API tests to API v3 and API v4
    
    * Revert "Remove SnapshotGetMonitoringLegacyHandler()"
    
    * Revert crconfig.SnapshotGetMonitoringLegacyHandler() changes
    
    * Add godocs
    
    * Check the length of profiles, not cdns
    
    * Remove redundant condition
    
    * gofmt
    
    (cherry picked from commit a233b14a9a72c80f855c2b91ee88b71a783c49d0)
---
 CHANGELOG.md                                |  1 +
 lib/go-tc/nullable_test.go                  |  1 +
 lib/go-tc/traffic_monitor.go                | 47 ++++++++++++++--
 traffic_ops/testing/api/v1/crconfig_test.go | 83 ++++++++++++++++++++++++++++-
 traffic_ops/testing/api/v1/tc-fixtures.json | 36 +++++++------
 traffic_ops/testing/api/v2/crconfig_test.go | 83 ++++++++++++++++++++++++++++-
 traffic_ops/testing/api/v2/tc-fixtures.json | 36 +++++++------
 traffic_ops/testing/api/v3/crconfig_test.go | 81 ++++++++++++++++++++++++++++
 traffic_ops/testing/api/v3/tc-fixtures.json | 36 +++++++------
 9 files changed, 353 insertions(+), 51 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d5759f5..f0a94c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - [#5712](https://github.com/apache/trafficcontrol/issues/5712) - Ensure that 5.x Traffic Stats is compatible with 5.x Traffic Monitor and 5.x Traffic Ops, and that it doesn't log all 0's for `cache_stats`
 - Fixed ORT being unable to update URLSIG keys for Delivery Services
 - Fixed an issue where Traffic Ops becoming unavailable caused Traffic Monitor to segfault and crash
+- [#5754](https://github.com/apache/trafficcontrol/issues/5754) - Ensure Health Threshold Parameters use legacy format for legacy Monitoring Config handler
 
 ## [5.1.1] - 2021-03-19
 ### Added
diff --git a/lib/go-tc/nullable_test.go b/lib/go-tc/nullable_test.go
index 9c0fcdf..442bc8e 100644
--- a/lib/go-tc/nullable_test.go
+++ b/lib/go-tc/nullable_test.go
@@ -2,6 +2,7 @@
 // skip this unless specifically testing for nullable vs non-nullable struct comparison
 
 // Run as `go test -tags nullable`
+//go:build nullable
 // +build nullable
 
 package tc
diff --git a/lib/go-tc/traffic_monitor.go b/lib/go-tc/traffic_monitor.go
index 6f7f1b2..e4b7d09 100644
--- a/lib/go-tc/traffic_monitor.go
+++ b/lib/go-tc/traffic_monitor.go
@@ -80,6 +80,10 @@ type TrafficMonitorConfig struct {
 	Profiles []TMProfile `json:"profiles,omitempty"`
 }
 
+const healthThresholdAvailableBandwidthInKbps = "availableBandwidthInKbps"
+const healthThresholdLoadAverage = "loadavg"
+const healthThresholdQueryTime = "queryTime"
+
 // ToLegacyConfig converts TrafficMonitorConfig to LegacyTrafficMonitorConfig.
 func (tmc *TrafficMonitorConfig) ToLegacyConfig() LegacyTrafficMonitorConfig {
 	var servers []LegacyTrafficServer
@@ -87,6 +91,23 @@ func (tmc *TrafficMonitorConfig) ToLegacyConfig() LegacyTrafficMonitorConfig {
 		servers = append(servers, s.ToLegacyServer())
 	}
 
+	for profileIndex, profile := range tmc.Profiles {
+		thresholds := profile.Parameters.Thresholds
+		if _, exists := thresholds[healthThresholdAvailableBandwidthInKbps]; exists {
+			tmc.Profiles[profileIndex].Parameters.AvailableBandwidthInKbps = thresholds[healthThresholdAvailableBandwidthInKbps].String()
+			delete(tmc.Profiles[profileIndex].Parameters.Thresholds, healthThresholdAvailableBandwidthInKbps)
+		}
+		if _, exists := thresholds[healthThresholdLoadAverage]; exists {
+			tmc.Profiles[profileIndex].Parameters.LoadAverage = thresholds[healthThresholdLoadAverage].String()
+			delete(tmc.Profiles[profileIndex].Parameters.Thresholds, healthThresholdLoadAverage)
+		}
+		if _, exists := thresholds[healthThresholdQueryTime]; exists {
+			//tmc.Profiles[profileIndex].Parameters.QueryTime = int(thresholds[healthThresholdQueryTime].Val)
+			tmc.Profiles[profileIndex].Parameters.QueryTime = thresholds[healthThresholdQueryTime].String()
+			delete(tmc.Profiles[profileIndex].Parameters.Thresholds, healthThresholdQueryTime)
+		}
+	}
+
 	legacy := LegacyTrafficMonitorConfig{
 		CacheGroups:      tmc.CacheGroups,
 		Config:           tmc.Config,
@@ -426,7 +447,25 @@ type TMParameters struct {
 	HealthPollingType       string `json:"health.polling.type"`
 	HistoryCount            int    `json:"history.count"`
 	MinFreeKbps             int64
-	Thresholds              map[string]HealthThreshold `json:"health_threshold"`
+	// HealthThresholdJSONParameters contains the Parameters contained in the
+	// Thresholds field, formatted as individual string Parameters, rather than as
+	// a JSON object.
+	Thresholds map[string]HealthThreshold `json:"health_threshold,omitempty"`
+	HealthThresholdJSONParameters
+}
+
+// HealthThresholdJSONParameters contains Parameters whose Thresholds must be met in order for
+// Caches using the Profile containing these Parameters to be marked as Healthy.
+type HealthThresholdJSONParameters struct {
+	// AvailableBandwidthInKbps is The total amount of bandwidth that servers using this profile are
+	// allowed, in Kilobits per second. This is a string and using comparison operators to specify
+	// ranges, e.g. ">10" means "more than 10 kbps".
+	AvailableBandwidthInKbps string `json:"health.threshold.availableBandwidthInKbps,omitempty"`
+	// LoadAverage is the UNIX loadavg at which the server should be marked "unhealthy".
+	LoadAverage string `json:"health.threshold.loadavg,omitempty"`
+	// QueryTime is the highest allowed length of time for completing health queries (after
+	// connection has been established) in milliseconds.
+	QueryTime string `json:"health.threshold.queryTime,omitempty"`
 }
 
 const DefaultHealthThresholdComparator = "<"
@@ -446,11 +485,11 @@ func (t HealthThreshold) String() string {
 	return fmt.Sprintf("%s%f", t.Comparator, t.Val)
 }
 
-// strToThreshold takes a string like ">=42" and returns a HealthThreshold with
+// StrToThreshold takes a string like ">=42" and returns a HealthThreshold with
 // a Val of `42` and a Comparator of `">="`. If no comparator exists,
 // `DefaultHealthThresholdComparator` is used. If the string does not match
 // "(>|<|)(=|)\d+" an error is returned.
-func strToThreshold(s string) (HealthThreshold, error) {
+func StrToThreshold(s string) (HealthThreshold, error) {
 	// The order of these is important - don't re-order without considering the
 	// consequences.
 	comparators := []string{">=", "<=", ">", "<", "="}
@@ -523,7 +562,7 @@ func (params *TMParameters) UnmarshalJSON(bytes []byte) (err error) {
 		if strings.HasPrefix(k, ThresholdPrefix) {
 			stat := k[len(ThresholdPrefix):]
 			vStr := fmt.Sprintf("%v", v) // allows string or numeric JSON types. TODO check if a type switch is faster.
-			if t, err := strToThreshold(vStr); err != nil {
+			if t, err := StrToThreshold(vStr); err != nil {
 				return fmt.Errorf("Unmarshalling TMParameters `%s` parameter value not of the form `(>|)(=|)\\d+`: stat '%s' value '%v': %v", ThresholdPrefix, k, v, err)
 			} else {
 				params.Thresholds[stat] = t
diff --git a/traffic_ops/testing/api/v1/crconfig_test.go b/traffic_ops/testing/api/v1/crconfig_test.go
index e02a0c8..d964029 100644
--- a/traffic_ops/testing/api/v1/crconfig_test.go
+++ b/traffic_ops/testing/api/v1/crconfig_test.go
@@ -26,6 +26,7 @@ import (
 func TestCRConfig(t *testing.T) {
 	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
 		UpdateTestCRConfigSnapshot(t)
+		MonitoringConfig(t)
 		SnapshotTestCDNbyName(t)
 		SnapshotTestCDNbyInvalidName(t)
 		SnapshotTestCDNbyID(t)
@@ -35,7 +36,7 @@ func TestCRConfig(t *testing.T) {
 
 func UpdateTestCRConfigSnapshot(t *testing.T) {
 	if len(testData.CDNs) < 1 {
-		t.Error("no cdn test data")
+		t.Fatalf("no cdn test data")
 	}
 	cdn := testData.CDNs[0].Name
 
@@ -156,6 +157,86 @@ func UpdateTestCRConfigSnapshot(t *testing.T) {
 	}
 }
 
+func MonitoringConfig(t *testing.T) {
+	if len(testData.CDNs) < 1 {
+		t.Fatalf("no cdn test data")
+	}
+	const cdnName = "cdn1"
+	const profileName = "EDGE1"
+	cdns, _, err := TOSession.GetCDNByName(cdnName)
+	if err != nil {
+		t.Fatalf("getting CDNs with name '%s': %s", cdnName, err.Error())
+	}
+	if len(cdns) < 1 {
+		t.Fatalf("expected to find a CDN named %s", cdnName)
+	}
+	if len(cdns) > 1 {
+		t.Fatalf("expected exactly 1 CDN named %s but found %d CDNs", cdnName, len(cdns))
+	}
+	profiles, _, err := TOSession.GetProfileByName(profileName)
+	if err != nil {
+		t.Fatalf("getting Profiles with name '%s': %s", profileName, err.Error())
+	}
+	if len(profiles) != 1 {
+		t.Fatalf("expected exactly 1 Profiles named %s but found %d Profiles", profileName, len(profiles))
+	}
+	parameters, _, err := TOSession.GetParametersByProfileName(profileName)
+	if err != nil {
+		t.Fatalf("getting Parameters by Profile name '%s': %s", profileName, err.Error())
+	}
+	parameterMap := map[string]tc.HealthThreshold{}
+	parameterFound := map[string]bool{}
+	const thresholdPrefixLength = len(tc.ThresholdPrefix)
+	for _, parameter := range parameters {
+		if !strings.HasPrefix(parameter.Name, tc.ThresholdPrefix) {
+			continue
+		}
+		parameterName := parameter.Name[thresholdPrefixLength:]
+		parameterMap[parameterName], err = tc.StrToThreshold(parameter.Value)
+		if err != nil {
+			t.Fatalf("converting string '%s' to HealthThreshold: %s", parameter.Value, err.Error())
+		}
+		parameterFound[parameterName] = false
+	}
+	const expectedThresholdParameters = 3
+	if len(parameterMap) != expectedThresholdParameters {
+		t.Fatalf("expected Profile '%s' to contain %d Parameters with names starting with '%s' but %d such Parameters were found", profileName, expectedThresholdParameters, tc.ThresholdPrefix, len(parameterMap))
+	}
+	tmConfig, _, err := TOSession.GetTrafficMonitorConfig(cdnName)
+	if err != nil {
+		t.Fatalf("getting Traffic Monitor Config: %s", err.Error())
+	}
+	profileFound := false
+	var profile tc.TMProfile
+	for _, profile = range tmConfig.Profiles {
+		if profile.Name == profileName {
+			profileFound = true
+			break
+		}
+	}
+	if !profileFound {
+		t.Fatalf("Traffic Monitor Config contained no Profile named '%s", profileName)
+	}
+	for parameterName, value := range profile.Parameters.Thresholds {
+		if _, ok := parameterFound[parameterName]; !ok {
+			t.Fatalf("unexpected Threshold Parameter name '%s' found in Profile '%s' in Traffic Monitor Config", parameterName, profileName)
+		}
+		parameterFound[parameterName] = true
+		if parameterMap[parameterName].String() != value.String() {
+			t.Fatalf("expected '%s' but received '%s' for Threshold Parameter '%s' in Profile '%s' in Traffic Monitor Config", parameterMap[parameterName].String(), value.String(), parameterName, profileName)
+		}
+	}
+	missingParameters := []string{}
+	for parameterName, found := range parameterFound {
+		if !found {
+			missingParameters = append(missingParameters, parameterName)
+		}
+	}
+	if len(missingParameters) != 0 {
+		t.Fatalf("Threshold parameters defined for Profile '%s' but missing for Profile '%s' in Traffic Monitor Config: %s", profileName, profileName, strings.Join(missingParameters, ", "))
+	}
+}
+
 func SnapshotTestCDNbyName(t *testing.T) {
 
 	firstCDN := testData.CDNs[0]
diff --git a/traffic_ops/testing/api/v1/tc-fixtures.json b/traffic_ops/testing/api/v1/tc-fixtures.json
index 20af859..1c45592 100644
--- a/traffic_ops/testing/api/v1/tc-fixtures.json
+++ b/traffic_ops/testing/api/v1/tc-fixtures.json
@@ -794,20 +794,6 @@
     "parameters": [
         {
             "configFile": "rascal.properties",
-            "lastUpdated": "2018-01-19T19:01:21.455131+00:00",
-            "name": "health.threshold.loadavg",
-            "secure": false,
-            "value": "25.0"
-        },
-        {
-            "configFile": "rascal.properties",
-            "lastUpdated": "2018-01-19T19:01:21.472279+00:00",
-            "name": "health.threshold.availableBandwidthInKbps",
-            "secure": false,
-            "value": ">1750000"
-        },
-        {
-            "configFile": "rascal.properties",
             "lastUpdated": "2018-01-19T19:01:21.489534+00:00",
             "name": "history.count",
             "secure": false,
@@ -1363,7 +1349,27 @@
             "lastUpdated": "2018-03-02T17:27:11.818418+00:00",
             "name": "EDGE1",
             "routing_disabled": false,
-            "type": "ATS_PROFILE"
+            "type": "ATS_PROFILE",
+            "params": [
+                {
+                    "configFile": "rascal.properties",
+                    "name": "health.threshold.loadavg",
+                    "secure": false,
+                    "value": "25.0"
+                },
+                {
+                    "configFile": "rascal.properties",
+                    "name": "health.threshold.availableBandwidthInKbps",
+                    "secure": false,
+                    "value": ">1750000"
+                },
+                {
+                    "configFile": "rascal.properties",
+                    "name": "health.threshold.queryTime",
+                    "secure": false,
+                    "value": "1000"
+                }
+            ]
         },
         {
             "cdnName": "cdn2",
diff --git a/traffic_ops/testing/api/v2/crconfig_test.go b/traffic_ops/testing/api/v2/crconfig_test.go
index c5d2c57..ab9dae5 100644
--- a/traffic_ops/testing/api/v2/crconfig_test.go
+++ b/traffic_ops/testing/api/v2/crconfig_test.go
@@ -26,6 +26,7 @@ import (
 func TestCRConfig(t *testing.T) {
 	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
 		UpdateTestCRConfigSnapshot(t)
+		MonitoringConfig(t)
 		SnapshotTestCDNbyName(t)
 		SnapshotTestCDNbyInvalidName(t)
 		SnapshotTestCDNbyID(t)
@@ -35,7 +36,7 @@ func TestCRConfig(t *testing.T) {
 
 func UpdateTestCRConfigSnapshot(t *testing.T) {
 	if len(testData.CDNs) < 1 {
-		t.Error("no cdn test data")
+		t.Fatalf("no cdn test data")
 	}
 	cdn := testData.CDNs[0].Name
 
@@ -156,6 +157,86 @@ func UpdateTestCRConfigSnapshot(t *testing.T) {
 	}
 }
 
+func MonitoringConfig(t *testing.T) {
+	if len(testData.CDNs) < 1 {
+		t.Fatalf("no cdn test data")
+	}
+	const cdnName = "cdn1"
+	const profileName = "EDGE1"
+	cdns, _, err := TOSession.GetCDNByName(cdnName)
+	if err != nil {
+		t.Fatalf("getting CDNs with name '%s': %s", cdnName, err.Error())
+	}
+	if len(cdns) < 1 {
+		t.Fatalf("expected to find a CDN named %s", cdnName)
+	}
+	if len(cdns) > 1 {
+		t.Fatalf("expected exactly 1 CDN named %s but found %d CDNs", cdnName, len(cdns))
+	}
+	profiles, _, err := TOSession.GetProfileByName(profileName)
+	if err != nil {
+		t.Fatalf("getting Profiles with name '%s': %s", profileName, err.Error())
+	}
+	if len(profiles) != 1 {
+		t.Fatalf("expected exactly 1 Profiles named %s but found %d Profiles", profileName, len(profiles))
+	}
+	parameters, _, err := TOSession.GetParametersByProfileName(profileName)
+	if err != nil {
+		t.Fatalf("getting Parameters by Profile name '%s': %s", profileName, err.Error())
+	}
+	parameterMap := map[string]tc.HealthThreshold{}
+	parameterFound := map[string]bool{}
+	const thresholdPrefixLength = len(tc.ThresholdPrefix)
+	for _, parameter := range parameters {
+		if !strings.HasPrefix(parameter.Name, tc.ThresholdPrefix) {
+			continue
+		}
+		parameterName := parameter.Name[thresholdPrefixLength:]
+		parameterMap[parameterName], err = tc.StrToThreshold(parameter.Value)
+		if err != nil {
+			t.Fatalf("converting string '%s' to HealthThreshold: %s", parameter.Value, err.Error())
+		}
+		parameterFound[parameterName] = false
+	}
+	const expectedThresholdParameters = 3
+	if len(parameterMap) != expectedThresholdParameters {
+		t.Fatalf("expected Profile '%s' to contain %d Parameters with names starting with '%s' but %d such Parameters were found", profileName, expectedThresholdParameters, tc.ThresholdPrefix, len(parameterMap))
+	}
+	tmConfig, _, err := TOSession.GetTrafficMonitorConfig(cdnName)
+	if err != nil {
+		t.Fatalf("getting Traffic Monitor Config: %s", err.Error())
+	}
+	profileFound := false
+	var profile tc.TMProfile
+	for _, profile = range tmConfig.Profiles {
+		if profile.Name == profileName {
+			profileFound = true
+			break
+		}
+	}
+	if !profileFound {
+		t.Fatalf("Traffic Monitor Config contained no Profile named '%s", profileName)
+	}
+	for parameterName, value := range profile.Parameters.Thresholds {
+		if _, ok := parameterFound[parameterName]; !ok {
+			t.Fatalf("unexpected Threshold Parameter name '%s' found in Profile '%s' in Traffic Monitor Config", parameterName, profileName)
+		}
+		parameterFound[parameterName] = true
+		if parameterMap[parameterName].String() != value.String() {
+			t.Fatalf("expected '%s' but received '%s' for Threshold Parameter '%s' in Profile '%s' in Traffic Monitor Config", parameterMap[parameterName].String(), value.String(), parameterName, profileName)
+		}
+	}
+	missingParameters := []string{}
+	for parameterName, found := range parameterFound {
+		if !found {
+			missingParameters = append(missingParameters, parameterName)
+		}
+	}
+	if len(missingParameters) != 0 {
+		t.Fatalf("Threshold parameters defined for Profile '%s' but missing for Profile '%s' in Traffic Monitor Config: %s", profileName, profileName, strings.Join(missingParameters, ", "))
+	}
+}
+
 func SnapshotTestCDNbyName(t *testing.T) {
 
 	firstCDN := testData.CDNs[0]
diff --git a/traffic_ops/testing/api/v2/tc-fixtures.json b/traffic_ops/testing/api/v2/tc-fixtures.json
index e8046de..cb5e1ac 100644
--- a/traffic_ops/testing/api/v2/tc-fixtures.json
+++ b/traffic_ops/testing/api/v2/tc-fixtures.json
@@ -825,20 +825,6 @@
     "parameters": [
         {
             "configFile": "rascal.properties",
-            "lastUpdated": "2018-01-19T19:01:21.455131+00:00",
-            "name": "health.threshold.loadavg",
-            "secure": false,
-            "value": "25.0"
-        },
-        {
-            "configFile": "rascal.properties",
-            "lastUpdated": "2018-01-19T19:01:21.472279+00:00",
-            "name": "health.threshold.availableBandwidthInKbps",
-            "secure": false,
-            "value": ">1750000"
-        },
-        {
-            "configFile": "rascal.properties",
             "lastUpdated": "2018-01-19T19:01:21.489534+00:00",
             "name": "history.count",
             "secure": false,
@@ -1387,7 +1373,27 @@
             "lastUpdated": "2018-03-02T17:27:11.818418+00:00",
             "name": "EDGE1",
             "routing_disabled": false,
-            "type": "ATS_PROFILE"
+            "type": "ATS_PROFILE",
+            "params": [
+                {
+                    "configFile": "rascal.properties",
+                    "name": "health.threshold.loadavg",
+                    "secure": false,
+                    "value": "25.0"
+                },
+                {
+                    "configFile": "rascal.properties",
+                    "name": "health.threshold.availableBandwidthInKbps",
+                    "secure": false,
+                    "value": ">1750000"
+                },
+                {
+                    "configFile": "rascal.properties",
+                    "name": "health.threshold.queryTime",
+                    "secure": false,
+                    "value": "1000"
+                }
+            ]
         },
         {
             "cdnName": "cdn2",
diff --git a/traffic_ops/testing/api/v3/crconfig_test.go b/traffic_ops/testing/api/v3/crconfig_test.go
index 1a651f7..b3db34c 100644
--- a/traffic_ops/testing/api/v3/crconfig_test.go
+++ b/traffic_ops/testing/api/v3/crconfig_test.go
@@ -26,6 +26,7 @@ import (
 func TestCRConfig(t *testing.T) {
 	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, DeliveryServices}, func() {
 		UpdateTestCRConfigSnapshot(t)
+		MonitoringConfig(t)
 		SnapshotTestCDNbyName(t)
 		SnapshotTestCDNbyInvalidName(t)
 		SnapshotTestCDNbyID(t)
@@ -157,6 +158,86 @@ func UpdateTestCRConfigSnapshot(t *testing.T) {
 	}
 }
 
+func MonitoringConfig(t *testing.T) {
+	if len(testData.CDNs) < 1 {
+		t.Fatalf("no cdn test data")
+	}
+	const cdnName = "cdn1"
+	const profileName = "EDGE1"
+	cdns, _, err := TOSession.GetCDNByNameWithHdr(cdnName, nil)
+	if err != nil {
+		t.Fatalf("getting CDNs with name '%s': %s", cdnName, err.Error())
+	}
+	if len(cdns) < 1 {
+		t.Fatalf("expected to find a CDN named %s", cdnName)
+	}
+	if len(cdns) > 1 {
+		t.Fatalf("expected exactly 1 CDN named %s but found %d CDNs", cdnName, len(cdns))
+	}
+	profiles, _, err := TOSession.GetProfileByNameWithHdr(profileName, nil)
+	if err != nil {
+		t.Fatalf("getting Profiles with name '%s': %s", profileName, err.Error())
+	}
+	if len(profiles) != 1 {
+		t.Fatalf("expected exactly 1 Profiles named %s but found %d Profiles", profileName, len(profiles))
+	}
+	parameters, _, err := TOSession.GetParametersByProfileNameWithHdr(profileName, nil)
+	if err != nil {
+		t.Fatalf("getting Parameters by Profile name '%s': %s", profileName, err.Error())
+	}
+	parameterMap := map[string]tc.HealthThreshold{}
+	parameterFound := map[string]bool{}
+	const thresholdPrefixLength = len(tc.ThresholdPrefix)
+	for _, parameter := range parameters {
+		if !strings.HasPrefix(parameter.Name, tc.ThresholdPrefix) {
+			continue
+		}
+		parameterName := parameter.Name[thresholdPrefixLength:]
+		parameterMap[parameterName], err = tc.StrToThreshold(parameter.Value)
+		if err != nil {
+			t.Fatalf("converting string '%s' to HealthThreshold: %s", parameter.Value, err.Error())
+		}
+		parameterFound[parameterName] = false
+	}
+	const expectedThresholdParameters = 3
+	if len(parameterMap) != expectedThresholdParameters {
+		t.Fatalf("expected Profile '%s' to contain %d Parameters with names starting with '%s' but %d such Parameters were found", profileName, expectedThresholdParameters, tc.ThresholdPrefix, len(parameterMap))
+	}
+	tmConfig, _, err := TOSession.GetTrafficMonitorConfig(cdnName)
+	if err != nil {
+		t.Fatalf("getting Traffic Monitor Config: %s", err.Error())
+	}
+	profileFound := false
+	var profile tc.TMProfile
+	for _, profile = range tmConfig.Profiles {
+		if profile.Name == profileName {
+			profileFound = true
+			break
+		}
+	}
+	if !profileFound {
+		t.Fatalf("Traffic Monitor Config contained no Profile named '%s", profileName)
+	}
+	for parameterName, value := range profile.Parameters.Thresholds {
+		if _, ok := parameterFound[parameterName]; !ok {
+			t.Fatalf("unexpected Threshold Parameter name '%s' found in Profile '%s' in Traffic Monitor Config", parameterName, profileName)
+		}
+		parameterFound[parameterName] = true
+		if parameterMap[parameterName].String() != value.String() {
+			t.Fatalf("expected '%s' but received '%s' for Threshold Parameter '%s' in Profile '%s' in Traffic Monitor Config", parameterMap[parameterName].String(), value.String(), parameterName, profileName)
+		}
+	}
+	missingParameters := []string{}
+	for parameterName, found := range parameterFound {
+		if !found {
+			missingParameters = append(missingParameters, parameterName)
+		}
+	}
+	if len(missingParameters) != 0 {
+		t.Fatalf("Threshold parameters defined for Profile '%s' but missing for Profile '%s' in Traffic Monitor Config: %s", profileName, profileName, strings.Join(missingParameters, ", "))
+	}
+}
+
 func SnapshotTestCDNbyName(t *testing.T) {
 
 	firstCDN := testData.CDNs[0]
diff --git a/traffic_ops/testing/api/v3/tc-fixtures.json b/traffic_ops/testing/api/v3/tc-fixtures.json
index bb24c57..10c2d0f 100644
--- a/traffic_ops/testing/api/v3/tc-fixtures.json
+++ b/traffic_ops/testing/api/v3/tc-fixtures.json
@@ -1668,20 +1668,6 @@
     "parameters": [
         {
             "configFile": "rascal.properties",
-            "lastUpdated": "2018-01-19T19:01:21.455131+00:00",
-            "name": "health.threshold.loadavg",
-            "secure": false,
-            "value": "25.0"
-        },
-        {
-            "configFile": "rascal.properties",
-            "lastUpdated": "2018-01-19T19:01:21.472279+00:00",
-            "name": "health.threshold.availableBandwidthInKbps",
-            "secure": false,
-            "value": ">1750000"
-        },
-        {
-            "configFile": "rascal.properties",
             "lastUpdated": "2018-01-19T19:01:21.489534+00:00",
             "name": "history.count",
             "secure": false,
@@ -2248,7 +2234,27 @@
             "lastUpdated": "2018-03-02T17:27:11.818418+00:00",
             "name": "EDGE1",
             "routing_disabled": false,
-            "type": "ATS_PROFILE"
+            "type": "ATS_PROFILE",
+            "params": [
+                {
+                    "configFile": "rascal.properties",
+                    "name": "health.threshold.loadavg",
+                    "secure": false,
+                    "value": "25.0"
+                },
+                {
+                    "configFile": "rascal.properties",
+                    "name": "health.threshold.availableBandwidthInKbps",
+                    "secure": false,
+                    "value": ">1750000"
+                },
+                {
+                    "configFile": "rascal.properties",
+                    "name": "health.threshold.queryTime",
+                    "secure": false,
+                    "value": "1000"
+                }
+            ]
         },
         {
             "cdnName": "cdn2",