Skip to content
Open
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
18 changes: 18 additions & 0 deletions app/graphql/types/runtime_feature_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module Types
class RuntimeFeatureType < Types::BaseObject
description 'Represents a runtime feature'

authorize :read_runtime

field :runtime_status, Types::RuntimeStatusType,
null: false, description: 'The runtime status this feature belongs to'

field :descriptions, [Types::TranslationType], null: true, description: 'Description of the function'
field :names, [Types::TranslationType], null: true, description: 'Name of the function'

id_field RuntimeFeature
timestamps
end
end
12 changes: 12 additions & 0 deletions app/graphql/types/runtime_status_configuration_endpoint_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

module Types
class RuntimeStatusConfigurationEndpointType < Types::BaseObject
description 'Detailed information about a runtime status'

field :endpoint, String, null: false, description: 'The endpoint URL of the runtime'

id_field ::RuntimeStatusConfiguration
timestamps
end
end
15 changes: 15 additions & 0 deletions app/graphql/types/runtime_status_configuration_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module Types
class RuntimeStatusConfigurationType < Types::BaseUnion
description 'Detailed configuration about a runtime status, either: endpoint, ...'

possible_types Types::RuntimeStatusConfigurationEndpointType

def self.resolve_type(object, _context)
return Types::RuntimeStatusConfigurationEndpointType if object.endpoint.present?

raise "Unknown RuntimeStatusInformation type: #{object.class.name}"
end
end
end
10 changes: 10 additions & 0 deletions app/graphql/types/runtime_status_enum.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

module Types
class RuntimeStatusEnum < BaseEnum
description 'Represent all available aquila statuses'

value :CONNECTED, 'No problem with the connection to aquila', value: :connected
value :DISCONNECTED, 'The runtime is disconnected, cause unknown', value: :disconnected
end
end
12 changes: 12 additions & 0 deletions app/graphql/types/runtime_status_status_enum.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

module Types
class RuntimeStatusStatusEnum < Types::BaseEnum
description 'The enum status of the detailed status'

value :NOT_RESPONDING, 'The runtime is not responding to heartbeats', value: 'NOT_RESPONDING'
value :NOT_READY, 'The runtime is not ready to compute stuff', value: 'NOT_READY'
value :RUNNING, 'The runtime is running and healthy', value: 'RUNNING'
value :STOPPED, 'The runtime has been stopped', value: 'STOPPED'
end
end
24 changes: 20 additions & 4 deletions app/graphql/types/runtime_status_type.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
# frozen_string_literal: true

module Types
class RuntimeStatusType < BaseEnum
description 'Represent all available types of statuses of a runtime'
class RuntimeStatusType < Types::BaseObject
description 'A runtime status information entry'

value :CONNECTED, 'No problem with connection, everything works as expected', value: 'connected'
value :DISCONNECTED, 'The runtime is disconnected, cause unknown', value: 'disconnected'
field :configurations, Types::RuntimeStatusConfigurationType.connection_type,
null: false,
description: 'The detailed configuration entries for this runtime status (only for adapters)',
method: :runtime_status_configurations
field :identifier, String, null: false, description: 'The unique identifier for this runtime status'
field :last_heartbeat, Types::TimeType, null: true,
description: 'The timestamp of the last heartbeat received from the runtime'
field :runtime_features, [Types::RuntimeFeatureType], null: false,
description: 'The set of features supported by the runtime'
field :status, Types::RuntimeStatusStatusEnum,
null: false,
description: 'The current status of the runtime (e.g. running, stopped)'
field :type, Types::RuntimeStatusTypeEnum,
null: false,
description: 'The type of runtime status information (e.g. adapter, execution)', method: :status_type

id_field RuntimeStatus
timestamps
end
end
10 changes: 10 additions & 0 deletions app/graphql/types/runtime_status_type_enum.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

module Types
class RuntimeStatusTypeEnum < Types::BaseEnum
description 'The type of runtime status'

