-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmodule_manager_spec.cr
More file actions
283 lines (219 loc) · 9.09 KB
/
module_manager_spec.cr
File metadata and controls
283 lines (219 loc) · 9.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
require "./helper"
module PlaceOS::Core
describe ModuleManager, tags: "processes" do
describe "edge" do
end
describe "local" do
it "loads modules that hash to the node" do
_, _, _, resource_manager = create_resources
# Start module manager
module_manager = module_manager_mock
module_manager.start
# Check that the module is loaded, and the module manager can be received
module_manager.local_processes.run_count.modules.should eq 1
module_manager.stop
ensure
module_manager.try &.stop
resource_manager.try &.stop
end
it "load_module" do
_, driver, mod = setup
module_manager = module_manager_mock
builder = DriverResource.new(startup: true, module_manager: module_manager)
# Clone, compile, etcd
resource_manager = ResourceManager.new(driver_builder: builder) # (cloning: cloning, compilation: compilation)
resource_manager.start { }
mod_id = mod.id.as(String)
driver_path = module_manager.store.driver_binary_path(driver.file_name, driver.commit).to_s
mod.reload!
mod.driver = mod.driver.not_nil!.reload!
module_manager.load_module(mod)
module_manager.local_processes.run_count.should eq(ProcessManager::Count.new(1, 1))
module_manager.local_processes.protocol_manager_by_module?(mod_id).should_not be_nil
module_manager.local_processes.protocol_manager_by_driver?(driver_path).should_not be_nil
module_manager.local_processes.protocol_manager_by_module?(mod_id).should eq(module_manager.local_processes.protocol_manager_by_driver?(driver_path))
ensure
module_manager.try &.stop
resource_manager.try &.stop
end
end
describe "lazy modules (launch_on_execute)" do
it "registers lazy module without spawning driver" do
_, driver, mod = setup(role: PlaceOS::Model::Driver::Role::Service)
module_manager = module_manager_mock
builder = DriverResource.new(startup: true, module_manager: module_manager)
resource_manager = ResourceManager.new(driver_builder: builder)
resource_manager.start { }
mod_id = mod.id.as(String)
mod.reload!
mod.driver = mod.driver.not_nil!.reload!
# Set module as lazy-load
mod.launch_on_execute = true
mod.running = true
mod.save!
# Load the lazy module
module_manager.load_module(mod)
# Driver should NOT be spawned
module_manager.local_processes.run_count.modules.should eq 0
module_manager.local_processes.module_loaded?(mod_id).should be_false
# But module should be registered as lazy
module_manager.lazy_module?(mod_id).should be_true
# Metadata should be populated in Redis
metadata = Driver::RedisStorage.with_redis { |r| r.get("interface/#{mod_id}") }
metadata.should_not be_nil
ensure
module_manager.try &.stop
resource_manager.try &.stop
end
it "spawns driver on execute and unloads after idle" do
# Use a short unload delay for testing
original_delay = ModuleManager.lazy_unload_delay
ModuleManager.lazy_unload_delay = 500.milliseconds
_, driver, mod = setup(role: PlaceOS::Model::Driver::Role::Service)
module_manager = module_manager_mock
builder = DriverResource.new(startup: true, module_manager: module_manager)
resource_manager = ResourceManager.new(driver_builder: builder)
resource_manager.start { }
mod_id = mod.id.as(String)
mod.reload!
mod.driver = mod.driver.not_nil!.reload!
# Set module as lazy-load
mod.launch_on_execute = true
mod.running = true
mod.save!
# Reload to get fresh associations
mod = Model::Module.find!(mod_id)
mod.driver = driver.reload!
# Register the lazy module
module_manager.load_module(mod)
module_manager.lazy_module?(mod_id).should be_true
module_manager.local_processes.module_loaded?(mod_id).should be_false
# Execute should spawn driver and load module
result, code = module_manager.local_processes.execute(
module_id: mod_id,
payload: ModuleManager.execute_payload(:used_for_place_testing),
user_id: nil
)
result.should eq %("you can delete this file")
code.should eq 200
# Module should now be loaded
module_manager.local_processes.module_loaded?(mod_id).should be_true
# Wait for idle unload
sleep 1.second
# Module should be unloaded and back to lazy state
module_manager.local_processes.module_loaded?(mod_id).should be_false
module_manager.lazy_module?(mod_id).should be_true
# Metadata should still be in Redis
metadata = Driver::RedisStorage.with_redis { |r| r.get("interface/#{mod_id}") }
metadata.should_not be_nil
ensure
ModuleManager.lazy_unload_delay = original_delay.not_nil!
module_manager.try &.stop
resource_manager.try &.stop
end
it "does not unload while executions are active" do
original_delay = ModuleManager.lazy_unload_delay
ModuleManager.lazy_unload_delay = 200.milliseconds
_, driver, mod = setup(role: PlaceOS::Model::Driver::Role::Service)
module_manager = module_manager_mock
builder = DriverResource.new(startup: true, module_manager: module_manager)
resource_manager = ResourceManager.new(driver_builder: builder)
resource_manager.start { }
mod_id = mod.id.as(String)
mod.reload!
mod.driver = mod.driver.not_nil!.reload!
mod.launch_on_execute = true
mod.running = true
mod.save!
module_manager.load_module(mod)
# Start multiple concurrent executions
results = Channel(Tuple(String, Int32)).new(3)
3.times do
spawn do
r, c = module_manager.local_processes.execute(
module_id: mod_id,
payload: ModuleManager.execute_payload(:used_for_place_testing),
user_id: nil
)
results.send({r, c})
end
end
# Collect results
3.times do
result, code = results.receive
result.should eq %("you can delete this file")
code.should eq 200
end
# Module should still be loaded (unload scheduled but not executed yet)
# Give a tiny bit of time for the last execution to complete
sleep 50.milliseconds
module_manager.local_processes.module_loaded?(mod_id).should be_true
# Wait for unload
sleep 500.milliseconds
module_manager.local_processes.module_loaded?(mod_id).should be_false
ensure
ModuleManager.lazy_unload_delay = original_delay.not_nil!
module_manager.try &.stop
resource_manager.try &.stop
end
it "clears metadata when lazy module is stopped" do
_, driver, mod = setup(role: PlaceOS::Model::Driver::Role::Service)
module_manager = module_manager_mock
builder = DriverResource.new(startup: true, module_manager: module_manager)
resource_manager = ResourceManager.new(driver_builder: builder)
resource_manager.start { }
mod_id = mod.id.as(String)
mod.reload!
mod.driver = mod.driver.not_nil!.reload!
mod.launch_on_execute = true
mod.running = true
mod.save!
module_manager.load_module(mod)
# Metadata should exist
metadata = Driver::RedisStorage.with_redis { |r| r.get("interface/#{mod_id}") }
metadata.should_not be_nil
# Stop the module
module_manager.stop_module(mod)
# Metadata should be cleared
metadata = Driver::RedisStorage.with_redis { |r| r.get("interface/#{mod_id}") }
metadata.should be_nil
# Module should not be in lazy tracking
module_manager.lazy_module?(mod_id).should be_false
ensure
module_manager.try &.stop
resource_manager.try &.stop
end
end
describe "startup" do
it "registers to redis" do
# Clear relevant tables
Model::Driver.clear
Model::Module.clear
Model::Repository.clear
# Start module manager
module_manager = ModuleManager.new(uri: CORE_URL)
module_manager.start
core_uri = URI.parse(CORE_URL)
# Check that the node is registered in etcd
tries = 0
loop do
sleep 3.seconds
break if tries > 5 || module_manager.discovery.nodes.includes?(core_uri)
tries += 1
end
module_manager.discovery.nodes.should contain(core_uri)
# Check that the node is no longer registered in etcd
module_manager.stop
tries = 0
loop do
sleep 3.seconds
break if tries > 5 || !module_manager.discovery.nodes.includes?(core_uri)
tries += 1
end
module_manager.discovery.nodes.should_not contain(core_uri)
ensure
module_manager.try &.stop
end
end
end
end