You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ro...@apache.org on 2018/06/29 14:36:19 UTC

[trafficcontrol] branch master updated: Conversion Utility to modify Traffic Ops profile based on conversion rules.

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

rob pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 6d69a88  Conversion Utility to modify Traffic Ops profile based on conversion rules.
6d69a88 is described below

commit 6d69a881ae1465f2c51255797a587bde5fafbf73
Author: Eric Friedrich <ef...@cisco.com>
AuthorDate: Tue Jun 19 15:21:32 2018 -0400

    Conversion Utility to modify Traffic Ops profile based on conversion rules.
    
    Includes conversion rules to convert an ATS6 profile into an ATS7 profile
---
 .../admin/traffic_ops/migration_from_20_to_22.rst  |  15 ++
 traffic_ops/build/traffic_ops.spec                 |  16 ++
 .../bin/convert_profile/convert622to713.json       | 101 +++++++
 .../install/bin/convert_profile/convert_profile.go | 293 +++++++++++++++++++++
 .../bin/convert_profile/convert_profile_test.go    | 125 +++++++++
 5 files changed, 550 insertions(+)

diff --git a/docs/source/admin/traffic_ops/migration_from_20_to_22.rst b/docs/source/admin/traffic_ops/migration_from_20_to_22.rst
index d27fed4..db642a8 100644
--- a/docs/source/admin/traffic_ops/migration_from_20_to_22.rst
+++ b/docs/source/admin/traffic_ops/migration_from_20_to_22.rst
@@ -120,3 +120,18 @@ Apache Traffic Server 7.x (Logging)
 Trafficserver v7 has changed the logging format.  Previously this was an xml file and now it is a lua file. Traffic Control compensates for this
 automatically depending upon the filename used for the logging parameters.  Previously the file used was ``logs_xml.config``, for ATS 7 it is now
 ``logging.config``.  The same parameters will work this new file, ``LogFormat.Format``, ``LogFormat.Name``, ``LogObject.Format`` etc.
+
+
+Traffic Ops Profile Modifications
+-------------------------------------------
+When upgrading to ATS 7.x, the Traffic Ops EDGE and MID cache profiles must be modified to provide new configuration values. Trafficserver recommends changes to the following parameters: https://cwiki.apache.org/confluence/display/TS/Upgrading+to+v7.0
+
+Most users of Traffic Control have enough profiles where making these modifications manually is a tedious and time consuming process. A new utility `traffic_ops/install/bin/convert_profile/convert_profile` is provided to automatically convert an ATS6 profile to an ATS7 profile. This utility can be reused in the future for ATS7->8 as well. 
+
+Usage example:
+  1. Use Traffic Portal GUI to export profile to JSON
+  2. Modify the trafficserver version numbers to match your current Traffic Server v6 RPM version and planned Traffic Server v7 RPM version
+  3. Run ``convert_profile -input_profile <exported_file> -rules convert622to713.json -out <new_profile_name>``
+  4. Review output messages and make manual updates if needed. If you have modified a default value the script also wants to change, it will prompt you to make the update manually. You may either do this directly in the JSON file or through the Traffic Portal GUI after import. 
+  5. Use Traffic Portal GUI to import the newly created profile
+
diff --git a/traffic_ops/build/traffic_ops.spec b/traffic_ops/build/traffic_ops.spec
index 1e2d265..b47251e 100644
--- a/traffic_ops/build/traffic_ops.spec
+++ b/traffic_ops/build/traffic_ops.spec
@@ -111,6 +111,16 @@ Built: %(date) by %{getenv: USER}
       go build -ldflags "-X main.version=traffic_ops-%{version}-%{release} -B 0x`git rev-parse HEAD`" \
     ) || { echo "Could not build go program at $(pwd): $!"; exit 1; }
 
