Interfaces first migration (#4635)

* Added (hopefully) working goose up section

* finished 'down' section, added 'ON UPDATE/DELETE' clauses

* Fixed check to handle IPv6, trim existing subnets from gateways

* Fixed typo

* Added type aliases for ease of array selection
diff --git a/traffic_ops/app/db/migrations/20200414000000_interfaces.sql b/traffic_ops/app/db/migrations/20200414000000_interfaces.sql
new file mode 100644
index 0000000..85e9c39e
--- /dev/null
+++ b/traffic_ops/app/db/migrations/20200414000000_interfaces.sql
@@ -0,0 +1,162 @@
+/*
+	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
+CREATE TABLE interface (
+	max_bandwidth bigint DEFAULT NULL CHECK (max_bandwidth IS NULL OR max_bandwidth >= 0),
+	monitor boolean NOT NULL,
+	mtu bigint DEFAULT 1500 CHECK (mtu IS NULL OR mtu > 1280),
+	name text NOT NULL CHECK (name != ''),
+	server bigint NOT NULL,
+	PRIMARY KEY (name, server),
+	FOREIGN KEY (server) REFERENCES server(id) ON DELETE RESTRICT ON UPDATE CASCADE
+);
+CREATE TABLE ip_address (
+	address inet NOT NULL,
+	gateway inet CHECK (
+		gateway IS NULL OR (
+			family(gateway) = 4 AND
+			masklen(gateway) = 32
+		) OR (
+			family(gateway) = 6 AND
+			masklen(gateway) = 128
+		)
+	),
+	interface text NOT NULL,
+	server bigint NOT NULL,
+	service_address boolean NOT NULL DEFAULT FALSE,
+	PRIMARY KEY (address, interface, server),
+	FOREIGN KEY (server) REFERENCES server(id) ON DELETE RESTRICT ON UPDATE CASCADE,
+	FOREIGN KEY (interface, server) REFERENCES interface(name, server) ON DELETE RESTRICT ON UPDATE CASCADE
+);
+
+CREATE TYPE server_ip_address AS (address inet, gateway inet, service_address boolean);
+CREATE TYPE server_interface AS (ip_addresses server_ip_address ARRAY, max_bandwidth bigint, monitor boolean, mtu bigint, name text);
+
+INSERT INTO interface(
+	max_bandwidth,
+	monitor,
+	mtu,
+	name,
+	server
+)
+SELECT NULL, FALSE, NULL, 'mgmt', id
+FROM server
+WHERE server.mgmt_ip_address IS NOT NULL
+AND server.mgmt_ip_address != '';
+
+INSERT INTO interface(
+	max_bandwidth,
+	monitor,
+	mtu,
+	name,
+	server
+)
+SELECT NULL, TRUE, server.interface_mtu::bigint, server.interface_name, id
+FROM server;
+
+INSERT INTO ip_address(
+	address,
+	gateway,
+	interface,
+	server,
+	service_address
+)
+SELECT
+	set_masklen(
+		server.mgmt_ip_address::inet,
+		CASE
+			WHEN server.mgmt_ip_netmask IS NULL THEN 32
+			WHEN server.mgmt_ip_netmask = '' THEN 32
+			ELSE
+				(SELECT SUM(
+					get_bit(digit, 0) +
+					get_bit(digit, 1) +
+					get_bit(digit, 2) +
+					get_bit(digit, 3) +
+					get_bit(digit, 4) +
+					get_bit(digit, 5) +
+					get_bit(digit, 6) +
+					get_bit(digit, 7)
+				)::int FROM (
+					SELECT regexp_split_to_table::int::bit(8) AS digit
+					FROM regexp_split_to_table(server.mgmt_ip_netmask, '\.')
+				) AS digits)
+		END
+	),
+	NULLIF(regexp_replace(server.mgmt_ip_gateway, '/\d+$', ''), '')::inet,
+	'mgmt',
+	server.id,
+	FALSE
+FROM server
+WHERE server.mgmt_ip_address IS NOT NULL
+AND server.mgmt_ip_address != '';
+
+INSERT INTO ip_address(
+	address,
+	gateway,
+	interface,
+	server,
+	service_address
+)
+SELECT
+	set_masklen(
+		server.ip_address::inet,
+		CASE
+			WHEN server.ip_netmask IS NULL THEN 32
+			WHEN server.ip_netmask = '' THEN 32
+			ELSE
+				(SELECT SUM(
+					get_bit(digit, 0) +
+					get_bit(digit, 1) +
+					get_bit(digit, 2) +
+					get_bit(digit, 3) +
+					get_bit(digit, 4) +
+					get_bit(digit, 5) +
+					get_bit(digit, 6) +
+					get_bit(digit, 7)
+				)::int FROM (
+					SELECT regexp_split_to_table::int::bit(8) AS digit
+					FROM regexp_split_to_table(server.ip_netmask, '\.')
+				) AS digits)
+		END
+	),
+	NULLIF(regexp_replace(server.ip_gateway, '/\d+$', ''), '')::inet,
+	server.interface_name,
+	server.id,
+	server.ip_address_is_service
+FROM server
+WHERE server.ip_address IS NOT NULL
+AND server.ip_address != '';
+
+INSERT INTO ip_address(
+	address,
+	gateway,
+	interface,
+	server,
+	service_address
+)
+SELECT
+	trim(BOTH '[]' FROM server.ip6_address)::inet,
+	NULLIF(regexp_replace(server.ip6_gateway, '/\d+$', ''), '')::inet,
+	server.interface_name,
+	server.id,
+	server.ip6_address_is_service
+FROM server
+WHERE server.ip6_address IS NOT NULL
+AND server.ip6_address != '';
+
+-- +goose Down
+DROP TABLE IF EXISTS ip_address;
+DROP TABLE IF EXISTS interface;