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 +4 -4
- data/CHANGELOG.md +15 -0
- data/CLAUDE.md +5 -2
- data/README.md +1 -1
- data/lex-node.gemspec +7 -0
- data/lib/legion/extensions/node/helpers/rabbitmq.rb +128 -0
- data/lib/legion/extensions/node/transport/messages/beat.rb +9 -0
- data/lib/legion/extensions/node/version.rb +1 -1
- data/lib/legion/extensions/node.rb +1 -0
- metadata +100 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb28b6c1ef5c870a1498dee8b03b6490babb8933f1f50782f06f774fbcbeca83
|
|
4
|
+
data.tar.gz: 154f3a22cfef52b5d2b5a23af3b7f207dc76b81930774846e22097a4c0b7a0d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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/
|
|
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.
|
|
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
|
|
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.
|
|
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
|