You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@trafficcontrol.apache.org by GitBox <gi...@apache.org> on 2018/12/13 21:32:26 UTC

[GitHub] dneuman64 closed pull request #108: Multi delivery services with same domain name and different path prefixes

dneuman64 closed pull request #108: Multi delivery services with same domain name and different path prefixes
URL: https://github.com/apache/trafficcontrol/pull/108
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/docs/source/development/traffic_ops_api/v12/deliveryservice.rst b/docs/source/development/traffic_ops_api/v12/deliveryservice.rst
index 12bc0a560..0238ca250 100644
--- a/docs/source/development/traffic_ops_api/v12/deliveryservice.rst
+++ b/docs/source/development/traffic_ops_api/v12/deliveryservice.rst
@@ -1742,6 +1742,8 @@ SSL Keys
   +------------------------+----------+---------------------------------------------------------------------------------------------------------+
   | xmlId                  | yes      | Unique string that describes this deliveryservice.                                                      |
   +------------------------+----------+---------------------------------------------------------------------------------------------------------+
+  | pathPrefixList         | no       | Path prefixes                                                                                           |
+  +------------------------+----------+---------------------------------------------------------------------------------------------------------+
 
 
   **Request Example** ::
@@ -2129,6 +2131,8 @@ SSL Keys
   +------------------------+----------+---------------------------------------------------------------------------------------------------------+
   | xmlId                  | yes      | Unique string that describes this deliveryservice.                                                      |
   +------------------------+----------+---------------------------------------------------------------------------------------------------------+
+  | pathPrefixList         | no       | Path prefixes                                                                                           |
+  +------------------------+----------+---------------------------------------------------------------------------------------------------------+
 
 
   **Request Example** ::
diff --git a/traffic_ops/app/db/migrations/20161111001502_add_ds_path_prefix.sql b/traffic_ops/app/db/migrations/20161111001502_add_ds_path_prefix.sql
new file mode 100644
index 000000000..88ad20d1c
--- /dev/null
+++ b/traffic_ops/app/db/migrations/20161111001502_add_ds_path_prefix.sql
@@ -0,0 +1,29 @@
+/*
+	Copyright 2016 Cisco Systems, Inc.
+
+	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.
+*/
+
+-- +goose Up
+-- SQL in section 'Up' is executed when this migration is applied
+CREATE TABLE `deliveryservice_path_prefix` (
+  `deliveryservice` int(11) NOT NULL,
+  `path_prefix` varchar(255) NOT NULL,
+  `last_updated` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`deliveryservice`,`path_prefix`),
+  CONSTRAINT `fk_ds_to_path_prefix_deliveryservice1` FOREIGN KEY (`deliveryservice`) REFERENCES `deliveryservice` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE = InnoDB DEFAULT CHARACTER SET = latin1;
+
+-- +goose Down
+-- SQL section 'Down' is executed when this migration is rolled back
+DROP TABLE deliveryservice_path_prefix;
diff --git a/traffic_ops/app/lib/API/Deliveryservice2.pm b/traffic_ops/app/lib/API/Deliveryservice2.pm
index 81b340c33..c3d50badb 100644
--- a/traffic_ops/app/lib/API/Deliveryservice2.pm
+++ b/traffic_ops/app/lib/API/Deliveryservice2.pm
@@ -74,6 +74,7 @@ sub delivery_services {
 				);
 			}
 
+        		my @path_prefixes = $self->db->resultset('DeliveryservicePathPrefix')->search( { deliveryservice => $row->id } )->get_column('path_prefix')->all();
 			my $cdn_domain = $self->get_cdn_domain_by_ds_id($row->id);
 			my $regexp_set = &UI::DeliveryService::get_regexp_set( $self, $row->id );
 			my @example_urls = &UI::DeliveryService::get_example_urls( $self, $row->id, $regexp_set, $row, $cdn_domain, $row->protocol );
@@ -111,6 +112,7 @@ sub delivery_services {
 					"longDesc1"                => $row->long_desc_1,
 					"longDesc2"                => $row->long_desc_2,
 					"matchList"                => \@matchlist,
+					"pathPrefixList"           => \@path_prefixes,
 					"maxDnsAnswers"            => $row->max_dns_answers,
 					"midHeaderRewrite"         => $row->mid_header_rewrite,
 					"missLat"                  => $row->miss_lat,
@@ -266,6 +268,18 @@ sub create {
 			$de_re_insert->insert();
 		}
 
+		if ( defined($params->{pathPrefixList}) ) {
+			foreach my $path_prefix ( @{ $params->{pathPrefixList} } ) {
+				my $ds_path_prefix_insert = $self->db->resultset('DeliveryservicePathPrefix')->create(
+					{
+						deliveryservice => $new_id,
+						path_prefix     => $path_prefix,
+					}
+				);
+				$ds_path_prefix_insert->insert();
+			}
+		}
+
 		my $profile_id=$transformed_params->{ profile_id };
 		$self->update_profileparameter($new_id, $profile_id, $params);
 
@@ -452,22 +466,6 @@ sub _check_params {
 		if ((scalar $match_list) == 0) {
 			return (undef, "At least have 1 pattern in matchList.");
 		}
-
-		my $cdn_domain = undef;
-
-		if (defined($ds_id)) {
-			$cdn_domain = $self->get_cdn_domain_by_ds_id($ds_id);
-		} else {
-			my $profile_id = $self->get_profile_id_for_name($params->{profileName});
-			$cdn_domain = $self->get_cdn_domain_by_profile_id($profile_id);
-		}
-
-		foreach my $match_item (@$match_list) {
-			my $conflicting_regex = $self->find_existing_host_regex($match_item->{'type'}, $match_item->{'pattern'}, $cdn_domain, $cdn_id, $ds_id);
-			if (defined($conflicting_regex)) {
-				return(undef, "Another delivery service is already using host regex $conflicting_regex");
-			}
-		}
 	} else {
 		return (undef, "parameter matchList is must." );
 	}
@@ -504,6 +502,37 @@ sub _check_params {
 		$transformed_params->{logsEnabled} = 0;
 	}
 
+	my $path_prefixes;
+	if ( defined($params->{pathPrefixList}) ) {
+		foreach my $path_prefix ( @{ $params->{pathPrefixList} } ) {
+			if ( length($path_prefix) > 255 ) {
+				return (undef, "Max length of path prefix is 255." );
+			}
+			if ( (not $path_prefix =~ /^\/[0-9a-zA-Z_\!\~\*\'\(\)\.\;\?\:\@\&\=\+\$\,\%\#\-\/]+$/) || $path_prefix =~ /\/\//) {
+				return (undef, "Invalid path prefix: " .  $path_prefix);
+			}
+		}
+		if ( (scalar @{ $params->{pathPrefixList} }) > 10 ) {
+			return (undef, "At most 10 path prefixes can be configured per delivery service.");
+		}
+		$path_prefixes = $params->{pathPrefixList};
+	} else {
+		$path_prefixes->[0] = "/";
+	}
+
+	my $new_regex = [];
+	my $match_list = $params->{matchList};
+	foreach my $match_item (@$match_list) {
+		if ( $match_item->{'type'} eq "HOST_REGEXP" ) {
+			push( @{$new_regex}, $match_item->{'pattern'} );
+		}
+	}
+
+        my $conflicting_regex_path_prefix = $self->find_existing_host_regex_path_prefix( $new_regex, $cdn_id, $path_prefixes, $ds_id );
+	if ( defined($conflicting_regex_path_prefix) ) {
+		return (undef, "Host_regex/path_prefix conflict: " . $conflicting_regex_path_prefix);
+	}
+
 	return ($transformed_params, undef);
 }
 
@@ -635,6 +664,9 @@ sub get_response {
 	}
 	$response->{matchList} = \@pats;
 
+	my @path_prefixes = $self->db->resultset('DeliveryservicePathPrefix')->search( { deliveryservice => $ds_id } )->get_column('path_prefix')->all();
+	$response->{pathPrefixList} = \@path_prefixes;
+
 	return $response;
 }
 
@@ -724,6 +756,23 @@ sub update {
 		}
 	}
 
