You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ma...@apache.org on 2021/01/13 20:18:46 UTC

[trafficcontrol] branch master updated: Adding check to make sure that you cannot add two servers with identical content (#5415)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 468065c  Adding check to make sure that you cannot add two servers with identical content (#5415)
468065c is described below

commit 468065cdb3f2060d2216a9474dc82362cc1631c5
Author: Srijeet Chatterjee <30...@users.noreply.github.com>
AuthorDate: Wed Jan 13 13:18:28 2021 -0700

    Adding check to make sure that you cannot add two servers with identical content (#5415)
    
    * Adding check to make sure that you cannot add two servers with identical content
    
    * wip
    
    * move the modifications from the code to migrations
    
    * changelog entry
    
    * Code review fixes
    
    * code review fixes
---
 CHANGELOG.md                                       |   3 +-
 ...0900000000_server_ip_profile_trigger_update.sql | 124 +++++++++++++++++++++
 traffic_ops/testing/api/v1/servers_test.go         |  57 ++++++++++
 .../testing/api/v3/deliveryserviceservers_test.go  |  19 ++++
 traffic_ops/traffic_ops_golang/api/api.go          |  14 +++
 traffic_ops/traffic_ops_golang/server/servers.go   |   5 +-
 6 files changed, 218 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0455670..9df5ee8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,7 +31,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
     on page refresh.
 - [#5295](https://github.com/apache/trafficcontrol/issues/5295) - TP types/servers table now clears all filters instead
     of just column filters
-- #2881 Some API endpoints have incorrect Content-Types
+- [#5407](https://github.com/apache/trafficcontrol/issues/5407) - Make sure that you cannot add two servers with identical content
+- [#2881](https://github.com/apache/trafficcontrol/issues/2881) - Some API endpoints have incorrect Content-Types
 - [#5311](https://github.com/apache/trafficcontrol/issues/5311) - Better TO log messages when failures calling TM CacheStats
 - [#5364](https://github.com/apache/trafficcontrol/issues/5364) - Cascade server deletes to delete corresponding IP addresses and interfaces
 - [#5390](https://github.com/apache/trafficcontrol/issues/5390) - Improve the way TO deals with delivery service server assignments
diff --git a/traffic_ops/app/db/migrations/2021010900000000_server_ip_profile_trigger_update.sql b/traffic_ops/app/db/migrations/2021010900000000_server_ip_profile_trigger_update.sql
new file mode 100644
index 0000000..35cc584
--- /dev/null
+++ b/traffic_ops/app/db/migrations/2021010900000000_server_ip_profile_trigger_update.sql
@@ -0,0 +1,124 @@
+/*
+	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
+DROP TRIGGER IF EXISTS before_update_ip_address_trigger ON ip_address;
+DROP TRIGGER IF EXISTS before_create_ip_address_trigger ON ip_address;
+-- +goose StatementBegin
+CREATE OR REPLACE FUNCTION before_ip_address_table()
+    RETURNS TRIGGER
+AS
+$$
+DECLARE
+    server_count   BIGINT;
+    server_id      BIGINT;
+    server_profile BIGINT;
+BEGIN
+    WITH server_ips AS (
+        SELECT s.id as sid, ip.interface, i.name, ip.address, s.profile, ip.server
+        FROM server s
+                 JOIN interface i
+                      on i.server = s.ID
+                 JOIN ip_address ip
+                      on ip.Server = s.ID and ip.interface = i.name
+        WHERE ip.service_address = true
+    )
+    SELECT count(distinct(sip.sid)), sip.sid, sip.profile
+    INTO server_count, server_id, server_profile
+    FROM server_ips sip
+    WHERE (sip.server <> NEW.server AND (SELECT host(sip.address)) = (SELECT host(NEW.address)) AND sip.profile = (SELECT profile from server s WHERE s.id = NEW.server))
+    GROUP BY sip.sid, sip.profile;
+
+    IF server_count > 0 THEN
+        RAISE EXCEPTION 'ip_address is not unique across the server [id:%] profile [id:%], [%] conflicts',
+            server_id,
+            server_profile,
+            server_count;
+    END IF;
+    RETURN NEW;
+END;
+$$ LANGUAGE PLPGSQL;
+-- +goose StatementEnd
+
+CREATE TRIGGER before_create_ip_address_trigger
+    BEFORE INSERT
+    ON ip_address
+    FOR EACH ROW
+EXECUTE PROCEDURE before_ip_address_table();
+
+CREATE TRIGGER before_update_ip_address_trigger
+    BEFORE UPDATE
+    ON ip_address
+    FOR EACH ROW
+    WHEN (NEW.address <> OLD.address)
+EXECUTE PROCEDURE before_ip_address_table();
+
+-- +goose Down
+-- SQL section 'Down' is executed when this migration is rolled back
+DROP TRIGGER IF EXISTS before_update_ip_address_trigger ON ip_address;
+DROP TRIGGER IF EXISTS before_create_ip_address_trigger ON ip_address;
+
+DROP FUNCTION IF EXISTS before_ip_address_table();
+
+-- +goose StatementBegin
+CREATE OR REPLACE FUNCTION before_ip_address_table()
+    RETURNS TRIGGER
+AS
+$$
+DECLARE
+    server_count   BIGINT;
+    server_id      BIGINT;
+    server_profile BIGINT;
+BEGIN
+    WITH server_ips AS (
+        SELECT s.id as sid, ip.interface, i.name, ip.address, s.profile, ip.server
+        FROM server s
+                 JOIN interface i
+                     on i.server = s.ID
+                 JOIN ip_address ip
+                     on ip.Server = s.ID and ip.interface = i.name
+        WHERE i.monitor = true
+    )
+    SELECT count(sip.sid), sip.sid, sip.profile
+    INTO server_count, server_id, server_profile
+    FROM server_ips sip
+             JOIN server_ips sip2 on sip.sid <> sip2.sid
+    WHERE (sip.server = NEW.server AND sip.address = NEW.address AND sip.interface = NEW.interface)
+      AND sip2.address = sip.address
+      AND sip2.profile = sip.profile
+    GROUP BY sip.sid, sip.profile;
+
+    IF server_count > 0 THEN
+        RAISE EXCEPTION 'ip_address is not unique across the server [id:%] profile [id:%], [%] conflicts',
+            server_id,
+            server_profile,
+            server_count;
+    END IF;
+    RETURN NEW;
+END;
+$$ LANGUAGE PLPGSQL;
+-- +goose StatementEnd
+
+CREATE TRIGGER before_create_ip_address_trigger
+    BEFORE INSERT
+    ON ip_address
+    FOR EACH ROW
+EXECUTE PROCEDURE before_ip_address_table();
+
+CREATE TRIGGER before_update_ip_address_trigger
+    BEFORE UPDATE
+    ON ip_address
+    FOR EACH ROW
+    WHEN (NEW.address <> OLD.address)
+EXECUTE PROCEDURE before_ip_address_table();
diff --git a/traffic_ops/testing/api/v1/servers_test.go b/traffic_ops/testing/api/v1/servers_test.go
index 584aad4..148eceb 100644
--- a/traffic_ops/testing/api/v1/servers_test.go
+++ b/traffic_ops/testing/api/v1/servers_test.go
@@ -23,6 +23,7 @@ import (
 
 func TestServers(t *testing.T) {
 	WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, DeliveryServices, Servers}, func() {
+		CreateTestServerWithSameServiceIPAddressAndProfile(t)
 		UpdateTestServers(t)
 		GetTestServers(t)
 	})
@@ -122,6 +123,62 @@ func UpdateTestServers(t *testing.T) {
 
 }
 
+func CreateTestServerWithSameServiceIPAddressAndProfile(t *testing.T) {
+	if len(testData.Servers) == 0 {
+		t.Fatal("no test data servers, quitting")
+	}
+	firstServer := testData.Servers[0]
+	hostName := firstServer.HostName
+	resp, _, err := TOSession.GetServerByHostName(hostName)
+	if err != nil {
+		t.Errorf("cannot GET Server by hostname: %v - %v", firstServer.HostName, err)
+	}
+	if len(resp) == 0 {
+		t.Fatal("no response servers, quitting")
+	}
+	newServer := tc.ServerV1{
+		TCPPort:        resp[0].TCPPort,
+		HostName:       "testServerCreate",
+		DomainName:     resp[0].DomainName,
+		HTTPSPort:      resp[0].HTTPSPort,
+		InterfaceName:  "test-interface",
+		IPAddress:      "100.100.100.100",
+		ProfileID:      resp[0].ProfileID,
+		CDNID:          resp[0].CDNID,
+		TypeID:         resp[0].TypeID,
+		StatusID:       resp[0].StatusID,
+		PhysLocationID: resp[0].PhysLocationID,
+		CachegroupID:   resp[0].CachegroupID,
+		InterfaceMtu:   resp[0].InterfaceMtu,
+	}
+	_, _, err = TOSession.CreateServer(newServer)
+	if err != nil {
+		t.Fatalf("error while CREATEing a new server: %v", err.Error())
+	}
+	s, _, err := TOSession.GetServerByHostName("testServerCreate")
+	if err != nil {
+		t.Fatalf("error GETting the server with hostname %s, that was just created: %v", newServer.HostName, err.Error())
+	}
+	if len(s) == 0 {
+		t.Fatalf("no servers returned")
+	}
+	// Try creating a server with the same service IP address
+	_, _, err = TOSession.CreateServer(newServer)
+	if err == nil {
+		t.Error("expected error about an existing server with the same profile and IP address, but got nothing")
+	}
+	// Now try creating a server with the same service IP address but different netmask
+	newServer.IPNetmask = "255.255.100.100"
+	_, _, err = TOSession.CreateServer(newServer)
+	if err == nil {
+		t.Error("expected error about an existing server with the same profile and IP address, but got nothing")
+	}
+	_, _, err = TOSession.DeleteServerByID(s[0].ID)
+	if err != nil {
+		t.Errorf("error DELETEing the server just created: %v", err.Error())
+	}
+}
+
 func DeleteTestServers(t *testing.T) {
 
 	for _, server := range testData.Servers {
diff --git a/traffic_ops/testing/api/v3/deliveryserviceservers_test.go b/traffic_ops/testing/api/v3/deliveryserviceservers_test.go
index ca81378..47170f4 100644
--- a/traffic_ops/testing/api/v3/deliveryserviceservers_test.go
+++ b/traffic_ops/testing/api/v3/deliveryserviceservers_test.go
@@ -157,6 +157,25 @@ func TryToRemoveLastServerInDeliveryService(t *testing.T) {
 
 	server.HostName = util.StrPtr(dssaTestingXMLID + "-quest")
 	server.ID = nil
+	if len(server.Interfaces) == 0 {
+		t.Fatal("no interfaces in this server, quitting")
+	}
+	interfaces := make([]tc.ServerInterfaceInfo, 0)
+	ipAddresses := make([]tc.ServerIPAddress, 0)
+	gateway := "1.2.3.4"
+	ipAddresses = append(ipAddresses, tc.ServerIPAddress{
+		Address:        "1.1.1.1",
+		Gateway:        &gateway,
+		ServiceAddress: true,
+	})
+	interfaces = append(interfaces, tc.ServerInterfaceInfo{
+		IPAddresses:  ipAddresses,
+		MaxBandwidth: server.Interfaces[0].MaxBandwidth,
+		Monitor:      false,
+		MTU:          server.Interfaces[0].MTU,
+		Name:         server.Interfaces[0].Name,
+	})
+	server.Interfaces = interfaces
 	alerts, _, err = TOSession.CreateServerWithHdr(server, nil)
 	if err != nil {
 		t.Fatalf("Failed to create server: %v - alerts: %s", err, strings.Join(alerts.ToStrings(), ", "))
diff --git a/traffic_ops/traffic_ops_golang/api/api.go b/traffic_ops/traffic_ops_golang/api/api.go
index 737b878..0d06e97 100644
--- a/traffic_ops/traffic_ops_golang/api/api.go
+++ b/traffic_ops/traffic_ops_golang/api/api.go
@@ -745,6 +745,16 @@ func toCamelCase(str string) string {
 	return strings.Replace(string(mutable[:]), "_", "", -1)
 }
 
+// parses pq errors for any trigger based conflicts
+func parseTriggerConflicts(err *pq.Error) (error, error, int) {
+	pattern := regexp.MustCompile(`^(.*?)conflicts`)
+	match := pattern.FindStringSubmatch(err.Message)
+	if match == nil {
+		return nil, nil, http.StatusOK
+	}
+	return fmt.Errorf("%s", toCamelCase(match[0])), nil, http.StatusBadRequest
+}
+
 // parses pq errors for not null constraint
 func parseNotNullConstraint(err *pq.Error) (error, error, int) {
 	pattern := regexp.MustCompile(`null value in column "(.+)" violates not-null constraint`)
@@ -861,6 +871,10 @@ func ParseDBError(ierr error) (error, error, int) {
 		return usrErr, sysErr, errCode
 	}
 
+	if usrErr, sysErr, errCode := parseTriggerConflicts(err); errCode != http.StatusOK {
+		return usrErr, sysErr, errCode
+	}
+
 	return nil, err, http.StatusInternalServerError
 }
 
diff --git a/traffic_ops/traffic_ops_golang/server/servers.go b/traffic_ops/traffic_ops_golang/server/servers.go
index ecfd8c4..2ab8f2c 100644
--- a/traffic_ops/traffic_ops_golang/server/servers.go
+++ b/traffic_ops/traffic_ops_golang/server/servers.go
@@ -605,7 +605,7 @@ SELECT s.ID, ip.address FROM server s
 JOIN profile p on p.Id = s.Profile
 JOIN interface i on i.server = s.ID
 JOIN ip_address ip on ip.Server = s.ID and ip.interface = i.name
-WHERE i.monitor = true
+WHERE ip.service_address = true
 and p.id = $1
 `
 	var rows *sql.Rows
@@ -1175,7 +1175,6 @@ func createInterfaces(id int, interfaces []tc.ServerInterfaceInfo, tx *sql.Tx) (
 	if err != nil {
 		return api.ParseDBError(err)
 	}
-
 	return nil, nil, http.StatusOK
 }
 
@@ -1509,7 +1508,7 @@ func createV1(inf *api.APIInfo, w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if userErr, sysErr, errCode := createInterfaces(*server.ID, ifaces, tx); err != nil {
+	if userErr, sysErr, errCode := createInterfaces(*server.ID, ifaces, tx); userErr != nil || sysErr != nil || errCode != http.StatusOK {
 		api.HandleErr(w, r, tx, errCode, userErr, sysErr)
 		return
 	}