kubemq 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +30 -0
- data/LICENSE +201 -0
- data/README.md +237 -0
- data/lib/kubemq/base_client.rb +180 -0
- data/lib/kubemq/cancellation_token.rb +63 -0
- data/lib/kubemq/channel_info.rb +84 -0
- data/lib/kubemq/configuration.rb +247 -0
- data/lib/kubemq/cq/client.rb +446 -0
- data/lib/kubemq/cq/command_message.rb +59 -0
- data/lib/kubemq/cq/command_received.rb +52 -0
- data/lib/kubemq/cq/command_response.rb +44 -0
- data/lib/kubemq/cq/command_response_message.rb +58 -0
- data/lib/kubemq/cq/commands_subscription.rb +45 -0
- data/lib/kubemq/cq/queries_subscription.rb +45 -0
- data/lib/kubemq/cq/query_message.rb +70 -0
- data/lib/kubemq/cq/query_received.rb +52 -0
- data/lib/kubemq/cq/query_response.rb +59 -0
- data/lib/kubemq/cq/query_response_message.rb +67 -0
- data/lib/kubemq/error_codes.rb +181 -0
- data/lib/kubemq/errors/error_mapper.rb +134 -0
- data/lib/kubemq/errors.rb +276 -0
- data/lib/kubemq/interceptors/auth_interceptor.rb +78 -0
- data/lib/kubemq/interceptors/error_mapping_interceptor.rb +75 -0
- data/lib/kubemq/interceptors/metrics_interceptor.rb +95 -0
- data/lib/kubemq/interceptors/retry_interceptor.rb +119 -0
- data/lib/kubemq/proto/kubemq_pb.rb +43 -0
- data/lib/kubemq/proto/kubemq_services_pb.rb +35 -0
- data/lib/kubemq/pubsub/client.rb +475 -0
- data/lib/kubemq/pubsub/event_message.rb +52 -0
- data/lib/kubemq/pubsub/event_received.rb +48 -0
- data/lib/kubemq/pubsub/event_send_result.rb +31 -0
- data/lib/kubemq/pubsub/event_sender.rb +112 -0
- data/lib/kubemq/pubsub/event_store_message.rb +53 -0
- data/lib/kubemq/pubsub/event_store_received.rb +47 -0
- data/lib/kubemq/pubsub/event_store_result.rb +33 -0
- data/lib/kubemq/pubsub/event_store_sender.rb +164 -0
- data/lib/kubemq/pubsub/events_store_subscription.rb +81 -0
- data/lib/kubemq/pubsub/events_subscription.rb +43 -0
- data/lib/kubemq/queues/client.rb +366 -0
- data/lib/kubemq/queues/downstream_receiver.rb +247 -0
- data/lib/kubemq/queues/queue_message.rb +99 -0
- data/lib/kubemq/queues/queue_message_received.rb +148 -0
- data/lib/kubemq/queues/queue_poll_request.rb +77 -0
- data/lib/kubemq/queues/queue_poll_response.rb +138 -0
- data/lib/kubemq/queues/queue_send_result.rb +49 -0
- data/lib/kubemq/queues/upstream_sender.rb +180 -0
- data/lib/kubemq/server_info.rb +57 -0
- data/lib/kubemq/subscription.rb +98 -0
- data/lib/kubemq/telemetry/otel.rb +64 -0
- data/lib/kubemq/telemetry/semconv.rb +51 -0
- data/lib/kubemq/transport/channel_manager.rb +212 -0
- data/lib/kubemq/transport/converter.rb +287 -0
- data/lib/kubemq/transport/grpc_transport.rb +411 -0
- data/lib/kubemq/transport/message_buffer.rb +105 -0
- data/lib/kubemq/transport/reconnect_manager.rb +111 -0
- data/lib/kubemq/transport/state_machine.rb +150 -0
- data/lib/kubemq/types.rb +80 -0
- data/lib/kubemq/validation/validator.rb +216 -0
- data/lib/kubemq/version.rb +6 -0
- data/lib/kubemq.rb +118 -0
- metadata +138 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KubeMQ
|
|
4
|
+
# Information about a KubeMQ broker returned by {BaseClient#ping}.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# info = client.ping
|
|
8
|
+
# puts "Connected to #{info.host} v#{info.version}, up #{info.server_up_time_seconds}s"
|
|
9
|
+
#
|
|
10
|
+
# @see BaseClient#ping
|
|
11
|
+
class ServerInfo
|
|
12
|
+
# @!attribute [r] host
|
|
13
|
+
# @return [String] broker hostname or IP address
|
|
14
|
+
# @!attribute [r] version
|
|
15
|
+
# @return [String] broker software version
|
|
16
|
+
# @!attribute [r] server_start_time
|
|
17
|
+
# @return [Integer] broker start time as a Unix timestamp
|
|
18
|
+
# @!attribute [r] server_up_time_seconds
|
|
19
|
+
# @return [Integer] broker uptime in seconds
|
|
20
|
+
attr_reader :host, :version, :server_start_time, :server_up_time_seconds
|
|
21
|
+
|
|
22
|
+
# Creates a new ServerInfo instance.
|
|
23
|
+
#
|
|
24
|
+
# @param host [String] broker hostname or IP address
|
|
25
|
+
# @param version [String] broker software version
|
|
26
|
+
# @param server_start_time [Integer] broker start time as a Unix timestamp
|
|
27
|
+
# @param server_up_time_seconds [Integer] broker uptime in seconds
|
|
28
|
+
def initialize(host:, version:, server_start_time:, server_up_time_seconds:)
|
|
29
|
+
@host = host
|
|
30
|
+
@version = version
|
|
31
|
+
@server_start_time = server_start_time
|
|
32
|
+
@server_up_time_seconds = server_up_time_seconds
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Constructs a {ServerInfo} from a protobuf ping result.
|
|
36
|
+
#
|
|
37
|
+
# @api private
|
|
38
|
+
# @param ping_result [Kubemq::PingResult] protobuf ping response
|
|
39
|
+
# @return [ServerInfo]
|
|
40
|
+
def self.from_proto(ping_result)
|
|
41
|
+
new(
|
|
42
|
+
host: ping_result.Host,
|
|
43
|
+
version: ping_result.Version,
|
|
44
|
+
server_start_time: ping_result.ServerStartTime,
|
|
45
|
+
server_up_time_seconds: ping_result.ServerUpTimeSeconds
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns a human-readable summary of the server information.
|
|
50
|
+
#
|
|
51
|
+
# @return [String]
|
|
52
|
+
def to_s
|
|
53
|
+
"ServerInfo(host=#{@host}, version=#{@version}, " \
|
|
54
|
+
"up_time=#{@server_up_time_seconds}s)"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KubeMQ
|
|
4
|
+
# Handle for an active subscription running on a background thread.
|
|
5
|
+
#
|
|
6
|
+
# Returned by +subscribe_to_*+ methods on {PubSubClient} and {CQClient}.
|
|
7
|
+
# Use {#cancel} to stop the subscription, {#active?} to check its state,
|
|
8
|
+
# and {#wait} or {#join} to block until the subscription thread exits.
|
|
9
|
+
#
|
|
10
|
+
# @note This class is thread-safe. Status transitions and error state are
|
|
11
|
+
# protected by a mutex.
|
|
12
|
+
#
|
|
13
|
+
# @example Cancel a subscription gracefully
|
|
14
|
+
# subscription = client.subscribe_to_events(sub) { |e| process(e) }
|
|
15
|
+
# # ... later ...
|
|
16
|
+
# subscription.cancel
|
|
17
|
+
# subscription.wait(5) # wait up to 5 seconds for thread to exit
|
|
18
|
+
#
|
|
19
|
+
# @see CancellationToken
|
|
20
|
+
# @see PubSubClient#subscribe_to_events
|
|
21
|
+
# @see CQClient#subscribe_to_commands
|
|
22
|
+
class Subscription
|
|
23
|
+
# @!attribute [r] status
|
|
24
|
+
# @return [Symbol] current subscription state — +:active+, +:cancelled+,
|
|
25
|
+
# +:error+, or +:closed+
|
|
26
|
+
# @!attribute [r] last_error
|
|
27
|
+
# @return [StandardError, nil] the most recent error, or +nil+ if none
|
|
28
|
+
attr_reader :status, :last_error
|
|
29
|
+
|
|
30
|
+
# Creates a new subscription handle.
|
|
31
|
+
#
|
|
32
|
+
# @api private
|
|
33
|
+
# @param thread [Thread] the background thread running the subscription loop
|
|
34
|
+
# @param cancellation_token [CancellationToken] token used to signal cancellation
|
|
35
|
+
def initialize(thread:, cancellation_token:)
|
|
36
|
+
@thread = thread
|
|
37
|
+
@cancellation_token = cancellation_token
|
|
38
|
+
@status = :active
|
|
39
|
+
@last_error = nil
|
|
40
|
+
@mutex = Mutex.new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Cancels the subscription, stops the gRPC stream, and waits up to
|
|
44
|
+
# 5 seconds for the background thread to exit.
|
|
45
|
+
#
|
|
46
|
+
# @return [void]
|
|
47
|
+
def cancel
|
|
48
|
+
@mutex.synchronize { @status = :cancelled }
|
|
49
|
+
@cancellation_token.cancel
|
|
50
|
+
begin; @thread[:grpc_call]&.cancel; rescue StandardError; end
|
|
51
|
+
@thread.join(5)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Returns whether the subscription is still actively receiving messages.
|
|
55
|
+
#
|
|
56
|
+
# @return [Boolean] +true+ if status is +:active+ and the thread is alive
|
|
57
|
+
def active?
|
|
58
|
+
@mutex.synchronize { @status == :active } && @thread.alive?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Blocks the calling thread until the subscription thread exits or the
|
|
62
|
+
# timeout elapses.
|
|
63
|
+
#
|
|
64
|
+
# @param timeout [Numeric, nil] maximum seconds to wait (+nil+ for indefinite)
|
|
65
|
+
# @return [Thread, nil] the subscription thread if it exited, +nil+ on timeout
|
|
66
|
+
def wait(timeout = nil)
|
|
67
|
+
@thread.join(timeout)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Alias for {#wait}. Blocks until the subscription thread exits.
|
|
71
|
+
#
|
|
72
|
+
# @param timeout [Numeric, nil] maximum seconds to wait (+nil+ for indefinite)
|
|
73
|
+
# @return [Thread, nil] the subscription thread if it exited, +nil+ on timeout
|
|
74
|
+
def join(timeout = nil)
|
|
75
|
+
@thread.join(timeout)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Records an error on this subscription.
|
|
79
|
+
#
|
|
80
|
+
# @api private
|
|
81
|
+
# @param error [StandardError] the error that occurred
|
|
82
|
+
# @return [void]
|
|
83
|
+
def mark_error(error)
|
|
84
|
+
@mutex.synchronize do
|
|
85
|
+
@status = :error
|
|
86
|
+
@last_error = error
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Marks this subscription as closed.
|
|
91
|
+
#
|
|
92
|
+
# @api private
|
|
93
|
+
# @return [void]
|
|
94
|
+
def mark_closed
|
|
95
|
+
@mutex.synchronize { @status = :closed }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'semconv'
|
|
4
|
+
|
|
5
|
+
module KubeMQ
|
|
6
|
+
# OpenTelemetry integration for distributed tracing of KubeMQ operations.
|
|
7
|
+
#
|
|
8
|
+
# Provides a thin wrapper around the OpenTelemetry API that degrades
|
|
9
|
+
# gracefully when the SDK is not loaded. Attribute names follow the
|
|
10
|
+
# {SemanticConventions} module.
|
|
11
|
+
#
|
|
12
|
+
# @see SemanticConventions
|
|
13
|
+
# @see Interceptors::MetricsInterceptor
|
|
14
|
+
module Telemetry
|
|
15
|
+
# OpenTelemetry tracer name registered for this SDK.
|
|
16
|
+
TRACER_NAME = 'kubemq-ruby'
|
|
17
|
+
|
|
18
|
+
# Returns whether the OpenTelemetry API is loaded and configured.
|
|
19
|
+
#
|
|
20
|
+
# @return [Boolean] +true+ if +OpenTelemetry.tracer_provider+ is available
|
|
21
|
+
def self.otel_available?
|
|
22
|
+
defined?(OpenTelemetry) && OpenTelemetry.respond_to?(:tracer_provider)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Runs +block+ inside an OpenTelemetry span when the API is loaded;
|
|
26
|
+
# otherwise yields +nil+ once.
|
|
27
|
+
#
|
|
28
|
+
# Always sets +messaging.system+ to {SemanticConventions::MESSAGING_SYSTEM}.
|
|
29
|
+
# Additional attributes are merged and their keys are coerced to strings.
|
|
30
|
+
#
|
|
31
|
+
# @param name [String] descriptive span name (e.g., +"kubemq send_event orders"+)
|
|
32
|
+
# @param kind [Symbol] OpenTelemetry span kind -- +:producer+, +:consumer+,
|
|
33
|
+
# or +:client+ (default: {SemanticConventions::SPAN_KIND_CLIENT})
|
|
34
|
+
# @param attributes [Hash{String, Symbol => String, Numeric, Boolean}]
|
|
35
|
+
# extra span attributes
|
|
36
|
+
# @yield [span] the block to execute inside the span
|
|
37
|
+
# @yieldparam span [OpenTelemetry::Trace::Span, nil] the active span,
|
|
38
|
+
# or +nil+ when OpenTelemetry is not available
|
|
39
|
+
# @return [Object] the return value of the block
|
|
40
|
+
# @raise [ArgumentError] if no block is given
|
|
41
|
+
def self.with_span(name, kind: SemanticConventions::SPAN_KIND_CLIENT, attributes: {}, &block)
|
|
42
|
+
raise ArgumentError, 'block required' unless block
|
|
43
|
+
|
|
44
|
+
return block.call(nil) unless otel_available?
|
|
45
|
+
|
|
46
|
+
merged = {
|
|
47
|
+
SemanticConventions::ATTR_MESSAGING_SYSTEM => SemanticConventions::MESSAGING_SYSTEM
|
|
48
|
+
}.merge(stringify_attributes(attributes))
|
|
49
|
+
|
|
50
|
+
tracer = OpenTelemetry.tracer_provider.tracer(TRACER_NAME)
|
|
51
|
+
tracer.in_span(name, attributes: merged, kind: kind, &block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Coerces attribute keys to strings.
|
|
55
|
+
#
|
|
56
|
+
# @param attributes [Hash] raw attribute map
|
|
57
|
+
# @return [Hash{String => Object}] attributes with string keys
|
|
58
|
+
def self.stringify_attributes(attributes)
|
|
59
|
+
attributes.to_h.transform_keys(&:to_s)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private_class_method :stringify_attributes
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KubeMQ
|
|
4
|
+
module Telemetry
|
|
5
|
+
# OpenTelemetry messaging semantic convention constants.
|
|
6
|
+
#
|
|
7
|
+
# Provides stable attribute names and span-kind symbols aligned with the
|
|
8
|
+
# OpenTelemetry Semantic Conventions for Messaging (semconv v1.27+).
|
|
9
|
+
# Used by {Telemetry.with_span} and {Interceptors::MetricsInterceptor}
|
|
10
|
+
# to emit consistent telemetry data.
|
|
11
|
+
#
|
|
12
|
+
# @see Telemetry.with_span
|
|
13
|
+
# @see Interceptors::MetricsInterceptor
|
|
14
|
+
module SemanticConventions
|
|
15
|
+
# Value for the +messaging.system+ attribute when talking to KubeMQ.
|
|
16
|
+
MESSAGING_SYSTEM = 'kubemq'
|
|
17
|
+
|
|
18
|
+
# @!group Attribute Keys
|
|
19
|
+
|
|
20
|
+
# Identifies the messaging system (always {MESSAGING_SYSTEM} for KubeMQ).
|
|
21
|
+
ATTR_MESSAGING_SYSTEM = 'messaging.system'
|
|
22
|
+
|
|
23
|
+
# Name of the messaging operation (e.g., +"send"+, +"receive"+, +"process"+).
|
|
24
|
+
ATTR_MESSAGING_OPERATION_NAME = 'messaging.operation.name'
|
|
25
|
+
|
|
26
|
+
# Logical name of the destination channel or queue.
|
|
27
|
+
ATTR_MESSAGING_DESTINATION_NAME = 'messaging.destination.name'
|
|
28
|
+
|
|
29
|
+
# Unique identifier of the message being traced.
|
|
30
|
+
ATTR_MESSAGING_MESSAGE_ID = 'messaging.message.id'
|
|
31
|
+
|
|
32
|
+
# Identifier of the client that produced or consumed the message.
|
|
33
|
+
ATTR_MESSAGING_CLIENT_ID = 'messaging.client.id'
|
|
34
|
+
|
|
35
|
+
# @!endgroup
|
|
36
|
+
|
|
37
|
+
# @!group Span Kinds
|
|
38
|
+
|
|
39
|
+
# Span kind for message-producing operations (e.g., send, publish).
|
|
40
|
+
SPAN_KIND_PRODUCER = :producer
|
|
41
|
+
|
|
42
|
+
# Span kind for message-consuming operations (e.g., subscribe, poll).
|
|
43
|
+
SPAN_KIND_CONSUMER = :consumer
|
|
44
|
+
|
|
45
|
+
# Span kind for synchronous client operations (e.g., ping, create_channel).
|
|
46
|
+
SPAN_KIND_CLIENT = :client
|
|
47
|
+
|
|
48
|
+
# @!endgroup
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative '../proto/kubemq_pb'
|
|
6
|
+
|
|
7
|
+
module KubeMQ
|
|
8
|
+
module Transport
|
|
9
|
+
# Channel lifecycle operations (create, delete, list, purge) sent as
|
|
10
|
+
# internal queries over the KubeMQ cluster management channel.
|
|
11
|
+
#
|
|
12
|
+
# All methods are module-level (+module_function+) and delegate the
|
|
13
|
+
# underlying gRPC call to the provided {GrpcTransport} instance.
|
|
14
|
+
#
|
|
15
|
+
# @api private
|
|
16
|
+
#
|
|
17
|
+
# @see GrpcTransport
|
|
18
|
+
# @see BaseClient
|
|
19
|
+
# rubocop:disable Metrics/ModuleLength -- internal channel CRUD + list helpers
|
|
20
|
+
module ChannelManager
|
|
21
|
+
# Cluster-internal channel used for management requests.
|
|
22
|
+
INTERNAL_CHANNEL = 'kubemq.cluster.internal.requests'
|
|
23
|
+
|
|
24
|
+
# Default timeout (in milliseconds) for management requests.
|
|
25
|
+
INTERNAL_TIMEOUT = 10_000
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# Creates a channel on the KubeMQ broker.
|
|
30
|
+
#
|
|
31
|
+
# @param transport [GrpcTransport] the active transport instance
|
|
32
|
+
# @param client_id [String] the client identifier
|
|
33
|
+
# @param channel_name [String] name for the new channel
|
|
34
|
+
# @param channel_type [String] one of {ChannelType} constants
|
|
35
|
+
# @return [Boolean] +true+ on success
|
|
36
|
+
# @raise [ChannelError] if the broker rejects the operation
|
|
37
|
+
#
|
|
38
|
+
# @see BaseClient#create_channel
|
|
39
|
+
# rubocop:disable Naming/PredicateMethod -- command-style API returns true on success
|
|
40
|
+
def create_channel(transport, client_id, channel_name, channel_type)
|
|
41
|
+
request = build_request(
|
|
42
|
+
client_id: client_id,
|
|
43
|
+
metadata: 'create-channel',
|
|
44
|
+
tags: {
|
|
45
|
+
'channel_type' => channel_type,
|
|
46
|
+
'channel' => channel_name,
|
|
47
|
+
'client_id' => client_id
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
response = transport.kubemq_client.send_request(request)
|
|
52
|
+
check_response_error!(response, 'create_channel', channel_name)
|
|
53
|
+
true
|
|
54
|
+
end
|
|
55
|
+
# rubocop:enable Naming/PredicateMethod
|
|
56
|
+
|
|
57
|
+
# Deletes a channel from the KubeMQ broker.
|
|
58
|
+
#
|
|
59
|
+
# @param transport [GrpcTransport] the active transport instance
|
|
60
|
+
# @param client_id [String] the client identifier
|
|
61
|
+
# @param channel_name [String] name of the channel to delete
|
|
62
|
+
# @param channel_type [String] one of {ChannelType} constants
|
|
63
|
+
# @return [Boolean] +true+ on success
|
|
64
|
+
# @raise [ChannelError] if the broker rejects the operation
|
|
65
|
+
#
|
|
66
|
+
# @see BaseClient#delete_channel
|
|
67
|
+
# rubocop:disable Naming/PredicateMethod -- command-style API returns true on success
|
|
68
|
+
def delete_channel(transport, client_id, channel_name, channel_type)
|
|
69
|
+
request = build_request(
|
|
70
|
+
client_id: client_id,
|
|
71
|
+
metadata: 'delete-channel',
|
|
72
|
+
tags: {
|
|
73
|
+
'channel_type' => channel_type,
|
|
74
|
+
'channel' => channel_name
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
response = transport.kubemq_client.send_request(request)
|
|
79
|
+
check_response_error!(response, 'delete_channel', channel_name)
|
|
80
|
+
true
|
|
81
|
+
end
|
|
82
|
+
# rubocop:enable Naming/PredicateMethod
|
|
83
|
+
|
|
84
|
+
# Lists channels of the specified type, with optional name filtering.
|
|
85
|
+
#
|
|
86
|
+
# Retries up to 3 times when the cluster snapshot is not ready.
|
|
87
|
+
#
|
|
88
|
+
# @param transport [GrpcTransport] the active transport instance
|
|
89
|
+
# @param client_id [String] the client identifier
|
|
90
|
+
# @param channel_type [String] one of {ChannelType} constants
|
|
91
|
+
# @param search [String, nil] substring filter for channel names
|
|
92
|
+
# @return [Array<ChannelInfo>] matching channels with metadata
|
|
93
|
+
# @raise [ChannelError] if the broker rejects the operation after retries
|
|
94
|
+
#
|
|
95
|
+
# @see BaseClient#list_channels
|
|
96
|
+
def list_channels(transport, client_id, channel_type, search = nil)
|
|
97
|
+
tags = { 'channel_type' => channel_type }
|
|
98
|
+
tags['channel_search'] = search if search && !search.empty?
|
|
99
|
+
|
|
100
|
+
request = build_request(
|
|
101
|
+
client_id: client_id,
|
|
102
|
+
metadata: 'list-channels',
|
|
103
|
+
tags: tags
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
max_retries = 3
|
|
107
|
+
response = nil
|
|
108
|
+
max_retries.times do |attempt|
|
|
109
|
+
response = transport.kubemq_client.send_request(request)
|
|
110
|
+
|
|
111
|
+
raise StandardError, response.Error if response.Error&.include?('cluster snapshot not ready')
|
|
112
|
+
|
|
113
|
+
break
|
|
114
|
+
rescue StandardError => e
|
|
115
|
+
if e.message.include?('cluster snapshot not ready') && attempt < max_retries - 1
|
|
116
|
+
sleep(1)
|
|
117
|
+
next
|
|
118
|
+
end
|
|
119
|
+
raise ChannelError.new(
|
|
120
|
+
"list_channels failed after #{attempt + 1} attempts: #{e.message}",
|
|
121
|
+
operation: 'list_channels'
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
check_response_error!(response, 'list_channels')
|
|
126
|
+
parse_channel_list(response.Body, channel_type)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Purges all pending messages from a queue channel.
|
|
130
|
+
#
|
|
131
|
+
# @param transport [GrpcTransport] the active transport instance
|
|
132
|
+
# @param client_id [String] the client identifier
|
|
133
|
+
# @param channel_name [String] queue channel to purge
|
|
134
|
+
# @return [Hash{Symbol => Integer}] +{ affected_messages: Integer }+
|
|
135
|
+
# @raise [ChannelError] if the broker rejects the operation
|
|
136
|
+
#
|
|
137
|
+
# @see BaseClient#purge_queue_channel
|
|
138
|
+
def purge_queue(transport, client_id, channel_name)
|
|
139
|
+
request = ::Kubemq::AckAllQueueMessagesRequest.new(
|
|
140
|
+
RequestID: SecureRandom.uuid,
|
|
141
|
+
ClientID: client_id,
|
|
142
|
+
Channel: channel_name,
|
|
143
|
+
WaitTimeSeconds: 5
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
response = transport.kubemq_client.ack_all_queue_messages(request)
|
|
147
|
+
if response.IsError && !response.Error.empty?
|
|
148
|
+
raise ChannelError.new(
|
|
149
|
+
"Failed to purge queue '#{channel_name}': #{response.Error}",
|
|
150
|
+
code: ErrorCode::INTERNAL,
|
|
151
|
+
operation: 'purge_queue',
|
|
152
|
+
channel: channel_name
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
{ affected_messages: response.AffectedMessages }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Builds a protobuf management request targeting the internal channel.
|
|
160
|
+
#
|
|
161
|
+
# @param client_id [String] the client identifier
|
|
162
|
+
# @param metadata [String] management operation name
|
|
163
|
+
# @param tags [Hash{String => String}] operation parameters as tags
|
|
164
|
+
# @return [Kubemq::Request] protobuf request
|
|
165
|
+
def build_request(client_id:, metadata:, tags:)
|
|
166
|
+
::Kubemq::Request.new(
|
|
167
|
+
RequestID: SecureRandom.uuid,
|
|
168
|
+
RequestTypeData: RequestType::QUERY,
|
|
169
|
+
ClientID: client_id,
|
|
170
|
+
Channel: INTERNAL_CHANNEL,
|
|
171
|
+
Metadata: metadata,
|
|
172
|
+
Timeout: INTERNAL_TIMEOUT,
|
|
173
|
+
Tags: tags
|
|
174
|
+
)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Raises {ChannelError} if the response contains a non-empty error string.
|
|
178
|
+
#
|
|
179
|
+
# @param response [Kubemq::Response] protobuf response to check
|
|
180
|
+
# @param operation [String] the operation name for error context
|
|
181
|
+
# @param channel [String, nil] the channel name for error context
|
|
182
|
+
# @return [void]
|
|
183
|
+
# @raise [ChannelError] if the response indicates failure
|
|
184
|
+
def check_response_error!(response, operation, channel = nil)
|
|
185
|
+
return if response.Error.nil? || response.Error.empty?
|
|
186
|
+
|
|
187
|
+
raise ChannelError.new(
|
|
188
|
+
"#{operation} failed: #{response.Error}",
|
|
189
|
+
code: ErrorCode::INTERNAL,
|
|
190
|
+
operation: operation,
|
|
191
|
+
channel: channel
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Parses a JSON channel list response body into {ChannelInfo} objects.
|
|
196
|
+
#
|
|
197
|
+
# @param body [String, nil] JSON response body
|
|
198
|
+
# @param channel_type [String] channel type to assign to each entry
|
|
199
|
+
# @return [Array<ChannelInfo>] parsed channel info objects
|
|
200
|
+
def parse_channel_list(body, channel_type)
|
|
201
|
+
return [] if body.nil? || body.empty?
|
|
202
|
+
|
|
203
|
+
data = JSON.parse(body.dup.force_encoding('UTF-8'))
|
|
204
|
+
channels = data.is_a?(Array) ? data : (data['channels'] || data['items'] || [data])
|
|
205
|
+
channels.compact.map { |ch| ChannelInfo.from_json(ch.merge('type' => channel_type)) }
|
|
206
|
+
rescue JSON::ParserError
|
|
207
|
+
[]
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
# rubocop:enable Metrics/ModuleLength
|
|
211
|
+
end
|
|
212
|
+
end
|