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/06/14 17:21:49 UTC

[GitHub] elsloo closed pull request #2314: Remove deliveryservice.org_server_fqdn column/compute it from Origin table

elsloo closed pull request #2314: Remove deliveryservice.org_server_fqdn column/compute it from Origin table
URL: https://github.com/apache/trafficcontrol/pull/2314
 
 
   

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/api/v13/origin.rst b/docs/source/api/v13/origin.rst
index 4f96cbb6a..185f9ca69 100644
--- a/docs/source/api/v13/origin.rst
+++ b/docs/source/api/v13/origin.rst
@@ -48,6 +48,8 @@ Origin
   +-------------------------+-----------------+---------------------------------------------------+
   | ``profileId``           | no              | Filter Origins by profile ID.                     |
   +-------------------------+-----------------+---------------------------------------------------+
+  | ``primary``             | no              | Filter Origins by isPrimary.                      |
+  +-------------------------+-----------------+---------------------------------------------------+
   | ``tenant``              | no              | Filter Origins by tenant ID.                      |
   +-------------------------+-----------------+---------------------------------------------------+
 
@@ -171,8 +173,6 @@ Origin
   +-----------------------------------+-------------------+--------------------------------------------------------------------------+
   | ``ipAddress``                     | no                | IPv4 address of the Origin                                               |
   +-----------------------------------+-------------------+--------------------------------------------------------------------------+
-  | ``isPrimary``                     | yes               | Whether or not this is the primary Origin for the delivery service       |
-  +-----------------------------------+-------------------+--------------------------------------------------------------------------+
   | ``name``                          | yes               | The name of the Origin                                                   |
   +-----------------------------------+-------------------+--------------------------------------------------------------------------+
   | ``port``                          | no                | The TCP port on which the Origin listens                                 |
@@ -193,7 +193,6 @@ Origin
         "fqdn": "foo.example.com",
         "ip6Address": "cafe:dead:d0d0::42",
         "ipAddress": "10.2.3.4",
-        "isPrimary": false,
         "name": "origin1",
         "port": 443,
         "profileId": 1,
@@ -320,8 +319,6 @@ Origin
   +-----------------------------------+-------------------+--------------------------------------------------------------------------+
   | ``ipAddress``                     | no                | IPv4 address of the Origin                                               |
   +-----------------------------------+-------------------+--------------------------------------------------------------------------+
-  | ``isPrimary``                     | yes               | Whether or not this is the primary Origin for the delivery service       |
-  +-----------------------------------+-------------------+--------------------------------------------------------------------------+
   | ``name``                          | yes               | The name of the Origin                                                   |
   +-----------------------------------+-------------------+--------------------------------------------------------------------------+
   | ``port``                          | no                | The TCP port on which the Origin listens                                 |
@@ -343,7 +340,6 @@ Origin
         "id": 1,
         "ip6Address": "cafe:dead:d0d0::42",
         "ipAddress": "10.2.3.4",
-        "isPrimary": false,
         "name": "origin1",
         "port": 443,
         "profileId": 1,