+	if ( defined($params->{pathPrefixList}) ) {
+		my $path_prefix_set_orig = &UI::DeliveryService::get_path_prefix_set( $self, $id );
+		if ( not ( @{$path_prefix_set_orig} ~~ @{$params->{pathPrefixList}} ) ) {
+                        my $delete = $self->db->resultset('DeliveryservicePathPrefix')->search( { deliveryservice => $id } );
+                        $delete->delete();
+                        foreach my $path_prefix ( @{$params->{pathPrefixList}} ) {
+                                my $ds_path_prefix_insert = $self->db->resultset('DeliveryservicePathPrefix')->create(
+                                        {
+                                                deliveryservice => $id,
+                                                path_prefix     => $path_prefix,
+                                        }
+                                );
+                                $ds_path_prefix_insert->insert();
+                        }
+                }
+	}
+
 	my $profile_id=$transformed_params->{ profile_id };
 	$self->update_profileparameter($id, $profile_id, $params);
 
diff --git a/traffic_ops/app/lib/MojoPlugins/DeliveryService.pm b/traffic_ops/app/lib/MojoPlugins/DeliveryService.pm
index ccaa43fbd..7f2f44198 100755
--- a/traffic_ops/app/lib/MojoPlugins/DeliveryService.pm
+++ b/traffic_ops/app/lib/MojoPlugins/DeliveryService.pm
@@ -23,6 +23,7 @@ use Utils::Helper::DateHelper;
 use JSON;
 use HTTP::Date;
 use Common::ReturnCodes qw(SUCCESS ERROR);
+use Storable 'dclone';
 
 sub register {
 	my ( $self, $app, $conf ) = @_;
@@ -209,6 +210,68 @@ sub register {
 		}
 	);
 
