Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions app/grpc/flow_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ class FlowHandler < Tucana::Sagittarius::FlowService::Service

def self.update_runtime(runtime)
flows = []
runtime.projects.each do |project|
project.flows.each do |flow|
runtime.project_assignments.compatible.each do |assignment|
assignment.project.flows.each do |flow|
flows << flow.to_grpc
end
end

# TODO: Add check to check for primary runtime conflicts

send_update(
Tucana::Sagittarius::FlowResponse.new(
flows: Tucana::Shared::Flows.new(
Expand Down
14 changes: 14 additions & 0 deletions app/jobs/update_runtime_compatibility_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class UpdateRuntimeCompatibilityJob < ApplicationJob
def perform(conditions)
assignments = NamespaceProjectRuntimeAssignment.where(conditions)

assignments.each do |assignment|
res = Runtimes::CheckRuntimeCompatibilityService.new(assignment.runtime, assignment.namespace_project).execute

assignment.compatible = res.success?
assignment.save!
end
end
end
2 changes: 2 additions & 0 deletions app/models/namespace_project_runtime_assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class NamespaceProjectRuntimeAssignment < ApplicationRecord
validate :validate_namespaces, if: :runtime_changed?
validate :validate_namespaces, if: :namespace_project_changed?

scope :compatible, -> { where(compatible: true) }

private

def validate_namespaces
Expand Down
4 changes: 4 additions & 0 deletions app/services/error_code.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ def self.error_codes
primary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' },
secondary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' },
tertiary_level_exceeds_parameters: { description: '', deprecation_reason: 'Outdated concept' },

missing_primary_runtime: { description: 'The project is missing a primary runtime' },
missing_definition: { description: 'The primary runtime has more definitions than this one' },
outdated_definition: { description: 'The primary runtime has a newer definition than this one' },
}
end
end
Expand Down
2 changes: 2 additions & 0 deletions app/services/namespaces/projects/assign_runtimes_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def execute
)
end

UpdateRuntimeCompatibilityJob.perform_later({ namespace_project_id: namespace_project.id })

AuditService.audit(
:project_runtimes_assigned,
author_id: current_authentication.user.id,
Expand Down
63 changes: 63 additions & 0 deletions app/services/runtimes/check_runtime_compatibility_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

module Runtimes
class CheckRuntimeCompatibilityService
attr_reader :runtime, :namespace_project

def initialize(runtime, namespace_project)
@runtime = runtime
@namespace_project = namespace_project
end

def execute
primary_runtime = namespace_project.primary_runtime

if primary_runtime.nil?
return ServiceResponse.error(message: 'No primary runtime given',
error_code: :missing_primary_runtime)
end

{ DataType => :identifier, FlowType => :identifier,
RuntimeFunctionDefinition => :runtime_name }.each do |model, identifier_field|
res = check_versions(model, identifier_field)
return res if res.error?
end
ServiceResponse.success(message: 'Runtime is compatible', payload: runtime)
end

def check_versions(model, identifier_field = :identifier)
to_check_types = model.where(runtime: runtime)
primary_types = model.where(runtime: namespace_project.primary_runtime)

if to_check_types.size < primary_types.size
return ServiceResponse.error(message: "#{model} amount dont match",
error_code: :missing_definition)
end

primary_types.each do |curr_type|
to_check = model.find_by(runtime: runtime, identifier_field => curr_type.send(identifier_field))
if to_check.nil?
return ServiceResponse.error(message: "#{model} is not present in new runtime",
error_code: :missing_definition)
end

result = compare_version(curr_type.parsed_version, to_check.parsed_version)

unless result
return ServiceResponse.error(message: "#{model} is outdated",
error_code: :outdated_definition)
end
end
ServiceResponse.success
end

# true: compatible
# false: not compatible
def compare_version(primary_version, to_check_version)
return false if primary_version.segments[0] != to_check_version.segments[0]
return false if primary_version.segments[1] > to_check_version.segments[1]

true
end
end
end
2 changes: 2 additions & 0 deletions app/services/runtimes/data_types/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ def execute
end
end

UpdateRuntimeCompatibilityJob.perform_later({ runtime_id: current_runtime.id })

ServiceResponse.success(message: 'Updated data types', payload: data_types)
end
end
Expand Down
2 changes: 2 additions & 0 deletions app/services/runtimes/flow_types/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ def execute
end
end

UpdateRuntimeCompatibilityJob.perform_later({ runtime_id: current_runtime.id })

ServiceResponse.success(message: 'Updated data types', payload: flow_types)
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def execute
end
end

UpdateRuntimeCompatibilityJob.perform_later({ runtime_id: current_runtime.id })

ServiceResponse.success(message: 'Updated runtime function definition', payload: runtime_function_definitions)
end
end
Expand Down
3 changes: 3 additions & 0 deletions config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test

# Use test adapter for ActiveJob to avoid connection leaks from GoodJob
config.active_job.queue_adapter = :test

# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddIsCompatibleToPrimaryRuntime < Code0::ZeroTrack::Database::Migration[1.0]
def change
add_column :namespace_project_runtime_assignments, :compatible, :boolean, null: false, default: false
end
end
1 change: 1 addition & 0 deletions db/schema_migrations/20251109141754
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d8a8171eddb13a575d76518ba6092f9f96d3b1e238d04da86104efc117ffbf14
3 changes: 2 additions & 1 deletion db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,8 @@ CREATE TABLE namespace_project_runtime_assignments (
runtime_id bigint NOT NULL,
namespace_project_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
updated_at timestamp with time zone NOT NULL,
compatible boolean DEFAULT false NOT NULL
);

CREATE SEQUENCE namespace_project_runtime_assignments_id_seq
Expand Down
3 changes: 3 additions & 0 deletions docs/graphql/enum/errorcodeenum.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,19 @@ Represents the available error responses
| `LOADING_IDENTITY_FAILED` | Failed to load user identity from external provider |
| `MFA_FAILED` | Invalid MFA data provided |
| `MFA_REQUIRED` | MFA is required |
| `MISSING_DEFINITION` | The primary runtime has more definitions than this one |
| `MISSING_IDENTITY_DATA` | This external identity is missing data |
| `MISSING_PARAMETER` | Not all required parameters are present |
| `MISSING_PERMISSION` | The user is not permitted to perform this operation |
| `MISSING_PRIMARY_RUNTIME` | The project is missing a primary runtime |
| `NAMESPACE_MEMBER_NOT_FOUND` | The namespace member with the given identifier was not found |
| `NAMESPACE_NOT_FOUND` | The namespace with the given identifier was not found |
| `NAMESPACE_PROJECT_NOT_FOUND` | The namespace project with the given identifier was not found |
| `NAMESPACE_ROLE_NOT_FOUND` | The namespace role with the given identifier was not found |
| `NO_FREE_LICENSE_SEATS` | There are no free license seats to complete this operation |
| `NO_PRIMARY_RUNTIME` | The project does not have a primary runtime |
| `ORGANIZATION_NOT_FOUND` | The organization with the given identifier was not found |
| `OUTDATED_DEFINITION` | The primary runtime has a newer definition than this one |
| `PRIMARY_LEVEL_NOT_FOUND` | **Deprecated:** Outdated concept |
| `PROJECT_NOT_FOUND` | The namespace project with the given identifier was not found |
| `REGISTRATION_DISABLED` | Self-registration is disabled |
Expand Down
32 changes: 32 additions & 0 deletions spec/jobs/update_runtime_compatibility_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe UpdateRuntimeCompatibilityJob do
include ActiveJob::TestHelper

it 'calls the compatibility service for each assignment and updates compatible' do
assignment1 = create(:namespace_project_runtime_assignment, compatible: false)
assignment2 = create(:namespace_project_runtime_assignment, compatible: false)

success_res = ServiceResponse.success
err_response = ServiceResponse.error(error_code: :outdated_definition, message: 'Runtime is outdated')

service1 = instance_double(Runtimes::CheckRuntimeCompatibilityService, execute: success_res)
service2 = instance_double(Runtimes::CheckRuntimeCompatibilityService, execute: err_response)

allow(Runtimes::CheckRuntimeCompatibilityService).to receive(:new)
.with(assignment1.runtime, assignment1.namespace_project).and_return(service1)
allow(Runtimes::CheckRuntimeCompatibilityService).to receive(:new)
.with(assignment2.runtime, assignment2.namespace_project).and_return(service2)

conditions = { id: [assignment1.id, assignment2.id] }

perform_enqueued_jobs do
described_class.perform_later(conditions)
end

expect(assignment1.reload.compatible).to be true
expect(assignment2.reload.compatible).to be false
end
end
61 changes: 61 additions & 0 deletions spec/services/runtimes/check_runtime_compatibility_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Runtimes::CheckRuntimeCompatibilityService do
subject(:service_response) do
described_class.new(runtime, project).execute
end

let(:primary_runtime) { create(:runtime) }
let(:runtime) { create(:runtime) }
let(:project) { create(:namespace_project, primary_runtime: primary_runtime) }

context 'when primary runtime is missing' do
let(:project) { create(:namespace_project) }

it 'returns an error with :missing_primary_runtime payload' do
expect(service_response).to be_error
expect(service_response.payload[:error_code]).to eq(:missing_primary_runtime)
end
end

context 'when a model has fewer types on the runtime than on the primary' do
before do
create(:data_type, runtime: primary_runtime)
end

it 'returns missing_datatypes error' do
expect(service_response.error?).to be true
expect(service_response.payload[:error_code]).to eq(:missing_definition)
end
end

context 'when secondary runtime has outdated definitions' do
before do
create(:data_type, runtime: primary_runtime, identifier: 'dt1', version: '1.3.0')
create(:data_type, runtime: runtime, identifier: 'dt1', version: '1.2.0')
end

it 'returns outdated_data_type error' do
expect(service_response.error?).to be true
expect(service_response.payload[:error_code]).to eq(:outdated_definition)
end
end

context 'when all models are compatible' do
before do
create(:data_type, runtime: primary_runtime, identifier: 'dt1', version: '1.3.0')
create(:data_type, runtime: runtime, identifier: 'dt1', version: '1.3.0')
create(:flow_type, runtime: primary_runtime, identifier: 'ft1', version: '2.1.0')
create(:flow_type, runtime: runtime, identifier: 'ft1', version: '2.2.0')
create(:runtime_function_definition, runtime_name: 'rfd1', runtime: primary_runtime, version: '3.0.0')
create(:runtime_function_definition, runtime_name: 'rfd1', runtime: runtime, version: '3.1.0')
end

it 'returns success with the runtime as payload' do
expect(service_response).to be_success
expect(service_response.payload).to eq(runtime)
end
end
end