diff --git a/traffic_ops/app/db/migrations/20180606000000_remove_org_server_fqdn.sql b/traffic_ops/app/db/migrations/20180606000000_remove_org_server_fqdn.sql
new file mode 100644
index 000000000..967d1f662
--- /dev/null
+++ b/traffic_ops/app/db/migrations/20180606000000_remove_org_server_fqdn.sql
@@ -0,0 +1,46 @@
+/*
+
+    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
+
+INSERT INTO origin (name, protocol, fqdn, port, deliveryservice, tenant, is_primary)
+SELECT
+d.xml_id,
+lower(split_part(d.org_server_fqdn, '://', 1))::origin_protocol,
+regexp_replace(d.org_server_fqdn, '(^https?://)|(:\d+$)', '', 'gi'),
+(SELECT (regexp_matches(d.org_server_fqdn, '(?<=:)\d+$'))[1]::bigint),
+d.id,
+d.tenant_id,
+TRUE
+FROM deliveryservice d
+WHERE d.org_server_fqdn IS NOT NULL;
+
+ALTER TABLE deliveryservice DROP COLUMN org_server_fqdn;
+
+-- +goose Down
+-- SQL section 'Down' is executed when this migration is rolled back
+
+ALTER TABLE deliveryservice ADD COLUMN org_server_fqdn text;
+
+UPDATE deliveryservice d
+SET org_server_fqdn = (
+    SELECT o.protocol::text || '://' || o.fqdn || rtrim(concat(':', o.port::text), ':')
+    FROM origin o
+    WHERE o.deliveryservice = d.id AND o.is_primary
+);
+
+DELETE FROM origin o
+WHERE o.is_primary;
diff --git a/traffic_ops/app/lib/API/Configs/ApacheTrafficServer.pm b/traffic_ops/app/lib/API/Configs/ApacheTrafficServer.pm
index 4d6cd4dcd..d33cefebb 100755
--- a/traffic_ops/app/lib/API/Configs/ApacheTrafficServer.pm
+++ b/traffic_ops/app/lib/API/Configs/ApacheTrafficServer.pm
@@ -526,7 +526,10 @@ sub delivery_service_data_by_profile {
 		deliveryservice.routing_name,
 		deliveryservice.signing_algorithm,
 		deliveryservice.qstring_ignore,
-		deliveryservice.org_server_fqdn,
+		(SELECT o.protocol::text || \'://\' || o.fqdn || rtrim(concat(\':\', o.port::text), \':\')
+			FROM origin o
+			WHERE o.deliveryservice = deliveryservice.id
+			AND o.is_primary) as org_server_fqdn,
 		deliveryservice.origin_shield,
 		regex.pattern AS pattern,
     	retype.name AS re_type,
@@ -2237,7 +2240,7 @@ sub cachegroup_profiles {
 		if ( $row->type->name eq 'ORG' ) {
 			my $rs_ds = $self->db->resultset('DeliveryserviceServer')->search( { server => $row->id }, { prefetch => ['deliveryservice'] } );
 			while ( my $ds_row = $rs_ds->next ) {
-				my $org_uri = URI->new( $ds_row->deliveryservice->org_server_fqdn );
+				my $org_uri = URI->new( UI::DeliveryService::compute_org_server_fqdn($self, $ds_row->deliveryservice->id) );
 				push( @{ $deliveryservices->{ $org_uri->host } }, $row );
 			}
 		}
diff --git a/traffic_ops/app/lib/API/Deliveryservice.pm b/traffic_ops/app/lib/API/Deliveryservice.pm
index 9793e2573..d8bc37a9b 100644
--- a/traffic_ops/app/lib/API/Deliveryservice.pm
+++ b/traffic_ops/app/lib/API/Deliveryservice.pm
@@ -138,7 +138,7 @@ sub index {
 				"missLat"              => defined( $row->miss_lat ) ? 0.0 + $row->miss_lat : undef,
 				"missLong"             => defined( $row->miss_long ) ? 0.0 + $row->miss_long : undef,
 				"multiSiteOrigin"      => \$row->multi_site_origin,
-				"orgServerFqdn"        => $row->org_server_fqdn,
+				"orgServerFqdn"        => UI::DeliveryService::compute_org_server_fqdn($self, $row->id),
 				"originShield"         => $row->origin_shield,
 				"profileId"            => defined( $row->profile ) ? $row->profile->id : undef,
 				"profileName"          => defined( $row->profile ) ? $row->profile->name : undef,
@@ -262,7 +262,7 @@ sub show {
 				"missLat"              => defined( $row->miss_lat ) ? 0.0 + $row->miss_lat : undef,
 				"missLong"             => defined( $row->miss_long ) ? 0.0 + $row->miss_long : undef,
 				"multiSiteOrigin"      => \$row->multi_site_origin,
-				"orgServerFqdn"        => $row->org_server_fqdn,
+				"orgServerFqdn"        => UI::DeliveryService::compute_org_server_fqdn($self, $row->id),
 				"originShield"         => $row->origin_shield,
 				"profileId"            => defined( $row->profile ) ? $row->profile->id : undef,
 				"profileName"          => defined( $row->profile ) ? $row->profile->name : undef,
@@ -376,7 +376,6 @@ sub update {
 		miss_lat               => $params->{missLat},
 		miss_long              => $params->{missLong},
 		multi_site_origin      => $params->{multiSiteOrigin},
-		org_server_fqdn        => $params->{orgServerFqdn},
 		origin_shield          => $params->{originShield},
 		profile                => $params->{profileId},
 		protocol               => $params->{protocol},
@@ -413,6 +412,21 @@ sub update {
 	my $rs = $ds->update($values);
 	if ($rs) {
 
+		# find this DS's primary Origin and update it too
+		my $origin_rs = $self->db->resultset('Origin')->find( { deliveryservice => $id, is_primary => 1 } );
+		my $origin = UI::DeliveryService::get_primary_origin_from_deliveryservice($id, $values, $params->{orgServerFqdn});
+		if ( defined( $origin ) && defined( $origin_rs ) ) {
+			$origin_rs->update($origin);
+			&log( $self, "Updated primary origin [ '" . $origin_rs->name . "' ] with id: " . $origin_rs->id, "APICHANGE" );
+		} elsif ( defined( $origin ) && !defined( $origin_rs ) ) {
+			$origin_rs = $self->db->resultset('Origin')->create($origin)->insert();
+			&log( $self, "Created primary origin [ '" . $origin_rs->name . "' ] with id: " . $origin_rs->id, "APICHANGE" );
+		} elsif ( !defined( $origin ) && defined( $origin_rs ) ) {
+			my $name = $origin_rs->name;
+			$origin_rs->delete();
+			&log( $self, "Deleted primary origin [ '" . $name . "' ] ", "APICHANGE" );
+		}
+
 		# create location parameters for header_rewrite*, regex_remap* and cacheurl* config files if necessary
 		&UI::DeliveryService::header_rewrite( $self, $rs->id, $values->{profileId}, $values->{xmlId}, $values->{edgeHeaderRewrite}, "edge" );
 		&UI::DeliveryService::header_rewrite( $self, $rs->id, $values->{profileId}, $values->{xmlId}, $values->{midHeaderRewrite},  "mid" );
@@ -480,7 +494,7 @@ sub update {
 				"missLat"                  => defined($rs->miss_lat) ? 0.0 + $rs->miss_lat : undef,
 				"missLong"                 => defined($rs->miss_long) ? 0.0 + $rs->miss_long : undef,
 				"multiSiteOrigin"          => $rs->multi_site_origin,
-				"orgServerFqdn"            => $rs->org_server_fqdn,
+				"orgServerFqdn"            => UI::DeliveryService::compute_org_server_fqdn($self, $rs->id),
 				"originShield"             => $rs->origin_shield,
 				"profileId"                => defined($rs->profile) ? $rs->profile->id : undef,
 				"profileName"              => defined($rs->profile) ? $rs->profile->name : undef,
@@ -614,7 +628,7 @@ sub safe_update {
 				"missLat"                  => defined($rs->miss_lat) ? 0.0 + $rs->miss_lat : undef,
 				"missLong"                 => defined($rs->miss_long) ? 0.0 + $rs->miss_long : undef,
 				"multiSiteOrigin"          => $rs->multi_site_origin,
-				"orgServerFqdn"            => $rs->org_server_fqdn,
+				"orgServerFqdn"            => UI::DeliveryService::compute_org_server_fqdn($self, $rs->id),
 				"originShield"             => $rs->origin_shield,
 				"profileId"                => defined($rs->profile) ? $rs->profile->id : undef,
 				"profileName"              => defined($rs->profile) ? $rs->profile->name : undef,
@@ -719,7 +733,6 @@ sub create {
 		miss_lat               => $params->{missLat},
 		miss_long              => $params->{missLong},
 		multi_site_origin      => $params->{multiSiteOrigin},
-		org_server_fqdn        => $params->{orgServerFqdn},
 		origin_shield          => $params->{originShield},
 		profile                => $params->{profileId},
 		protocol               => $params->{protocol},
@@ -756,6 +769,12 @@ sub create {
 
 		&log( $self, "Created delivery service [ '" . $insert->xml_id . "' ] with id: " . $insert->id, "APICHANGE" );
 
+		my $origin = UI::DeliveryService::get_primary_origin_from_deliveryservice($insert->id, $values, $params->{orgServerFqdn});
+		if (defined( $origin )) {
+			my $origin_rs = $self->db->resultset('Origin')->create($origin)->insert();
+			&log( $self, "Created origin [ '" . $origin_rs->name . "' ] with id: " . $origin_rs->id, "APICHANGE" );
+		}
+
 		# create location parameters for header_rewrite*, regex_remap* and cacheurl* config files if necessary
 		&UI::DeliveryService::header_rewrite( $self, $insert->id, $values->{id}, $values->{xml_id}, $values->{edge_header_rewrite}, "edge" );
 		&UI::DeliveryService::header_rewrite( $self, $insert->id, $values->{profile_id}, $values->{xml_id}, $values->{mid_header_rewrite},  "mid" );
@@ -834,7 +853,7 @@ sub create {
 				"missLat"                  => defined($insert->miss_lat) ? 0.0 + $insert->miss_lat : undef,
 				"missLong"                 => defined($insert->miss_long) ? 0.0 + $insert->miss_long : undef,
 				"multiSiteOrigin"          => $insert->multi_site_origin,
-				"orgServerFqdn"            => $insert->org_server_fqdn,
+				"orgServerFqdn"            => UI::DeliveryService::compute_org_server_fqdn($self, $insert->id),
 				"originShield"             => $insert->origin_shield,
 				"profileId"                => defined($insert->profile) ? $insert->profile->id : undef,
 				"profileName"              => defined($insert->profile) ? $insert->profile->name : undef,
@@ -1027,7 +1046,7 @@ sub get_deliveryservices_by_serverId {
 					"missLat"              => defined( $row->miss_lat ) ? 0.0 + $row->miss_lat : undef,
 					"missLong"             => defined( $row->miss_long ) ? 0.0 + $row->miss_long : undef,
 					"multiSiteOrigin"      => \$row->multi_site_origin,
-					"orgServerFqdn"        => $row->org_server_fqdn,
+					"orgServerFqdn"        => UI::DeliveryService::compute_org_server_fqdn($self, $row->id),
 					"originShield"         => $row->origin_shield,
 					"profileId"            => defined( $row->profile ) ? $row->profile->id : undef,
 					"profileName"          => defined( $row->profile ) ? $row->profile->name : undef,
@@ -1128,7 +1147,7 @@ sub get_deliveryservices_by_userId {
 					"missLat"              => defined( $row->miss_lat ) ? 0.0 + $row->miss_lat : undef,
 					"missLong"             => defined( $row->miss_long ) ? 0.0 + $row->miss_long : undef,
 					"multiSiteOrigin"      => \$row->multi_site_origin,
-					"orgServerFqdn"        => $row->org_server_fqdn,
+					"orgServerFqdn"        => UI::DeliveryService::compute_org_server_fqdn($self, $row->id),
 					"originShield"         => $row->origin_shield,
 					"profileId"            => defined( $row->profile ) ? $row->profile->id : undef,
 					"profileName"          => defined( $row->profile ) ? $row->profile->name : undef,
diff --git a/traffic_ops/app/lib/Fixtures/Deliveryservice.pm b/traffic_ops/app/lib/Fixtures/Deliveryservice.pm
index 59f6cf428..e087be905 100644
--- a/traffic_ops/app/lib/Fixtures/Deliveryservice.pm
+++ b/traffic_ops/app/lib/Fixtures/Deliveryservice.pm
@@ -44,7 +44,6 @@ my %definition_for = (
 			long_desc_2           => 'test-ds1 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://test-ds1.edge',
 			info_url              => 'http://test-ds1.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -86,7 +85,6 @@ my %definition_for = (
 			long_desc_2           => 'test-ds2 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://test-ds2.edge',
 			info_url              => 'http://test-ds2.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -128,7 +126,6 @@ my %definition_for = (
 			long_desc_2           => 'test-ds3 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://test-ds3.edge',
 			info_url              => 'http://test-ds3.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -170,7 +167,6 @@ my %definition_for = (
 			long_desc_2           => 'test-ds4 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://test-ds4.edge',
 			info_url              => 'http://test-ds4.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -212,7 +208,6 @@ my %definition_for = (
 			long_desc_2           => 'test-ds5 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://test-ds5.edge',
 			info_url              => 'http://test-ds5.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -254,7 +249,6 @@ my %definition_for = (
 			long_desc_2           => 'test-ds6 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://test-ds6.edge',
 			info_url              => 'http://test-ds6.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -295,7 +289,6 @@ my %definition_for = (
 			long_desc_2           => 'steering-ds1 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://steering-ds1.edge',
 			info_url              => 'http://steering-ds1.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -335,7 +328,6 @@ my %definition_for = (
 			long_desc_2           => 'steering-ds2 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://steering-ds2.edge',
 			info_url              => 'http://steering-ds2.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -375,7 +367,6 @@ my %definition_for = (
 			long_desc_2           => 'new-steering-ds long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://new-steering-ds.edge',
 			info_url              => 'http://new-steering-ds.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -415,7 +406,6 @@ my %definition_for = (
 			long_desc_2           => 'target-ds1 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://target-ds1.edge',
 			info_url              => 'http://target-ds1.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -455,7 +445,6 @@ my %definition_for = (
 			long_desc_2           => 'target-ds2 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://target-ds2.edge',
 			info_url              => 'http://target-ds2.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -495,7 +484,6 @@ my %definition_for = (
 			long_desc_2           => 'target-ds3 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://target-ds3.edge',
 			info_url              => 'http://target-ds3.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -535,7 +523,6 @@ my %definition_for = (
 			long_desc_2           => 'target-ds4 long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://target-ds4.edge',
 			info_url              => 'http://target-ds4.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -576,7 +563,6 @@ my %definition_for = (
 			long_desc_2           => 'test-ds1-root long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://test-ds1-root.edge',
 			info_url              => 'http://test-ds1-root.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
@@ -618,7 +604,6 @@ my %definition_for = (
 			long_desc_2           => 'foo.bar long_desc_2',
 			max_dns_answers       => 0,
 			protocol              => 0,
-			org_server_fqdn       => 'http://foo.bar.edge',
 			info_url              => 'http://foo.bar.edge/info_url.html',
 			miss_lat              => '41.881944',
 			miss_long             => '-87.627778',
diff --git a/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm b/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm
index 678df65b2..5acc84dfb 100644
--- a/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm
+++ b/traffic_ops/app/lib/Fixtures/Integration/Deliveryservice.pm
@@ -70,7 +70,6 @@ my %definition_for = (
 			cdn_id                      => '2',
 			dns_bypass_ttl              => undef,
 			initial_dispersion          => '1',
-			org_server_fqdn             => 'http://cdl.origin.kabletown.net',
 			range_request_handling      => '0',
 			signing_algorithm           => 'url_sig',
 			dns_bypass_ip               => '',
@@ -117,7 +116,6 @@ my %definition_for = (
 			display_name                => 'games-c1',
 			http_bypass_fqdn            => '',
 			info_url                    => 'http://games.info.kabletown.net',
-			org_server_fqdn             => 'http://games.origin.kabletown.net',
 			ccr_dns_ttl                 => '3600',
 			dns_bypass_ip6              => undef,
 			last_updated                => '2015-12-10 15:44:37',
@@ -150,7 +148,6 @@ my %definition_for = (
 			deep_caching_type           => 'NEVER',
 			routing_name                => 'foo',
 			last_updated                => '2015-12-10 15:44:37',
-			org_server_fqdn             => 'http://images.origin.kabletown.net',
 			tr_response_headers         => undef,
 			cacheurl                    => undef,
 			check_path                  => '/crossdomain.xml',
@@ -224,7 +221,6 @@ my %definition_for = (
 			edge_header_rewrite         => 'cond %{REMAP_PSEUDO_HOOK} __RETURN__ set-config proxy.config.http.transaction_active_timeout_out 5 [L]',
 			global_max_mbps             => '0',
 			ssl_key_version             => '0',
-			org_server_fqdn             => 'http://movies.origin.kabletown.net',
 			range_request_handling      => '0',
 			regex_remap                 => undef,
 			miss_long                   => '-87.627778',
@@ -278,7 +274,6 @@ my %definition_for = (
 			check_path                  => '/crossdomain.xml',
 			dns_bypass_ip               => '',
 			tr_response_headers         => undef,
-			org_server_fqdn             => 'http://movies.origin.kabletown.net',
 			geo_limit                   => '0',
 			long_desc_2                 => 'test-ds1 long_desc_2',
 			edge_header_rewrite         => undef,
@@ -322,7 +317,6 @@ my %definition_for = (
 			fq_pacing_rate              => '0',			
 			http_bypass_fqdn            => '',
 			miss_long                   => '-87.627778',
-			org_server_fqdn             => 'https://games.origin.kabletown.net',
 			multi_site_origin           => undef,
 			cacheurl                    => undef,
 			dns_bypass_ip               => '',
@@ -374,7 +368,6 @@ my %definition_for = (
 			fq_pacing_rate              => '0',			
 			initial_dispersion          => '1',
 			multi_site_origin           => undef,
-			org_server_fqdn             => 'http://national-tv.origin.kabletown.net',
 			signing_algorithm           => undef,
 			display_name                => 'tv-nat-c2',
 			dns_bypass_ip6              => undef,
@@ -433,7 +426,6 @@ my %definition_for = (
 			regex_remap                 => undef,
 			remap_text                  => undef,
 			miss_long                   => '-87.627778',
-			org_server_fqdn             => 'http://cc.origin.kabletown.net',
 			display_name                => 'tv-nocache-c2',
 			qstring_ignore              => '0',
 			dns_bypass_ip6              => undef,
diff --git a/traffic_ops/app/lib/Fixtures/Integration/Origin.pm b/traffic_ops/app/lib/Fixtures/Integration/Origin.pm
new file mode 100644
index 000000000..548750689
--- /dev/null
+++ b/traffic_ops/app/lib/Fixtures/Integration/Origin.pm
@@ -0,0 +1,186 @@
+package Fixtures::Integration::Origin;
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+use Moose;
+extends 'DBIx::Class::EasyFixture';
+use namespace::autoclean;
+
+my %definition_for = (
+	## id => 1
+	'0' => {
+		new      => 'Origin',
+		using => {
+            name                  => 'test-origin1',
+            fqdn                  => 'cdl.origin.kabletown.net',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 1,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+		},
+	},
+	## id => 2
+	'1' => {
+		new      => 'Origin',
+		using => {
+            name                  => 'test-origin2',
+            fqdn                  => 'games.origin.kabletown.net',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 2,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+		},
+	},
+	## id => 3
+	'2' => {
+		new      => 'Origin',
+		using => {
+            name                  => 'test-origin3',
+            fqdn                  => 'images.origin.kabletown.net',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 3,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+		},
+	},
+	## id => 4
+	'3' => {
+		new      => 'Origin',
+		using => {
+            name                  => 'test-origin4',
+            fqdn                  => 'movies.origin.kabletown.net',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 4,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+		},
+	},
+	## id => 5
+	'4' => {
+		new      => 'Origin',
+		using => {
+            name                  => 'test-origin5',
+            fqdn                  => 'movies.origin.kabletown.net',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 5,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+		},
+	},
+	## id => 6
+	'5' => {
+		new      => 'Origin',
+		using => {
+            name                  => 'test-origin6',
+            fqdn                  => 'games.origin.kabletown.net',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 6,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+		},
+	},
+	## id => 7
+	'6' => {
+		new      => 'Origin',
+		using => {
+            name                  => 'test-origin7',
+            fqdn                  => 'national-tv.origin.kabletown.net',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 7,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+		},
+	},
+	## id => 8
+	'7' => {
+		new      => 'Origin',
+		using => {
+            name                  => 'test-origin8',
+            fqdn                  => 'cc.origin.kabletown.net',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 8,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+		},
+	},
+);
+
+sub name {
+	return "Origin";
+}
+
+sub get_definition {
+	my ( $self, $name ) = @_;
+	return $definition_for{$name};
+}
+
+sub all_fixture_names {
+	# sort by db name to guarantee insertion order
+	return (sort { $definition_for{$a}{using}{name} cmp $definition_for{$b}{using}{name} } keys %definition_for);
+}
+__PACKAGE__->meta->make_immutable;
+1;
diff --git a/traffic_ops/app/lib/Fixtures/Origin.pm b/traffic_ops/app/lib/Fixtures/Origin.pm
new file mode 100644
index 000000000..baf60ee5a
--- /dev/null
+++ b/traffic_ops/app/lib/Fixtures/Origin.pm
@@ -0,0 +1,307 @@
+package Fixtures::Origin;
+
+#
+#
+# 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.
+#
+use Moose;
+extends 'DBIx::Class::EasyFixture';
+use namespace::autoclean;
+
+my %definition_for = (
+    origin_cdn1 => {
+    new   => 'Origin',
+        using => {
+            id                    => 100,
+            name                  => 'test-origin1',
+            fqdn                  => 'test-ds1.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 100,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    origin_cdn2 => {
+        new   => 'Origin',
+        using => {
+            id                    => 200,
+            name                  => 'test-origin2',
+            fqdn                  => 'test-ds2.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 200,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    origin_cdn3 => {
+        new   => 'Origin',
+        using => {
+            id                    => 300,
+            name                  => 'test-origin3',
+            fqdn                  => 'test-ds3.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 300,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    origin_cdn4 => {
+        new   => 'Origin',
+        using => {
+            id                    => 400,
+            name                  => 'test-origin4',
+            fqdn                  => 'test-ds4.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 400,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    origin_dns => {
+        new   => 'Origin',
+        using => {
+            id                    => 500,
+            name                  => 'test-origin5',
+            fqdn                  => 'test-ds5.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 500,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    origin_http_no_cache => {
+        new   => 'Origin',
+        using => {
+            id                    => 600,
+            name                  => 'test-origin6',
+            fqdn                  => 'test-ds6.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 600,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    steering_origin1 => {
+        new   => 'Origin',
+        using => {
+            id                    => 700,
+            name                  => 'test-origin7',
+            fqdn                  => 'steering-ds1.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 700,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    steering_origin2 => {
+        new   => 'Origin',
+        using => {
+            id                    => 800,
+            name                  => 'test-origin8',
+            fqdn                  => 'steering-ds2.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 800,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    new_origin_steering => {
+        new   => 'Origin',
+        using => {
+            id                    => 900,
+            name                  => 'test-origin9',
+            fqdn                  => 'new-steering-ds.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 900,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    target_origin1 => {
+        new   => 'Origin',
+        using => {
+            id                    => 1000,
+            name                  => 'test-origin10',
+            fqdn                  => 'target-ds1.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 1000,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    target_origin2 => {
+        new   => 'Origin',
+        using => {
+            id                    => 1100,
+            name                  => 'test-origin11',
+            fqdn                  => 'target-ds2.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 1100,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    target_origin3 => {
+        new   => 'Origin',
+        using => {
+            id                    => 1200,
+            name                  => 'test-origin12',
+            fqdn                  => 'target-ds3.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 1200,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    target_origin4 => {
+        new   => 'Origin',
+        using => {
+            id                    => 1300,
+            name                  => 'test-origin13',
+            fqdn                  => 'target-ds4.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 1300,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    origin_cdn1_root => {
+        new   => 'Origin',
+        using => {
+            id                    => 2100,
+            name                  => 'test-origin14',
+            fqdn                  => 'test-ds1-root.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 2100,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => 10**9,
+        },
+    },
+    origin_period1 => {
+        new   => 'Origin',
+        using => {
+            id                    => 2200,
+            name                  => 'test-origin15',
+            fqdn                  => 'foo.bar.edge',
+            protocol              => 'http',
+            is_primary            => 1,
+            port                  => undef,
+            ip_address            => undef,
+            ip6_address           => undef,
+            deliveryservice       => 2200,
+            coordinate            => undef,
+            profile               => undef,
+            cachegroup            => undef,
+            tenant                => undef,
+        },
+    },
+    
+);
+
+sub get_definition {
+    my ( $self, $name ) = @_;
+    return $definition_for{$name};
+}
+
+sub all_fixture_names {
+    # sort by db id to guarantee insertion order
+    return (sort { $definition_for{$a}{using}{id} cmp $definition_for{$b}{using}{id} } keys %definition_for);
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/traffic_ops/app/lib/MojoPlugins/Job.pm b/traffic_ops/app/lib/MojoPlugins/Job.pm
index 1ec0ed069..1f7634cf5 100755
--- a/traffic_ops/app/lib/MojoPlugins/Job.pm
+++ b/traffic_ops/app/lib/MojoPlugins/Job.pm
@@ -180,7 +180,7 @@ sub register {
 			}
 			my $start_time_gmt = strftime( "%Y-%m-%d %H:%M:%S", gmtime($start_time) );
 			my $entered_time   = strftime( "%Y-%m-%d %H:%M:%S", gmtime() );
-			my $org_server_fqdn = $self->db->resultset("Deliveryservice")->search( { id => $ds_id } )->get_column('org_server_fqdn')->single();
+			my $org_server_fqdn = UI::DeliveryService::compute_org_server_fqdn($self, $ds_id);
 
 			my $tm_user_id = $self->db->resultset('TmUser')->search( { username => $self->current_user()->{username} } )->get_column('id')->single();
 
@@ -227,7 +227,7 @@ sub register {
 				my ( $scheme, $asset_hostname, $path, $query, $fragment ) = $asset =~ m|(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?|;
 
 				while ( my $ds_row = $rs_ds->next ) {
-					my $org_server_fqdn = $ds_row->org_server_fqdn;
+					my $org_server_fqdn = UI::DeliveryService::compute_org_server_fqdn($self, $ds_row->id);
 					if ( defined($org_server_fqdn) && $asset =~ /$org_server_fqdn/ ) {
 						return 1;    # Success
 					}
diff --git a/traffic_ops/app/lib/Schema/Result/Cachegroup.pm b/traffic_ops/app/lib/Schema/Result/Cachegroup.pm
index fea22b3ee..efedcfe47 100644
--- a/traffic_ops/app/lib/Schema/Result/Cachegroup.pm
+++ b/traffic_ops/app/lib/Schema/Result/Cachegroup.pm
@@ -72,7 +72,7 @@ __PACKAGE__->table("cachegroup");
 
   data_type: 'timestamp with time zone'
   default_value: current_timestamp
-  is_nullable: 1
+  is_nullable: 0
   original: {default_value => \"now()"}
 
 =cut
@@ -103,7 +103,7 @@ __PACKAGE__->add_columns(
   {
     data_type     => "timestamp with time zone",
     default_value => \"current_timestamp",
-    is_nullable   => 1,
+    is_nullable   => 0,
     original      => { default_value => \"now()" },
   },
 );
@@ -124,7 +124,7 @@ __PACKAGE__->set_primary_key("id", "type");
 
 =head1 UNIQUE CONSTRAINTS
 
-=head2 C<idx_54252_cg_name_unique>
+=head2 C<idx_140208_cg_name_unique>
 
 =over 4
 
@@ -134,9 +134,9 @@ __PACKAGE__->set_primary_key("id", "type");
 
 =cut
 
-__PACKAGE__->add_unique_constraint("idx_54252_cg_name_unique", ["name"]);
+__PACKAGE__->add_unique_constraint("idx_140208_cg_name_unique", ["name"]);
 
-=head2 C<idx_54252_cg_short_unique>
+=head2 C<idx_140208_cg_short_unique>
 
 =over 4
 
@@ -146,9 +146,9 @@ __PACKAGE__->add_unique_constraint("idx_54252_cg_name_unique", ["name"]);
 
 =cut
 
-__PACKAGE__->add_unique_constraint("idx_54252_cg_short_unique", ["short_name"]);
+__PACKAGE__->add_unique_constraint("idx_140208_cg_short_unique", ["short_name"]);
 
-=head2 C<idx_54252_lo_id_unique>
+=head2 C<idx_140208_lo_id_unique>
 
 =over 4
 
@@ -158,7 +158,7 @@ __PACKAGE__->add_unique_constraint("idx_54252_cg_short_unique", ["short_name"]);
 
 =cut
 
-__PACKAGE__->add_unique_constraint("idx_54252_lo_id_unique", ["id"]);
+__PACKAGE__->add_unique_constraint("idx_140208_lo_id_unique", ["id"]);
 
 =head1 RELATIONS
 
@@ -222,6 +222,21 @@ __PACKAGE__->has_many(
   { cascade_copy => 0, cascade_delete => 0 },
 );
 
+=head2 origins
+
+Type: has_many
+
+Related object: L<Schema::Result::Origin>
+
+=cut
+
+__PACKAGE__->has_many(
+  "origins",
+  "Schema::Result::Origin",
+  { "foreign.cachegroup" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
 =head2 parent_cachegroup
 
 Type: belongs_to
@@ -308,8 +323,8 @@ __PACKAGE__->belongs_to(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.07046 @ 2016-11-18 22:45:19
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:lU7dUVFuoTyhpC7x7BGaDg
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2018-05-15 16:06:00
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:2EeelbrXDdiyrV9BXGuIeA
 
 # You can replace this text with custom code or comments, and it will be preserved on regeneration
 #
diff --git a/traffic_ops/app/lib/Schema/Result/Coordinate.pm b/traffic_ops/app/lib/Schema/Result/Coordinate.pm
new file mode 100644
index 000000000..7463079fc
--- /dev/null
+++ b/traffic_ops/app/lib/Schema/Result/Coordinate.pm
@@ -0,0 +1,131 @@
+use utf8;
+package Schema::Result::Coordinate;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Schema::Result::Coordinate
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<coordinate>
+
+=cut
+
+__PACKAGE__->table("coordinate");
+
+=head1 ACCESSORS
+
+=head2 id
+
+  data_type: 'bigint'
+  is_auto_increment: 1
+  is_nullable: 0
+  sequence: 'coordinate_id_seq'
+
+=head2 name
+
+  data_type: 'text'
+  is_nullable: 0
+
+=head2 latitude
+
+  data_type: 'numeric'
+  default_value: 0.0
+  is_nullable: 0
+
+=head2 longitude
+
+  data_type: 'numeric'
+  default_value: 0.0
+  is_nullable: 0
+
+=head2 last_updated
+
+  data_type: 'timestamp with time zone'
+  default_value: current_timestamp
+  is_nullable: 0
+  original: {default_value => \"now()"}
+
+=cut
+
+__PACKAGE__->add_columns(
+  "id",
+  {
+    data_type         => "bigint",
+    is_auto_increment => 1,
+    is_nullable       => 0,
+    sequence          => "coordinate_id_seq",
+  },
+  "name",
+  { data_type => "text", is_nullable => 0 },
+  "latitude",
+  { data_type => "numeric", default_value => "0.0", is_nullable => 0 },
+  "longitude",
+  { data_type => "numeric", default_value => "0.0", is_nullable => 0 },
+  "last_updated",
+  {
+    data_type     => "timestamp with time zone",
+    default_value => \"current_timestamp",
+    is_nullable   => 0,
+    original      => { default_value => \"now()" },
+  },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</id>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("id");
+
+=head1 UNIQUE CONSTRAINTS
+
+=head2 C<coordinate_name_key>
+
+=over 4
+
+=item * L</name>
+
+=back
+
+=cut
+
+__PACKAGE__->add_unique_constraint("coordinate_name_key", ["name"]);
+
+=head1 RELATIONS
+
+=head2 origins
+
+Type: has_many
+
+Related object: L<Schema::Result::Origin>
+
+=cut
+
+__PACKAGE__->has_many(
+  "origins",
+  "Schema::Result::Origin",
+  { "foreign.coordinate" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2018-05-15 16:06:00
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:FZ64Zkbh+B6CECd1k/h66w
+
+
+# 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/Schema/Result/DeliveryServiceInfoForDomainList.pm b/traffic_ops/app/lib/Schema/Result/DeliveryServiceInfoForDomainList.pm
index bb0f2310b..bc14e24da 100644
--- a/traffic_ops/app/lib/Schema/Result/DeliveryServiceInfoForDomainList.pm
+++ b/traffic_ops/app/lib/Schema/Result/DeliveryServiceInfoForDomainList.pm
@@ -45,7 +45,10 @@ SELECT
     deliveryservice.routing_name,
     deliveryservice.signing_algorithm,
     deliveryservice.qstring_ignore,
-    deliveryservice.org_server_fqdn,
+    (SELECT o.protocol::text || '://' || o.fqdn || rtrim(concat(':', o.port::text), ':')
+        FROM origin o
+        WHERE o.deliveryservice = deliveryservice.id
+        AND o.is_primary) as org_server_fqdn,
     deliveryservice.multi_site_origin,
     deliveryservice.range_request_handling,
     deliveryservice.fq_pacing_rate,  
diff --git a/traffic_ops/app/lib/Schema/Result/DeliveryServiceInfoForServerList.pm b/traffic_ops/app/lib/Schema/Result/DeliveryServiceInfoForServerList.pm
index f190d3341..ce325948f 100644
--- a/traffic_ops/app/lib/Schema/Result/DeliveryServiceInfoForServerList.pm
+++ b/traffic_ops/app/lib/Schema/Result/DeliveryServiceInfoForServerList.pm
@@ -45,7 +45,10 @@ SELECT
     deliveryservice.routing_name AS routing_name,
     deliveryservice.signing_algorithm AS signing_algorithm,
     deliveryservice.qstring_ignore AS qstring_ignore,
-    deliveryservice.org_server_fqdn as org_server_fqdn,
+    (SELECT o.protocol::text || '://' || o.fqdn || rtrim(concat(':', o.port::text), ':')
+        FROM origin o
+        WHERE o.deliveryservice = deliveryservice.id
+        AND o.is_primary) as org_server_fqdn,
     deliveryservice.multi_site_origin as multi_site_origin,
     deliveryservice.range_request_handling as range_request_handling,
     deliveryservice.fq_pacing_rate as fq_pacing_rate,
diff --git a/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm b/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm
index dab1cce37..94d4b9c1a 100644
--- a/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm
+++ b/traffic_ops/app/lib/Schema/Result/Deliveryservice.pm
@@ -87,11 +87,6 @@ __PACKAGE__->table("deliveryservice");
   data_type: 'bigint'
   is_nullable: 1
 
-=head2 org_server_fqdn
-
-  data_type: 'text'
-  is_nullable: 1
-
 =head2 type
 
   data_type: 'bigint'
@@ -170,7 +165,7 @@ __PACKAGE__->table("deliveryservice");
 
   data_type: 'timestamp with time zone'
   default_value: current_timestamp
-  is_nullable: 1
+  is_nullable: 0
   original: {default_value => \"now()"}
 
 =head2 protocol
@@ -349,8 +344,6 @@ __PACKAGE__->add_columns(
   { data_type => "text", is_nullable => 1 },
   "dns_bypass_ttl",
   { data_type => "bigint", is_nullable => 1 },
-  "org_server_fqdn",
-  { data_type => "text", is_nullable => 1 },
   "type",
   { data_type => "bigint", is_foreign_key => 1, is_nullable => 0 },
   "profile",
@@ -383,7 +376,7 @@ __PACKAGE__->add_columns(
   {
     data_type     => "timestamp with time zone",
     default_value => \"current_timestamp",
-    is_nullable   => 1,
+    is_nullable   => 0,
     original      => { default_value => \"now()" },
   },
   "protocol",
@@ -464,7 +457,7 @@ __PACKAGE__->set_primary_key("id", "type");
 
 =head1 UNIQUE CONSTRAINTS
 
-=head2 C<idx_89502_ds_id_unique>
+=head2 C<idx_140234_ds_id_unique>
 
 =over 4
 
@@ -474,9 +467,9 @@ __PACKAGE__->set_primary_key("id", "type");
 
 =cut
 
-__PACKAGE__->add_unique_constraint("idx_89502_ds_id_unique", ["id"]);
+__PACKAGE__->add_unique_constraint("idx_140234_ds_id_unique", ["id"]);
 
-=head2 C<idx_89502_ds_name_unique>
+=head2 C<idx_140234_ds_name_unique>
 
 =over 4
 
@@ -486,7 +479,7 @@ __PACKAGE__->add_unique_constraint("idx_89502_ds_id_unique", ["id"]);
 
 =cut
 
-__PACKAGE__->add_unique_constraint("idx_89502_ds_name_unique", ["xml_id"]);
+__PACKAGE__->add_unique_constraint("idx_140234_ds_name_unique", ["xml_id"]);
 
 =head1 RELATIONS
 
@@ -580,6 +573,21 @@ __PACKAGE__->has_many(
   { cascade_copy => 0, cascade_delete => 0 },
 );
 
+=head2 origins
+
+Type: has_many
+
+Related object: L<Schema::Result::Origin>
+
+=cut
+
+__PACKAGE__->has_many(
+  "origins",
+  "Schema::Result::Origin",
+  { "foreign.deliveryservice" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
 =head2 profile
 
 Type: belongs_to
@@ -681,8 +689,8 @@ __PACKAGE__->belongs_to(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.07047 @ 2018-02-28 21:54:35
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3ETqht/3FTxKgD/YuRf3Bg
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2018-05-17 16:24:12
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Yjz2V+duaN88QPILxLqoHg
 
 # You can replace this text with custom code or comments, and it will be preserved on regeneration
 #
diff --git a/traffic_ops/app/lib/Schema/Result/Origin.pm b/traffic_ops/app/lib/Schema/Result/Origin.pm
new file mode 100644
index 000000000..6c0126eb3
--- /dev/null
+++ b/traffic_ops/app/lib/Schema/Result/Origin.pm
@@ -0,0 +1,285 @@
+use utf8;
+package Schema::Result::Origin;
+
+# Created by DBIx::Class::Schema::Loader
+# DO NOT MODIFY THE FIRST PART OF THIS FILE
+
+=head1 NAME
+
+Schema::Result::Origin
+
+=cut
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+=head1 TABLE: C<origin>
+
+=cut
+
+__PACKAGE__->table("origin");
+
+=head1 ACCESSORS
+
+=head2 id
+
+  data_type: 'bigint'
+  is_auto_increment: 1
+  is_nullable: 0
+  sequence: 'origin_id_seq'
+
+=head2 name
+
+  data_type: 'text'
+  is_nullable: 0
+
+=head2 fqdn
+
+  data_type: 'text'
+  is_nullable: 0
+
+=head2 protocol
+
+  data_type: 'enum'
+  default_value: 'http'
+  extra: {custom_type_name => "origin_protocol",list => ["http","https"]}
+  is_nullable: 0
+
+=head2 is_primary
+
+  data_type: 'boolean'
+  default_value: false
+  is_nullable: 0
+
+=head2 port
+
+  data_type: 'bigint'
+  is_nullable: 1
+
+=head2 ip_address
+
+  data_type: 'text'
+  is_nullable: 1
+
+=head2 ip6_address
+
+  data_type: 'text'
+  is_nullable: 1
+
+=head2 deliveryservice
+
+  data_type: 'bigint'
+  is_foreign_key: 1
+  is_nullable: 0
+
+=head2 coordinate
+
+  data_type: 'bigint'
+  is_foreign_key: 1
+  is_nullable: 1
+
+=head2 profile
+
+  data_type: 'bigint'
+  is_foreign_key: 1
+  is_nullable: 1
+
+=head2 cachegroup
+
+  data_type: 'bigint'
+  is_foreign_key: 1
+  is_nullable: 1
+
+=head2 tenant
+
+  data_type: 'bigint'
+  is_foreign_key: 1
+  is_nullable: 1
+
+=head2 last_updated
+
+  data_type: 'timestamp with time zone'
+  default_value: current_timestamp
+  is_nullable: 0
+  original: {default_value => \"now()"}
+
+=cut
+
+__PACKAGE__->add_columns(
+  "id",
+  {
+    data_type         => "bigint",
+    is_auto_increment => 1,
+    is_nullable       => 0,
+    sequence          => "origin_id_seq",
+  },
+  "name",
+  { data_type => "text", is_nullable => 0 },
+  "fqdn",
+  { data_type => "text", is_nullable => 0 },
+  "protocol",
+  {
+    data_type => "enum",
+    default_value => "http",
+    extra => { custom_type_name => "origin_protocol", list => ["http", "https"] },
+    is_nullable => 0,
+  },
+  "is_primary",
+  { data_type => "boolean", default_value => \"false", is_nullable => 0 },
+  "port",
+  { data_type => "bigint", is_nullable => 1 },
+  "ip_address",
+  { data_type => "text", is_nullable => 1 },
+  "ip6_address",
+  { data_type => "text", is_nullable => 1 },
+  "deliveryservice",
+  { data_type => "bigint", is_foreign_key => 1, is_nullable => 0 },
+  "coordinate",
+  { data_type => "bigint", is_foreign_key => 1, is_nullable => 1 },
+  "profile",
+  { data_type => "bigint", is_foreign_key => 1, is_nullable => 1 },
+  "cachegroup",
+  { data_type => "bigint", is_foreign_key => 1, is_nullable => 1 },
+  "tenant",
+  { data_type => "bigint", is_foreign_key => 1, is_nullable => 1 },
+  "last_updated",
+  {
+    data_type     => "timestamp with time zone",
+    default_value => \"current_timestamp",
+    is_nullable   => 0,
+    original      => { default_value => \"now()" },
+  },
+);
+
+=head1 PRIMARY KEY
+
+=over 4
+
+=item * L</id>
+
+=back
+
+=cut
+
+__PACKAGE__->set_primary_key("id");
+
+=head1 UNIQUE CONSTRAINTS
+
+=head2 C<origin_name_key>
+
+=over 4
+
+=item * L</name>
+
+=back
+
+=cut
+
+__PACKAGE__->add_unique_constraint("origin_name_key", ["name"]);
+
+=head1 RELATIONS
+
+=head2 cachegroup
+
+Type: belongs_to
+
+Related object: L<Schema::Result::Cachegroup>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "cachegroup",
+  "Schema::Result::Cachegroup",
+  { id => "cachegroup" },
+  {
+    is_deferrable => 0,
+    join_type     => "LEFT",
+    on_delete     => "RESTRICT",
+    on_update     => "NO ACTION",
+  },
+);
+
+=head2 coordinate
+
+Type: belongs_to
+
+Related object: L<Schema::Result::Coordinate>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "coordinate",
+  "Schema::Result::Coordinate",
+  { id => "coordinate" },
+  {
+    is_deferrable => 0,
+    join_type     => "LEFT",
+    on_delete     => "RESTRICT",
+    on_update     => "NO ACTION",
+  },
+);
+
+=head2 deliveryservice
+
+Type: belongs_to
+
+Related object: L<Schema::Result::Deliveryservice>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "deliveryservice",
+  "Schema::Result::Deliveryservice",
+  { id => "deliveryservice" },
+  { is_deferrable => 0, on_delete => "CASCADE", on_update => "NO ACTION" },
+);
+
+=head2 profile
+
+Type: belongs_to
+
+Related object: L<Schema::Result::Profile>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "profile",
+  "Schema::Result::Profile",
+  { id => "profile" },
+  {
+    is_deferrable => 0,
+    join_type     => "LEFT",
+    on_delete     => "RESTRICT",
+    on_update     => "NO ACTION",
+  },
+);
+
+=head2 tenant
+
+Type: belongs_to
+
+Related object: L<Schema::Result::Tenant>
+
+=cut
+
+__PACKAGE__->belongs_to(
+  "tenant",
+  "Schema::Result::Tenant",
+  { id => "tenant" },
+  {
+    is_deferrable => 0,
+    join_type     => "LEFT",
+    on_delete     => "RESTRICT",
+    on_update     => "NO ACTION",
+  },
+);
+
+
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2018-05-15 16:06:00
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:EFdWsJg/ANV/vUHBHfK0iA
+
+
+# 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/Schema/Result/Profile.pm b/traffic_ops/app/lib/Schema/Result/Profile.pm
index 998a8fa8f..c5deafe3d 100644
--- a/traffic_ops/app/lib/Schema/Result/Profile.pm
+++ b/traffic_ops/app/lib/Schema/Result/Profile.pm
@@ -44,7 +44,7 @@ __PACKAGE__->table("profile");
 
   data_type: 'timestamp with time zone'
   default_value: current_timestamp
-  is_nullable: 1
+  is_nullable: 0
   original: {default_value => \"now()"}
 
 =head2 type
@@ -83,7 +83,7 @@ __PACKAGE__->add_columns(
   {
     data_type     => "timestamp with time zone",
     default_value => \"current_timestamp",
-    is_nullable   => 1,
+    is_nullable   => 0,
     original      => { default_value => \"now()" },
   },
   "type",
@@ -179,6 +179,21 @@ __PACKAGE__->has_many(
   { cascade_copy => 0, cascade_delete => 0 },
 );
 
+=head2 origins
+
+Type: has_many
+
+Related object: L<Schema::Result::Origin>
+
+=cut
+
+__PACKAGE__->has_many(
+  "origins",
+  "Schema::Result::Origin",
+  { "foreign.profile" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
 =head2 profile_parameters
 
 Type: has_many
@@ -210,8 +225,8 @@ __PACKAGE__->has_many(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.07047 @ 2017-09-05 09:54:32
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BzUCSsUKInomx0bcvL5eAw
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2018-05-15 16:06:00
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:eX4sXLElEMpaA0xfHTv5lw
 
 
 # You can replace this text with custom code or comments, and it will be preserved on regeneration
diff --git a/traffic_ops/app/lib/Schema/Result/Tenant.pm b/traffic_ops/app/lib/Schema/Result/Tenant.pm
index 646e2c918..27b127998 100644
--- a/traffic_ops/app/lib/Schema/Result/Tenant.pm
+++ b/traffic_ops/app/lib/Schema/Result/Tenant.pm
@@ -52,7 +52,7 @@ __PACKAGE__->table("tenant");
 
   data_type: 'timestamp with time zone'
   default_value: current_timestamp
-  is_nullable: 1
+  is_nullable: 0
   original: {default_value => \"now()"}
 
 =cut
@@ -80,7 +80,7 @@ __PACKAGE__->add_columns(
   {
     data_type     => "timestamp with time zone",
     default_value => \"current_timestamp",
-    is_nullable   => 1,
+    is_nullable   => 0,
     original      => { default_value => \"now()" },
   },
 );
@@ -128,6 +128,21 @@ __PACKAGE__->has_many(
   { cascade_copy => 0, cascade_delete => 0 },
 );
 
+=head2 origins
+
+Type: has_many
+
+Related object: L<Schema::Result::Origin>
+
+=cut
+
+__PACKAGE__->has_many(
+  "origins",
+  "Schema::Result::Origin",
+  { "foreign.tenant" => "self.id" },
+  { cascade_copy => 0, cascade_delete => 0 },
+);
+
 =head2 parent
 
 Type: belongs_to
@@ -179,8 +194,8 @@ __PACKAGE__->has_many(
 );
 
 
-# Created by DBIx::Class::Schema::Loader v0.07046 @ 2017-04-04 20:51:35
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7I7o08tTBHjshtXVypmOUw
+# Created by DBIx::Class::Schema::Loader v0.07042 @ 2018-05-15 16:06:00
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:fLrBvjW6JLyIRv59Qkrr+Q
 
 # You can replace this text with custom code or comments, and it will be preserved on regeneration
 #
diff --git a/traffic_ops/app/lib/Test/IntegrationTestHelper.pm b/traffic_ops/app/lib/Test/IntegrationTestHelper.pm
index e86166f4c..9213d68b7 100644
--- a/traffic_ops/app/lib/Test/IntegrationTestHelper.pm
+++ b/traffic_ops/app/lib/Test/IntegrationTestHelper.pm
@@ -45,6 +45,7 @@ use Fixtures::Integration::JobAgent;
 use Fixtures::Integration::Job;
 use Fixtures::Integration::JobStatus;
 use Fixtures::Integration::Log;
+use Fixtures::Integration::Origin;
 use Fixtures::Integration::Parameter;
 use Fixtures::Integration::PhysLocation;
 use Fixtures::Integration::ProfileParameter;
@@ -157,6 +158,7 @@ sub load_core_data {
 	$self->load_all_fixtures( Fixtures::Integration::Asn->new($schema_values) );
 	$self->load_all_fixtures( Fixtures::Integration::Server->new($schema_values) );
 	$self->load_all_fixtures( Fixtures::Integration::Deliveryservice->new($schema_values) );
+	$self->load_all_fixtures( Fixtures::Integration::Origin->new($schema_values) );
 	$self->load_all_fixtures( Fixtures::Integration::DeliveryserviceRegex->new($schema_values) );
 	$self->load_all_fixtures( Fixtures::Integration::DeliveryserviceServer->new($schema_values) );
 	$self->load_all_fixtures( Fixtures::Integration::ToExtension->new($schema_values) );
diff --git a/traffic_ops/app/lib/Test/TestHelper.pm b/traffic_ops/app/lib/Test/TestHelper.pm
index 6c38f3253..388f18143 100644
--- a/traffic_ops/app/lib/Test/TestHelper.pm
+++ b/traffic_ops/app/lib/Test/TestHelper.pm
@@ -29,6 +29,7 @@ use Schema;
 use Utils::Tenant;
 use Fixtures::Cdn;
 use Fixtures::Deliveryservice;
+use Fixtures::Origin;
 use Fixtures::DeliveryserviceTmuser;
 use Fixtures::Asn;
 use Fixtures::Cachegroup;
@@ -136,6 +137,7 @@ sub load_core_data {
 	$self->load_all_fixtures( Fixtures::Server->new($schema_values) );
 	$self->load_all_fixtures( Fixtures::Asn->new($schema_values) );
 	$self->load_all_fixtures( Fixtures::Deliveryservice->new($schema_values) );
+	$self->load_all_fixtures( Fixtures::Origin->new($schema_values) );
 	$self->load_all_fixtures( Fixtures::Regex->new($schema_values) );
 	$self->load_all_fixtures( Fixtures::DeliveryserviceRegex->new($schema_values) );
 	$self->load_all_fixtures( Fixtures::DeliveryserviceTmuser->new($schema_values) );
@@ -162,6 +164,7 @@ sub unload_core_data {
 	$self->teardown($schema, 'DeliveryserviceRegex');
 	$self->teardown($schema, 'Regex');
 	$self->teardown($schema, 'DeliveryserviceServer');
+	$self->teardown($schema, 'Origin');
 	$self->teardown($schema, 'Deliveryservice');
 	$self->teardown($schema, 'Server');
 	$self->teardown($schema, 'PhysLocation');
diff --git a/traffic_ops/app/lib/UI/Cdn.pm b/traffic_ops/app/lib/UI/Cdn.pm
index dd1cb921a..72f2aea69 100644
--- a/traffic_ops/app/lib/UI/Cdn.pm
+++ b/traffic_ops/app/lib/UI/Cdn.pm
@@ -475,7 +475,7 @@ sub adeliveryservice {
         my $cdn_name = defined( $row->cdn_id ) ? $row->cdn->name : "";
 
         # This will be undefined for 'Steering' delivery services
-        my $org_server_fqdn = defined($row->org_server_fqdn) ? $row->org_server_fqdn : "";
+        my $org_server_fqdn = UI::DeliveryService::compute_org_server_fqdn($self, $row->id) // "";
 
         my $ptext = defined($row->profile) ? $row->profile->name : "-";
         my $line = [
diff --git a/traffic_ops/app/lib/UI/ConfigFiles.pm b/traffic_ops/app/lib/UI/ConfigFiles.pm
index ae1c937fc..90c4bd2b5 100644
--- a/traffic_ops/app/lib/UI/ConfigFiles.pm
+++ b/traffic_ops/app/lib/UI/ConfigFiles.pm
@@ -445,7 +445,7 @@ sub cachegroup_profiles {
 		if ( $row->type->name eq 'ORG' ) {
 			my $rs_ds = $self->db->resultset('DeliveryserviceServer')->search( { server => $row->id }, { prefetch => ['deliveryservice'] } );
 			while ( my $ds_row = $rs_ds->next ) {
-				my $ds_domain = $ds_row->deliveryservice->org_server_fqdn;
+				my $ds_domain = UI::DeliveryService::compute_org_server_fqdn($self, $ds_row->deliveryservice->id);
 				$ds_domain =~ s/https?:\/\/(.*)/$1/;
 				push( @{ $deliveryservices->{$ds_domain} }, $row );
 			}
diff --git a/traffic_ops/app/lib/UI/DeliveryService.pm b/traffic_ops/app/lib/UI/DeliveryService.pm
index a7e45a371..12417f55f 100644
--- a/traffic_ops/app/lib/UI/DeliveryService.pm
+++ b/traffic_ops/app/lib/UI/DeliveryService.pm
@@ -58,11 +58,15 @@ sub edit {
 	my $server_count = $self->db->resultset('DeliveryserviceServer')->search( { deliveryservice => $id } )->count();
 	my $static_count = $self->db->resultset('Staticdnsentry')->search( { deliveryservice => $id } )->count();
 
+	my $origin = {};
+	$origin->{org_server_fqdn} = compute_org_server_fqdn($self, $id);
+
 	$self->stash_profile_selector('DS_PROFILE', defined($data->profile) ? $data->profile->id : undef);
 	$self->stash_cdn_selector($data->cdn->id);
 	&stash_role($self);
 	$self->stash(
 		ds           => $data,
+		origin       => $origin,
 		server_count => $server_count,
 		static_count => $static_count,
 		fbox_layout  => 1,
@@ -73,6 +77,24 @@ sub edit {
 	);
 }
 
+sub compute_org_server_fqdn {
+	my $self = shift;
+	my $ds_id = shift;
+
+	my $origin = $self->db->resultset('Origin')->search( { deliveryservice => $ds_id, is_primary => 1 } )->single();
+	if (!defined( $origin )) {
+		return undef;
+	}
+
+	my $protocol = $origin->protocol;
+	my $fqdn = $origin->fqdn;
+	my $port = $origin->port;
+
+	my $url = $protocol . "://" . $fqdn;
+
+	return defined($port) ? $url . ":" . $port : $url;
+}
+
 sub get_example_urls {
 	my $self       = shift;
 	my $id         = shift;
@@ -223,7 +245,7 @@ sub read {
 				"dns_bypass_ip6"              => $row->dns_bypass_ip6,
 				"dns_bypass_cname"            => $row->dns_bypass_cname,
 				"dns_bypass_ttl"              => $row->dns_bypass_ttl,
-				"org_server_fqdn"             => $row->org_server_fqdn,
+				"org_server_fqdn"             => compute_org_server_fqdn($self, $row->id),
 				"multi_site_origin"           => \$row->multi_site_origin,
 				"ccr_dns_ttl"                 => $row->ccr_dns_ttl,
 				"type"                        => $row->type->id,
@@ -440,13 +462,13 @@ sub check_deliveryservice_input {
 		$self->field('ds.routing_name')->is_equal("", $self->param('ds.routing_name') . " is not a valid hostname without periods.");
 	}
 
-	my $org_host_name = $self->param('ds.org_server_fqdn');
-	$self->field('ds.org_server_fqdn')->is_like( qr/^(https?:\/\/)/, "Origin Server Base URL must start with http(s)://" );
+	my $org_host_name = $self->param('origin.org_server_fqdn');
+	$self->field('origin.org_server_fqdn')->is_like( qr/^(https?:\/\/)/, "Origin Server Base URL must start with http(s)://" );
 	$org_host_name =~ s!^https?://?!!i;
 	$org_host_name =~ s/:(.*)$//;
 	my $port = defined($1) ? $1 : 80;
 	if ( !&is_hostname($org_host_name) || $port !~ /^[1-9][0-9]*$/ ) {
-		$self->field('ds.org_server_fqdn')
+		$self->field('origin.org_server_fqdn')
 			->is_equal( "", $org_host_name . " is not a valid org server name (rfc1123) or " . $port . " is not a valid port" );
 	}
 	if ( $self->param('ds.http_bypass_fqdn') ne ""
@@ -792,6 +814,31 @@ sub delete_cfg_file {
 	}
 }
 
+sub get_primary_origin_from_deliveryservice {
+	my $deliveryservice_id = shift;
+	my $deliveryservice = shift;
+	my $org_server_fqdn = shift;
+
+	if ( !defined( $org_server_fqdn ) || $org_server_fqdn eq "" ) {
+		return undef;
+	}
+
+	$org_server_fqdn =~ m{^(https?)://([^:]+)(:(\d+))?$}i;
+	my $protocol = lc($1);
+	my $fqdn = $2;
+	my $port = $4;
+
+	return {
+		name => $deliveryservice->{xml_id},
+		deliveryservice => $deliveryservice_id,
+		fqdn => $fqdn,
+		protocol => $protocol,
+		is_primary => 1,
+		port => $port,
+		tenant => $deliveryservice ->{tenant_id}
+	};
+}
+
 # Update
 sub update {
 	my $self = shift;
@@ -815,7 +862,6 @@ sub update {
 			geo_limit_countries         => sanitize_geo_limit_countries( $self->paramAsScalar('ds.geo_limit_countries') ),
 			geolimit_redirect_url       => $self->param('ds.geolimit_redirect_url'),
 			geo_provider                => $self->paramAsScalar('ds.geo_provider'),
-			org_server_fqdn             => $self->paramAsScalar('ds.org_server_fqdn'),
 			multi_site_origin           => $self->paramAsScalar('ds.multi_site_origin'),
 			ccr_dns_ttl                 => $self->paramAsScalar('ds.ccr_dns_ttl'),
 			type                        => $self->typeid(),
@@ -873,6 +919,15 @@ sub update {
 		$update->update();
 		&log( $self, "Update deliveryservice with xml_id:" . $self->param('ds.xml_id'), "UICHANGE" );
 
+		# find this DS's primary Origin and update it too
+		my $origin_rs = $self->db->resultset('Origin')->find( { deliveryservice => $id, is_primary => 1 } );
+		if ( defined( $origin_rs ) ) {
+			my $origin = get_primary_origin_from_deliveryservice($id, \%hash, $self->paramAsScalar('origin.org_server_fqdn'));
+			if ( defined( $origin ) ) {
+				$origin_rs->update($origin);
+			}
+		}
+
 		# get the existing regexp set in a hash
 		my $regexp_set;
 		my $i = 0;
@@ -979,12 +1034,15 @@ sub update {
 		my $regexp_set   = &get_regexp_set( $self, $id );
 		my @example_urls = &get_example_urls( $self, $id, $regexp_set, $data, $cdn_domain, $data->protocol );
 		my $action;
+		my $origin = {};
+		$origin->{org_server_fqdn} = compute_org_server_fqdn($self, $id);
 
 		$self->stash_profile_selector('DS_PROFILE', defined($data->profile) ? $data->profile->id : undef);
 		$self->stash_cdn_selector($data->cdn->id);
 
 		$self->stash(
 			ds           => $data,
+			origin       => $origin,
 			fbox_layout  => 1,
 			server_count => $server_count,
 			static_count => $static_count,
@@ -1056,7 +1114,6 @@ sub create {
 				dns_bypass_ip6              => $self->paramAsScalar('ds.dns_bypass_ip6'),
 				dns_bypass_cname            => $self->paramAsScalar('ds.dns_bypass_cname'),
 				dns_bypass_ttl              => $self->paramAsScalar('ds.dns_bypass_ttl'),
-				org_server_fqdn             => $self->paramAsScalar('ds.org_server_fqdn'),
 				multi_site_origin           => $self->paramAsScalar('ds.multi_site_origin'),
 				ccr_dns_ttl                 => $self->paramAsScalar('ds.ccr_dns_ttl'),
 				type                        => $self->paramAsScalar('ds.type'),
@@ -1098,6 +1155,14 @@ sub create {
 		$new_id = $insert->id;
 		&log( $self, "Create deliveryservice with xml_id:" . $self->param('ds.xml_id'), "UICHANGE" );
 
+		# create primary Origin for this DS
+		my $origin = get_primary_origin_from_deliveryservice($insert->id, $new_ds, $self->paramAsScalar('origin.org_server_fqdn'));
+		if (defined( $origin )) {
+			my $origin_rs = $self->db->resultset('Origin')->create($origin)->insert();
+			&log( $self, "Created origin [ '" . $origin_rs->name . "' ] with id: " . $origin_rs->id, "UICHANGE" );
+		}
+
+
 		if ( $new_id == -1 ) {    # there was an error the flash will already be set,
 			my $referer = $self->req->headers->header('referer');
 			my $qstring = "?";
@@ -1183,6 +1248,7 @@ sub create {
 				&stash_role($self);
 				$self->stash(
 					ds               => {},
+					origin           => {},
 					fbox_layout      => 1,
 					selected_type    => $selected_type,
 					selected_profile => $selected_profile,
@@ -1204,6 +1270,7 @@ sub create {
 		&stash_role($self);
 		$self->stash(
 			ds               => {},
+			origin           => {},
 			fbox_layout      => 1,
 			selected_type    => $selected_type,
 			selected_profile => $selected_profile,
@@ -1327,6 +1394,7 @@ sub add {
 	$self->stash(
 		fbox_layout      => 1,
 		ds               => {},
+		origin           => {},
 		selected_type    => "",
 		selected_profile => "",
 		selected_cdn     => "",
diff --git a/traffic_ops/app/lib/UI/DeliveryServiceServer.pm b/traffic_ops/app/lib/UI/DeliveryServiceServer.pm
index 1007c8eda..b60c9060b 100644
--- a/traffic_ops/app/lib/UI/DeliveryServiceServer.pm
+++ b/traffic_ops/app/lib/UI/DeliveryServiceServer.pm
@@ -119,7 +119,7 @@ sub edit {
 
 	$self->stash( ds_id            => $id );
 	$self->stash( assigned_servers => $dss_data );
-	$self->stash( ds_name          => $ds->xml_id . ' (' . $ds->org_server_fqdn . ')' );
+	$self->stash( ds_name          => $ds->xml_id . ' (' . UI::DeliveryService::compute_org_server_fqdn($self, $ds->id) . ')' );
 	$self->stash( fbox_layout      => 1 );
 	$self->stash( dss_data         => $dss_data );
 	$self->stash( totals           => $totals );
diff --git a/traffic_ops/app/lib/UI/Job.pm b/traffic_ops/app/lib/UI/Job.pm
index 2be18108d..e875107de 100644
--- a/traffic_ops/app/lib/UI/Job.pm
+++ b/traffic_ops/app/lib/UI/Job.pm
@@ -74,7 +74,7 @@ sub newjob {
 			$org_server_fqdn =~ s/\/$//;
 		}
 		else {
-			$org_server_fqdn = $ds->org_server_fqdn;
+			$org_server_fqdn = UI::DeliveryService::compute_org_server_fqdn($self, $ds->id);
 		}
 		my $ds_id = $ds->id;
 
diff --git a/traffic_ops/app/t/deliveryservice.t b/traffic_ops/app/t/deliveryservice.t
index ce3fdfcb7..eca63e37c 100644
--- a/traffic_ops/app/t/deliveryservice.t
+++ b/traffic_ops/app/t/deliveryservice.t
@@ -77,7 +77,7 @@ ok $t->post_ok(
 		'ds.max_dns_answers'             => '0',
 		'ds.miss_lat'                    => '41.881944',
 		'ds.miss_long'                   => '-87.627778',
-		'ds.org_server_fqdn'             => 'http://jvd.knutsel.com',
+		'origin.org_server_fqdn'         => 'http://jvd.knutsel.com',
 		'ds.multi_site_origin'           => '0',
 		'ds.multi_site_origin_algorithm' => '0',
 		'ds.profile'                     => '100',
@@ -130,7 +130,7 @@ ok $t->post_ok(
 		'ds.max_dns_answers'             => '0',
 		'ds.miss_lat'                    => '41.881944',
 		'ds.miss_long'                   => '-87.627778',
-		'ds.org_server_fqdn'             => 'http://jvd-1.knutsel.com',
+		'origin.org_server_fqdn'         => 'http://jvd-1.knutsel.com',
 		'ds.multi_site_origin'           => '0',
 		'ds.multi_site_origin_algorithm' => '0',
 		'ds.profile'                     => '100',
@@ -183,7 +183,7 @@ ok $t->post_ok(
 		'ds.max_dns_answers'             => '0',
 		'ds.miss_lat'                    => '41.881944',
 		'ds.miss_long'                   => '-87.627778',
-		'ds.org_server_fqdn'             => 'http://jvd.knutsel.com',
+		'origin.org_server_fqdn'         => 'http://jvd.knutsel.com',
 		'ds.multi_site_origin'           => '0',
 		'ds.multi_site_origin_algorithm' => '0',
 		'ds.profile'                     => '100',
@@ -252,7 +252,7 @@ ok $t->post_ok(
 		'ds.max_dns_answers'             => '1',
 		'ds.miss_lat'                    => '0',
 		'ds.miss_long'                   => '0',
-		'ds.org_server_fqdn'             => 'http://update.knutsel.com',
+		'origin.org_server_fqdn'         => 'http://update.knutsel.com',
 		'ds.multi_site_origin'           => '0',
 		'ds.multi_site_origin_algorithm' => '0',
 		'ds.profile'                     => '200',
diff --git a/traffic_ops/app/t/purge.t b/traffic_ops/app/t/purge.t
index 97286e69c..dd3bdbea1 100644
--- a/traffic_ops/app/t/purge.t
+++ b/traffic_ops/app/t/purge.t
@@ -48,7 +48,10 @@ $t->post_ok( '/login', => form => { u => 'admin', p => 'password' } )->status_is
 
 my $q = "SELECT deliveryservice.id, 
            deliveryservice.xml_id, 
-           deliveryservice.org_server_fqdn,
+           (SELECT o.protocol::text || '://' || o.fqdn || rtrim(concat(':', o.port::text), ':')
+               FROM origin o
+               WHERE o.deliveryservice = deliveryservice.id
+               AND o.is_primary) as org_server_fqdn,
            deliveryservice.type,
            profile.id AS profile_id, 
            cdn.name AS cdn_name 
diff --git a/traffic_ops/app/templates/delivery_service/_form.html.ep b/traffic_ops/app/templates/delivery_service/_form.html.ep
index 35b72a59c..ed500d49c 100644
--- a/traffic_ops/app/templates/delivery_service/_form.html.ep
+++ b/traffic_ops/app/templates/delivery_service/_form.html.ep
@@ -341,14 +341,14 @@
 		<% } %>
 	</div>
 	<div class="block form-row" id="org_server_fqdn_row">
-		<% unless (field('ds.org_server_fqdn')->valid) { %>
-			<span class="field-with-error"><%= field('ds.org_server_fqdn')->error %></span>
+		<% unless (field('origin.org_server_fqdn')->valid) { %>
+			<span class="field-with-error"><%= field('origin.org_server_fqdn')->error %></span>
 		<% } %>
-		%= label_for 'org_server_fqdn' => '* Origin Server Base URL', class => 'label'
+		%= label_for 'origin.org_server_fqdn' => '* Origin Server Base URL', class => 'label'
 		<% if ($priv_level >= 20) { %>
-		%= field('ds.org_server_fqdn')->text(class => 'field', id => 'org_server_fqdn', name => 'ds.org_server_fqdn');
+		%= field('origin.org_server_fqdn')->text(class => 'field', id => 'org_server_fqdn', name => 'origin.org_server_fqdn');
 		<% } else { %>
-		%= field('ds.org_server_fqdn')->text(class => 'field', readonly => 'readonly');
+		%= field('origin.org_server_fqdn')->text(class => 'field', readonly => 'readonly');
 		<% } %>
 	</div>
 	<div class="block form-row" id="multi_site_origin_row">
diff --git a/traffic_ops/testing/api/v13/tc-fixtures.json b/traffic_ops/testing/api/v13/tc-fixtures.json
index 26530514e..6ce32b163 100644
--- a/traffic_ops/testing/api/v13/tc-fixtures.json
+++ b/traffic_ops/testing/api/v13/tc-fixtures.json
@@ -329,7 +329,6 @@
             "fqdn": "origin1.example.com",
             "ipAddress": "1.2.3.4",
             "ip6Address": "dead:beef:cafe::42",
-            "isPrimary": false,
             "port": 1234,
             "protocol": "http"
         },
@@ -338,7 +337,6 @@
             "fqdn": "origin2.example.com",
             "ipAddress": "5.6.7.8",
             "ip6Address": "cafe::42",
-            "isPrimary": false,
             "port": 5678,
             "protocol": "https"
         }
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
index 1b95ce120..cdfc319fe 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
@@ -260,7 +260,8 @@ func validateTypeFields(db *sqlx.DB, ds *tc.DeliveryServiceNullableV12) []error
 		"multiSiteOrigin": validation.Validate(ds.MultiSiteOrigin,
 			validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
 		"orgServerFqdn": validation.Validate(ds.OrgServerFQDN,
-			validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
+			validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName)),
+			validation.NewStringRule(validateOrgServerFQDN, "must start with http:// or https:// and be followed by a valid hostname with an optional port (no trailing slash)")),
 		"protocol": validation.Validate(ds.Protocol,
 			validation.By(requiredIfMatchesTypeName([]string{SteeringRegexType, DNSRegexType, HTTPRegexType}, typeName))),
 		"qstringIgnore": validation.Validate(ds.QStringIgnore,
@@ -275,6 +276,14 @@ func validateTypeFields(db *sqlx.DB, ds *tc.DeliveryServiceNullableV12) []error
 	return nil
 }
 
+func validateOrgServerFQDN(orgServerFQDN string) bool {
+	_, fqdn, port, err := parseOrgServerFQDN(orgServerFQDN)
+	if err != nil || !govalidator.IsHost(*fqdn) || (port != nil && !govalidator.IsPort(*port)) {
+		return false
+	}
+	return true
+}
+
 func requiredIfMatchesTypeName(patterns []string, typeName string) func(interface{}) error {
 	return func(value interface{}) error {
 		switch v := value.(type) {
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
index 50497807a..ebdd55049 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
@@ -25,6 +25,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"regexp"
 	"strconv"
 	"strings"
 
@@ -196,7 +197,7 @@ func create(db *sql.DB, cfg config.Config, user *auth.CurrentUser, ds tc.Deliver
 	commitTx := false
 	defer dbhelpers.FinishTx(tx, &commitTx)
 
-	resultRows, err := tx.Query(insertQuery(), &ds.Active, &ds.CacheURL, &ds.CCRDNSTTL, &ds.CDNID, &ds.CheckPath, &deepCachingType, &ds.DisplayName, &ds.DNSBypassCNAME, &ds.DNSBypassIP, &ds.DNSBypassIP6, &ds.DNSBypassTTL, &ds.DSCP, &ds.EdgeHeaderRewrite, &ds.GeoLimitRedirectURL, &ds.GeoLimit, &ds.GeoLimitCountries, &ds.GeoProvider, &ds.GlobalMaxMBPS, &ds.GlobalMaxTPS, &ds.FQPacingRate, &ds.HTTPBypassFQDN, &ds.InfoURL, &ds.InitialDispersion, &ds.IPV6RoutingEnabled, &ds.LogsEnabled, &ds.LongDesc, &ds.LongDesc1, &ds.LongDesc2, &ds.MaxDNSAnswers, &ds.MidHeaderRewrite, &ds.MissLat, &ds.MissLong, &ds.MultiSiteOrigin, &ds.OrgServerFQDN, &ds.OriginShield, &ds.ProfileID, &ds.Protocol, &ds.QStringIgnore, &ds.RangeRequestHandling, &ds.RegexRemap, &ds.RegionalGeoBlocking, &ds.RemapText, &ds.RoutingName, &ds.SigningAlgorithm, &ds.SSLKeyVersion, &ds.TenantID, &ds.TRRequestHeaders, &ds.TRResponseHeaders, &ds.TypeID, &ds.XMLID)
+	resultRows, err := tx.Query(insertQuery(), &ds.Active, &ds.CacheURL, &ds.CCRDNSTTL, &ds.CDNID, &ds.CheckPath, &deepCachingType, &ds.DisplayName, &ds.DNSBypassCNAME, &ds.DNSBypassIP, &ds.DNSBypassIP6, &ds.DNSBypassTTL, &ds.DSCP, &ds.EdgeHeaderRewrite, &ds.GeoLimitRedirectURL, &ds.GeoLimit, &ds.GeoLimitCountries, &ds.GeoProvider, &ds.GlobalMaxMBPS, &ds.GlobalMaxTPS, &ds.FQPacingRate, &ds.HTTPBypassFQDN, &ds.InfoURL, &ds.InitialDispersion, &ds.IPV6RoutingEnabled, &ds.LogsEnabled, &ds.LongDesc, &ds.LongDesc1, &ds.LongDesc2, &ds.MaxDNSAnswers, &ds.MidHeaderRewrite, &ds.MissLat, &ds.MissLong, &ds.MultiSiteOrigin, &ds.OriginShield, &ds.ProfileID, &ds.Protocol, &ds.QStringIgnore, &ds.RangeRequestHandling, &ds.RegexRemap, &ds.RegionalGeoBlocking, &ds.RemapText, &ds.RoutingName, &ds.SigningAlgorithm, &ds.SSLKeyVersion, &ds.TenantID, &ds.TRRequestHeaders, &ds.TRResponseHeaders, &ds.TypeID, &ds.XMLID)
 
 	if err != nil {
 		if pqerr, ok := err.(*pq.Error); ok {
@@ -270,6 +271,11 @@ func create(db *sql.DB, cfg config.Config, user *auth.CurrentUser, ds tc.Deliver
 	if err := createDNSSecKeys(tx, cfg, *ds.ID, *ds.XMLID, cdnName, cdnDomain, dnssecEnabled, ds.ExampleURLs); err != nil {
 		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating DNSSEC keys: " + err.Error())
 	}
+
+	if err := createPrimaryOrigin(db, tx, user, ds); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating delivery service: " + err.Error())
+	}
+
 	ds.LastUpdated = &lastUpdated
 	commitTx = true
 	api.CreateChangeLogRaw(api.ApiChange, "Created ds: "+*ds.XMLID+" id: "+strconv.Itoa(*ds.ID), *user, db)
@@ -306,6 +312,88 @@ func createDefaultRegex(tx *sql.Tx, dsID int, xmlID string) error {
 	return nil
 }
 
+func parseOrgServerFQDN(orgServerFQDN string) (*string, *string, *string, error) {
+	originRegex := regexp.MustCompile(`^(https?)://([^:]+)(:(\d+))?$`)
+	matches := originRegex.FindStringSubmatch(orgServerFQDN)
+	if len(matches) == 0 {
+		return nil, nil, nil, fmt.Errorf("unable to parse invalid orgServerFqdn: '%s'", orgServerFQDN)
+	}
+
+	protocol := strings.ToLower(matches[1])
+	FQDN := matches[2]
+
+	if len(protocol) == 0 || len(FQDN) == 0 {
+		return nil, nil, nil, fmt.Errorf("empty Origin protocol or FQDN parsed from '%s'", orgServerFQDN)
+	}
+
+	var port *string
+	if len(matches[4]) != 0 {
+		port = &matches[4]
+	}
+	return &protocol, &FQDN, port, nil
+}
+
+func createPrimaryOrigin(db *sql.DB, tx *sql.Tx, user *auth.CurrentUser, ds tc.DeliveryServiceNullableV13) error {
+	if ds.OrgServerFQDN == nil {
+		return nil
+	}
+
+	protocol, fqdn, port, err := parseOrgServerFQDN(*ds.OrgServerFQDN)
+	if err != nil {
+		return fmt.Errorf("creating primary origin: %v", err)
+	}
+
+	originID := 0
+	q := `INSERT INTO origin (name, fqdn, protocol, is_primary, port, deliveryservice, tenant) VALUES ($1, $2, $3, TRUE, $4, $5, $6) RETURNING id`
+	if err := tx.QueryRow(q, ds.XMLID, fqdn, protocol, port, ds.ID, ds.TenantID).Scan(&originID); err != nil {
+		return fmt.Errorf("insert origin from '%s': %s", *ds.OrgServerFQDN, err.Error())
+	}
+
+	api.CreateChangeLogRaw(api.ApiChange, "Created primary origin id: "+strconv.Itoa(originID)+" for delivery service: "+*ds.XMLID, *user, db)
+
+	return nil
+}
+
+func updatePrimaryOrigin(db *sql.DB, tx *sql.Tx, user *auth.CurrentUser, ds tc.DeliveryServiceNullableV13) error {
+	count := 0
+	q := `SELECT count(*) FROM origin WHERE deliveryservice = $1 AND is_primary`
+	if err := tx.QueryRow(q, *ds.ID).Scan(&count); err != nil {
+		return fmt.Errorf("querying existing primary origin for ds %s: %s", *ds.XMLID, err.Error())
+	}
+
+	if ds.OrgServerFQDN == nil || *ds.OrgServerFQDN == "" {
+		if count == 1 {
+			// the update is removing the existing orgServerFQDN, so the existing row needs to be deleted
+			q = `DELETE FROM origin WHERE deliveryservice = $1 AND is_primary`
+			if _, err := tx.Exec(q, *ds.ID); err != nil {
+				return fmt.Errorf("deleting primary origin for ds %s: %s", *ds.XMLID, err.Error())
+			}
+			api.CreateChangeLogRaw(api.ApiChange, "Deleted primary origin for delivery service: "+*ds.XMLID, *user, db)
+		}
+		return nil
+	}
+
+	if count == 0 {
+		// orgServerFQDN is going from null to not null, so the primary origin needs to be created
+		return createPrimaryOrigin(db, tx, user, ds)
+	}
+
+	protocol, fqdn, port, err := parseOrgServerFQDN(*ds.OrgServerFQDN)
+	if err != nil {
+		return fmt.Errorf("updating primary origin: %v", err)
+	}
+
+	name := ""
+	q = `UPDATE origin SET protocol = $1, fqdn = $2, port = $3 WHERE is_primary AND deliveryservice = $4 RETURNING name`
+	if err := tx.QueryRow(q, protocol, fqdn, port, *ds.ID).Scan(&name); err != nil {
+		return fmt.Errorf("update primary origin for ds %s from '%s': %s", *ds.XMLID, *ds.OrgServerFQDN, err.Error())
+	}
+
+	api.CreateChangeLogRaw(api.ApiChange, "Updated primary origin: "+name+" for delivery service: "+*ds.XMLID, *user, db)
+
+	return nil
+}
+
 func getOldHostName(id int, tx *sql.Tx) (string, error) {
 	q := `
 SELECT ds.xml_id, ds.protocol, type.name, ds.routing_name, cdn.domain_name
@@ -423,7 +511,7 @@ func update(db *sql.DB, cfg config.Config, user auth.CurrentUser, ds *tc.Deliver
 		deepCachingType = ds.DeepCachingType.String() // necessary, because DeepCachingType's default needs to insert the string, not "", and Query doesn't call .String().
 	}
 
-	resultRows, err := tx.Query(updateDSQuery(), &ds.Active, &ds.CacheURL, &ds.CCRDNSTTL, &ds.CDNID, &ds.CheckPath, &deepCachingType, &ds.DisplayName, &ds.DNSBypassCNAME, &ds.DNSBypassIP, &ds.DNSBypassIP6, &ds.DNSBypassTTL, &ds.DSCP, &ds.EdgeHeaderRewrite, &ds.GeoLimitRedirectURL, &ds.GeoLimit, &ds.GeoLimitCountries, &ds.GeoProvider, &ds.GlobalMaxMBPS, &ds.GlobalMaxTPS, &ds.FQPacingRate, &ds.HTTPBypassFQDN, &ds.InfoURL, &ds.InitialDispersion, &ds.IPV6RoutingEnabled, &ds.LogsEnabled, &ds.LongDesc, &ds.LongDesc1, &ds.LongDesc2, &ds.MaxDNSAnswers, &ds.MidHeaderRewrite, &ds.MissLat, &ds.MissLong, &ds.MultiSiteOrigin, &ds.OrgServerFQDN, &ds.OriginShield, &ds.ProfileID, &ds.Protocol, &ds.QStringIgnore, &ds.RangeRequestHandling, &ds.RegexRemap, &ds.RegionalGeoBlocking, &ds.RemapText, &ds.RoutingName, &ds.SigningAlgorithm, &ds.SSLKeyVersion, &ds.TenantID, &ds.TRRequestHeaders, &ds.TRResponseHeaders, &ds.TypeID, &ds.XMLID, &ds.ID)
+	resultRows, err := tx.Query(updateDSQuery(), &ds.Active, &ds.CacheURL, &ds.CCRDNSTTL, &ds.CDNID, &ds.CheckPath, &deepCachingType, &ds.DisplayName, &ds.DNSBypassCNAME, &ds.DNSBypassIP, &ds.DNSBypassIP6, &ds.DNSBypassTTL, &ds.DSCP, &ds.EdgeHeaderRewrite, &ds.GeoLimitRedirectURL, &ds.GeoLimit, &ds.GeoLimitCountries, &ds.GeoProvider, &ds.GlobalMaxMBPS, &ds.GlobalMaxTPS, &ds.FQPacingRate, &ds.HTTPBypassFQDN, &ds.InfoURL, &ds.InitialDispersion, &ds.IPV6RoutingEnabled, &ds.LogsEnabled, &ds.LongDesc, &ds.LongDesc1, &ds.LongDesc2, &ds.MaxDNSAnswers, &ds.MidHeaderRewrite, &ds.MissLat, &ds.MissLong, &ds.MultiSiteOrigin, &ds.OriginShield, &ds.ProfileID, &ds.Protocol, &ds.QStringIgnore, &ds.RangeRequestHandling, &ds.RegexRemap, &ds.RegionalGeoBlocking, &ds.RemapText, &ds.RoutingName, &ds.SigningAlgorithm, &ds.SSLKeyVersion, &ds.TenantID, &ds.TRRequestHeaders, &ds.TRResponseHeaders, &ds.TypeID, &ds.XMLID, &ds.ID)
 
 	if err != nil {
 		if err, ok := err.(*pq.Error); ok {
@@ -506,6 +594,11 @@ func update(db *sql.DB, cfg config.Config, user auth.CurrentUser, ds *tc.Deliver
 	if err := ensureCacheURLParams(tx, *ds.ID, *ds.XMLID, ds.CacheURL); err != nil {
 		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating mid cacheurl parameters: " + err.Error())
 	}
+
+	if err := updatePrimaryOrigin(db, tx, &user, *ds); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("updating delivery service: " + err.Error())
+	}
+
 	ds.LastUpdated = &lastUpdated
 	commitTx = true
 	api.CreateChangeLogRaw(api.ApiChange, "Updated ds: "+*ds.XMLID+" id: "+strconv.Itoa(*ds.ID), user, db)
@@ -1017,7 +1110,10 @@ ds.mid_header_rewrite,
 COALESCE(ds.miss_lat, 0.0),
 COALESCE(ds.miss_long, 0.0),
 ds.multi_site_origin,
-ds.org_server_fqdn,
+(SELECT o.protocol::::text || ':://' || o.fqdn || rtrim(concat('::', o.port::::text), '::')
+	FROM origin o
+	WHERE o.deliveryservice = ds.id
+	AND o.is_primary) as org_server_fqdn,
 ds.origin_shield,
 ds.profile as profileID,
 profile.name as profile_name,
@@ -1084,24 +1180,23 @@ mid_header_rewrite=$30,
 miss_lat=$31,
 miss_long=$32,
 multi_site_origin=$33,
-org_server_fqdn=$34,
-origin_shield=$35,
-profile=$36,
-protocol=$37,
-qstring_ignore=$38,
-range_request_handling=$39,
-regex_remap=$40,
-regional_geo_blocking=$41,
-remap_text=$42,
-routing_name=$43,
-signing_algorithm=$44,
-ssl_key_version=$45,
-tenant_id=$46,
-tr_request_headers=$47,
-tr_response_headers=$48,
-type=$49,
-xml_id=$50
-WHERE id=$51
+origin_shield=$34,
+profile=$35,
+protocol=$36,
+qstring_ignore=$37,
+range_request_handling=$38,
+regex_remap=$39,
+regional_geo_blocking=$40,
+remap_text=$41,
+routing_name=$42,
+signing_algorithm=$43,
+ssl_key_version=$44,
+tenant_id=$45,
+tr_request_headers=$46,
+tr_response_headers=$47,
+type=$48,
+xml_id=$49
+WHERE id=$50
 RETURNING last_updated
 `
 }
@@ -1142,7 +1237,6 @@ mid_header_rewrite,
 miss_lat,
 miss_long,
 multi_site_origin,
-org_server_fqdn,
 origin_shield,
 profile,
 protocol,
@@ -1160,7 +1254,7 @@ tr_response_headers,
 type,
 xml_id
 )
-VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50)
+VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49)
 RETURNING id, last_updated
 `
 }
diff --git a/traffic_ops/traffic_ops_golang/origin/origins.go b/traffic_ops/traffic_ops_golang/origin/origins.go
index f5140439e..8db754153 100644
--- a/traffic_ops/traffic_ops_golang/origin/origins.go
+++ b/traffic_ops/traffic_ops_golang/origin/origins.go
@@ -98,7 +98,6 @@ func (origin *TOOrigin) Validate(db *sqlx.DB) []error {
 		"fqdn":              validation.Validate(origin.FQDN, validation.Required, is.DNSName),
 		"ip6Address":        validation.Validate(origin.IP6Address, validation.NilOrNotEmpty, is.IPv6),
 		"ipAddress":         validation.Validate(origin.IPAddress, validation.NilOrNotEmpty, is.IPv4),
-		"isPrimary":         validation.Validate(origin.Name, validation.NotNil),
 		"name":              validation.Validate(origin.Name, validation.Required, noSpaces),
 		"port":              validation.Validate(origin.Port, validation.NilOrNotEmpty.Error(portErr), validation.Min(1).Error(portErr), validation.Max(65535).Error(portErr)),
 		"profileId":         validation.Validate(origin.ProfileID, validation.Min(1)),
@@ -202,6 +201,7 @@ func getOrigins(params map[string]string, db *sqlx.DB, privLevel int) ([]v13.Ori
 		"deliveryservice": dbhelpers.WhereColumnInfo{"o.deliveryservice", api.IsInt},
 		"id":              dbhelpers.WhereColumnInfo{"o.id", api.IsInt},
 		"name":            dbhelpers.WhereColumnInfo{"o.name", nil},
+		"primary":         dbhelpers.WhereColumnInfo{"o.is_primary", api.IsBool},
 		"profileId":       dbhelpers.WhereColumnInfo{"o.profile", api.IsInt},
 		"tenant":          dbhelpers.WhereColumnInfo{"o.tenant", api.IsInt},
 	}
@@ -331,6 +331,17 @@ func (origin *TOOrigin) Update(db *sqlx.DB, user auth.CurrentUser) (error, tc.Ap
 		return tc.DBError, tc.SystemError
 	}
 
+	isPrimary := false
+	ds := 0
+	q := `SELECT is_primary, deliveryservice FROM origin WHERE id = $1`
+	if err := tx.QueryRow(q, *origin.ID).Scan(&isPrimary, &ds); err != nil {
+		log.Errorf("updating origin %d, received error in select: %v", *origin.ID, err)
+		return tc.DBError, tc.SystemError
+	}
+	if isPrimary && *origin.DeliveryServiceID != ds {
+		return errors.New("cannot update the delivery service of a primary origin"), tc.DataConflictError
+	}
+
 	log.Debugf("about to run exec query: %s with origin: %++v", updateQuery(), origin)
 	resultRows, err := tx.NamedQuery(updateQuery(), origin)
 	if err != nil {
@@ -385,7 +396,6 @@ deliveryservice=:deliveryservice_id,
 fqdn=:fqdn,
 ip6_address=:ip6_address,
 ip_address=:ip_address,
-is_primary=:is_primary,
 name=:name,
 port=:port,
 profile=:profile_id,
@@ -479,7 +489,6 @@ deliveryservice,
 fqdn,
 ip6_address,
 ip_address,
-is_primary,
 name,
 port,
 profile,
@@ -491,7 +500,6 @@ tenant) VALUES (
 :fqdn,
 :ip6_address,
 :ip_address,
-:is_primary,
 :name,
 :port,
 :profile_id,
@@ -519,6 +527,17 @@ func (origin *TOOrigin) Delete(db *sqlx.DB, user auth.CurrentUser) (error, tc.Ap
 		log.Error.Printf("could not begin transaction: %v", err)
 		return tc.DBError, tc.SystemError
 	}
+
+	isPrimary := false
+	q := `SELECT is_primary FROM origin WHERE id = $1`
+	if err := tx.QueryRow(q, *origin.ID).Scan(&isPrimary); err != nil {
+		log.Errorf("deleting origin %d, received error selecting is_primary: %v", *origin.ID, err)
+		return tc.DBError, tc.SystemError
+	}
+	if isPrimary {
+		return errors.New("cannot delete a primary origin"), tc.DataConflictError
+	}
+
 	log.Debugf("about to run exec query: %s with origin: %++v", deleteQuery(), origin)
 	result, err := tx.NamedExec(deleteQuery(), origin)
 	if err != nil {
diff --git a/traffic_ops/traffic_ops_golang/origin/origins_test.go b/traffic_ops/traffic_ops_golang/origin/origins_test.go
index c7d522a61..a44c1e609 100644
--- a/traffic_ops/traffic_ops_golang/origin/origins_test.go
+++ b/traffic_ops/traffic_ops_golang/origin/origins_test.go
@@ -179,7 +179,6 @@ func TestValidate(t *testing.T) {
 		Name:              nil,
 		DeliveryServiceID: nil,
 		FQDN:              nil,
-		IsPrimary:         nil,
 		Protocol:          nil,
 	}
 	errs := test.SortErrors(c.Validate(nil))
@@ -187,7 +186,6 @@ func TestValidate(t *testing.T) {
 	expectedErrs := []error{
 		errors.New(`'deliveryServiceId' is required`),
 		errors.New(`'fqdn' cannot be blank`),
-		errors.New(`'isPrimary' is required`),
 		errors.New(`'name' cannot be blank`),
 		errors.New(`'protocol' cannot be blank`),
 	}
@@ -202,7 +200,6 @@ func TestValidate(t *testing.T) {
 	fqdn := "is.a.valid.hostname"
 	ip6 := "dead:beef::42"
 	ip := "1.2.3.4"
-	primary := false
 	port := 65535
 	pro := "http"
 	lu := tc.TimeNoMod{Time: time.Now()}
@@ -212,7 +209,6 @@ func TestValidate(t *testing.T) {
 		FQDN:              &fqdn,
 		IP6Address:        &ip6,
 		IPAddress:         &ip,
-		IsPrimary:         &primary,
 		Port:              &port,
 		Protocol:          &pro,
 		LastUpdated:       &lu,


 

----------------------------------------------------------------
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