+    # build TO profile converter
+    convert_dir=src/github.com/apache/trafficcontrol/traffic_ops/install/bin/convert_profile
+    ( mkdir -p "$convert_dir" && \
+      cd "$convert_dir" && \
+      cp -r "$TC_DIR"/traffic_ops/install/bin/convert_profile/* . && \
+      echo "go building at $(pwd)" && \
+      go get -v &&\
+      go build \
+    ) || { echo "Could not build go profile converter at $(pwd): $!"; exit 1; };
+
 %install
 
     if [ -d $RPM_BUILD_ROOT ]; then
@@ -139,6 +149,11 @@ Built: %(date) by %{getenv: USER}
 
     src=src/github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang
     %__cp -p  "$src"/traffic_ops_golang        "${RPM_BUILD_ROOT}"/opt/traffic_ops/app/bin/traffic_ops_golang
+
+    convert_profile_src=src/github.com/apache/trafficcontrol/traffic_ops/install/bin/convert_profile
+    %__cp -p  "$convert_profile_src"/convert_profile           "${RPM_BUILD_ROOT}"/opt/traffic_ops/install/bin/convert_profile
+    %__rm $RPM_BUILD_ROOT/%{PACKAGEDIR}/install/bin/convert_profile/*.go
+
 %pre
     /usr/bin/getent group %{TRAFFIC_OPS_GROUP} || /usr/sbin/groupadd -r %{TRAFFIC_OPS_GROUP}
     /usr/bin/getent passwd %{TRAFFIC_OPS_USER} || /usr/sbin/useradd -r -d %{PACKAGEDIR} -s /sbin/nologin %{TRAFFIC_OPS_USER} -g %{TRAFFIC_OPS_GROUP}
@@ -233,5 +248,6 @@ fi
 %{PACKAGEDIR}/app/public
 %{PACKAGEDIR}/app/templates
 %{PACKAGEDIR}/install
+%attr(755, %{TRAFFIC_OPS_USER},%{TRAFFIC_OPS_GROUP}) %{PACKAGEDIR}/install/bin/convert_profile/convert_profile
 %{PACKAGEDIR}/etc
 %doc %{PACKAGEDIR}/doc
diff --git a/traffic_ops/install/bin/convert_profile/convert622to713.json b/traffic_ops/install/bin/convert_profile/convert622to713.json
new file mode 100644
index 0000000..f83e9aa
--- /dev/null
+++ b/traffic_ops/install/bin/convert_profile/convert622to713.json
@@ -0,0 +1,101 @@
+{"description": "Upgrade profile from ATS6 to ATS7 based on https://cwiki.apache.org/confluence/display/TS/Upgrading+to+v7.0",
+ "replace_name": {"old": "621", "new": "713"},
+ "replace_description": {"old": "v6.2.1", "new": "v7.1.3"},
+ "validate_parameters": [{"name": "trafficserver", "config_file": "package",  "value": "6.2.1-62.3f63b3a.el7.centos.x86_64"}],
+ "conversion_actions": [
+     {"match_parameter": {"name": "trafficserver", "config_file": "package", "value": "6.2.1-62.3f63b3a.el7.centos.x86_64"}, "new_value": "<PLACE TRAFFICSERVER7 VERSION STRING HERE>"},
+     {"match_parameter": {"name": "trafficserver-debuginfo", "config_file": "package", "value": "6.2.1-62.3f63b3a.el7.centos.x86_64"}, "new_value": "<PLACE TRAFFICSERVER7 VERSION STRING HERE>"},
+     {"match_parameter": {"name": "location", "config_file": "logs_xml\\.config", "value": ".*"}, "new_config_file": "logging.config"},
+     {"match_parameter": {"name": "LogFormat\\.*", "config_file": "logs_xml\\.config", "value": ".*"}, "new_config_file": "logging.config"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.log\\.xml_config_file",
+			  "config_file": "records\\.config",
+			  "value": "STRING logs_xml\\.config"},
+      "new_name": "CONFIG proxy.config.log.config.filename", "new_value": "STRING logging.confxsig"},
+     
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.hostdb\\.timeout",
+			  "config_file": "records\\.config",
+			  "value": "INT 1440"},
+      "new_value": "INT 86400"},
+
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.http\\.keep_alive_no_activity_timeout_in",
+			  "config_file": "records\\.config",
+			  "value": "INT 115"},
+      "new_value": "INT 120"},
+
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.http\\.cache\\.fuzz\\.time",
+			  "config_file": "records\\.config",
+			  "value": "INT 240"},
+      "new_value": "INT 0"},
+
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.http\\.cache\\.fuzz\\.probability",
+			  "config_file": "records\\.config",
+			  "value": "FLOAT 0.005"},
+      "new_value": "INT 0"},
+
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.http\\.anonymize_insert_client_ip",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "new_name": "CONFIG proxy.config.http.insert_client_ip"},
+     
+     {"match_parameter": {"name": "CONFIG proxy\\.node\\.num_processes",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.ssl\\.compression",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.ssl\\.number\\.threads",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.system\\.mmap_max",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.stats\\.enable_lua",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.hostdb\\.size",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.hostdb\\.storage_size",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.ping\\.npack_to_trans",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.ping\\.timeout_sec",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.http\\.enable_url_expandomatic",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},          
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.dns\\.url_expansions",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.ssl\\.SSLv2",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.icp\\..*",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.config\\.spdy\\..*",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"},
+     {"match_parameter": {"name": "CONFIG proxy\\.cluster\\..*",
+			  "config_file": "records\\.config",
+			  "value": ".*"},
+      "action": "delete"}    
+ ]}
diff --git a/traffic_ops/install/bin/convert_profile/convert_profile.go b/traffic_ops/install/bin/convert_profile/convert_profile.go
new file mode 100644
index 0000000..143677e
--- /dev/null
+++ b/traffic_ops/install/bin/convert_profile/convert_profile.go
@@ -0,0 +1,293 @@
+/**
+ *
+ *
+ * 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.
+ *
+ *
+ * Convert a Traffic Control Trafficserver Mid/Edge Cache Profile to a newer version
+ *
+ */
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"regexp"
+	"strings"
+)
+
+type InputConfigParams struct {
+	InProfile string
+	OutFile   string
+	Rules     string
+	Force     bool
+}
+
+// TrafficOps Profile Parsing
+type Profile struct {
+	Parameters  []Parameter `json:"parameters"`
+	Description ProfileDesc `json:"profile"`
+}
+
+type Parameter struct {
+	Name       string `json:"name"`
+	ConfigFile string `json:"config_file"`
+	Value      string `json:"value"`
+}
+
+type ProfileDesc struct {
+	Description string `json:"description"`
+	Name        string `json:"name"`
+	Type        string `json:"type"`
+}
+
+// ConversionPolicy Parsing
+type ConversionPolicy struct {
+	ValidateParameters []Parameter      `json:"validate_parameters"`
+	ReplaceName        ReplaceRule      `json:"replace_name"`
+	ReplaceDescription ReplaceRule      `json:"replace_description"`
+	ConversionRules    []ConversionRule `json:"conversion_actions"`
+}
+
+type ReplaceRule struct {
+	Old string `json:"old"`
+	New string `json:"new"`
+}
+
+type ConversionRule struct {
+	MatchParameter Parameter `json:"match_parameter"`
+	NewName        string    `json:"new_name"`
+	NewConfigFile  string    `json:"new_config_file"`
+	NewValue       string    `json:"new_value"`
+	Action         string    `json:"action"`
+}
+
+func formatParam(p Parameter) string {
+	return fmt.Sprintf(`{"%s", "%s", "%s"}`, p.Name, p.ConfigFile, p.Value)
+}
+
+// Applies the rule represented by cr to the input parameter.
+//   Any non-empty string value will be replaced in the input with its new value
+//   Additionally an action may indicate a non-replacement operation, such as delete
+func (cr ConversionRule) Apply(param Parameter) (Parameter, bool) {
+	inParam := formatParam(param)
+
+	if cr.Action == "delete" {
+		fmt.Fprintf(os.Stderr, "Deleting parameter %s\n", inParam)
+		return param, false
+
+	} else if cr.Action != "" {
+		fmt.Fprintf(os.Stderr, "[WARNING] Unknown action %s, skipping action\n", cr.Action)
+	}
+
+	if cr.NewName != "" {
+		param.Name = cr.NewName
+	}
+
+	if cr.NewConfigFile != "" {
+		param.ConfigFile = cr.NewConfigFile
+	}
+
+	if cr.NewValue != "" {
+		param.Value = cr.NewValue
+	}
+	fmt.Fprintf(os.Stderr, "Updating parameter %s to %s\n", inParam, formatParam(param))
+
+	return param, true
+}
+
+func parseArgs() InputConfigParams {
+	inputConfig := InputConfigParams{}
+	flag.StringVar(&inputConfig.InProfile, "input_profile", "", "Path of input profile")
+	flag.StringVar(&inputConfig.Rules, "rules", "", "Path to conversion rules")
+	flag.StringVar(&inputConfig.OutFile, "out", "", "Path to write output file to. If not given, uses stdout")
+	flag.BoolVar(&inputConfig.Force, "force", false, "Ignore parameter value, making all recommended changes")
+	flag.Parse()
+
+	if inputConfig.InProfile == "" {
+		fmt.Fprintf(os.Stderr, "[ERROR] Missing required -input_profile parameter\n")
+		os.Exit(1)
+	}
+
+	if inputConfig.Rules == "" {
+		fmt.Fprintf(os.Stderr, "[ERROR] Missing required -rules parameter\n")
+		os.Exit(1)
+	}
+
+	return inputConfig
+}
+
+func readFile(inFile string) []byte {
+	file, err := ioutil.ReadFile(inFile)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "[ERROR] Cannot open input file: %s\n", inFile)
+		panic(err)
+	}
+
+	return file
+}
+
+func parseInputProfile(inFile string) *Profile {
+	var pt Profile
+	err := json.Unmarshal(readFile(inFile), &pt)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "[ERROR] Cannot parse input profile\n")
+		panic(err)
+	}
+
+	return &pt
+}
+
+func parseInputRules(inFile string) *ConversionPolicy {
+	var cp ConversionPolicy
+	err := json.Unmarshal(readFile(inFile), &cp)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "[ERROR] Cannot parse conversion rules\n")
+		panic(err)
+	}
+
+	return &cp
+}
+
+// ValidateParameters will verify that all parameters in paramsToValidate appear
+//  exactly in profile
+func ValidateParameters(profile *Profile,
+	paramsToValidate []Parameter) bool {
+
+	for _, validate := range paramsToValidate {
+		found := false
+
+		for _, param := range profile.Parameters {
+			if param.Name == validate.Name && param.ConfigFile == validate.ConfigFile {
+				found = true
+
+				if param.Value != validate.Value {
+					fmt.Fprintf(os.Stderr, "[ERROR] Parameter %s does not match value\n", param.Name)
+					fmt.Fprintf(os.Stderr, "[ERROR]   Actual Value: %s Expected Value: %s\n", param.Value, validate.Value)
+					return false
+				}
+			}
+		}
+
+		if !found {
+			return false
+		}
+	}
+
+	return true
+}
+
+// ConvertProfile will modify paramaters as described by matching entries in conversionActions
+// If ignoreValue is set to true, the Value field in matcher will be ignored, effectively matching
+// all values
+func ConvertProfile(profile *Profile,
+	rules []ConversionRule,
+	ignoreValue bool) {
+	filteredParams := profile.Parameters[:0]
+
+	for _, param := range profile.Parameters {
+
+		matched := false
+		for _, rule := range rules {
+			if paramsMatch(rule.MatchParameter, param, ignoreValue) {
+				matched = true
+
+				updatedParam, keep := rule.Apply(param)
+				if keep {
+					filteredParams = append(filteredParams, updatedParam)
+				}
+
+				break
+			}
+		}
+
+		// If there is no matching rule for a parameter, it automatically falls through unmodified
+		if !matched {
+			filteredParams = append(filteredParams, param)
+		}
+	}
+
+	profile.Parameters = filteredParams
+}
+
+// paramsMatch returns true when param fulfills all matching critera in matcher
+func paramsMatch(matcher Parameter, param Parameter, ignoreValue bool) bool {
+	nameRe := regexp.MustCompile(matcher.Name)
+	cfgRe := regexp.MustCompile(matcher.ConfigFile)
+	valueRe := regexp.MustCompile(matcher.Value)
+
+	if nil != nameRe.FindStringIndex(param.Name) &&
+		nil != cfgRe.FindStringIndex(param.ConfigFile) {
+
+		if ignoreValue || nil != valueRe.FindStringIndex(param.Value) {
+			return true
+
+		} else {
+			fmt.Fprintf(os.Stderr, "[ACTION REQUIRED] Found modified value. Skip modifying {\"%s\", \"%s\", \"%s\"}. Please update manually\n",
+				param.Name, param.ConfigFile, param.Value)
+		}
+	}
+	return false
+}
+
+func UpdateDetails(p *Profile, rules *ConversionPolicy) {
+	p.Description.Name = strings.Replace(p.Description.Name, rules.ReplaceName.Old, rules.ReplaceName.New, -1)
+	p.Description.Description = strings.Replace(p.Description.Description, rules.ReplaceDescription.Old, rules.ReplaceDescription.New, -1)
+}
+
+func main() {
+	cfgParam := parseArgs()
+	fmt.Fprintf(os.Stderr, "Traffic Control Profile Conversion Utility\n")
+	fmt.Fprintf(os.Stderr, "Input Profile: %s\n", cfgParam.InProfile)
+	fmt.Fprintf(os.Stderr, "Conversion Rules: %s\n", cfgParam.Rules)
+	if cfgParam.Force {
+		fmt.Fprintf(os.Stderr, "[WARNING] Ignoring existing parameter values in comparisons, making all suggested changes\n")
+	}
+
+	inProfile := parseInputProfile(cfgParam.InProfile)
+	rules := parseInputRules(cfgParam.Rules)
+
+	if !ValidateParameters(inProfile, rules.ValidateParameters) {
+		fmt.Fprintf(os.Stderr, "[ERROR] Failed to validate required parameters in profile\n")
+		os.Exit(-1)
+	}
+	ConvertProfile(inProfile, rules.ConversionRules, cfgParam.Force)
+	UpdateDetails(inProfile, rules)
+
+	// Can't use the standard JSON Marshaller because it forces HTML escape
+	buf := new(bytes.Buffer)
+	enc := json.NewEncoder(buf)
+	enc.SetEscapeHTML(false)
+	if err := enc.Encode(inProfile); err != nil {
+		panic(err)
+	}
+
+	indentedBuffer := new(bytes.Buffer)
+	if err := json.Indent(indentedBuffer, buf.Bytes(), "", "    "); err != nil {
+		panic(err)
+	}
+
+	if cfgParam.OutFile != "" {
+		err := ioutil.WriteFile(cfgParam.OutFile, indentedBuffer.Bytes(), 0644)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "[ERROR] Cannot write output file")
+			panic(err)
+		}
+	} else {
+		fmt.Printf("%s", indentedBuffer.String())
+	}
+}
diff --git a/traffic_ops/install/bin/convert_profile/convert_profile_test.go b/traffic_ops/install/bin/convert_profile/convert_profile_test.go
new file mode 100644
index 0000000..8a29240
--- /dev/null
+++ b/traffic_ops/install/bin/convert_profile/convert_profile_test.go
@@ -0,0 +1,125 @@
+/**
+ *
+ *
+ * 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.
+ *
+ *
+ * Unit Tests for Profile Conversion Utility
+ */
+package main
+
+import (
+	_ "fmt"
+	"testing"
+)
+
+func TestValidateParameter(t *testing.T) {
+	// Test happy days validation
+	profile := Profile{[]Parameter{{"trafficserver", "package", "622"}}, ProfileDesc{}}
+
+	validators := []Parameter{{"trafficserver", "package", "622"}}
+
+	if !ValidateParameters(&profile, validators) {
+		t.Error("Failed to validate parameter")
+	}
+
+	profile.Parameters[0].Value = "6.2.2"
+	if ValidateParameters(&profile, validators) {
+		t.Error("Failed to catch value mismatch")
+	}
+
+	// Test name not found
+	profile.Parameters[0].Value = "622"
+	profile.Parameters[0].Name = "ats"
+	if ValidateParameters(&profile, validators) {
+		t.Error("Failed to catch missing parameter")
+	}
+}
+
+func TestDeleteParameter(t *testing.T) {
+	profile := Profile{[]Parameter{{"proxy.cluster.a", "records.config", "INT 1"},
+		{"proxy.cluster.b", "records.config", "STRING abc"}}, ProfileDesc{}}
+
+	deleteRules := []ConversionRule{
+		{MatchParameter: Parameter{"proxy\\.cluster\\..+", "records\\.config", ".*"},
+			Action: "delete"}}
+
+	ConvertProfile(&profile, deleteRules, false)
+
+	if len(profile.Parameters) != 0 {
+		t.Error("Failed to delete parameter")
+	}
+}
+
+func TestModifyConfigFile(t *testing.T) {
+	profile := Profile{[]Parameter{{"LogFormat.Format", "logs_xml.config", "<abcdef> <ghi>>"},
+		Parameter{"LogFormat.Name", "logs_xml.config", "custom_ats"}}, ProfileDesc{}}
+
+	modifyRules := []ConversionRule{
+		{MatchParameter: Parameter{"LogFormat\\..*", "logs_xml\\.config", ".*"},
+			NewConfigFile: "logging.config"}}
+
+	ConvertProfile(&profile, modifyRules, false)
+
+	for _, param := range profile.Parameters {
+		if param.ConfigFile != "logging.config" {
+			t.Error("Failed to update config file")
+		}
+	}
+}
+
+func TestModifyValueForce(t *testing.T) {
+	profile := Profile{[]Parameter{{"proxy.config.hostdb.timeout", "records.config", "INT 1440"}}, ProfileDesc{}}
+
+	modifyRules := []ConversionRule{
+		{MatchParameter: Parameter{"proxy\\.config\\.hostdb\\.timeout", "records\\.config", "INT 1440"},
+			NewValue: "INT 86400"}}
+
+	ConvertProfile(&profile, modifyRules, true)
+
+	if profile.Parameters[0].Value != "INT 86400" {
+		t.Error("Failed to update value")
+	}
+}
+
+func TestModifyValueSkip(t *testing.T) {
+	profile := Profile{[]Parameter{{"proxy.config.hostdb.timeout", "records.config", "INT 5000"}}, ProfileDesc{}}
+
+	modifyRules := []ConversionRule{
+		{MatchParameter: Parameter{"proxy\\.config\\.hostdb\\.timeout", "records\\.config", "INT 1440"},
+			NewValue: "INT 86400"}}
+
+	ConvertProfile(&profile, modifyRules, false)
+
+	if profile.Parameters[0].Value != "INT 5000" {
+		t.Error("Incorrectly updated value")
+	}
+}
+
+func TestModifyNameValue(t *testing.T) {
+	profile := Profile{[]Parameter{{"proxy.config.log.xml_config_file", "records.config", "logs_xml.config"}}, ProfileDesc{}}
+
+	modifyRules := []ConversionRule{
+		{MatchParameter: Parameter{"proxy\\.config\\.log\\.xml_config_file", "records\\.config", "logs_xml\\.config"},
+			NewName:  "proxy.config.log.config.filename",
+			NewValue: "logging.config"}}
+
+	ConvertProfile(&profile, modifyRules, false)
+	if profile.Parameters[0].Name != "proxy.config.log.config.filename" {
+		t.Error("Failed to update parameter name")
+	}
+
+	if profile.Parameters[0].Value != "logging.config" {
+		t.Error("Failed to update parameter value")
+	}
+}