value :ADAPTER, 'Indicates that the runtime status is related to an adapter.', value: 'adapter'
value :EXECUTION, 'Indicates that the runtime status is related to an execution.', value: 'execution'
end
end
14 changes: 14 additions & 0 deletions app/graphql/types/runtime_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ class RuntimeType < Types::BaseObject
description: 'Projects associated with the runtime'
field :status, Types::RuntimeStatusType, null: false, description: 'The status of the runtime'

field :statuses, Types::RuntimeStatusType.connection_type, null: false,
description: 'Statuses of the runtime',
method: :runtime_statuses
field :token, String, null: true, description: 'Token belonging to the runtime, only present on creation'

expose_abilities %i[
Expand All @@ -29,6 +32,17 @@ class RuntimeType < Types::BaseObject
id_field Runtime
timestamps

# If the last heartbeat was within the last 10 minutes, consider the runtime as 'running'
def status
last_heartbeat = object.last_heartbeat

if last_heartbeat && last_heartbeat >= 10.minutes.ago
:connected
else
:disconnected
end
end

def token
object.token if object.token_previously_changed?
end
Expand Down
15 changes: 4 additions & 11 deletions app/grpc/flow_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ class FlowHandler < Tucana::Sagittarius::FlowService::Service
grpc_stream :update

def self.update_runtime(runtime)
runtime.last_heartbeat = Time.zone.now
runtime.save!

flows = []
runtime.project_assignments.compatible.each do |assignment|
assignment.namespace_project.flows.each do |flow|
Expand All @@ -25,22 +28,12 @@ def self.update_runtime(runtime)
)
end

def self.update_started(runtime_id)
runtime = Runtime.find(runtime_id)
runtime.connected!
runtime.save

def self.update_started(_runtime_id)
logger.info(message: 'Runtime connected', runtime_id: runtime.id)

update_runtime(runtime)
end

def self.update_died(runtime_id)
runtime = Runtime.find(runtime_id)
runtime.disconnected!
runtime.save
end

def self.encoders = { update: ->(grpc_object) { Tucana::Sagittarius::FlowResponse.encode(grpc_object) } }

def self.decoders = { update: ->(string) { Tucana::Sagittarius::FlowResponse.decode(string) } }
Expand Down
19 changes: 19 additions & 0 deletions app/grpc/runtime_status_handler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

class RuntimeStatusHandler < Tucana::Sagittarius::RuntimeStatusService::Service
include Code0::ZeroTrack::Loggable
include GrpcHandler

def update(request, _call)
current_runtime = Runtime.find(Code0::ZeroTrack::Context.current[:runtime][:id])

response = Runtimes::Grpc::RuntimeStatusUpdateService.new(
runtime: current_runtime,
status_info: request.send(request.status)
).execute

logger.debug("RuntimeFunctionHandler#update response: #{response.inspect}")

Tucana::Sagittarius::RuntimeStatusUpdateResponse.new(success: response.success?)
end
end
12 changes: 1 addition & 11 deletions app/models/runtime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,11 @@
class Runtime < ApplicationRecord
include TokenAttr

STATUS_TYPES = {
disconnected: 0,
connected: 1,
}.with_indifferent_access

belongs_to :namespace, optional: true

token_attr :token, prefix: 's_rt_', length: 48

enum :status, STATUS_TYPES, default: :disconnected

validates :status, presence: true,
inclusion: {
in: STATUS_TYPES.keys.map(&:to_s),
}
has_many :runtime_statuses, inverse_of: :runtime

has_many :project_assignments, class_name: 'NamespaceProjectRuntimeAssignment', inverse_of: :runtime
has_many :projects, class_name: 'NamespaceProject', through: :project_assignments, source: :namespace_project,
Expand Down
7 changes: 7 additions & 0 deletions app/models/runtime_feature.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class RuntimeFeature < ApplicationRecord
belongs_to :runtime_status, inverse_of: :runtime_features
has_many :names, -> { by_purpose(:name) }, class_name: 'Translation', as: :owner, inverse_of: :owner
has_many :descriptions, -> { by_purpose(:description) }, class_name: 'Translation', as: :owner, inverse_of: :owner
end
38 changes: 38 additions & 0 deletions app/models/runtime_status.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