+	$app->renderer->add_helper(
+		find_existing_host_regex_path_prefix => sub {
+			my $self = shift || confess($no_instance_message);
+			my $host_regex_set = shift || confess("Please supply a host regular expression set");
+			my $cdn_id = shift || confess("Please supply a cdn_name");
+			my $path_prefixes = shift || confess("Please supply a path prefix set");
+			my $ds_id = shift;
+
+			my $type_id = $self->db->resultset('Type')->search( { name => 'HOST_REGEXP' } )->get_column('id')->single();
+			my $rs = $self->db->resultset('DeliveryserviceRegex')->search(
+					 {
+					 	-and =>
+							[
+								'deliveryservice.cdn_id' => $cdn_id,
+								'regex.pattern' => { -in => $host_regex_set },
+								'regex.type' => $type_id
+							]
+					},
+					{ prefetch => [ 'deliveryservice', 'regex' ] }
+				);
+
+			my @other_path_prefixes=();
+			while (my $row = $rs->next) {
+				if (defined($ds_id) && $ds_id == $row->deliveryservice->id) {
+					next;
+				}
+				my @path_prefix = $self->db->resultset('DeliveryservicePathPrefix')->search( { deliveryservice => $row->deliveryservice->id } )->get_column('path_prefix')->all();
+				if ( @path_prefix ) {
+					push( @other_path_prefixes, @path_prefix );
+				} else {
+					return "conflict with delivery service \"" . $row->deliveryservice->xml_id . "\"";
+				}
+			}
+
+			my $path_prefix_new = scalar @{$path_prefixes};
+			my $path_prefix_orig = scalar @other_path_prefixes;
+			my $all_path_prefixes = dclone $path_prefixes;
+			push(@{$all_path_prefixes}, @other_path_prefixes);
+			my ($a, $b);
+			for ( my $i=0; $i<$path_prefix_new; $i++ ) {
+				for ( my $j=$i+1; $j<$path_prefix_new+$path_prefix_orig; $j++ ) {
+					if ( length($all_path_prefixes->[$i]) < length($all_path_prefixes->[$j]) ) {
+						$a = $all_path_prefixes->[$i];
+						$b = $all_path_prefixes->[$j];
+					} else {
+						$a = $all_path_prefixes->[$j];
+						$b = $all_path_prefixes->[$i];
+					}
+					if ( $a eq substr($b,0,length($a)) ) {
+						if ("/" eq $a) {
+							return "confilict with path prefix \"" . $b . "\"";
+						} else {
+							return "path prefix \"" . $a . "\"" . "confilict with path prefix \"" . $b . "\"";
+						}
+					}
+				}
+			}
+
+			return undef;
+		}
+	);
+
 	$app->renderer->add_helper(
 		get_cdn_domain_by_ds_id => sub {
 			my $self = shift || confess($no_instance_message);
diff --git a/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm b/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm
index a179d7fcc..63ecb70ff 100644
--- a/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm
+++ b/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm
@@ -451,6 +451,21 @@ __PACKAGE__->belongs_to(
   { is_deferrable => 0, on_delete => "RESTRICT", on_update => "RESTRICT" },
 );
 
+=head2 deliveryservice_path_prefixes
+
+Type: has_many
+
+Related object: L<Schema::Result::DeliveryservicePathPrefix>
+
+=cut
+
+__PACKAGE__->has_many(
+  "deliveryservice_path_prefixes",
+  "Schema::Result::DeliveryservicePathPrefix",
+  { "foreign.deliveryservice" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
 =head2 deliveryservice_regexes
 
 Type: has_many
diff --git a/traffic_ops/app/lib/Schema/Result/DeliveryservicePathPrefix.pm b/traffic_ops/app/lib/Schema/Result/DeliveryservicePathPrefix.pm
new file mode 100644
index 000000000..cca7b59c7
--- /dev/null
+++ b/traffic_ops/app/lib/Schema/Result/DeliveryservicePathPrefix.pm
@@ -0,0 +1,98 @@
+use utf8;
+package Schema::Result::DeliveryservicePathPrefix;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Schema::Result::DeliveryservicePathPrefix
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<deliveryservice_path_prefix>
+
+=cut
+
+__PACKAGE__->table("deliveryservice_path_prefix");
+
+=head1 ACCESSORS
+
+=head2 deliveryservice
+
+  data_type: 'integer'
+  is_foreign_key: 1
+  is_nullable: 0
+
+=head2 path_prefix
+
+  data_type: 'varchar'
+  is_nullable: 0
+  size: 255
+
+=head2 last_updated
+
+  data_type: 'timestamp'
+  datetime_undef_if_invalid: 1
+  default_value: current_timestamp
+  is_nullable: 1
+
+=cut
+
+__PACKAGE__->add_columns(
+  "deliveryservice",
+  { data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
+  "path_prefix",
+  { data_type => "varchar", is_nullable => 0, size => 255 },
+  "last_updated",
+  {
+    data_type => "timestamp",
+    datetime_undef_if_invalid => 1,
+    default_value => \"current_timestamp",
+    is_nullable => 1,
+  },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</deliveryservice>
+
+=item * L</path_prefix>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("deliveryservice", "path_prefix");
+
+=head1 RELATIONS
+
+=head2 deliveryservice
+
+Type: belongs_to
+
+Related object: L<Schema::Result::Deliveryservice>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "deliveryservice",
+  "Schema::Result::Deliveryservice",
+  { id => "deliveryservice" },
+  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07043 @ 2016-11-11 14:33:49
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:JTCGdZIfVw1GqE2JGW94YA
+
+
+# You can replace this text with custom code or comments, and it will be preserved on regeneration
+1;
diff --git a/traffic_ops/app/lib/UI/ConfigFiles.pm b/traffic_ops/app/lib/UI/ConfigFiles.pm
index 49ca73c20..dccd7b0a2 100644
--- a/traffic_ops/app/lib/UI/ConfigFiles.pm
+++ b/traffic_ops/app/lib/UI/ConfigFiles.pm
@@ -987,13 +987,19 @@ sub remap_dot_config {
 
 	# mids don't get here.
 	foreach my $remap ( @{ $data->{dslist} } ) {
-		foreach my $map_from ( keys %{ $remap->{remap_line} } ) {
-			my $map_to = $remap->{remap_line}->{$map_from};
-			$text = $self->build_remap_line( $server, $pdata, $text, $data, $remap, $map_from, $map_to );
+        	my @path_prefixes = $self->db->resultset('DeliveryservicePathPrefix')->search( { deliveryservice => $remap->{ds_id} } )->get_column('path_prefix')->all();
+        	if (0 == @path_prefixes) {
+			@path_prefixes = ("/");
 		}
-		foreach my $map_from ( keys %{ $remap->{remap_line2} } ) {
-			my $map_to = $remap->{remap_line2}->{$map_from};
-			$text = $self->build_remap_line( $server, $pdata, $text, $data, $remap, $map_from, $map_to );
+		foreach my $path_prefix ( @path_prefixes ) {
+			foreach my $map_from ( keys %{ $remap->{remap_line} } ) {
+				my $map_to = $remap->{remap_line}->{$map_from};
+				$text = $self->build_remap_line( $server, $pdata, $text, $data, $remap, $map_from, $map_to, $path_prefix );
+			}
+			foreach my $map_from ( keys %{ $remap->{remap_line2} } ) {
+				my $map_to = $remap->{remap_line2}->{$map_from};
+				$text = $self->build_remap_line( $server, $pdata, $text, $data, $remap, $map_from, $map_to, $path_prefix );
+			}
 		}
 	}
 	return $text;
@@ -1008,6 +1014,7 @@ sub build_remap_line {
 	my $remap    = shift;
 	my $map_from = shift;
 	my $map_to   = shift;
+	my $path     = shift;
 
 	if ( $remap->{type} eq 'ANY_MAP' ) {
 		$text .= $remap->{remap_text} . "\n";
@@ -1018,6 +1025,7 @@ sub build_remap_line {
 	my $dscp      = $remap->{dscp};
 
 	$map_from =~ s/ccr/$host_name/;
+	$map_from .= substr( $path, 1, length($path) );
 
 	if ( defined( $pdata->{'dscp_remap'} ) ) {
 		$text .= "map	" . $map_from . "     " . $map_to . " \@plugin=dscp_remap.so \@pparam=" . $dscp;
diff --git a/traffic_ops/app/lib/UI/DeliveryService.pm b/traffic_ops/app/lib/UI/DeliveryService.pm
index 51d7840f1..4ad7046b4 100644
--- a/traffic_ops/app/lib/UI/DeliveryService.pm
+++ b/traffic_ops/app/lib/UI/DeliveryService.pm
@@ -50,6 +50,7 @@ sub edit {
 	my $data = $rs_ds->single;
 
 	my $regexp_set   = &get_regexp_set( $self, $id );
+	my $path_prefix_set = &get_path_prefix_set( $self, $id );
 	my $cdn_domain = $data->cdn->domain_name;
 	my @example_urls = &get_example_urls( $self, $id, $regexp_set, $data, $cdn_domain, $data->protocol );
 
@@ -60,14 +61,15 @@ sub edit {
 	$self->stash_cdn_selector($data->cdn->id);
 	&stash_role($self);
 	$self->stash(
-		ds           => $data,
-		server_count => $server_count,
-		static_count => $static_count,
-		fbox_layout  => 1,
-		regexp_set   => $regexp_set,
-		example_urls => \@example_urls,
-		hidden       => {},               # for form validation purposes
-		mode         => 'edit'            # for form generation
+		ds              => $data,
+		server_count    => $server_count,
+		static_count    => $static_count,
+		fbox_layout     => 1,
+		regexp_set      => $regexp_set,
+		path_prefix_set => $path_prefix_set,
+		example_urls    => \@example_urls,
+		hidden          => {},               # for form validation purposes
+		mode            => 'edit'            # for form generation
 	);
 }
 
@@ -124,6 +126,12 @@ sub get_example_urls {
 		}
 	}
 	else { # TODO:  Is this necessary? Could this be consolidated?
+		my $path_prefix = "";
+		my @path_prefixes = $self->db->resultset('DeliveryservicePathPrefix')->search( { deliveryservice => $id } )->get_column('path_prefix')->all();
+		if ( @path_prefixes ) {
+			$path_prefix = $path_prefixes[0];
+		}
+
 		foreach my $re ( @{$regexp_set} ) {
 			if ( $re->{type} eq 'HOST_REGEXP' ) {
 				my $host = $re->{pattern};
@@ -152,10 +160,10 @@ sub get_example_urls {
 			}
 			elsif ( $re->{type} eq 'PATH_REGEXP' ) {
 				if ( defined( $example_urls[ $re->{set_number} ] ) ) {
-					$example_urls[ $re->{set_number} ] .= $re->{pattern};
+					$example_urls[ $re->{set_number} ] .= $path_prefix . $re->{pattern};
 				}
 				else {
-					$example_urls[ $re->{set_number} ] = $re->{pattern};
+					$example_urls[ $re->{set_number} ] = $path_prefix . $re->{pattern};
 				}
 			}
 		}
@@ -181,6 +189,19 @@ sub get_regexp_set {
 	return $regexp_set;
 }
 
+sub get_path_prefix_set {
+	my $self = shift;
+	my $id   = shift;
+	my $path_prefix_set = [];
+	my $i = 0;
+	my $rs = $self->db->resultset('DeliveryservicePathPrefix')->search( { deliveryservice => $id } );
+	while ( my $row = $rs->next ) {
+		$path_prefix_set->[$i] = $row->path_prefix;
+		$i++;
+	}
+	return $path_prefix_set;
+}
+
 # Read
 sub read {
 	my $self = shift;
@@ -372,29 +393,6 @@ sub check_deliveryservice_input {
 					$self->field('hidden.regex')->is_equal( "", $err );
 				}
 			}
-
-			if ( $param =~ /^re_re_(\d+)/ || $param =~ /^re_re_new_(\d+)/ ) {
-				my $order_no = $1;
-				my $new_regex;
-				my $new_regex_type;
-
-				if ( defined( $self->param( 're_type_' . $order_no ) ) ) {
-					$new_regex      = $self->param( 're_re_' . $order_no );
-					$new_regex_type = $self->param( 're_type_' . $order_no );
-				}
-
-				if ( defined( $self->param( 're_type_new_' . $order_no ) ) ) {
-					$new_regex      = $self->param( 're_re_new_' . $order_no );
-					$new_regex_type = $self->param( 're_type_new_' . $order_no );
-				}
-
-				my $conflicting_regex = $self->find_existing_host_regex( $new_regex_type, $new_regex, $cdn_domain, $cdn_id, $ds_id );
-				if ( defined($conflicting_regex) ) {
-					$self->field('hidden.regex')
-						->is_equal( "",
-						"There already is a HOST_REGEXP (" . $conflicting_regex . ") that matches " . $new_regex . "; Please choose another." );
-				}
-			}
 		}
 		elsif ( $param =~ /^re_order_.*(\d+)/ ) {
 			if ( $self->param($param) !~ /^\d+$/ ) {
@@ -421,6 +419,61 @@ sub check_deliveryservice_input {
 	if ( !$match_one ) {
 		$self->field('hidden.regex')->is_equal( "", "A minimum of one host regexp with order 0 is needed per delivery service." );
 	}
+
+	my $path_prefix_number=0;
+	my $path_prefixes;
+	foreach my $param ( $self->param ) {
+		if ( ( $param =~ /path_prefix_/ ) || ( $param =~ /path_prefix_new_/ ) ) {
+			if ( $self->param($param) eq "" ) {
+				next;
+			}
+			if ( length($self->param($param)) > 255 ) {
+				$self->field('hidden.prefix')->is_equal( "", "Max length of path prefix is 255." );
+			}
+			if ( (not $self->param($param) =~ /^\/[0-9a-zA-Z_\!\~\*\'\(\)\.\;\?\:\@\&\=\+\$\,\%\#\-\/]+$/) || $self->param($param) =~ /\/\//) {
+				$self->field('hidden.prefix')->is_equal( "", "Invalid path prefix: " . $self->param($param) );
+			}
+			$path_prefixes->[$path_prefix_number++] = $self->param($param);
+			if ( $path_prefix_number > 10 ) {
+				$self->field('hidden.prefix')->is_equal( "", "At most 10 path prefixes can be configured per delivery service." );
+			}
+		}
+	}
+	if ( 0 == $path_prefix_number ) {
+		$path_prefixes->[0] = "/";
+	}
+
+	my $new_regex;
+	my $index = 0;
+	foreach my $param ( $self->param ) {
+		if ( $self->param($param) eq 'HOST_REGEXP' ) {
+			if ( $param =~ /re_type_(\d+)/ ) {
+				if ( defined( $self->param( 're_re_' . $1 ) ) )
+				{
+					$new_regex->[$index++] = $self->param( 're_re_' . $1 );
+				}
+			}
+			if ( $param =~ /re_type_new_(\d+)/ ) {
+				if ( defined( $self->param( 're_re_new_' . $1 ) ) )
+				{
+					$new_regex->[$index++] = $self->param( 're_re_new_' . $1 );
+				}
+			}
+		}
+	}
+	my $conflicting_regex_path_prefix = $self->find_existing_host_regex_path_prefix( $new_regex, $cdn_id, $path_prefixes, $ds_id );
+	if ( defined($conflicting_regex_path_prefix) ) {
+		if ("/" eq $path_prefixes->[0]) {
+			$self->field('hidden.regex')
+				->is_equal( "",
+				"Please solve the conflict: " . $conflicting_regex_path_prefix );
+		} else {
+			$self->field('hidden.prefix')
+				->is_equal( "",
+				"Please solve the conflict: " . $conflicting_regex_path_prefix );
+		}
+	}
+
 	if ( $self->param('ds.dscp') !~ /^\d+$/ ) {
 		$self->field('ds.dscp')->is_equal( "", $self->param('ds.dscp') . " is not a valid dscp value." );
 	}
@@ -920,6 +973,29 @@ sub update {
 			}
 		}
 
+		my $path_prefix_set_orig = $self->get_path_prefix_set( $id );
+		my $path_prefix_set_new = [];
+		my $index = 0;
+		foreach my $param ( $self->param ) {
+			if ( ( ( $param =~ /path_prefix_/ ) || ( $param =~ /path_prefix_new/ ) ) && ( $self->param($param) ne "" ) ) {
+				$path_prefix_set_new->[$index] = $self->param($param);
+				$index++;
+			}
+		}
+		if ( not ( @{$path_prefix_set_orig} ~~ @{$path_prefix_set_new} ) ) {
+			my $delete = $self->db->resultset('DeliveryservicePathPrefix')->search( { deliveryservice => $id } );
+			$delete->delete();
+			foreach my $path_prefix ( @{$path_prefix_set_new} ) {
+				my $ds_path_prefix_insert = $self->db->resultset('DeliveryservicePathPrefix')->create(
+					{
+						deliveryservice => $id,
+						path_prefix     => $path_prefix,
+					}
+				);
+				$ds_path_prefix_insert->insert();
+			}
+		}
+
 		my $type = $self->db->resultset('Type')->search( { id => $self->paramAsScalar('ds.type') } )->get_column('name')->single();
 		$self->header_rewrite(
 			$self->param('id'),
@@ -950,18 +1026,20 @@ sub update {
 		my $server_count = $self->db->resultset('DeliveryserviceServer')->search( { deliveryservice => $id } )->count();
 		my $static_count = $self->db->resultset('Staticdnsentry')->search( { deliveryservice => $id } )->count();
 		my $regexp_set   = &get_regexp_set( $self, $id );
+		my $path_prefix_set = &get_path_prefix_set( $self, $id );
 		my @example_urls = &get_example_urls( $self, $id, $regexp_set, $data, $cdn_domain, $data->protocol );
 		my $action;
 
 		$self->stash(
-			ds           => $data,
-			fbox_layout  => 1,
-			server_count => $server_count,
-			static_count => $static_count,
-			regexp_set   => $regexp_set,
-			example_urls => \@example_urls,
-			hidden       => {},               # for form validation purposes
-			mode         => "edit",
+			ds              => $data,
+			fbox_layout     => 1,
+			server_count    => $server_count,
+			static_count    => $static_count,
+			regexp_set      => $regexp_set,
+			path_prefix_set => $path_prefix_set,
+			example_urls    => \@example_urls,
+			hidden          => {},               # for form validation purposes
+			mode            => "edit",
 		);
 		$self->render('delivery_service/edit');
 	}
@@ -1120,6 +1198,18 @@ sub create {
 			$de_re_insert->insert();
 		}
 
+		foreach my $param ( $self->param ) {
+			if ( ( $param =~ /path_prefix_/ ) && ( $self->param($param) ne "" ) ) {
+				my $ds_path_prefix_insert = $self->db->resultset('DeliveryservicePathPrefix')->create(
+					{
+						deliveryservice => $new_id,
+						path_prefix     => $self->param($param),
+					}
+				);
+				$ds_path_prefix_insert->insert();
+			}
+		}
+
 		my $type = $self->db->resultset('Type')->search( { id => $self->paramAsScalar('ds.type') } )->get_column('name')->single();
 		$self->header_rewrite( $new_id, $self->param('ds.profile'), $self->param('ds.xml_id'), $self->param('ds.edge_header_rewrite'), "edge", $type );
 		$self->header_rewrite( $new_id, $self->param('ds.profile'), $self->param('ds.xml_id'), $self->param('ds.mid_header_rewrite'),  "mid",  $type );
diff --git a/traffic_ops/app/lib/UI/Topology.pm b/traffic_ops/app/lib/UI/Topology.pm
index 8f24f3e5f..229c2bc27 100644
--- a/traffic_ops/app/lib/UI/Topology.pm
+++ b/traffic_ops/app/lib/UI/Topology.pm
@@ -27,6 +27,7 @@ use Time::HiRes qw(gettimeofday);
 use File::Basename;
 use File::Path;
 use Scalar::Util qw(looks_like_number);
+use Storable 'dclone';
 
 sub ccr_config {
     my $self     = shift;
@@ -335,6 +336,24 @@ sub gen_crconfig_json {
                 );
             }
         }
+	my @path_prefixes = $self->db->resultset('DeliveryservicePathPrefix')->search( { deliveryservice => $row->id } )->get_column('path_prefix')->all();
+	if (@path_prefixes) {
+		my @matchsets = @{ $data_obj->{'deliveryServices'}->{ $row->xml_id }->{'matchsets'} };
+		@{ $data_obj->{'deliveryServices'}->{ $row->xml_id }->{'matchsets'} } = ();
+		foreach my $path_prefix ( @path_prefixes ) {
+			foreach my $one_set ( @matchsets ) {
+				my $matchset = dclone $one_set;
+				push(
+					@{ $matchset->{'matchlist'} },
+					{ 'match-type' => 'PATH', 'regex' => "^" . $path_prefix . ".*" }
+				);
+				push(
+					@{ $data_obj->{'deliveryServices'}->{ $row->xml_id }->{'matchsets'} },
+					$matchset
+				);
+			}
+		}
+	}
         $data_obj->{'deliveryServices'}->{ $row->xml_id }->{'domains'} = \@domains;
 
         if ( scalar(@server_subrows) ) {
diff --git a/traffic_ops/app/t/api/1.2/deliveryservice.t b/traffic_ops/app/t/api/1.2/deliveryservice.t
index 0f23e71f3..d9f267864 100644
--- a/traffic_ops/app/t/api/1.2/deliveryservice.t
+++ b/traffic_ops/app/t/api/1.2/deliveryservice.t
@@ -343,6 +343,169 @@ $t->get_ok('/api/1.2/deliveryservices/list?logsEnabled=true')->status_is(200)->$
 
 ok $t->put_ok('/api/1.2/snapshot/cdn1')->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } );
 
+# path prefix
+ok $t->post_ok('/api/1.2/deliveryservices/create' => {Accept => 'application/json'} => json => {
+        "xmlId" => "ds_7",
+        "displayName" => "ds_displayname_7",
+        "protocol" => "1",
+        "orgServerFqdn" => "http://10.75.168.92",
+        "cdnName" => "cdn1",
+        "profileName" => "CCR1",
+        "type" => "HTTP",
+        "multiSiteOrigin" => "0",
+        "active" => "0",
+        "pathPrefixList" => ["/path1/"],
+        "matchList" => [
+            {
+                "type" =>  "HOST_REGEXP",
+                "setNumber" =>  "0",
+                "pattern" => ".*\\.ds_path_prefix\\..*"
+            }
+        ]})
+    ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } )
+    ->json_is( "/response/xmlId" => "ds_7")
+    ->json_is( "/response/displayName" => "ds_displayname_7")
+    ->json_is( "/response/orgServerFqdn" => "http://10.75.168.92")
+    ->json_is( "/response/cdnName" => "cdn1")
+    ->json_is( "/response/profileName" => "CCR1")
+    ->json_is( "/response/protocol" => "1")
+    ->json_is( "/response/multiSiteOrigin" => "0")
+    ->json_is( "/response/pathPrefixList" => ["/path1/"])
+    ->json_is( "/response/matchList/0/type" => "HOST_REGEXP")
+    ->json_is( "/response/matchList/0/setNumber" => "0")
+    ->json_is( "/response/matchList/0/pattern" => ".*\\.ds_path_prefix\\..*")
+            , 'Does the deliveryservice details return?';
+
+ok $t->post_ok('/api/1.2/deliveryservices/create' => {Accept => 'application/json'} => json => {
+        "xmlId" => "ds_8",
+        "displayName" => "ds_displayname_8",
+        "protocol" => "1",
+        "orgServerFqdn" => "http://10.75.168.92",
+        "cdnName" => "cdn1",
+        "profileName" => "CCR1",
+        "type" => "HTTP",
+        "multiSiteOrigin" => "0",
+        "active" => "0",
+        "pathPrefixList" => ["/path2/"],
+        "matchList" => [
+            {
+                "type" =>  "HOST_REGEXP",
+                "setNumber" =>  "0",
+                "pattern" => ".*\\.ds_path_prefix\\..*"
+            }
+        ]})
+    ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } )
+    ->json_is( "/response/xmlId" => "ds_8")
+    ->json_is( "/response/displayName" => "ds_displayname_8")
+    ->json_is( "/response/orgServerFqdn" => "http://10.75.168.92")
+    ->json_is( "/response/cdnName" => "cdn1")
+    ->json_is( "/response/profileName" => "CCR1")
+    ->json_is( "/response/protocol" => "1")
+    ->json_is( "/response/multiSiteOrigin" => "0")
+    ->json_is( "/response/pathPrefixList" => ["/path2/"])
+    ->json_is( "/response/matchList/0/type" => "HOST_REGEXP")
+    ->json_is( "/response/matchList/0/setNumber" => "0")
+    ->json_is( "/response/matchList/0/pattern" => ".*\\.ds_path_prefix\\..*")
+            , 'Does the deliveryservice details return?';
+
+$ds_id = &get_ds_id('ds_8');
+
+ok $t->put_ok('/api/1.2/deliveryservices/' . $ds_id . "/update" => {Accept => 'application/json'} => json => {
+        "xmlId" => "ds_8",
+        "displayName" => "ds_displayname_8",
+        "protocol" => "1",
+        "orgServerFqdn" => "http://10.75.168.92",
+        "cdnName" => "cdn1",
+        "profileName" => "CCR1",
+        "type" => "HTTP",
+        "multiSiteOrigin" => "0",
+        "active" => "0",
+        "pathPrefixList" => ["/path3/", "/path4/"],
+        "matchList" => [
+            {
+                "type" =>  "HOST_REGEXP",
+                "setNumber" =>  "0",
+                "pattern" => ".*\\.ds_path_prefix\\..*"
+            }
+        ]})
+    ->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content}; } )
+    ->json_is( "/response/xmlId" => "ds_8")
+    ->json_is( "/response/displayName" => "ds_displayname_8")
+    ->json_is( "/response/orgServerFqdn" => "http://10.75.168.92")
+    ->json_is( "/response/cdnName" => "cdn1")
+    ->json_is( "/response/profileName" => "CCR1")
+    ->json_is( "/response/protocol" => "1")
+    ->json_is( "/response/multiSiteOrigin" => "0")
+    ->json_is( "/response/pathPrefixList" => ["/path3/", "/path4/"])
+    ->json_is( "/response/matchList/0/type" => "HOST_REGEXP")
+    ->json_is( "/response/matchList/0/setNumber" => "0")
+    ->json_is( "/response/matchList/0/pattern" => ".*\\.ds_path_prefix\\..*")
+            , 'Does the deliveryservice details return?';
+
+ok $t->get_ok("/api/1.2/deliveryservices/" . $ds_id . "/get")->status_is(200)->or( sub { diag $t->tx->res->content->asset->{content} } )
+		->json_is( "/response/0/xmlId", "ds_8" )
+		->json_is( "/response/0/pathPrefixList", ["/path3/", "/path4/"] );
+
+ok $t->post_ok('/api/1.2/deliveryservices/create' => {Accept => 'application/json'} => json => {
+        "xmlId" => "ds_9",
+        "displayName" => "ds_displayname_9",
+        "protocol" => "1",
+        "orgServerFqdn" => "http://10.75.168.92",
+        "cdnName" => "cdn1",
+        "profileName" => "CCR1",
+        "type" => "HTTP",
+        "multiSiteOrigin" => "0",
+        "active" => "0",
+        "pathPrefixList" => ["/"],
+        "matchList" => [
+            {
+                "type" =>  "HOST_REGEXP",
+                "setNumber" =>  "0",
+                "pattern" => ".*\\.ds_path_prefix\\..*"
+            }
+        ]})
+    ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } );
+
+ok $t->post_ok('/api/1.2/deliveryservices/create' => {Accept => 'application/json'} => json => {
+        "xmlId" => "ds_9",
+        "displayName" => "ds_displayname_9",
+        "protocol" => "1",
+        "orgServerFqdn" => "http://10.75.168.92",
+        "cdnName" => "cdn1",
+        "profileName" => "CCR1",
+        "type" => "HTTP",
+        "multiSiteOrigin" => "0",
+        "active" => "0",
+        "pathPrefixList" => ["path"],
+        "matchList" => [
+            {
+                "type" =>  "HOST_REGEXP",
+                "setNumber" =>  "0",
+                "pattern" => ".*\\.ds_path_prefix\\..*"
+            }
+        ]})
+    ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } );
+
+ok $t->post_ok('/api/1.2/deliveryservices/create' => {Accept => 'application/json'} => json => {
+        "xmlId" => "ds_9",
+        "displayName" => "ds_displayname_9",
+        "protocol" => "1",
+        "orgServerFqdn" => "http://10.75.168.92",
+        "cdnName" => "cdn1",
+        "profileName" => "CCR1",
+        "type" => "HTTP",
+        "multiSiteOrigin" => "0",
+        "active" => "0",
+        "pathPrefixList" => ["/path1/sub/"],
+        "matchList" => [
+            {
+                "type" =>  "HOST_REGEXP",
+                "setNumber" =>  "0",
+                "pattern" => ".*\\.ds_path_prefix\\..*"
+            }
+        ]})
+    ->status_is(400)->or( sub { diag $t->tx->res->content->asset->{content}; } );
+
 ok $t->get_ok('/logout')->status_is(302)->or( sub { diag $t->tx->res->content->asset->{content}; } );
 $dbh->disconnect();
 done_testing();
diff --git a/traffic_ops/app/t/deliveryservice.t b/traffic_ops/app/t/deliveryservice.t
index 189791c92..20a4248b7 100644
--- a/traffic_ops/app/t/deliveryservice.t
+++ b/traffic_ops/app/t/deliveryservice.t
@@ -281,6 +281,91 @@ ok $t->post_ok(
 	}
 )->status_is(302), "update deliveryservice";
 
+# path prefix DS
+ok $t->post_ok(
+	'/ds/create' => form => {
+		'ds.active'                 => '1',
+		'ds.ccr_dns_ttl'            => '3600',
+		'ds.dscp'                   => '40',
+		'ds.geo_limit'              => '0',
+		'ds.http_bypass_fqdn'       => '',
+		'ds.org_server_fqdn'        => 'http://jvd.knutsel.com',
+		'ds.multi_site_origin'      => '0',
+		'ds.max_dns_answers'        => '0',
+		'ds.profile'                => '1',
+		'ds.cdn_id'                 => '1',
+		'ds.qstring_ignore'         => '0',
+		're_order_0'                => '0',
+		're_re_0'                   => '.*\.jvdtest_pathprefix\..*',
+		're_type_0'                 => 'HOST_REGEXP',
+		'path_prefix_0'             => '/path1/',
+		'ds.type'                   => '36',
+		'ds.xml_id'                 => 'tst_xml_id_5',
+		'ds.protocol'               => '0',
+		'ds.display_name'           => 'display name 5',
+		'ds.regional_geo_blocking'  => '0',
+		'ds.geolimit_redirect_url'  => '',
+		'ds.use_content_prepositioning' => '0',
+	}
+)->status_is(302), "create HTTP PREPOSITION delivery service";
+
+ok $t->post_ok(
+	'/ds/create' => form => {
+		'ds.active'                 => '1',
+		'ds.ccr_dns_ttl'            => '3600',
+		'ds.dscp'                   => '40',
+		'ds.geo_limit'              => '0',
+		'ds.http_bypass_fqdn'       => '',
+		'ds.org_server_fqdn'        => 'http://jvd.knutsel.com',
+		'ds.multi_site_origin'      => '0',
+		'ds.max_dns_answers'        => '0',
+		'ds.profile'                => '1',
+		'ds.cdn_id'                 => '1',
+		'ds.qstring_ignore'         => '0',
+		're_order_0'                => '0',
+		're_re_0'                   => '.*\.jvdtest_pathprefix\..*',
+		're_type_0'                 => 'HOST_REGEXP',
+		'path_prefix_0'             => '/path2/',
+		'ds.type'                   => '36',
+		'ds.xml_id'                 => 'tst_xml_id_6',
+		'ds.protocol'               => '0',
+		'ds.display_name'           => 'display name 6',
+		'ds.regional_geo_blocking'  => '0',
+		'ds.geolimit_redirect_url'  => '',
+		'ds.use_content_prepositioning' => '0',
+	}
+)->status_is(302), "create HTTP PREPOSITION delivery service";
+
+my $ds_id_path_prefix = &get_ds_id('tst_xml_id_6');
+
+ok $t->post_ok(
+	'/ds/' . $ds_id_path_prefix . '/update' => form => {
+		'ds.active'                 => '1',
+		'ds.ccr_dns_ttl'            => '3600',
+		'ds.dscp'                   => '40',
+		'ds.geo_limit'              => '0',
+		'ds.http_bypass_fqdn'       => '',
+		'ds.org_server_fqdn'        => 'http://jvd.knutsel.com',
+		'ds.multi_site_origin'      => '0',
+		'ds.max_dns_answers'        => '0',
+		'ds.profile'                => '1',
+		'ds.cdn_id'                 => '1',
+		'ds.qstring_ignore'         => '0',
+		're_order_0'                => '0',
+		're_re_0'                   => '.*\.jvdtest_pathprefix\..*',
+		're_type_0'                 => 'HOST_REGEXP',
+		'path_prefix_0'             => '/path3/',
+		'path_prefix_1'             => '/path4/',
+		'ds.type'                   => '36',
+		'ds.xml_id'                 => 'tst_xml_id_6',
+		'ds.protocol'               => '0',
+		'ds.display_name'           => 'display name 6',
+		'ds.regional_geo_blocking'  => '0',
+		'ds.geolimit_redirect_url'  => '',
+		'ds.use_content_prepositioning' => '0',
+	}
+)->status_is(302), "create HTTP PREPOSITION delivery service";
+
 #Validate update
 # 1.0 API
 # Note the 4 is the index, not the id.
diff --git a/traffic_ops/app/templates/delivery_service/add.html.ep b/traffic_ops/app/templates/delivery_service/add.html.ep
index c3fbd55fb..82f2b73ce 100644
--- a/traffic_ops/app/templates/delivery_service/add.html.ep
+++ b/traffic_ops/app/templates/delivery_service/add.html.ep
@@ -23,6 +23,7 @@
 
 	var re_row_num = Object;
 	var row = 0;
+	var path_prefix_row = 0;
 	$(function(){
 		$.get("/api/1.2/types.json", function(data){
 			$("#type").append("<option value=default selected=\"selected\">Choose a routing type</option>");
@@ -103,6 +104,9 @@
 		if (type_selected.match(/^HTTP/)) {
 			$("#regexp_selector :nth-child(3)").removeAttr('disabled');
 			$("#regexp_selector :nth-child(4)").removeAttr('disabled');
+			$('#path_prefix_headline').show(speed);
+			$('#path_prefix_table').show(speed);
+			$('#add_path_prefix').show(speed);
 			$('#ccr_dns_ttl').val(3600);
 			$('#ccr_dns_ttl_row').show(speed);
 			$('#protocoli_row').show(speed);
@@ -145,6 +149,9 @@
 		else if (type_selected.match(/^DNS/)) {
 			$("#regexp_selector :nth-child(3)").attr('disabled','disabled');
 			$("#regexp_selector :nth-child(4)").attr('disabled','disabled');
+			$('#path_prefix_headline').hide(speed);
+			$('#path_prefix_table').hide(speed);
+			$('#add_path_prefix').hide(speed);
 			$('#ccr_dns_ttl').val(30);
 			$('#ccr_dns_ttl_row').show(speed);
 			$('#protocoli_row').show(speed);
@@ -301,9 +308,30 @@
 
 		$('#regexp_selector :nth-child(1)').prop('selected', true); // set back to 0 index
 	}
+
+	function add_path_prefix_line() {
+		$('#path_prefix_table_body').append(
+			"<tr>" +
+			"<td>Path prefix:</td>" +
+			"<td><input type=\"text\" size=111 id=\"path_prefix_" + path_prefix_row + "\" name=\"path_prefix_" + path_prefix_row + "\"></input></td>" +
+			"</tr>"
+		);
+		path_prefix_row++;
+	}
+
 </script>
 <head>
     <script type="text/javascript" src="/js/application.js"></script>
+<style>
+#add_path_prefix {
+    border-radius: 8px;
+    background: #ffffff;
+    border: 2px solid;
+    padding: 2px;
+    width: 125px;
+    height: 15px;
+}
+</style>
 </head>
 <body>
 	<div id=accordion>
@@ -341,6 +369,25 @@
 		    <option>Add Header Regexp</option>
 	    </select><br>
 	    </div>
+
+	    <br>
+	    <h2 id="path_prefix_headline">Path prefixes for this delivery service:</h2>
+	    <!--DN: hidden field for validation of path prefix table.
+	    Cause I didnt re-factor the table to use FormFields. (yet?)-->
+	    <div class="block">
+		    %= label_for 'prefix' => '', class => 'label'
+		    %= field('hidden.prefix')->hidden(class => 'filed', id => 'hidden.prefix', name => 'hidden.prefix', value => '_____');
+		    <% unless (field('hidden.prefix')->valid) { %>
+			    <span class="field-with-error"><%= field('hidden.prefix')->error %></span>
+		    <% } %>
+	    </div>
+	    <table id="path_prefix_table" width=100%>
+		    <tbody id="path_prefix_table_body"></tbody>
+	    </table>
+	    <br>
+            <p id="add_path_prefix" onclick="add_path_prefix_line()">Add a new path prefix</p>
+	    <br>
+
 	</div>
 	<br><input class="button" type="submit" value="Save"/>
 	<button id="close_button" class="button" style="float:right; margin-right: 200px">Close</button>
diff --git a/traffic_ops/app/templates/delivery_service/edit.html.ep b/traffic_ops/app/templates/delivery_service/edit.html.ep
index e60b6390e..948d9ba1b 100644
--- a/traffic_ops/app/templates/delivery_service/edit.html.ep
+++ b/traffic_ops/app/templates/delivery_service/edit.html.ep
@@ -151,6 +151,7 @@
 
 	var re_row_num = Object;
 	var row = 0;
+	var path_prefix_row = 0;
 	$(function(){
 		$.get("/datatype/orderby/id", function(data){
 			$.each(data, function(idx, val) {
@@ -198,6 +199,9 @@ function setup_form(speed) {
 		if (type_selected.match(/^HTTP/)) {
 			$("#regexp_selector :nth-child(3)").removeAttr('disabled');
 			$("#regexp_selector :nth-child(4)").removeAttr('disabled');
+			$('#path_prefix_headline').show(speed);
+			$('#path_prefix_table').show(speed);
+			$('#add_path_prefix').show(speed);
 			$('#ccr_dns_ttl').show(speed);
 			$('#protocoli_row').show(speed);
 			$('#dscp_row').show(speed);
@@ -238,6 +242,9 @@ function setup_form(speed) {
 		else if (type_selected.match(/^DNS/)) {
 			$("#regexp_selector :nth-child(3)").attr('disabled','disabled');
 			$("#regexp_selector :nth-child(4)").attr('disabled','disabled');
+			$('#path_prefix_headline').hide(speed);
+			$('#path_prefix_table').hide(speed);
+			$('#add_path_prefix').hide(speed);
 			$('#ccr_dns_ttl').show(speed);
 			$('#protocoli_row').show(speed);
             $('#dscp_row').show(speed);
@@ -429,8 +436,27 @@ function setup_form(speed) {
       });
     });
 
+	function add_path_prefix_line() {
+		$('#path_prefix_table_body').append(
+			"<tr>" +
+			"<td>Path prefix:</td>" +
+			"<td><input type=\"text\" size=111 id=\"path_prefix_new_" + path_prefix_row + "\" name=\"path_prefix_new_" + path_prefix_row + "\"></input></td>" +
+			"</tr>"
+		);
+		path_prefix_row++;
+	}
 
 </script>
+<style>
+#add_path_prefix {
+    border-radius: 8px;
+    background: #ffffff;
+    border: 2px solid;
+    padding: 2px;
+    width: 125px;
+    height: 15px;
+}
+</style>
 </head>
 
    <div id='accordion' class "dialog_border" style='padding-left:10px;'>
@@ -518,6 +544,43 @@ function setup_form(speed) {
 				% }
 				</tbody>
 			</table>
+
+			<h2 id="path_prefix_headline" style="color:white;">Path prefixes for this delivery service:</h2>
+			<!--DN: hidden field for validation of path prefix table.
+				Cause I didnt re-factor the table to use FormFields. (yet?)-->
+			<div class="block">
+				%= label_for 'prefix' => '', class => 'label'
+				%= field('hidden.prefix')->hidden(class => 'field', id => 'hidden.prefix', name => 'prefix', value => '_____');
+				<% unless (field('hidden.prefix')->valid) { %>
+				<span class="field-with-error"><%= field('hidden.prefix')->error %></span>
+				<% } %>
+			</div>
+			<table id="path_prefix_table" width=100% cellpadding="10">
+				<tbody id="path_prefix_table_body">
+				%foreach ( my $prefix=0; $prefix<( scalar @{ $path_prefix_set } ); $prefix++ ) {
+					<tr id="tr_prefix_row_<%= $prefix %>">
+	            		<td width="130px">Path prefix:</td>
+	            		<td>
+	            			<% if ($priv_level >= 20) { %>
+	            			<input type="text" name="path_prefix_<%= $prefix %>" value= "<%= $path_prefix_set->[$prefix] %>" size=111/>
+	            			<% } else { %>
+	            			<input type="text" name="path_prefix_<%= $prefix %>" value= "<%= $path_prefix_set->[$prefix] %>" size=111 readonly/>
+	            			<% } %>
+	            		</td>
+	            		<% if ($priv_level >= 20) { %>
+	            		<td id="rm_prefix_row_<%= $prefix %>"><a href="#" onclick="removeRegexp(tr_prefix_row_<%= $prefix %>)">remove</a></td>
+	            		<% } %>
+	       			</tr>
+				% }
+				</tbody>
+			</table>
+			<br>
+			<% if ($priv_level >= 20) { %>
+			<p id="add_path_prefix" onclick="add_path_prefix_line()">Add a new path prefix</p>
+			<% } %>
+			<br>
+
+
 			<br><hr>
 			<% if ($priv_level >= 20) { %>
 			   <input class="button" style="margin-left:5px;" type="submit" value="Save"/>


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services