mirror of
https://github.com/czhu12/canine.git
synced 2025-12-17 00:44:33 -06:00
resource constraints and services refactor
This commit is contained in:
@@ -7,7 +7,6 @@ class AddOns::InstallHelmChart
|
|||||||
|
|
||||||
add_on.update_install_stage!(0)
|
add_on.update_install_stage!(0)
|
||||||
create_namespace(context.connection)
|
create_namespace(context.connection)
|
||||||
apply_resource_quota(context.connection)
|
|
||||||
|
|
||||||
if add_on.installed?
|
if add_on.installed?
|
||||||
add_on.updating!
|
add_on.updating!
|
||||||
@@ -65,16 +64,6 @@ class AddOns::InstallHelmChart
|
|||||||
kubectl.apply_yaml(namespace_yaml)
|
kubectl.apply_yaml(namespace_yaml)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.apply_resource_quota(connection)
|
|
||||||
add_on = connection.add_on
|
|
||||||
return unless add_on.resource_constraint.present?
|
|
||||||
|
|
||||||
runner = Cli::RunAndLog.new(add_on)
|
|
||||||
kubectl = K8::Kubectl.new(connection, runner)
|
|
||||||
resource_quota_yaml = K8::Stateless::ResourceQuota.new(add_on).to_yaml
|
|
||||||
kubectl.apply_yaml(resource_quota_yaml)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.get_values(add_on)
|
def self.get_values(add_on)
|
||||||
# Merge the values from the form with the values.yaml object and create a new values.yaml file
|
# Merge the values from the form with the values.yaml object and create a new values.yaml file
|
||||||
values = add_on.values
|
values = add_on.values
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ class ResourceConstraints::Save
|
|||||||
# Get params hash
|
# Get params hash
|
||||||
rc_params = context.params
|
rc_params = context.params
|
||||||
|
|
||||||
|
# Convert blank strings to nil
|
||||||
|
rc_params.each do |key, value|
|
||||||
|
rc_params[key] = nil if value.blank?
|
||||||
|
end
|
||||||
|
|
||||||
# Convert CPU cores to millicores
|
# Convert CPU cores to millicores
|
||||||
if rc_params[:cpu_request].present?
|
if rc_params[:cpu_request].present?
|
||||||
rc_params[:cpu_request] = (rc_params[:cpu_request].to_f * 1000).to_i
|
rc_params[:cpu_request] = (rc_params[:cpu_request].to_f * 1000).to_i
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
class Projects::Services::ResourceConstraintsController < Projects::Services::BaseController
|
||||||
|
before_action :set_service
|
||||||
|
|
||||||
|
def create
|
||||||
|
result = ResourceConstraints::Create.call(@service.build_resource_constraint, resource_constraint_params)
|
||||||
|
|
||||||
|
if result.success?
|
||||||
|
@service.updated!
|
||||||
|
render_partial
|
||||||
|
else
|
||||||
|
render :new, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render_partial
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
render_partial(show_form: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
result = ResourceConstraints::Update.call(@service.resource_constraint, resource_constraint_params)
|
||||||
|
@service.updated!
|
||||||
|
|
||||||
|
if result.success?
|
||||||
|
render_partial
|
||||||
|
else
|
||||||
|
raise StandardError, result.message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@service.resource_constraint.destroy
|
||||||
|
@service.updated!
|
||||||
|
render_partial
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def render_partial(locals = {})
|
||||||
|
render partial: "projects/services/resource_constraints/show", locals: { service: @service, resource_constraint: @service.resource_constraint }.merge(locals)
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource_constraint_params
|
||||||
|
params.require(:resource_constraint).permit(
|
||||||
|
:cpu_request,
|
||||||
|
:cpu_limit,
|
||||||
|
:memory_request,
|
||||||
|
:memory_limit,
|
||||||
|
:gpu_request
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
class ResourceConstraintsController < ApplicationController
|
|
||||||
before_action :set_constrainable
|
|
||||||
before_action :set_resource_constraint, only: [ :edit, :update, :destroy ]
|
|
||||||
|
|
||||||
def new
|
|
||||||
@resource_constraint = @constrainable.build_resource_constraint
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
@resource_constraint = @constrainable.build_resource_constraint
|
|
||||||
result = ResourceConstraints::Create.call(@resource_constraint, resource_constraint_params)
|
|
||||||
|
|
||||||
if result.success?
|
|
||||||
redirect_to_constrainable notice: "Resource constraints created successfully."
|
|
||||||
else
|
|
||||||
render :new, status: :unprocessable_entity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def edit
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
result = ResourceConstraints::Update.call(@resource_constraint, resource_constraint_params)
|
|
||||||
|
|
||||||
if result.success?
|
|
||||||
redirect_to_constrainable notice: "Resource constraints updated successfully."
|
|
||||||
else
|
|
||||||
render :edit, status: :unprocessable_entity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
@resource_constraint.destroy
|
|
||||||
redirect_to_constrainable notice: "Resource constraints removed."
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_constrainable
|
|
||||||
# Determine the constrainable type and ID from params
|
|
||||||
if params[:add_on_id]
|
|
||||||
@constrainable = current_account.add_ons.find(params[:add_on_id])
|
|
||||||
@constrainable_type = 'add_on'
|
|
||||||
elsif params[:project_id]
|
|
||||||
@constrainable = current_account.projects.find(params[:project_id])
|
|
||||||
@constrainable_type = 'project'
|
|
||||||
elsif params[:service_id]
|
|
||||||
# Services are nested under projects
|
|
||||||
project = current_account.projects.find(params[:project_id]) if params[:project_id]
|
|
||||||
@constrainable = project.services.find(params[:service_id])
|
|
||||||
@constrainable_type = 'service'
|
|
||||||
@project = project
|
|
||||||
else
|
|
||||||
redirect_to root_path, alert: "Invalid resource"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_resource_constraint
|
|
||||||
@resource_constraint = @constrainable.resource_constraint
|
|
||||||
redirect_to_constrainable(alert: "No resource constraints found.") unless @resource_constraint
|
|
||||||
end
|
|
||||||
|
|
||||||
def resource_constraint_params
|
|
||||||
params.require(:resource_constraint).permit(
|
|
||||||
:cpu_request,
|
|
||||||
:cpu_limit,
|
|
||||||
:memory_request,
|
|
||||||
:memory_limit,
|
|
||||||
:gpu_request
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def redirect_to_constrainable(options = {})
|
|
||||||
case @constrainable_type
|
|
||||||
when 'add_on'
|
|
||||||
redirect_to edit_add_on_path(@constrainable), **options
|
|
||||||
when 'project'
|
|
||||||
redirect_to edit_project_path(@constrainable), **options
|
|
||||||
when 'service'
|
|
||||||
redirect_to project_services_path(@project), **options
|
|
||||||
else
|
|
||||||
redirect_to root_path, **options
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
module ServicesHelper
|
module ServicesHelper
|
||||||
def services_layout(service, tab, &block)
|
def services_layout(service, tab, &block)
|
||||||
render layout: 'services/layout', locals: { service:, tab: }, &block
|
render layout: 'projects/services/layout', locals: { service:, tab: }, &block
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ module StorageHelper
|
|||||||
SIZE_UNITS.to_a.reverse.each do |unit, bytes|
|
SIZE_UNITS.to_a.reverse.each do |unit, bytes|
|
||||||
if integer >= bytes
|
if integer >= bytes
|
||||||
value = (integer.to_f / bytes).round(2)
|
value = (integer.to_f / bytes).round(2)
|
||||||
return "#{value}#{unit}i"
|
# Remove unnecessary trailing zeros and decimal point
|
||||||
|
formatted_value = value % 1 == 0 ? value.to_i : value
|
||||||
|
return "#{formatted_value}#{unit}i"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
integer.to_s
|
integer.to_s
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Controller } from "@hotwired/stimulus"
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static targets = ["container"]
|
static targets = ["container", "input", "slider", "cpu_requestField", "cpu_limitField", "memory_requestField", "memory_limitField"]
|
||||||
|
|
||||||
toggleResourceConstraints(event) {
|
toggleResourceConstraints(event) {
|
||||||
if (event.target.checked) {
|
if (event.target.checked) {
|
||||||
@@ -10,4 +10,37 @@ export default class extends Controller {
|
|||||||
this.containerTarget.classList.add("hidden")
|
this.containerTarget.classList.add("hidden")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleField(event) {
|
||||||
|
const fieldName = event.params.field
|
||||||
|
const isEnabled = event.target.checked
|
||||||
|
const fieldTarget = `${fieldName}FieldTarget`
|
||||||
|
|
||||||
|
if (this[fieldTarget]) {
|
||||||
|
const field = this[fieldTarget]
|
||||||
|
const numberInput = field.querySelector('input[type="number"]')
|
||||||
|
const rangeInput = field.querySelector('input[type="range"]')
|
||||||
|
|
||||||
|
if (isEnabled) {
|
||||||
|
// Enable and set default values
|
||||||
|
numberInput.removeAttribute('readonly')
|
||||||
|
numberInput.classList.remove('opacity-50', 'cursor-not-allowed')
|
||||||
|
rangeInput.disabled = false
|
||||||
|
|
||||||
|
if (fieldName.includes('cpu')) {
|
||||||
|
numberInput.value = '0.5'
|
||||||
|
rangeInput.value = '0.5'
|
||||||
|
} else if (fieldName.includes('memory')) {
|
||||||
|
numberInput.value = '128'
|
||||||
|
rangeInput.value = '128'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Make readonly and set to empty so form sends nil
|
||||||
|
numberInput.setAttribute('readonly', 'readonly')
|
||||||
|
numberInput.classList.add('opacity-50', 'cursor-not-allowed')
|
||||||
|
rangeInput.disabled = true
|
||||||
|
numberInput.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
app/javascript/controllers/turbo_tabs_controller.js
Normal file
21
app/javascript/controllers/turbo_tabs_controller.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["tabs", "content"]
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
console.log(this.contentTarget)
|
||||||
|
this.tabsTarget.addEventListener("click", (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
this.tabsTarget.querySelectorAll(".tab").forEach((radio) => {
|
||||||
|
radio.classList.remove("tab-active")
|
||||||
|
})
|
||||||
|
event.target.classList.add("tab-active")
|
||||||
|
// Show loading spinner
|
||||||
|
this.contentTarget.innerHTML = `<div class="flex items-center justify-center my-6">
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
</div>`
|
||||||
|
this.contentTarget.src = event.target.href
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ require "base64"
|
|||||||
require "json"
|
require "json"
|
||||||
|
|
||||||
class Projects::DeploymentJob < ApplicationJob
|
class Projects::DeploymentJob < ApplicationJob
|
||||||
DEPLOYABLE_RESOURCES = %w[ConfigMap ResourceQuota Deployment CronJob Service Ingress Pv Pvc]
|
DEPLOYABLE_RESOURCES = %w[ConfigMap Deployment CronJob Service Ingress Pv Pvc]
|
||||||
class DeploymentFailure < StandardError; end
|
class DeploymentFailure < StandardError; end
|
||||||
|
|
||||||
def perform(deployment, user)
|
def perform(deployment, user)
|
||||||
@@ -22,7 +22,6 @@ class Projects::DeploymentJob < ApplicationJob
|
|||||||
upload_registry_secrets(kubectl, deployment)
|
upload_registry_secrets(kubectl, deployment)
|
||||||
apply_config_map(project, kubectl)
|
apply_config_map(project, kubectl)
|
||||||
|
|
||||||
deploy_resource_quotas(project, kubectl)
|
|
||||||
deploy_volumes(project, kubectl)
|
deploy_volumes(project, kubectl)
|
||||||
predeploy(project, kubectl, connection)
|
predeploy(project, kubectl, connection)
|
||||||
# For each of the projects services
|
# For each of the projects services
|
||||||
@@ -44,12 +43,6 @@ class Projects::DeploymentJob < ApplicationJob
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def deploy_resource_quotas(project, kubectl)
|
|
||||||
if project.resource_constraint.present?
|
|
||||||
apply_resource_quota(project, kubectl)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def deploy_volumes(project, kubectl)
|
def deploy_volumes(project, kubectl)
|
||||||
project.volumes.each do |volume|
|
project.volumes.each do |volume|
|
||||||
begin
|
begin
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ class AddOn < ApplicationRecord
|
|||||||
include Loggable
|
include Loggable
|
||||||
belongs_to :cluster
|
belongs_to :cluster
|
||||||
has_one :account, through: :cluster
|
has_one :account, through: :cluster
|
||||||
has_one :resource_constraint, as: :constrainable, dependent: :destroy
|
|
||||||
|
|
||||||
enum :status, {
|
enum :status, {
|
||||||
installing: 0,
|
installing: 0,
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ class Project < ApplicationRecord
|
|||||||
|
|
||||||
has_one :project_credential_provider, dependent: :destroy
|
has_one :project_credential_provider, dependent: :destroy
|
||||||
has_one :build_configuration, dependent: :destroy
|
has_one :build_configuration, dependent: :destroy
|
||||||
has_one :resource_constraint, as: :constrainable, dependent: :destroy
|
|
||||||
|
|
||||||
has_one :child_fork, class_name: "ProjectFork", foreign_key: :child_project_id, dependent: :destroy
|
has_one :child_fork, class_name: "ProjectFork", foreign_key: :child_project_id, dependent: :destroy
|
||||||
has_many :forks, class_name: "ProjectFork", foreign_key: :parent_project_id, dependent: :destroy
|
has_many :forks, class_name: "ProjectFork", foreign_key: :parent_project_id, dependent: :destroy
|
||||||
|
|||||||
@@ -2,25 +2,24 @@
|
|||||||
#
|
#
|
||||||
# Table name: resource_constraints
|
# Table name: resource_constraints
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# constrainable_type :string not null
|
# cpu_limit :bigint
|
||||||
# cpu_limit :bigint
|
# cpu_request :bigint
|
||||||
# cpu_request :bigint
|
# gpu_request :integer
|
||||||
# gpu_request :integer
|
# memory_limit :bigint
|
||||||
# memory_limit :bigint
|
# memory_request :bigint
|
||||||
# memory_request :bigint
|
# created_at :datetime not null
|
||||||
# created_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# service_id :bigint not null
|
||||||
# constrainable_id :bigint not null
|
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_resource_constraints_on_constrainable (constrainable_type,constrainable_id)
|
# index_resource_constraints_on_service_id (service_id)
|
||||||
#
|
#
|
||||||
class ResourceConstraint < ApplicationRecord
|
class ResourceConstraint < ApplicationRecord
|
||||||
include StorageHelper
|
include StorageHelper
|
||||||
|
|
||||||
belongs_to :constrainable, polymorphic: true
|
belongs_to :service
|
||||||
|
|
||||||
validates :cpu_request, numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }
|
validates :cpu_request, numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }
|
||||||
validates :cpu_limit, numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }
|
validates :cpu_limit, numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }
|
||||||
@@ -28,5 +27,24 @@ class ResourceConstraint < ApplicationRecord
|
|||||||
validates :memory_limit, numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }
|
validates :memory_limit, numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }
|
||||||
validates :gpu_request, numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }
|
validates :gpu_request, numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }
|
||||||
|
|
||||||
# Convert string input to integers (e.g., "500m" -> 500, "1Gi" -> 1073741824)
|
# Formatted getters for Kubernetes YAML templates
|
||||||
|
def cpu_request_formatted
|
||||||
|
return nil if cpu_request.nil?
|
||||||
|
integer_to_compute(cpu_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cpu_limit_formatted
|
||||||
|
return nil if cpu_limit.nil?
|
||||||
|
integer_to_compute(cpu_limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
def memory_request_formatted
|
||||||
|
return nil if memory_request.nil?
|
||||||
|
integer_to_memory(memory_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
def memory_limit_formatted
|
||||||
|
return nil if memory_limit.nil?
|
||||||
|
integer_to_memory(memory_limit)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class Service < ApplicationRecord
|
|||||||
scope :running, -> { where(status: [ :healthy, :unhealthy, :updated ]) }
|
scope :running, -> { where(status: [ :healthy, :unhealthy, :updated ]) }
|
||||||
|
|
||||||
has_one :cron_schedule, dependent: :destroy
|
has_one :cron_schedule, dependent: :destroy
|
||||||
has_one :resource_constraint, as: :constrainable, dependent: :destroy
|
has_one :resource_constraint, dependent: :destroy
|
||||||
|
|
||||||
validates :cron_schedule, presence: true, if: :cron_job?
|
validates :cron_schedule, presence: true, if: :cron_job?
|
||||||
validates :command, presence: true, if: :cron_job?
|
validates :command, presence: true, if: :cron_job?
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ class K8::Base
|
|||||||
def to_yaml
|
def to_yaml
|
||||||
template_content = template_path.read
|
template_content = template_path.read
|
||||||
erb_template = ERB.new(template_content)
|
erb_template = ERB.new(template_content)
|
||||||
erb_template.result(binding)
|
result = erb_template.result(binding)
|
||||||
|
result.gsub(/\n\s*\n/, "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
def client
|
def client
|
||||||
|
|||||||
@@ -18,8 +18,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<!-- link to github -->
|
<!-- link to github -->
|
||||||
<%= link_to @build.commit_sha[0..6], "https://github.com/#{@project.repository_url}/commit/#{@build.commit_sha}", class: "underline", target: "_blank", rel: "noopener noreferrer" %>
|
<%= link_to(
|
||||||
<span class="font-light"><%= @build.commit_message.truncate(50) %></span>
|
@build.commit_sha[0..6],
|
||||||
|
"https://github.com/#{@project.repository_url}/commit/#{@build.commit_sha}",
|
||||||
|
class: "underline",
|
||||||
|
target: "_blank",
|
||||||
|
rel: "noopener noreferrer"
|
||||||
|
) %>
|
||||||
|
<span class="font-light"><%= @build.commit_message.truncate(75) %></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="mt-2 mb-6 border-base-content/10" />
|
<hr class="mt-2 mb-6 border-base-content/10" />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<%= services_layout(service, tab) do %>
|
<div>
|
||||||
<div>
|
<h2 class="text-xl font-bold">Resource Constraints</h2>
|
||||||
<h1>Advanced</h1>
|
<hr class="mt-3 mb-4 border-t border-base-300" />
|
||||||
</div>
|
<%= render "projects/services/resource_constraints/show", service: @service, resource_constraint: @service.resource_constraint %>
|
||||||
<% end %>
|
</div>
|
||||||
10
app/views/projects/services/_cron_job.html.erb
Normal file
10
app/views/projects/services/_cron_job.html.erb
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<%= render "projects/services/cron_job_history", service: service %>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4" data-tip="Run Job" class="tooltip">
|
||||||
|
<%= button_to "Run Job", project_service_jobs_path(service.project, service), class: "btn btn-primary btn-sm btn-outline #{!service.healthy? ? 'btn-disabled' : ''}", disabled: !service.healthy? %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,5 +1,21 @@
|
|||||||
<%= services_layout(service, tab) do %>
|
<div>
|
||||||
<div>
|
<div class="mb-6 space-y-2">
|
||||||
<h1>Networking <%= service.name %></h1>
|
<h2 class="text-xl font-bold">Internal URL</h2>
|
||||||
|
<hr class="mt-3 mb-4 border-t border-base-300" />
|
||||||
|
<pre
|
||||||
|
class="inline-block cursor-pointer"
|
||||||
|
data-controller="clipboard"
|
||||||
|
data-clipboard-text="<%= service.internal_url %>"><%= service.internal_url %></pre>
|
||||||
|
|
||||||
|
<%= render "projects/services/telepresence_guide", cluster: @project.cluster, url: "http://#{service.internal_url}" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<div>
|
||||||
|
<h2 class="text-xl font-bold">Public Networking</h2>
|
||||||
|
<hr class="mt-3 mb-4 border-t border-base-300" />
|
||||||
|
<% if service.allow_public_networking? %>
|
||||||
|
<div class="form-control form-group">
|
||||||
|
<%= render "projects/services/domains/index", service: service %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,97 +1,63 @@
|
|||||||
<div>
|
<div>
|
||||||
<%= services_layout(service, tab) do %>
|
<%= form_with(model: [service.project, service]) do |form| %>
|
||||||
<% if service.web_service? %>
|
<div class="grid gap-4 grid-cols-1 lg:grid-cols-2">
|
||||||
<div class="mb-4 space-y-2">
|
<div>
|
||||||
<h6 class="text-lg font-bold">Internal URL</h6>
|
|
||||||
<pre
|
|
||||||
class="inline-block cursor-pointer"
|
|
||||||
data-controller="clipboard"
|
|
||||||
data-clipboard-text="<%= service.internal_url %>"><%= service.internal_url %></pre>
|
|
||||||
|
|
||||||
<%= render "projects/services/telepresence_guide", cluster: @project.cluster, url: "http://#{service.internal_url}" %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if service.cron_job? %>
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<div class="flex-1">
|
|
||||||
<%= render "projects/services/cron_job_history", service: service %>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4" data-tip="Run Job" class="tooltip">
|
|
||||||
<%= button_to "Run Job", project_service_jobs_path(service.project, service), class: "btn btn-primary btn-sm btn-outline #{!service.healthy? ? 'btn-disabled' : ''}", disabled: !service.healthy? %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<%= form_with(model: [service.project, service]) do |form| %>
|
|
||||||
<div class="grid gap-4 grid-cols-1 lg:grid-cols-2">
|
|
||||||
<div>
|
|
||||||
<div class="form-control form-group">
|
|
||||||
<%= form.label :name %>
|
|
||||||
<%= form.text_field :name, class: "input input-bordered w-full", required: true, disabled: true %>
|
|
||||||
</div>
|
|
||||||
<div class="form-control form-group">
|
|
||||||
<%= form.label :command %>
|
|
||||||
<%= form.text_field :command, class: "input input-bordered w-full", required: false %>
|
|
||||||
</div>
|
|
||||||
<% if service.cron_job? %>
|
|
||||||
<div class="form-control form-group">
|
|
||||||
<%= form.fields_for :cron_schedule do |cron_schedule_form| %>
|
|
||||||
<%= cron_schedule_form.label :schedule %>
|
|
||||||
<%= cron_schedule_form.text_field :schedule, class: "input input-bordered w-full", placeholder: "0 0 * * *", value: service.cron_schedule.schedule %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% if service.web_service? %>
|
|
||||||
<div class="form-control form-group">
|
|
||||||
<%= form.label :container_port %>
|
|
||||||
<%= form.text_field :container_port, class: "input input-bordered w-full", required: false %>
|
|
||||||
</div>
|
|
||||||
<div class="form-control form-group">
|
|
||||||
<%= form.label :healthcheck_url %>
|
|
||||||
<%= form.text_field :healthcheck_url, class: "input input-bordered w-full", placeholder: "/health" %>
|
|
||||||
<span class="label-text-alt">Optional: The endpoint just needs to return a 200 status code to be considered healthy</span>
|
|
||||||
</div>
|
|
||||||
<div class="form-control rounded-lg bg-base-200 p-2 px-4">
|
|
||||||
<label class="label mt-1">
|
|
||||||
<span class="label-text cursor-pointer">Allow public networking</span>
|
|
||||||
<%= form.check_box :allow_public_networking, class: "checkbox" %>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<span class="label-text-alt">Checking this allows your service to be accessible from the public internet</span>
|
|
||||||
<% end %>
|
|
||||||
<% if service.web_service? || service.background_service? %>
|
|
||||||
<div>
|
|
||||||
<h2 class="text-lg my-2 mt-4">Resources</h2>
|
|
||||||
<div class="form-control form-group">
|
|
||||||
<%= form.label :replicas %>
|
|
||||||
<%= form.number_field :replicas, class: "input input-bordered w-full max-w-xs", placeholder: "1" %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<%= form.label :description %>
|
|
||||||
<%= render "shared/partials/markdown_editor", form: form %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-footer">
|
|
||||||
<%= form.submit class: "btn btn-primary" %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= button_to [service.project, service], method: :delete, class: "btn btn-error btn-outline mt-2", form: { data: { turbo_confirm: t("are_you_sure") } } do %>
|
|
||||||
<iconify-icon icon="lucide:trash" height="20" class="text-error-content"></iconify-icon>
|
|
||||||
Delete service
|
|
||||||
<% end %>
|
|
||||||
<% if service.web_service? && service.allow_public_networking? %>
|
|
||||||
<div class="my-8">
|
|
||||||
<h2 class="text-2xl font-bold">Networking</h2>
|
|
||||||
<hr class="mt-3 mb-4 border-t border-base-300" />
|
|
||||||
|
|
||||||
<div class="form-control form-group">
|
<div class="form-control form-group">
|
||||||
<%= render "projects/services/domains/index", service: service %>
|
<%= form.label :name %>
|
||||||
|
<%= form.text_field :name, class: "input input-bordered w-full", required: true, disabled: true %>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-control form-group">
|
||||||
|
<%= form.label :command %>
|
||||||
|
<%= form.text_field :command, class: "input input-bordered w-full", required: false %>
|
||||||
|
</div>
|
||||||
|
<% if service.cron_job? %>
|
||||||
|
<div class="form-control form-group">
|
||||||
|
<%= form.fields_for :cron_schedule do |cron_schedule_form| %>
|
||||||
|
<%= cron_schedule_form.label :schedule %>
|
||||||
|
<%= cron_schedule_form.text_field :schedule, class: "input input-bordered w-full", placeholder: "0 0 * * *", value: service.cron_schedule.schedule %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% if service.web_service? %>
|
||||||
|
<div class="form-control form-group">
|
||||||
|
<%= form.label :container_port %>
|
||||||
|
<%= form.text_field :container_port, class: "input input-bordered w-full", required: false %>
|
||||||
|
</div>
|
||||||
|
<div class="form-control form-group">
|
||||||
|
<%= form.label :healthcheck_url %>
|
||||||
|
<%= form.text_field :healthcheck_url, class: "input input-bordered w-full", placeholder: "/health" %>
|
||||||
|
<span class="label-text-alt">Optional: The endpoint just needs to return a 200 status code to be considered healthy</span>
|
||||||
|
</div>
|
||||||
|
<div class="form-control rounded-lg bg-base-200 p-2 px-4">
|
||||||
|
<label class="label mt-1">
|
||||||
|
<span class="label-text cursor-pointer">Allow public networking</span>
|
||||||
|
<%= form.check_box :allow_public_networking, class: "checkbox" %>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<span class="label-text-alt">Checking this allows your service to be accessible from the public internet</span>
|
||||||
|
<% end %>
|
||||||
|
<% if service.web_service? || service.background_service? %>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg my-2 mt-4">Resources</h2>
|
||||||
|
<div class="form-control form-group">
|
||||||
|
<%= form.label :replicas %>
|
||||||
|
<%= form.number_field :replicas, class: "input input-bordered w-full max-w-xs", placeholder: "1" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<div>
|
||||||
|
<%= form.label :description %>
|
||||||
|
<%= render "shared/partials/markdown_editor", form: form %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-footer">
|
||||||
|
<%= form.submit class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
|
||||||
|
<%= button_to [service.project, service], method: :delete, class: "btn btn-error btn-outline mt-2", form: { data: { turbo_confirm: t("are_you_sure") } } do %>
|
||||||
|
<iconify-icon icon="lucide:trash" height="20" class="text-error-content"></iconify-icon>
|
||||||
|
Delete service
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
<div>
|
<%= turbo_frame_tag("service_#{service.id}", data: { turbo_tabs_target: "content" }) do %>
|
||||||
<%= turbo_frame_tag "service_#{service.id}" do %>
|
<div class="my-6">
|
||||||
<% if tab == "overview" %>
|
<% if tab == "overview" %>
|
||||||
<%= render "projects/services/overview", service: service, tab: tab %>
|
<%= render "projects/services/overview", service:, tab: %>
|
||||||
|
<% elsif tab == "cron-jobs" %>
|
||||||
|
<%= render "projects/services/cron_job", service:, tab: %>
|
||||||
<% elsif tab == "networking" %>
|
<% elsif tab == "networking" %>
|
||||||
<%= render "projects/services/networking", service: service, tab: tab %>
|
<%= render "projects/services/networking", service:, tab: %>
|
||||||
<% elsif tab == "advanced" %>
|
<% elsif tab == "advanced" %>
|
||||||
<%= render "projects/services/advanced", service: service, tab: tab %>
|
<%= render "projects/services/advanced", service:, tab: %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
</div>
|
||||||
</div>
|
<% end %>
|
||||||
13
app/views/projects/services/_tabs.html.erb
Normal file
13
app/views/projects/services/_tabs.html.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<div role="tablist" class="tabs tabs-bordered" data-turbo-tabs-target="tabs">
|
||||||
|
<%= link_to "Overview", project_service_path(service.project, service, tab: 'overview'), class: "tab #{'tab-active' if tab == 'overview'}" %>
|
||||||
|
|
||||||
|
<% if service.web_service? %>
|
||||||
|
<%= link_to "Networking", project_service_path(service.project, service, tab: 'networking'), class: "tab #{'tab-active' if tab == 'networking'}" %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if service.cron_job? %>
|
||||||
|
<%= link_to "Cron Job History", project_service_path(service.project, service, tab: 'cron-jobs'), class: "tab #{'tab-active' if tab == 'cron-jobs'}" %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= link_to "Advanced", project_service_path(service.project, service, tab: 'advanced'), class: "tab #{'tab-active' if tab == 'advanced'}" %>
|
||||||
|
</div>
|
||||||
@@ -21,16 +21,14 @@
|
|||||||
<div class="collapse-title">
|
<div class="collapse-title">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-2 text-xl font-medium">
|
<div class="flex items-center gap-2 text-xl font-medium">
|
||||||
<div>
|
<% if service.web_service? %>
|
||||||
<% if service.web_service? %>
|
<iconify-icon icon="lucide:globe" height="16"></iconify-icon>
|
||||||
<iconify-icon icon="ph:globe-duotone" height="16"></iconify-icon>
|
<% elsif service.background_service? %>
|
||||||
<% elsif service.background_service? %>
|
<iconify-icon icon="lucide:cpu" height="16"></iconify-icon>
|
||||||
<iconify-icon icon="ph:server" height="16"></iconify-icon>
|
<% elsif service.cron_job? %>
|
||||||
<% elsif service.cron_job? %>
|
<iconify-icon icon="lucide:clock" height="16"></iconify-icon>
|
||||||
<iconify-icon icon="ph:clock" height="16"></iconify-icon>
|
<% end %>
|
||||||
<% end %>
|
<%= service.name %>
|
||||||
<%= service.name %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="my-1">
|
<div class="my-1">
|
||||||
<%= render "projects/services/status", service: service %>
|
<%= render "projects/services/status", service: service %>
|
||||||
@@ -38,7 +36,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<%= render "projects/services/show", service: service, tab: "overview" %>
|
<div data-controller="turbo-tabs">
|
||||||
|
<%= render "projects/services/tabs", service:, tab: "overview" %>
|
||||||
|
<%= render "projects/services/show", service:, tab: "overview" %>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<div data-controller="resource-constraints--slider" data-resource-constraints--slider-type-value="float" data-resource-constraints--form-target="<%= key %>Field">
|
||||||
|
<% value = resource_constraint.send(key) %>
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<%= check_box_tag "enable_#{key}", "1", value.present?,
|
||||||
|
class: "checkbox checkbox-sm",
|
||||||
|
data: {
|
||||||
|
action: "change->resource-constraints--form#toggleField",
|
||||||
|
resource_constraints__form_field_param: key
|
||||||
|
} %>
|
||||||
|
<span class="text-sm"><%= key.to_s.titleize %></span>
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<%= form.text_field(
|
||||||
|
key,
|
||||||
|
type: "number",
|
||||||
|
class: "input input-sm input-bordered font-mono text-sm w-[125px] text-right mr-2 #{'opacity-50 cursor-not-allowed' if value.blank?}",
|
||||||
|
value: value.present? ? (value / 1000.0) : '',
|
||||||
|
step: :any,
|
||||||
|
required: false,
|
||||||
|
placeholder: "0.5",
|
||||||
|
readonly: value.blank?,
|
||||||
|
data: {
|
||||||
|
resource_constraints__slider_target: "numberInput",
|
||||||
|
resource_constraints__form_target: "input",
|
||||||
|
action: "input->resource-constraints--slider#updateSlider"
|
||||||
|
}
|
||||||
|
) %>
|
||||||
|
<span class="text-gray-200">cores</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0.1"
|
||||||
|
max="16"
|
||||||
|
step="0.1"
|
||||||
|
value="<%= value.present? ? (value / 1000.0) : 0.5 %>"
|
||||||
|
class="w-full h-2 bg-base-300 rounded-lg appearance-none cursor-pointer slider"
|
||||||
|
disabled="<%= value.blank? %>"
|
||||||
|
data-action="input->resource-constraints--slider#updateValue"
|
||||||
|
data-resource-constraints--slider-target="slider"
|
||||||
|
data-resource-constraints--form-target="slider"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between text-xs text-gray-400 mt-1">
|
||||||
|
<span>0.1</span>
|
||||||
|
<span>16.0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<div class="space-y-6" data-controller="resource-constraints--form">
|
||||||
|
<div data-resource-constraints--form-target="container">
|
||||||
|
<!-- CPU Configuration -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-16 md:gap-32">
|
||||||
|
<div>
|
||||||
|
<div class="font-medium flex items-center gap-2 font-bold mb-4">
|
||||||
|
<iconify-icon icon="lucide:cpu" height="20"></iconify-icon>
|
||||||
|
<span>CPU</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= render "projects/services/resource_constraints/cpu_slider", form: form, resource_constraint:, key: :cpu_request %>
|
||||||
|
<%= render "projects/services/resource_constraints/cpu_slider", form: form, resource_constraint:, key: :cpu_limit %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="font-medium flex items-center gap-2 font-bold mb-4">
|
||||||
|
<iconify-icon icon="lucide:hard-drive" height="20"></iconify-icon>
|
||||||
|
<span>Memory</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= render "projects/services/resource_constraints/memory_slider", form: form, resource_constraint:, key: :memory_request %>
|
||||||
|
<%= render "projects/services/resource_constraints/memory_slider", form: form, resource_constraint:, key: :memory_limit %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<div data-controller="resource-constraints--slider" data-resource-constraints--slider-type-value="integer" data-resource-constraints--form-target="<%= key %>Field">
|
||||||
|
<% value = resource_constraint.send(key) %>
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<%= check_box_tag "enable_#{key}", "1", value.present?,
|
||||||
|
class: "checkbox checkbox-sm",
|
||||||
|
data: {
|
||||||
|
action: "change->resource-constraints--form#toggleField",
|
||||||
|
resource_constraints__form_field_param: key
|
||||||
|
} %>
|
||||||
|
<span class="text-sm"><%= key.to_s.titleize %></span>
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<%= form.text_field(
|
||||||
|
key,
|
||||||
|
type: "number",
|
||||||
|
class: "input input-sm input-bordered font-mono text-sm w-[125px] text-right mr-2 #{'opacity-50 cursor-not-allowed' if value.blank?}",
|
||||||
|
value: value || '',
|
||||||
|
placeholder: "128",
|
||||||
|
required: false,
|
||||||
|
readonly: value.blank?,
|
||||||
|
data: {
|
||||||
|
resource_constraints__slider_target: "numberInput",
|
||||||
|
resource_constraints__form_target: "input",
|
||||||
|
action: "input->resource-constraints--slider#updateSlider"
|
||||||
|
}
|
||||||
|
) %>
|
||||||
|
<span class="text-gray-200">MB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="128"
|
||||||
|
max="32768"
|
||||||
|
step="128"
|
||||||
|
value="<%= value || 128 %>"
|
||||||
|
class="w-full h-2 bg-base-300 rounded-lg appearance-none cursor-pointer slider"
|
||||||
|
disabled="<%= value.blank? %>"
|
||||||
|
data-action="input->resource-constraints--slider#updateValue"
|
||||||
|
data-resource-constraints--slider-target="slider"
|
||||||
|
data-resource-constraints--form-target="slider"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between text-xs text-gray-400 mt-1">
|
||||||
|
<span>128</span>
|
||||||
|
<span>32768</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<%= turbo_frame_tag "resource_constraint_#{service.id}" do %>
|
||||||
|
<% if (service.resource_constraint.present? && service.resource_constraint.persisted?) || (defined?(show_form) && show_form) %>
|
||||||
|
<% resource_constraint = service.resource_constraint || ResourceConstraint.new(service:) %>
|
||||||
|
<%= form_with(
|
||||||
|
model: resource_constraint,
|
||||||
|
url: project_service_resource_constraint_path(service.project, service),
|
||||||
|
method: resource_constraint.persisted? ? :put : :post,
|
||||||
|
data: { turbo_frame: "resource_constraint_form" }) do |form|
|
||||||
|
%>
|
||||||
|
<%= render "projects/services/resource_constraints/form", form: form, resource_constraint: %>
|
||||||
|
<% if resource_constraint.persisted? %>
|
||||||
|
<div class="form-footer mt-6">
|
||||||
|
<%= form.submit "Update Resource Constraints", class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="form-footer mt-6">
|
||||||
|
<%= link_to "Cancel", project_service_resource_constraint_path(service.project, service), class: "btn btn-outline" %>
|
||||||
|
<%= form.submit "Create Resource Constraints", class: "btn btn-primary" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<div class="mt-2">
|
||||||
|
<%= button_to "Delete", project_service_resource_constraint_path(service.project, service), method: :delete, class: "btn btn-error btn-outline" %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<%= link_to "Enable Resource Constraints", new_project_service_resource_constraint_path(service.project, service), class: "btn btn-primary" %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
<div class="space-y-6" data-controller="resource-constraints--form">
|
|
||||||
<div data-resource-constraints--form-target="container">
|
|
||||||
<!-- CPU Configuration -->
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-16 md:gap-32">
|
|
||||||
<div>
|
|
||||||
<div class="font-medium flex items-center gap-2 font-bold mb-4">
|
|
||||||
<iconify-icon icon="lucide:cpu" height="20"></iconify-icon>
|
|
||||||
<span>CPU</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-controller="resource-constraints--slider" data-resource-constraints--slider-type-value="float">
|
|
||||||
<%= form.text_field(
|
|
||||||
:cpu_limit,
|
|
||||||
type: "number",
|
|
||||||
class: "input input-sm input-bordered font-mono text-sm w-[125px] text-right mr-2",
|
|
||||||
value: resource_constraint.cpu_limit.present? ? (resource_constraint.cpu_limit / 1000.0) : nil,
|
|
||||||
step: :any,
|
|
||||||
required: true,
|
|
||||||
placeholder: "0.5",
|
|
||||||
data: {
|
|
||||||
resource_constraints__slider_target: "numberInput",
|
|
||||||
action: "input->resource-constraints--slider#updateSlider"
|
|
||||||
}
|
|
||||||
) %>
|
|
||||||
<span class="text-gray-200">cores</span>
|
|
||||||
<div class="mt-4">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="0.1"
|
|
||||||
max="16"
|
|
||||||
step="0.1"
|
|
||||||
value="<%= resource_constraint.cpu_limit.present? ? (resource_constraint.cpu_limit / 1000.0) : 0.5 %>"
|
|
||||||
class="w-full h-2 bg-base-300 rounded-lg appearance-none cursor-pointer slider"
|
|
||||||
data-action="input->resource-constraints--slider#updateValue"
|
|
||||||
data-resource-constraints--slider-target="slider"
|
|
||||||
>
|
|
||||||
<div class="flex justify-between text-xs text-gray-400 mt-1">
|
|
||||||
<span>0.1</span>
|
|
||||||
<span>16.0</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="font-medium flex items-center gap-2 font-bold mb-4">
|
|
||||||
<iconify-icon icon="lucide:hard-drive" height="20"></iconify-icon>
|
|
||||||
<span>Memory</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div data-controller="resource-constraints--slider" data-resource-constraints--slider-type-value="integer">
|
|
||||||
<%= form.text_field(
|
|
||||||
:memory_limit,
|
|
||||||
type: "number",
|
|
||||||
class: "input input-sm input-bordered font-mono text-sm w-[125px] text-right mr-2",
|
|
||||||
value: resource_constraint.cpu_limit,
|
|
||||||
placeholder: "128",
|
|
||||||
required: true,
|
|
||||||
data: {
|
|
||||||
resource_constraints__slider_target: "numberInput",
|
|
||||||
action: "input->resource-constraints--slider#updateSlider"
|
|
||||||
}
|
|
||||||
) %>
|
|
||||||
<span class="text-gray-200">MB</span>
|
|
||||||
<div class="mt-4">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="128"
|
|
||||||
max="32768"
|
|
||||||
step="128"
|
|
||||||
value="<%= resource_constraint.memory_limit || 128 %>"
|
|
||||||
class="w-full h-2 bg-base-300 rounded-lg appearance-none cursor-pointer slider"
|
|
||||||
data-action="input->resource-constraints--slider#updateValue"
|
|
||||||
data-resource-constraints--slider-target="slider"
|
|
||||||
>
|
|
||||||
<div class="flex justify-between text-xs text-gray-400 mt-1">
|
|
||||||
<span>128</span>
|
|
||||||
<span>32768</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<%= turbo_frame_tag "resource_constraint_form" do %>
|
|
||||||
<h3 class="text-lg font-bold mb-4">Edit Resource Constraints</h3>
|
|
||||||
<%
|
|
||||||
url = case @constrainable_type
|
|
||||||
when 'add_on'
|
|
||||||
add_on_resource_constraint_path(@constrainable)
|
|
||||||
when 'project'
|
|
||||||
project_resource_constraint_path(@constrainable)
|
|
||||||
when 'service'
|
|
||||||
project_service_resource_constraint_path(@project, @constrainable)
|
|
||||||
end
|
|
||||||
%>
|
|
||||||
<%= form_with(model: @resource_constraint, url: url, method: :patch, data: { turbo_frame: "resource_constraint_form" }) do |form| %>
|
|
||||||
<%= render "resource_constraints/form", form: form, resource_constraint: @resource_constraint %>
|
|
||||||
<div class="form-footer mt-6 flex gap-2">
|
|
||||||
<%= form.submit "Update Resource Constraints", class: "btn btn-primary", data: { turbo: false } %>
|
|
||||||
<%= button_to "Remove Constraints", url, method: :delete, class: "btn btn-error btn-outline", form: { data: { turbo_confirm: "Are you sure you want to remove resource constraints?" } } %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<%= turbo_frame_tag "resource_constraint_form" do %>
|
|
||||||
<h3 class="text-lg font-bold mb-4">New Resource Constraints</h3>
|
|
||||||
<%
|
|
||||||
url = case @constrainable_type
|
|
||||||
when 'add_on'
|
|
||||||
add_on_resource_constraint_path(@constrainable)
|
|
||||||
when 'project'
|
|
||||||
project_resource_constraint_path(@constrainable)
|
|
||||||
when 'service'
|
|
||||||
project_service_resource_constraint_path(@project, @constrainable)
|
|
||||||
end
|
|
||||||
%>
|
|
||||||
<%= form_with(model: @resource_constraint, url: url, method: :post, data: { turbo_frame: "resource_constraint_form" }) do |form| %>
|
|
||||||
<%= render "resource_constraints/form", form: form, resource_constraint: @resource_constraint %>
|
|
||||||
<div class="form-footer mt-6">
|
|
||||||
<%= form.submit "Create Resource Constraints", class: "btn btn-primary", data: { turbo: false } %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<div role="tablist" class="tabs tabs-bordered">
|
|
||||||
<a href="<%= project_service_path(service.project, service, tab: 'overview') %>" role="tab" class="tab <%= 'tab-active' if tab == 'overview' %>">Overview</a>
|
|
||||||
<% if service.web_service? %>
|
|
||||||
<a href="<%= project_service_path(service.project, service, tab: 'networking') %>" role="tab" class="tab <%= 'tab-active' if tab == 'networking' %>">Networking</a>
|
|
||||||
<% end %>
|
|
||||||
<a href="<%= project_service_path(service.project, service, tab: 'advanced') %>" role="tab" class="tab <%= 'tab-active' if tab == 'advanced' %>">Advanced</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= yield %>
|
|
||||||
@@ -65,7 +65,6 @@ Rails.application.routes.draw do
|
|||||||
resource :metrics, only: [ :show ], module: :add_ons
|
resource :metrics, only: [ :show ], module: :add_ons
|
||||||
resources :endpoints, only: %i[edit update], module: :add_ons
|
resources :endpoints, only: %i[edit update], module: :add_ons
|
||||||
resources :processes, only: %i[index show], module: :add_ons
|
resources :processes, only: %i[index show], module: :add_ons
|
||||||
resource :resource_constraint, only: %i[new create edit update destroy]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :providers, only: %i[index new create destroy]
|
resources :providers, only: %i[index new create destroy]
|
||||||
@@ -79,15 +78,14 @@ Rails.application.routes.draw do
|
|||||||
resources :project_forks, only: %i[index edit create], module: :projects
|
resources :project_forks, only: %i[index edit create], module: :projects
|
||||||
resources :volumes, only: %i[index new create destroy], module: :projects
|
resources :volumes, only: %i[index new create destroy], module: :projects
|
||||||
resources :processes, only: %i[index show create destroy], module: :projects
|
resources :processes, only: %i[index show create destroy], module: :projects
|
||||||
resource :resource_constraint, only: %i[new create edit update destroy]
|
|
||||||
resources :services, only: %i[index new create destroy update show], module: :projects do
|
resources :services, only: %i[index new create destroy update show], module: :projects do
|
||||||
|
resource :resource_constraint, only: %i[show new create update destroy], module: :services
|
||||||
resources :jobs, only: %i[create], module: :services
|
resources :jobs, only: %i[create], module: :services
|
||||||
resources :domains, only: %i[create destroy], module: :services do
|
resources :domains, only: %i[create destroy], module: :services do
|
||||||
collection do
|
collection do
|
||||||
post :check_dns
|
post :check_dns
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resource :resource_constraint, only: %i[new create edit update destroy]
|
|
||||||
end
|
end
|
||||||
resources :metrics, only: [ :index ], module: :projects
|
resources :metrics, only: [ :index ], module: :projects
|
||||||
resources :project_add_ons, only: %i[create destroy], module: :projects
|
resources :project_add_ons, only: %i[create destroy], module: :projects
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class CreateResourceConstraints < ActiveRecord::Migration[7.2]
|
class CreateResourceConstraints < ActiveRecord::Migration[7.2]
|
||||||
def change
|
def change
|
||||||
create_table :resource_constraints do |t|
|
create_table :resource_constraints do |t|
|
||||||
t.references :constrainable, polymorphic: true, null: false, index: true
|
t.references :service, null: false, index: true
|
||||||
t.bigint :cpu_request
|
t.bigint :cpu_request
|
||||||
t.bigint :cpu_limit
|
t.bigint :cpu_limit
|
||||||
t.bigint :memory_request
|
t.bigint :memory_request
|
||||||
|
|||||||
13
db/schema.rb
generated
13
db/schema.rb
generated
@@ -426,7 +426,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_14_025053) do
|
|||||||
t.text "postdestroy_command"
|
t.text "postdestroy_command"
|
||||||
t.bigint "project_fork_cluster_id"
|
t.bigint "project_fork_cluster_id"
|
||||||
t.integer "project_fork_status", default: 0
|
t.integer "project_fork_status", default: 0
|
||||||
t.string "docker_command"
|
|
||||||
t.index ["cluster_id"], name: "index_projects_on_cluster_id"
|
t.index ["cluster_id"], name: "index_projects_on_cluster_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -447,6 +446,18 @@ ActiveRecord::Schema[7.2].define(version: 2025_11_14_025053) do
|
|||||||
t.index ["user_id"], name: "index_providers_on_user_id"
|
t.index ["user_id"], name: "index_providers_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "resource_constraints", force: :cascade do |t|
|
||||||
|
t.bigint "service_id", null: false
|
||||||
|
t.bigint "cpu_request"
|
||||||
|
t.bigint "cpu_limit"
|
||||||
|
t.bigint "memory_request"
|
||||||
|
t.bigint "memory_limit"
|
||||||
|
t.integer "gpu_request"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["service_id"], name: "index_resource_constraints_on_service_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "services", force: :cascade do |t|
|
create_table "services", force: :cascade do |t|
|
||||||
t.bigint "project_id", null: false
|
t.bigint "project_id", null: false
|
||||||
t.integer "service_type", null: false
|
t.integer "service_type", null: false
|
||||||
|
|||||||
@@ -31,34 +31,6 @@ spec:
|
|||||||
mountPath: <%= volume.mount_path %>
|
mountPath: <%= volume.mount_path %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% resource_constraint = project.resource_constraint %>
|
|
||||||
<% if resource_constraint.present? %>
|
|
||||||
resources:
|
|
||||||
<% if resource_constraint.cpu_request.present? || resource_constraint.memory_request.present? || resource_constraint.gpu_request.present? %>
|
|
||||||
requests:
|
|
||||||
<% if resource_constraint.cpu_request.present? %>
|
|
||||||
cpu: "<%= resource_constraint.cpu_request_formatted %>"
|
|
||||||
<% end %>
|
|
||||||
<% if resource_constraint.memory_request.present? %>
|
|
||||||
memory: "<%= resource_constraint.memory_request_formatted %>"
|
|
||||||
<% end %>
|
|
||||||
<% if resource_constraint.gpu_request.present? && resource_constraint.gpu_request > 0 %>
|
|
||||||
nvidia.com/gpu: <%= resource_constraint.gpu_request %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% if resource_constraint.cpu_limit.present? || resource_constraint.memory_limit.present? %>
|
|
||||||
limits:
|
|
||||||
<% if resource_constraint.cpu_limit.present? %>
|
|
||||||
cpu: "<%= resource_constraint.cpu_limit_formatted %>"
|
|
||||||
<% end %>
|
|
||||||
<% if resource_constraint.memory_limit.present? %>
|
|
||||||
memory: "<%= resource_constraint.memory_limit_formatted %>"
|
|
||||||
<% end %>
|
|
||||||
<% if resource_constraint.gpu_request.present? && resource_constraint.gpu_request > 0 %>
|
|
||||||
nvidia.com/gpu: <%= resource_constraint.gpu_request %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
- name: dockerconfigjson-github-com
|
- name: dockerconfigjson-github-com
|
||||||
<% if @project.volumes.present? %>
|
<% if @project.volumes.present? %>
|
||||||
|
|||||||
@@ -21,34 +21,6 @@ spec:
|
|||||||
mountPath: <%= volume.mount_path %>
|
mountPath: <%= volume.mount_path %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% resource_constraint = project.resource_constraint %>
|
|
||||||
<% if resource_constraint.present? %>
|
|
||||||
resources:
|
|
||||||
<% if resource_constraint.cpu_request.present? || resource_constraint.memory_request.present? || resource_constraint.gpu_request.present? %>
|
|
||||||
requests:
|
|
||||||
<% if resource_constraint.cpu_request.present? %>
|
|
||||||
cpu: "<%= resource_constraint.cpu_request_formatted %>"
|
|
||||||
<% end %>
|
|
||||||
<% if resource_constraint.memory_request.present? %>
|
|
||||||
memory: "<%= resource_constraint.memory_request_formatted %>"
|
|
||||||
<% end %>
|
|
||||||
<% if resource_constraint.gpu_request.present? && resource_constraint.gpu_request > 0 %>
|
|
||||||
nvidia.com/gpu: <%= resource_constraint.gpu_request %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% if resource_constraint.cpu_limit.present? || resource_constraint.memory_limit.present? %>
|
|
||||||
limits:
|
|
||||||
<% if resource_constraint.cpu_limit.present? %>
|
|
||||||
cpu: "<%= resource_constraint.cpu_limit_formatted %>"
|
|
||||||
<% end %>
|
|
||||||
<% if resource_constraint.memory_limit.present? %>
|
|
||||||
memory: "<%= resource_constraint.memory_limit_formatted %>"
|
|
||||||
<% end %>
|
|
||||||
<% if resource_constraint.gpu_request.present? && resource_constraint.gpu_request > 0 %>
|
|
||||||
nvidia.com/gpu: <%= resource_constraint.gpu_request %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
name: <%= project.name %>
|
name: <%= project.name %>
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ RSpec.describe Projects::Update do
|
|||||||
expect(result.project.branch).to eq('develop')
|
expect(result.project.branch).to eq('develop')
|
||||||
expect(result.project.build_configuration.context_directory).to eq('./app')
|
expect(result.project.build_configuration.context_directory).to eq('./app')
|
||||||
expect(result.project.repository_url).to eq('updated/repo')
|
expect(result.project.repository_url).to eq('updated/repo')
|
||||||
expect(result.project.docker_command).to eq('bundle exec rails s')
|
|
||||||
expect(result.project.build_configuration.dockerfile_path).to eq('docker/Dockerfile')
|
expect(result.project.build_configuration.dockerfile_path).to eq('docker/Dockerfile')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ResourceConstraints::Save do
|
RSpec.describe ResourceConstraints::Save do
|
||||||
let(:project) { create(:project) }
|
let(:service) { create(:service) }
|
||||||
let(:resource_constraint) { build(:resource_constraint, :with_project, constrainable: project) }
|
let(:resource_constraint) { build(:resource_constraint, service: service) }
|
||||||
|
|
||||||
describe '.call' do
|
describe '.call' do
|
||||||
context 'with valid CPU core values' do
|
context 'with valid CPU core values' do
|
||||||
@@ -106,7 +106,7 @@ RSpec.describe ResourceConstraints::Save do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'with nil CPU values' do
|
context 'with nil CPU values' do
|
||||||
let(:resource_constraint) { ResourceConstraint.new(constrainable: project) }
|
let(:resource_constraint) { ResourceConstraint.new(service: service) }
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{
|
{
|
||||||
memory_request: '512',
|
memory_request: '512',
|
||||||
@@ -165,5 +165,35 @@ RSpec.describe ResourceConstraints::Save do
|
|||||||
expect(resource_constraint.gpu_request).to eq(1)
|
expect(resource_constraint.gpu_request).to eq(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with blank string values' do
|
||||||
|
let(:resource_constraint) do
|
||||||
|
create(:resource_constraint,
|
||||||
|
service: service,
|
||||||
|
cpu_request: 1000,
|
||||||
|
cpu_limit: 2000,
|
||||||
|
memory_request: 512,
|
||||||
|
memory_limit: 1024)
|
||||||
|
end
|
||||||
|
let(:params) do
|
||||||
|
{
|
||||||
|
cpu_request: '',
|
||||||
|
cpu_limit: '',
|
||||||
|
memory_request: '',
|
||||||
|
memory_limit: ''
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { described_class.execute(resource_constraint: resource_constraint, params: params) }
|
||||||
|
|
||||||
|
it 'converts blank strings to nil' do
|
||||||
|
result = subject
|
||||||
|
expect(result).to be_success
|
||||||
|
expect(resource_constraint.cpu_request).to be_nil
|
||||||
|
expect(resource_constraint.cpu_limit).to be_nil
|
||||||
|
expect(resource_constraint.memory_request).to be_nil
|
||||||
|
expect(resource_constraint.memory_limit).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,38 +2,29 @@
|
|||||||
#
|
#
|
||||||
# Table name: resource_constraints
|
# Table name: resource_constraints
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# constrainable_type :string not null
|
# cpu_limit :bigint
|
||||||
# cpu_limit :bigint
|
# cpu_request :bigint
|
||||||
# cpu_request :bigint
|
# gpu_request :integer
|
||||||
# gpu_request :integer
|
# memory_limit :bigint
|
||||||
# memory_limit :bigint
|
# memory_request :bigint
|
||||||
# memory_request :bigint
|
# created_at :datetime not null
|
||||||
# created_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# service_id :bigint not null
|
||||||
# constrainable_id :bigint not null
|
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_resource_constraints_on_constrainable (constrainable_type,constrainable_id)
|
# index_resource_constraints_on_service_id (service_id)
|
||||||
#
|
#
|
||||||
FactoryBot.define do
|
FactoryBot.define do
|
||||||
factory :resource_constraint do
|
factory :resource_constraint do
|
||||||
association :constrainable, factory: :service
|
service
|
||||||
cpu_request { 500 } # 500m
|
cpu_request { 500 } # 500m
|
||||||
cpu_limit { 1000 } # 1 CPU
|
cpu_limit { 1000 } # 1 CPU
|
||||||
memory_request { 536870912 } # 512Mi
|
memory_request { 536870912 } # 512Mi
|
||||||
memory_limit { 1073741824 } # 1Gi
|
memory_limit { 1073741824 } # 1Gi
|
||||||
gpu_request { 0 }
|
gpu_request { 0 }
|
||||||
|
|
||||||
trait :with_project do
|
|
||||||
association :constrainable, factory: :project
|
|
||||||
end
|
|
||||||
|
|
||||||
trait :with_add_on do
|
|
||||||
association :constrainable, factory: :add_on
|
|
||||||
end
|
|
||||||
|
|
||||||
trait :high_resources do
|
trait :high_resources do
|
||||||
cpu_request { 2000 } # 2 CPU
|
cpu_request { 2000 } # 2 CPU
|
||||||
cpu_limit { 4000 } # 4 CPU
|
cpu_limit { 4000 } # 4 CPU
|
||||||
|
|||||||
@@ -2,20 +2,19 @@
|
|||||||
#
|
#
|
||||||
# Table name: resource_constraints
|
# Table name: resource_constraints
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# constrainable_type :string not null
|
# cpu_limit :bigint
|
||||||
# cpu_limit :bigint
|
# cpu_request :bigint
|
||||||
# cpu_request :bigint
|
# gpu_request :integer
|
||||||
# gpu_request :integer
|
# memory_limit :bigint
|
||||||
# memory_limit :bigint
|
# memory_request :bigint
|
||||||
# memory_request :bigint
|
# created_at :datetime not null
|
||||||
# created_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# service_id :bigint not null
|
||||||
# constrainable_id :bigint not null
|
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_resource_constraints_on_constrainable (constrainable_type,constrainable_id)
|
# index_resource_constraints_on_service_id (service_id)
|
||||||
#
|
#
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
@@ -23,217 +22,37 @@ RSpec.describe ResourceConstraint, type: :model do
|
|||||||
let(:resource_constraint) { build(:resource_constraint) }
|
let(:resource_constraint) { build(:resource_constraint) }
|
||||||
|
|
||||||
describe 'associations' do
|
describe 'associations' do
|
||||||
it { is_expected.to belong_to(:constrainable) }
|
it { is_expected.to belong_to(:service) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'validations' do
|
describe 'validations' do
|
||||||
describe 'cpu_request' do
|
it 'validates numericality of resource fields' do
|
||||||
it 'accepts valid integer values' do
|
resource_constraint.cpu_request = -100
|
||||||
resource_constraint.cpu_request = 1000
|
resource_constraint.memory_limit = -500
|
||||||
expect(resource_constraint).to be_valid
|
resource_constraint.gpu_request = -1
|
||||||
end
|
|
||||||
|
|
||||||
it 'accepts nil values' do
|
expect(resource_constraint).not_to be_valid
|
||||||
resource_constraint.cpu_request = nil
|
expect(resource_constraint.errors[:cpu_request]).to be_present
|
||||||
expect(resource_constraint).to be_valid
|
expect(resource_constraint.errors[:memory_limit]).to be_present
|
||||||
end
|
expect(resource_constraint.errors[:gpu_request]).to be_present
|
||||||
|
|
||||||
it 'rejects negative values' do
|
|
||||||
resource_constraint.cpu_request = -100
|
|
||||||
expect(resource_constraint).not_to be_valid
|
|
||||||
expect(resource_constraint.errors[:cpu_request]).to be_present
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'cpu_limit' do
|
|
||||||
it 'accepts valid integer values' do
|
|
||||||
resource_constraint.cpu_limit = 2000
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'accepts nil values' do
|
|
||||||
resource_constraint.cpu_limit = nil
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'rejects negative values' do
|
|
||||||
resource_constraint.cpu_limit = -100
|
|
||||||
expect(resource_constraint).not_to be_valid
|
|
||||||
expect(resource_constraint.errors[:cpu_limit]).to be_present
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'must be greater than or equal to cpu_request' do
|
|
||||||
resource_constraint.cpu_request = 1000
|
|
||||||
resource_constraint.cpu_limit = 500
|
|
||||||
expect(resource_constraint).not_to be_valid
|
|
||||||
expect(resource_constraint.errors[:cpu_limit]).to include("must be greater than or equal to CPU request")
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is valid when cpu_limit equals cpu_request' do
|
|
||||||
resource_constraint.cpu_request = 1000
|
|
||||||
resource_constraint.cpu_limit = 1000
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is valid when cpu_limit is greater than cpu_request' do
|
|
||||||
resource_constraint.cpu_request = 500
|
|
||||||
resource_constraint.cpu_limit = 1000
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is valid when cpu_request is nil' do
|
|
||||||
resource_constraint.cpu_request = nil
|
|
||||||
resource_constraint.cpu_limit = 1000
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is valid when cpu_limit is nil' do
|
|
||||||
resource_constraint.cpu_request = 1000
|
|
||||||
resource_constraint.cpu_limit = nil
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'memory_request' do
|
|
||||||
it 'accepts valid integer values' do
|
|
||||||
resource_constraint.memory_request = 1073741824
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'accepts nil values' do
|
|
||||||
resource_constraint.memory_request = nil
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'rejects negative values' do
|
|
||||||
resource_constraint.memory_request = -100
|
|
||||||
expect(resource_constraint).not_to be_valid
|
|
||||||
expect(resource_constraint.errors[:memory_request]).to be_present
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'memory_limit' do
|
|
||||||
it 'accepts valid integer values' do
|
|
||||||
resource_constraint.memory_limit = 2147483648
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'accepts nil values' do
|
|
||||||
resource_constraint.memory_limit = nil
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'rejects negative values' do
|
|
||||||
resource_constraint.memory_limit = -100
|
|
||||||
expect(resource_constraint).not_to be_valid
|
|
||||||
expect(resource_constraint.errors[:memory_limit]).to be_present
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'must be greater than or equal to memory_request' do
|
|
||||||
resource_constraint.memory_request = 1073741824 # 1Gi
|
|
||||||
resource_constraint.memory_limit = 536870912 # 512Mi
|
|
||||||
expect(resource_constraint).not_to be_valid
|
|
||||||
expect(resource_constraint.errors[:memory_limit]).to include("must be greater than or equal to memory request")
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is valid when memory_limit equals memory_request' do
|
|
||||||
resource_constraint.memory_request = 1073741824
|
|
||||||
resource_constraint.memory_limit = 1073741824
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'is valid when memory_limit is greater than memory_request' do
|
|
||||||
resource_constraint.memory_request = 536870912
|
|
||||||
resource_constraint.memory_limit = 1073741824
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'gpu_request' do
|
|
||||||
it 'accepts valid integer values' do
|
|
||||||
resource_constraint.gpu_request = 2
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'accepts nil values' do
|
|
||||||
resource_constraint.gpu_request = nil
|
|
||||||
expect(resource_constraint).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'rejects negative values' do
|
|
||||||
resource_constraint.gpu_request = -1
|
|
||||||
expect(resource_constraint).not_to be_valid
|
|
||||||
expect(resource_constraint.errors[:gpu_request]).to be_present
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'string setters' do
|
describe 'formatted methods' do
|
||||||
it 'converts cpu string values to integers' do
|
it 'formats CPU values using millicores' do
|
||||||
resource_constraint.cpu_request = "500m"
|
|
||||||
resource_constraint.cpu_limit = "2"
|
|
||||||
expect(resource_constraint.cpu_request).to eq(500)
|
|
||||||
expect(resource_constraint.cpu_limit).to eq(2000)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'converts memory string values to integers' do
|
|
||||||
resource_constraint.memory_request = "512Mi"
|
|
||||||
resource_constraint.memory_limit = "1Gi"
|
|
||||||
expect(resource_constraint.memory_request).to eq(536870912)
|
|
||||||
expect(resource_constraint.memory_limit).to eq(1073741824)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'accepts integer values directly' do
|
|
||||||
resource_constraint.cpu_request = 1000
|
|
||||||
resource_constraint.memory_request = 1073741824
|
|
||||||
expect(resource_constraint.cpu_request).to eq(1000)
|
|
||||||
expect(resource_constraint.memory_request).to eq(1073741824)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'formatted getters' do
|
|
||||||
it 'returns formatted cpu values' do
|
|
||||||
resource_constraint.cpu_request = 500
|
resource_constraint.cpu_request = 500
|
||||||
resource_constraint.cpu_limit = 2000
|
resource_constraint.cpu_limit = 2000
|
||||||
|
|
||||||
expect(resource_constraint.cpu_request_formatted).to eq("500m")
|
expect(resource_constraint.cpu_request_formatted).to eq("500m")
|
||||||
expect(resource_constraint.cpu_limit_formatted).to eq("2000m")
|
expect(resource_constraint.cpu_limit_formatted).to eq("2000m")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns formatted memory values' do
|
it 'formats memory values using Kubernetes units' do
|
||||||
resource_constraint.memory_request = 536870912 # 512Mi
|
resource_constraint.memory_request = 536870912
|
||||||
resource_constraint.memory_limit = 1073741824 # 1Gi
|
resource_constraint.memory_limit = 1073741824
|
||||||
expect(resource_constraint.memory_request_formatted).to eq("512.0Mi")
|
|
||||||
expect(resource_constraint.memory_limit_formatted).to eq("1.0Gi")
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns nil for nil values' do
|
expect(resource_constraint.memory_request_formatted).to eq("512Mi")
|
||||||
resource_constraint.cpu_request = nil
|
expect(resource_constraint.memory_limit_formatted).to eq("1Gi")
|
||||||
resource_constraint.memory_limit = nil
|
|
||||||
expect(resource_constraint.cpu_request_formatted).to be_nil
|
|
||||||
expect(resource_constraint.memory_limit_formatted).to be_nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'polymorphic associations' do
|
|
||||||
it 'can be associated with a Service' do
|
|
||||||
service = create(:service)
|
|
||||||
resource_constraint = create(:resource_constraint, constrainable: service)
|
|
||||||
expect(resource_constraint.constrainable).to eq(service)
|
|
||||||
expect(service.resource_constraint).to eq(resource_constraint)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'can be associated with a Project' do
|
|
||||||
project = create(:project)
|
|
||||||
resource_constraint = create(:resource_constraint, constrainable: project)
|
|
||||||
expect(resource_constraint.constrainable).to eq(project)
|
|
||||||
expect(project.resource_constraint).to eq(resource_constraint)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'can be associated with an AddOn' do
|
|
||||||
add_on = create(:add_on)
|
|
||||||
resource_constraint = create(:resource_constraint, constrainable: add_on)
|
|
||||||
expect(resource_constraint.constrainable).to eq(add_on)
|
|
||||||
expect(add_on.resource_constraint).to eq(resource_constraint)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user