class RuntimeStatus < ApplicationRecord
belongs_to :runtime, inverse_of: :runtime_statuses
has_many :runtime_status_configurations, inverse_of: :runtime_status
has_many :runtime_features, inverse_of: :runtime_status

STATUS_TYPES = {
not_responding: 0,
not_ready: 1,
running: 2,
stopped: 3,
}.with_indifferent_access

enum :status, STATUS_TYPES, default: :stopped

STATUS_TYPE_TYPES = {
adapter: 0,
execution: 1,
}.with_indifferent_access

enum :status_type, STATUS_TYPE_TYPES

validates :identifier, presence: true,
allow_blank: false,
uniqueness: { case_sensitive: false, scope: :runtime_id }

validate :runtime_status_informations_only_for_adapter

private

def runtime_status_informations_only_for_adapter
return if adapter?
return if runtime_status_configurations.empty?

errors.add(:runtime_status_informations, :only_allowed_for_adapters)
end
end
8 changes: 8 additions & 0 deletions app/models/runtime_status_configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

class RuntimeStatusConfiguration < ApplicationRecord
belongs_to :runtime_status, inverse_of: :runtime_status_configurations

validates :endpoint, presence: true,
allow_blank: false
end
3 changes: 3 additions & 0 deletions app/services/error_code.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ def self.error_codes
function_value_not_found: { description: 'The id for the function value node does not exist' },
invalid_node_parameter: { description: 'The node parameter is invalid' },
invalid_node_function: { description: 'The node function is invalid' },
invalid_runtime_status: { description: 'The runtime status is invalid because of active model errors' },
invalid_runtime_status_configuration: { description: 'The runtime status configuration is invalid because of active model errors' },
invalid_runtime_feature: { description: 'The runtime feature is invalid because of active model errors' },

primary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' },
secondary_level_not_found: { description: '', deprecation_reason: 'Outdated concept' },
Expand Down
89 changes: 89 additions & 0 deletions app/services/runtimes/grpc/runtime_status_update_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# frozen_string_literal: true

module Runtimes
module Grpc
class RuntimeStatusUpdateService
include Sagittarius::Database::Transactional
include Code0::ZeroTrack::Loggable
include Runtimes::Grpc::TranslationUpdateHelper

attr_reader :runtime, :status_info

def initialize(runtime:, status_info:)
@runtime = runtime
@status_info = status_info
end

def execute
transactional do |t|
runtime.last_heartbeat = Time.zone.now

unless runtime.save
t.rollback_and_return ServiceResponse.error(
message: 'Failed to update runtime heartbeat',
error_code: :invalid_runtime,
details: runtime.errors
)
end

db_status = RuntimeStatus.find_or_initialize_by(runtime: runtime,
identifier: status_info.identifier)

db_status.last_heartbeat = Time.zone.at(status_info.timestamp.to_i)
db_status.status_type = if status_info.is_a?(Tucana::Shared::AdapterRuntimeStatus)
:adapter
else
:execution
end

db_status.runtime_features.clear

status_info.features.each do |feature|
db_feature = db_status.runtime_features.new

db_feature.names = update_translations(feature.name, db_feature.names)
db_feature.descriptions = update_translations(feature.description, db_feature.descriptions)

next if db_feature.save

t.rollback_and_return! ServiceResponse.error(
message: 'Failed to save runtime feature',
error_code: :invalid_runtime_feature,
details: db_feature.errors
)
end

db_status.status = status_info.status.downcase

db_configs = db_status.runtime_status_configurations.first(status_info.configurations.size)

status_info.configurations.each_with_index do |config, index|
db_configs[index] ||= db_status.runtime_status_configurations.build

db_configs[index].endpoint = config.endpoint

next if db_configs[index].save

t.rollback_and_return! ServiceResponse.error(
message: 'Failed to save runtime status configuration',
error_code: :invalid_runtime_status_configuration,
details: db_configs.errors
)
end

unless db_status.save
t.rollback_and_return! ServiceResponse.error(
message: 'Failed to save runtime status',
error_code: :invalid_runtime_status,
details: db_status.errors
)
end

return ServiceResponse.success(
message: 'Updated runtime status'
)
end
end
end
end
end
Loading