lex-node 0.3.1 → 0.3.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8bce7f974591cba97508bd1d4a9033315f4207050da3996f082438267c1de9c
4
- data.tar.gz: 3af059ccb67edb50d52c749adbd8014cd29c9d75af4fa3b85c84c678a453301b
3
+ metadata.gz: fb28b6c1ef5c870a1498dee8b03b6490babb8933f1f50782f06f774fbcbeca83
4
+ data.tar.gz: 154f3a22cfef52b5d2b5a23af3b7f207dc76b81930774846e22097a4c0b7a0d8
5
5
  SHA512:
6
- metadata.gz: 5dd82b1f0b200c50a7d4b3cedc639e73ad47b27335a8405182b6f2c1a9f6372930c51d1b3c52bcd0403198593d6b22679e989e83f944e75b28a59d86a19ce339
7
- data.tar.gz: 5a3c3d9ffdccca307b9e8981b73f18021eabdfb2ee44167231b75290d7c8dd33fd2474e08db6ccb377eede6fb634e5274de5c558fe77aa914a9818a96c88ebc9
6
+ metadata.gz: 1977037030ef413f64fdc1b5ba330190bb588bff2e46cb8746297bd64e05f26740ae8c6e6829282f9a24f449fa43a5b4c0a939ac3a495231f00a885baf00163a
7
+ data.tar.gz: 9d3524a4ab76589fdc35fde36ad51294d61116ed86dc3fbe2f735d2d906a2b806ec029a8841f83a629cf81f16450d06cd5dc801b4a1a9f8fa923a7a8302d08fb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.3] - 2026-03-22
4
+
5
+ ### Changed
6
+ - Add legion-cache, legion-crypt, legion-data, legion-json, legion-logging, legion-settings, legion-transport as runtime dependencies
7
+ - Update spec_helper with real sub-gem helper stubs (Legion::Logging::Helper, Legion::Settings::Helper, Legion::Cache::Helper, Legion::Crypt::Helper, Legion::Data::Helper, Legion::JSON::Helper, Legion::Transport::Helper)
8
+ - Update specs to stub Legion::Crypt methods explicitly now that the real gem is loaded in tests
9
+ - Update beat actor spec to stub `settings` at instance level for beat_interval lookup
10
+
11
+ ## [0.3.2] - 2026-03-21
12
+
13
+ ### Added
14
+ - `Helpers::Rabbitmq` module: queries RabbitMQ Management API for cluster health metrics (node count, quorum queue leaders, shovel link status)
15
+ - Beat heartbeat message now includes `rabbitmq_cluster` section with node count, quorum leaders, and shovel links
16
+ - 15 new specs covering all Rabbitmq helper methods (111 total, 0 failures)
17
+
3
18
  ## [0.3.1] - 2026-03-19
4
19
 
5
20
  ### Fixed
data/CLAUDE.md CHANGED
@@ -10,7 +10,7 @@ Core Legion Extension responsible for node identity within a LegionIO cluster. H
10
10
 
11
11
  **GitHub**: https://github.com/LegionIO/lex-node
12
12
  **License**: MIT
13
- **Version**: 0.3.0
13
+ **Version**: 0.3.2
14
14
 
15
15
  ## Architecture
16
16
 
@@ -50,6 +50,8 @@ Legion::Extensions::Node
50
50
  │ ├── RequestVaultToken # Request Vault token from peer
51
51
  │ ├── PushVaultToken # Distribute encrypted Vault token
52
52
  │ └── UpdateResult # Operation result (update_gem / update_settings outcomes)
53
+ ├── Helpers/
54
+ │ └── Rabbitmq # RabbitMQ Management API cluster health (node count, quorum leaders, shovel links)
53
55
  └── DataTest/
54
56
  └── Migrations/
55
57
  ├── 001_nodes_table # Core nodes table
@@ -67,7 +69,8 @@ Legion::Extensions::Node
67
69
  | `lib/legion/extensions/node/runners/crypt.rb` | RSA keypair and cluster secret exchange |
68
70
  | `lib/legion/extensions/node/runners/node.rb` | Dynamic config distribution, update_settings, update_gem, public key relay |
69
71
  | `lib/legion/extensions/node/runners/vault.rb` | Vault token request/receive/push lifecycle |
70
- | `lib/legion/extensions/node/transport/messages/beat.rb` | Heartbeat message (routing_key: 'status', TTL 5s) with resource metrics, hosted worker IDs, and Legion version |
72
+ | `lib/legion/extensions/node/helpers/rabbitmq.rb` | RabbitMQ Management API cluster health helper (node count, quorum leaders, shovel links) |
73
+ | `lib/legion/extensions/node/transport/messages/beat.rb` | Heartbeat message (routing_key: 'status', TTL 5s) with resource metrics, hosted worker IDs, RabbitMQ cluster health, and Legion version |
71
74
  | `lib/legion/extensions/node/transport/messages/update_result.rb` | Operation result message for update_gem/update_settings |
