SQL for queries using value lists (e.g. `column IN (value_list)`) varies by the number of elements in the value list, because a separate placeholder is generally used for each element in the value list. You can get consistent SQL by using the equivalent `column = ANY(array_expr::type[])`, which will only use a single placeholder for the array expression. This is useful if you want to view/audit the queries that the application is generating. Sequel has had a pg_auto_parameterize_in_array Database extension for a while that handles most of this. However, in the case where the value list is Ruby strings, it is ambiguous what the type of the array should be. Postgres use the unknown type, not the text type, when there is not an explicit/implicit cast for a single quoted string. The pg_auto_parameterize_in_array extension can assume the text[] type in such cases, but it will break queries that need a different cast. However, such breakage is explicit (DatabaseError raised), and not silent, and can be fixed. This affects queries where the column values are plain strings in Ruby, but use a non-text database type, such as the uuid type or an enum type. This adds a couple singleton methods on Sequel, any_uuid and any_type, which allow conversion of arrays (normally treated as value lists) to an `ANY(array_expr::type[])` expression. This converts the cases that would fail with an explicit cast to text[] to use a uuid[] or lb_node_state[] cast instead. This handles most of the explicit use of `IN (value_list)`. There is a significant amount of implicit `IN (value_list)`, because that is what is used by default for eager loading. This uses a new pg_eager_any_typed_array plugin I developed to handle this case. This will automatically use `column = ANY(array_expr::type[])`, using the appropriate database type of the predicate key using for eager loading.
129 lines
5.8 KiB
Ruby
129 lines
5.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "../model"
|
|
|
|
class LoadBalancer < Sequel::Model
|
|
many_to_one :project
|
|
many_to_many :vms
|
|
many_to_many :active_vms, class: :Vm, left_key: :load_balancer_id, right_key: :vm_id, join_table: :load_balancers_vms, conditions: {state: "up"}
|
|
many_to_many :vms_to_dns, class: :Vm, left_key: :load_balancer_id, right_key: :vm_id, join_table: :load_balancers_vms, conditions: Sequel.~(state: Sequel.any_type(["evacuating", "detaching"], :lb_node_state))
|
|
one_to_one :strand, key: :id
|
|
many_to_one :private_subnet
|
|
one_to_many :load_balancers_vms, key: :load_balancer_id, class: :LoadBalancersVms
|
|
many_to_many :certs, join_table: :certs_load_balancers, left_key: :load_balancer_id, right_key: :cert_id
|
|
one_to_many :certs_load_balancers, key: :load_balancer_id, class: :CertsLoadBalancers
|
|
many_to_one :custom_hostname_dns_zone, class: :DnsZone, key: :custom_hostname_dns_zone_id
|
|
|
|
plugin :association_dependencies, load_balancers_vms: :destroy, certs_load_balancers: :destroy
|
|
|
|
include ResourceMethods
|
|
include SemaphoreMethods
|
|
include ObjectTag::Cleanup
|
|
dataset_module Pagination
|
|
semaphore :destroy, :update_load_balancer, :rewrite_dns_records, :refresh_cert
|
|
|
|
def path
|
|
"/location/#{private_subnet.display_location}/load-balancer/#{name}"
|
|
end
|
|
|
|
def add_vm(vm)
|
|
DB.transaction do
|
|
LoadBalancersVms.create_with_id(load_balancer_id: id, vm_id: vm.id)
|
|
|
|
Strand.create_with_id(prog: "Vnet::CertServer", label: "put_certificate", stack: [{subject_id: id, vm_id: vm.id}], parent_id: id)
|
|
incr_rewrite_dns_records
|
|
end
|
|
end
|
|
|
|
def detach_vm(vm)
|
|
load_balancers_vms_dataset.where(vm_id: vm.id, state: Sequel.any_type(["up", "down", "evacuating"], :lb_node_state)).update(state: "detaching")
|
|
Strand.create_with_id(prog: "Vnet::CertServer", label: "remove_cert_server", stack: [{subject_id: id, vm_id: vm.id}], parent_id: id)
|
|
incr_update_load_balancer
|
|
end
|
|
|
|
def evacuate_vm(vm)
|
|
DB.transaction do
|
|
load_balancers_vms_dataset.where(vm_id: vm.id, state: Sequel.any_type(["up", "down"], :lb_node_state)).update(state: "evacuating")
|
|
Strand.create_with_id(prog: "Vnet::CertServer", label: "remove_cert_server", stack: [{subject_id: id, vm_id: vm.id}], parent_id: id)
|
|
incr_update_load_balancer
|
|
incr_rewrite_dns_records
|
|
end
|
|
end
|
|
|
|
def remove_vm(vm)
|
|
load_balancers_vms_dataset[vm_id: vm.id].destroy
|
|
incr_rewrite_dns_records
|
|
end
|
|
|
|
def hostname
|
|
custom_hostname || "#{name}.#{private_subnet.ubid[-5...]}.#{Config.load_balancer_service_hostname}"
|
|
end
|
|
|
|
def dns_zone
|
|
custom_hostname_dns_zone || DnsZone[project_id: Config.load_balancer_service_project_id, name: Config.load_balancer_service_hostname]
|
|
end
|
|
|
|
def need_certificates?
|
|
return true if certs_dataset.empty?
|
|
|
|
certs_dataset.where { created_at > Time.now - 60 * 60 * 24 * 30 * 2 }.empty?
|
|
end
|
|
|
|
def active_cert
|
|
certs_dataset.where { created_at > Time.now - 60 * 60 * 24 * 30 * 3 }.order(Sequel.desc(:created_at)).first
|
|
end
|
|
|
|
def ipv4_enabled?
|
|
stack == Stack::IPV4 || stack == Stack::DUAL
|
|
end
|
|
|
|
def ipv6_enabled?
|
|
stack == Stack::IPV6 || stack == Stack::DUAL
|
|
end
|
|
|
|
module Stack
|
|
IPV4 = "ipv4"
|
|
IPV6 = "ipv6"
|
|
DUAL = "dual"
|
|
end
|
|
end
|
|
|
|
# Table: load_balancer
|
|
# Columns:
|
|
# id | uuid | PRIMARY KEY
|
|
# name | text | NOT NULL
|
|
# algorithm | lb_algorithm | NOT NULL DEFAULT 'round_robin'::lb_algorithm
|
|
# src_port | integer | NOT NULL
|
|
# dst_port | integer | NOT NULL
|
|
# private_subnet_id | uuid | NOT NULL
|
|
# health_check_endpoint | text | NOT NULL
|
|
# health_check_interval | integer | NOT NULL DEFAULT 10
|
|
# health_check_timeout | integer | NOT NULL DEFAULT 5
|
|
# health_check_up_threshold | integer | NOT NULL DEFAULT 5
|
|
# health_check_down_threshold | integer | NOT NULL DEFAULT 3
|
|
# health_check_protocol | lb_hc_protocol | NOT NULL DEFAULT 'http'::lb_hc_protocol
|
|
# custom_hostname | text |
|
|
# custom_hostname_dns_zone_id | uuid |
|
|
# stack | lb_stack | NOT NULL DEFAULT 'dual'::lb_stack
|
|
# project_id | uuid | NOT NULL
|
|
# Indexes:
|
|
# load_balancer_pkey | PRIMARY KEY btree (id)
|
|
# load_balancer_custom_hostname_key | UNIQUE btree (custom_hostname)
|
|
# load_balancer_private_subnet_id_name_uidx | UNIQUE btree (private_subnet_id, name)
|
|
# Check constraints:
|
|
# health_check_down_threshold_gt_0 | (health_check_down_threshold > 0)
|
|
# health_check_interval_gt_0 | (health_check_interval > 0)
|
|
# health_check_interval_lt_600 | (health_check_interval < 600)
|
|
# health_check_timeout_gt_0 | (health_check_timeout > 0)
|
|
# health_check_timeout_lt_health_check_interval | (health_check_timeout <= health_check_interval)
|
|
# health_check_up_threshold_gt_0 | (health_check_up_threshold > 0)
|
|
# Foreign key constraints:
|
|
# load_balancer_custom_hostname_dns_zone_id_fkey | (custom_hostname_dns_zone_id) REFERENCES dns_zone(id)
|
|
# load_balancer_private_subnet_id_fkey | (private_subnet_id) REFERENCES private_subnet(id)
|
|
# load_balancer_project_id_fkey | (project_id) REFERENCES project(id)
|
|
# Referenced By:
|
|
# certs_load_balancers | certs_load_balancers_load_balancer_id_fkey | (load_balancer_id) REFERENCES load_balancer(id)
|
|
# inference_endpoint | inference_endpoint_load_balancer_id_fkey | (load_balancer_id) REFERENCES load_balancer(id)
|
|
# kubernetes_cluster | kubernetes_cluster_api_server_lb_id_fkey | (api_server_lb_id) REFERENCES load_balancer(id)
|
|
# load_balancers_vms | load_balancers_vms_load_balancer_id_fkey | (load_balancer_id) REFERENCES load_balancer(id)
|