Files
ubicloud/lib/option.rb
Maciek Sarnowicz 335c1746fa Creation of Standard VMs in slices
Using the VmHostSlice introduced earlier to allocate a Standard VM. The main premise here is that VMs-in-slices are hosted on separate hosts from standalone VMs (the current code). This logic is guarded by two flags:
* VmHost.accept_slices - indicates host instances that can accept VMs allocated in slices. Hosts with this flag set to `true` will not be used for standalone VMs
* Project feature flag - `use_slices_for_allocation` - only projects with this flag enabled will use the new logic of creating VMs in slices.

With those two flags we can control the rollout of this feature. Note that if project is marked with `use_slices_for_allocation` but there are not VmHosts that can accept_slices, the user will see 'no capacity' error.

The allocator is modified to use a new VmHostSliceAllocation class that wraps and replaces VmHostAllocation when slice logic is enabled. It finds the capacity the same way as before, but when `update` is called it creates a slice first together with allocation of VmHostCpus before starting the VM creation process. This is a building block that will be expanded upon when we introduce 'burstable' instances later, when multiple VMs can share a slice.

For now, for 'Standard' family there is a 1:1 mapping between the slice and a VM inside it, and their lifespans. Slice is created first, then the VM. Slice creation updates the utilization of the VmHost, and the VM creation updates the utilization of the slice hosting it. Again, this will be more relevant when multiple VMs will be sharing a slice.
2025-01-28 17:16:51 -08:00

96 lines
4.1 KiB
Ruby

# frozen_string_literal: true
require "yaml"
module Option
ai_models = YAML.load_file("config/ai_models.yml")
providers = YAML.load_file("config/providers.yml")
Provider = Struct.new(:name, :display_name)
Location = Struct.new(:provider, :name, :display_name, :ui_name, :visible)
PROVIDERS = {}
LOCATIONS = []
providers.each do |provider|
provider_internal_name = provider["provider_internal_name"]
PROVIDERS[provider_internal_name] = Provider.new(provider_internal_name, provider["provider_display_name"])
Provider.const_set(provider_internal_name.gsub(/[^a-zA-Z]/, "_").upcase, provider_internal_name)
provider["locations"].each do |location|
LOCATIONS.push(Location.new(
PROVIDERS[provider_internal_name],
location["internal_name"],
location["display_name"],
location["ui_name"],
location["visible"]
))
end
end
AI_MODELS = ai_models.select { _1["enabled"] }.freeze
PROVIDERS.freeze
LOCATIONS.freeze
def self.locations(only_visible: true, feature_flags: [])
Option::LOCATIONS.select { !only_visible || (_1.visible || feature_flags.include?("location_#{_1.name.tr("-", "_")}")) }
end
def self.postgres_locations
Option::LOCATIONS.select { _1.name == "hetzner-fsn1" || _1.name == "leaseweb-wdc02" }
end
BootImage = Struct.new(:name, :display_name)
BootImages = [
["ubuntu-noble", "Ubuntu Noble 24.04 LTS"],
["ubuntu-jammy", "Ubuntu Jammy 22.04 LTS"],
["debian-12", "Debian 12"],
["almalinux-9", "AlmaLinux 9"]
].map { |args| BootImage.new(*args) }.freeze
IoLimits = Struct.new(:max_ios_per_sec, :max_read_mbytes_per_sec, :max_write_mbytes_per_sec)
NO_IO_LIMITS = IoLimits.new(nil, nil, nil).freeze
VmSize = Struct.new(:name, :family, :cores, :vcpus, :cpu_percent_limit, :cpu_burst_percent_limit, :memory_gib, :storage_size_options, :io_limits, :visible, :gpu, :arch) do
alias_method :display_name, :name
end
VmSizes = [2, 4, 8, 16, 30, 60].map {
storage_size_options = [_1 * 20, _1 * 40]
VmSize.new("standard-#{_1}", "standard", _1 / 2, _1, _1 * 100, 0, _1 * 4, storage_size_options, NO_IO_LIMITS, true, false, "x64")
}.concat([2, 4, 8, 16, 30, 60].map {
storage_size_options = [_1 * 20, _1 * 40]
VmSize.new("standard-#{_1}", "standard", _1, _1, _1 * 100, 0, (_1 * 3.2).to_i, storage_size_options, NO_IO_LIMITS, false, false, "arm64")
}).concat([6].map {
VmSize.new("standard-gpu-#{_1}", "standard-gpu", _1 / 2, _1, _1 * 100, 0, (_1 * 5.34).to_i, [_1 * 30], NO_IO_LIMITS, false, true, "x64")
}).freeze
PostgresSize = Struct.new(:location, :name, :vm_size, :family, :vcpu, :memory, :storage_size_options) do
alias_method :display_name, :name
end
PostgresSizes = Option.postgres_locations.product([2, 4, 8, 16, 30, 60]).flat_map {
storage_size_options = [_2 * 32, _2 * 64, _2 * 128]
storage_size_options.map! { |size| size / 15 * 16 } if [30, 60].include?(_2)
storage_size_limiter = [4096, storage_size_options.last].min.fdiv(storage_size_options.last)
storage_size_options.map! { |size| size * storage_size_limiter }
[
PostgresSize.new(_1.name, "standard-#{_2}", "standard-#{_2}", PostgresResource::Flavor::STANDARD, _2, _2 * 4, storage_size_options),
PostgresSize.new(_1.name, "standard-#{_2}", "standard-#{_2}", PostgresResource::Flavor::PARADEDB, _2, _2 * 4, storage_size_options),
PostgresSize.new(_1.name, "standard-#{_2}", "standard-#{_2}", PostgresResource::Flavor::LANTERN, _2, _2 * 4, storage_size_options)
]
}.freeze
PostgresHaOption = Struct.new(:name, :standby_count, :title, :explanation)
PostgresHaOptions = [[PostgresResource::HaType::NONE, 0, "No Standbys", "No replication"],
[PostgresResource::HaType::ASYNC, 1, "1 Standby", "Asynchronous replication"],
[PostgresResource::HaType::SYNC, 2, "2 Standbys", "Synchronous replication with quorum"]].map {
PostgresHaOption.new(*_1)
}.freeze
POSTGRES_VERSION_OPTIONS = {
PostgresResource::Flavor::STANDARD => ["16", "17"],
PostgresResource::Flavor::PARADEDB => ["16", "17"],
PostgresResource::Flavor::LANTERN => ["16"]
}
end