72
75
  | `lib/legion/extensions/node/transport/messages/` | All node-to-node message types |
73
76
  | `lib/legion/extensions/node/transport/queues/node.rb` | Per-node queue (exclusive, auto-delete, classic type) |
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Node identity and cluster management for [LegionIO](https://github.com/LegionIO/LegionIO). Handles heartbeat broadcasting, dynamic configuration distribution, cluster secret exchange, Vault token management, and RSA public key distribution between nodes.
4
4
 
5
- **Version**: 0.3.0
5
+ **Version**: 0.3.2
6
6
 
7
7
  This is a core LEX installed by default with LegionIO.
8
8
 
data/lex-node.gemspec CHANGED
@@ -27,4 +27,11 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ['lib']
28
28
 
29
29
  spec.add_dependency 'base64'
30
+ spec.add_dependency 'legion-cache', '>= 1.3.11'
31
+ spec.add_dependency 'legion-crypt', '>= 1.4.9'
32
+ spec.add_dependency 'legion-data', '>= 1.4.17'
33
+ spec.add_dependency 'legion-json', '>= 1.2.1'
34
+ spec.add_dependency 'legion-logging', '>= 1.3.2'
35
+ spec.add_dependency 'legion-settings', '>= 1.3.14'
36
+ spec.add_dependency 'legion-transport', '>= 1.3.9'
30
37
  end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+
7
+ module Legion
8
+ module Extensions
9
+ module Node
10
+ module Helpers
11
+ module Rabbitmq
12
+ module_function
13
+
14
+ def cluster_health(settings: nil)
15
+ settings ||= resolve_settings
16
+ http = build_http(settings)
17
+ {
18
+ node_count: fetch_node_count(http, settings),
19
+ quorum_leaders: fetch_quorum_leaders(http, settings),
20
+ shovel_links: fetch_shovel_links(http, settings)
21
+ }
22
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError, Net::OpenTimeout, Net::ReadTimeout => e
23
+ log_warn("RabbitMQ management API unreachable: #{e.message}")
24
+ { node_count: unreachable, quorum_leaders: unreachable, shovel_links: unreachable }
25
+ end
26
+
27
+ def fetch_node_count(http, settings)
28
+ body = api_get(http, '/api/nodes', settings)
29
+ return unreachable unless body
30
+
31
+ running = body.count { |n| n['running'] }
32
+ total = body.size
33
+ status = if running == total
34
+ 'ok'
35
+ else
36
+ (running.positive? ? 'warn' : 'critical')
37
+ end
38
+ { status: status, running: running, total: total }
39
+ end
40
+
41
+ def fetch_quorum_leaders(http, settings)
42
+ vhost = settings.dig(:connection, :vhost) || '/'
43
+ encoded_vhost = URI.encode_www_form_component(vhost)
44
+ body = api_get(http, "/api/queues/#{encoded_vhost}", settings)
45
+ return unreachable unless body
46
+
47
+ quorum_queues = body.select { |q| q['type'] == 'quorum' }
48
+ return { status: 'ok', quorum_queues: 0, leaders_on_this_node: 0 } if quorum_queues.empty?
49
+
50
+ node_name = resolve_node_name(http, settings)
51
+ leaders_here = quorum_queues.count { |q| q['leader'] == node_name }
52
+ { status: 'ok', quorum_queues: quorum_queues.size, leaders_on_this_node: leaders_here }
53
+ end
54
+
55
+ def fetch_shovel_links(http, settings)
56
+ vhost = settings.dig(:connection, :vhost) || '/'
57
+ encoded_vhost = URI.encode_www_form_component(vhost)
58
+ body = api_get(http, "/api/shovels/#{encoded_vhost}", settings)
59
+ return unreachable unless body
60
+
61
+ running = body.count { |s| s['state'] == 'running' }
62
+ total = body.size
63
+ status = if total.zero?
64
+ 'ok'
65
+ else
66
+ (running == total ? 'ok' : 'warn')
67
+ end
68
+ { status: status, total: total, running: running }
69
+ rescue StandardError
70
+ { status: 'ok', total: 0, running: 0 }
71
+ end
72
+
73
+ def build_http(settings)
74
+ host = settings.dig(:connection, :host) || '127.0.0.1'
75
+ port = settings[:management_port] || 15_672
76
+ http = Net::HTTP.new(host, port)
77
+ http.open_timeout = 3
78
+ http.read_timeout = 5
79
+ http
80
+ end
81
+
82
+ def api_get(http, path, settings)
83
+ req = Net::HTTP::Get.new(path)
84
+ req.basic_auth(
85
+ settings.dig(:connection, :user) || 'guest',
86
+ settings.dig(:connection, :password) || 'guest'
87
+ )
88
+ response = http.request(req)
89
+ return nil unless response.code.start_with?('2')
90
+
91
+ ::JSON.parse(response.body)
92
+ rescue StandardError
93
+ nil
94
+ end
95
+
96
+ def resolve_node_name(http, settings)
97
+ body = api_get(http, '/api/whoami', settings)
98
+ return nil unless body
99
+
100
+ nodes = api_get(http, '/api/nodes', settings)
101
+ return nil unless nodes
102
+
103
+ nodes.first&.dig('name')
104
+ end
105
+
106
+ def resolve_settings
107
+ return Legion::Settings[:transport].to_h if defined?(Legion::Settings) && Legion::Settings.respond_to?(:[])
108
+
109
+ { connection: { host: '127.0.0.1', user: 'guest', password: 'guest', vhost: '/' },
110
+ management_port: 15_672 }
111
+ end
112
+
113
+ def unreachable
114
+ { status: 'unknown', detail: 'management api unreachable' }
115
+ end
116
+
117
+ def log_warn(msg)
118
+ if defined?(Legion::Logging)
119
+ Legion::Logging.warn { msg }
120
+ elsif defined?(Legion::Transport) && Legion::Transport.respond_to?(:logger)
121
+ Legion::Transport.logger.warn(msg)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -33,6 +33,7 @@ module Legion
33
33
  hash[:version] = Legion::VERSION if defined?(Legion::VERSION)
34
34
  hash[:metrics] = collect_metrics
35
35
  hash[:hosted_worker_ids] = collect_worker_ids
36
+ hash[:rabbitmq_cluster] = collect_rabbitmq_cluster
36
37
  hash
37
38
  end
38
39
 
@@ -76,6 +77,14 @@ module Legion
76
77
  (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - BOOT_TIME).round(0)
77
78
  end
78
79
 
80
+ def collect_rabbitmq_cluster
81
+ return { status: 'unknown', detail: 'helper not loaded' } unless defined?(Legion::Extensions::Node::Helpers::Rabbitmq)
82
+
83
+ Legion::Extensions::Node::Helpers::Rabbitmq.cluster_health
84
+ rescue StandardError => e
85
+ { status: 'unknown', detail: e.message }
86
+ end
87
+
79
88
  def collect_worker_ids
80
89
  return [] unless defined?(Legion::DigitalWorker)
81
90
 
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Node
6
- VERSION = '0.3.1'
6
+ VERSION = '0.3.3'
7
7
  end
8
8
  end
9
9
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/node/version'
4
+ require 'legion/extensions/node/helpers/rabbitmq'
4
5
 
5
6
  module Legion
6
7
  module Extensions
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-node
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -23,6 +23,104 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: legion-cache
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.3.11
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 1.3.11
40
+ - !ruby/object:Gem::Dependency
41
+ name: legion-crypt
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 1.4.9
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.4.9
54
+ - !ruby/object:Gem::Dependency
55
+ name: legion-data
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.4.17
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 1.4.17
68
+ - !ruby/object:Gem::Dependency
69
+ name: legion-json
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 1.2.1
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 1.2.1
82
+ - !ruby/object:Gem::Dependency
83
+ name: legion-logging
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 1.3.2
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 1.3.2
96
+ - !ruby/object:Gem::Dependency
97
+ name: legion-settings
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 1.3.14
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 1.3.14
110
+ - !ruby/object:Gem::Dependency
111
+ name: legion-transport
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 1.3.9
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 1.3.9
26
124
  description: This lex is responsible for sending heartbeats, allowing for dynamic
27
125
  config, cluster secret, etc
28
126
  email:
@@ -53,6 +151,7 @@ files:
53
151
  - lib/legion/extensions/node/data_test/migrations/002_node_history_table.rb
54
152
  - lib/legion/extensions/node/data_test/migrations/003_legion_version_colume.rb
55
153
  - lib/legion/extensions/node/data_test/migrations/004_node_extensions.rb
154
+ - lib/legion/extensions/node/helpers/rabbitmq.rb
56
155
  - lib/legion/extensions/node/runners/beat.rb
57
156
  - lib/legion/extensions/node/runners/node.rb
58
157
  - lib/legion/extensions/node/runners/vault.rb