nutella_framework 0.4.5 → 0.4.8
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/Gemfile +14 -15
- data/VERSION +1 -1
- data/framework_components/beacon-cloud-bot/README.md +27 -0
- data/framework_components/beacon-cloud-bot/beacon_cloud_bot.rb +154 -0
- data/framework_components/beacon-cloud-bot/nutella.json +6 -0
- data/framework_components/beacon-cloud-bot/startup +4 -0
- data/framework_components/beacon-cloud-interface/LICENSE +21 -0
- data/framework_components/beacon-cloud-interface/Readme.md +0 -0
- data/framework_components/beacon-cloud-interface/bower.json +29 -0
- data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/.bower.json +23 -0
- data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/bower.json +14 -0
- data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/mqttws31.js +2081 -0
- data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/readme.md +4 -0
- data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/.bower.json +37 -0
- data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/LICENSE +21 -0
- data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/README.md +15 -0
- data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/bower.json +28 -0
- data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/browser/mqtt_client_hello_world.html +23 -0
- data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/browser/nutella_hello_world.html +52 -0
- data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/node/mqtt_client_hello_world.js +14 -0
- data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/node/nutella_hello_world.js +38 -0
- data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/nutella_lib.js +789 -0
- data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/package.json +30 -0
- data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/simple-js-mqtt-client.js +428 -0
- data/framework_components/beacon-cloud-interface/css/animation.css +17 -0
- data/framework_components/beacon-cloud-interface/css/cursor.css +16 -0
- data/framework_components/beacon-cloud-interface/css/page_layout.css +73 -0
- data/framework_components/beacon-cloud-interface/index.html +157 -0
- data/framework_components/beacon-cloud-interface/js/lib/nutella_lib.js +4039 -0
- data/framework_components/beacon-cloud-interface/js/react/beacon-add.js +102 -0
- data/framework_components/beacon-cloud-interface/js/react/beacon-table.js +73 -0
- data/framework_components/beacon-cloud-interface/js/react/beacon.js +97 -0
- data/framework_components/beacon-cloud-interface/nutella.json +6 -0
- data/framework_components/example_framework_web_interface/index.html +11 -2
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/.npmignore +10 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/.travis.yml +5 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/LICENSE +21 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/README.md +27 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js +4039 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js.map +1 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/browser_hello_world.html +67 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/node_hello_world.js +51 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/gulpfile.js +31 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/package.json +41 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core.js +19 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core_browser.js +17 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_log.js +50 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_net.js +279 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_persist.js +20 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_core_browser.js +17 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_log.js +50 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_net.js +499 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i.js +74 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i_browser.js +130 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib.js +91 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib_browser.js +90 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_log.js +51 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_net.js +84 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_persist.js +20 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/util/net.js +327 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/test/nutella.test.js +16 -0
- data/framework_components/example_framework_web_interface/node_modules/nutella_lib/test/runner.html +22 -0
- data/framework_components/example_framework_web_interface/package.json +15 -0
- data/framework_components/{order.json.example → order.json} +0 -0
- data/framework_components/runs_list_bot/{app_runs_list_bot.rb → runs_list_bot.rb} +9 -3
- data/framework_components/runs_list_bot/startup +1 -1
- data/lib/commands/meta/run_command.rb +21 -36
- data/lib/commands/start.rb +9 -199
- data/lib/commands/util/components_list.rb +68 -0
- data/lib/commands/util/components_starter.rb +169 -0
- data/nutella_framework.gemspec +109 -47
- data/nutella_lib/framework_net.rb +17 -13
- metadata +84 -106
data/framework_components/example_framework_web_interface/node_modules/nutella_lib/test/runner.html
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Nutella Tests</title>
|
|
6
|
+
<link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="mocha"></div>
|
|
10
|
+
<script src="../node_modules/mocha/mocha.js" type="text/javascript"></script>
|
|
11
|
+
<script src="../node_modules/chai/chai.js" type="text/javascript"></script>
|
|
12
|
+
<script>
|
|
13
|
+
mocha.setup('bdd');
|
|
14
|
+
mocha.setup({ignoreLeaks: true});
|
|
15
|
+
</script>
|
|
16
|
+
<script src="../dist/nutella_lib.js"></script>
|
|
17
|
+
<script src="nutella.test.js"></script>
|
|
18
|
+
<script>
|
|
19
|
+
mocha.run();
|
|
20
|
+
</script>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "example_framework_web_interface",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "An example framework interface",
|
|
5
|
+
"main": "index.html",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"author": "Alessandro Gnoli",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"private": true,
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"nutella_lib": "*"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
File without changes
|
|
@@ -22,15 +22,21 @@ nutella.f.net.handle_requests_on_all_apps('app_runs_list', lambda do |req, app_i
|
|
|
22
22
|
Nutella.runlist.runs_for_app app_id
|
|
23
23
|
end)
|
|
24
24
|
|
|
25
|
+
# Listen for runs_list requests (done by framework interfaces when they connect)
|
|
26
|
+
nutella.f.net.handle_requests('runs_list', lambda do |req, from|
|
|
27
|
+
Nutella.runlist.all_runs
|
|
28
|
+
end)
|
|
29
|
+
|
|
25
30
|
|
|
26
|
-
# Whenever the runs list is updated, fire an updated runlist to all the apps
|
|
31
|
+
# Whenever the runs list is updated, fire an updated runlist to all the apps and all framework components
|
|
27
32
|
p = Nutella.runlist.all_runs
|
|
28
|
-
while sleep .
|
|
33
|
+
while sleep 0.25
|
|
29
34
|
n = Nutella.runlist.all_runs
|
|
30
35
|
if p!=n
|
|
31
|
-
all_apps.each do |app_id, _|
|
|
36
|
+
Nutella.runlist.all_apps.each do |app_id, _|
|
|
32
37
|
nutella.f.net.publish_to_app(app_id, 'app_runs_list', Nutella.runlist.runs_for_app(app_id))
|
|
33
38
|
end
|
|
39
|
+
nutella.f.net.publish 'runs_list', Nutella.runlist.all_runs
|
|
34
40
|
p = n
|
|
35
41
|
end
|
|
36
42
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'commands/meta/command'
|
|
2
|
+
require 'commands/util/components_list'
|
|
2
3
|
require 'slop'
|
|
3
4
|
|
|
4
5
|
module Nutella
|
|
@@ -50,40 +51,6 @@ module Nutella
|
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
|
|
53
|
-
# Returns all the components in a certain directory
|
|
54
|
-
def components_in_dir( dir )
|
|
55
|
-
Dir.entries(dir).select {|entry| File.directory?(File.join(dir, entry)) && !(entry =='.' || entry == '..') }
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# Executes a code block for each component in a certain directory
|
|
60
|
-
# @param [String] dir directory where we are iterating
|
|
61
|
-
# @yield [component] Passes the component name to the block
|
|
62
|
-
def for_each_component_in_dir( dir, &block )
|
|
63
|
-
components_in_dir(dir).each do |component|
|
|
64
|
-
block.call component
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# Runs a script for each bot in a certain directory.
|
|
70
|
-
# Message is displayed in case something goes wrong
|
|
71
|
-
def run_script_for_all_bots_in( dir, script, message )
|
|
72
|
-
for_each_component_in_dir dir do |bot|
|
|
73
|
-
# Skip bot if there is no script
|
|
74
|
-
next unless File.exist? "#{dir}/bots/#{bot}/#{script}"
|
|
75
|
-
# Output message
|
|
76
|
-
console.info "#{message} bot #{bot}."
|
|
77
|
-
# Execute 'script' script
|
|
78
|
-
cur_dir = Dir.pwd
|
|
79
|
-
Dir.chdir "#{dir}/bots/#{bot}"
|
|
80
|
-
system "./#{script}"
|
|
81
|
-
Dir.chdir cur_dir
|
|
82
|
-
end
|
|
83
|
-
true
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
|
|
87
54
|
# Prints a success message if the command completes correctly
|
|
88
55
|
def print_success_message(app_id, run_id, action)
|
|
89
56
|
if run_id == 'default'
|
|
@@ -100,15 +67,33 @@ module Nutella
|
|
|
100
67
|
console.warn 'The current directory is not a nutella application'
|
|
101
68
|
return
|
|
102
69
|
end
|
|
103
|
-
|
|
104
70
|
# Run script
|
|
105
71
|
return unless run_script_for_all_bots_in( Dir.pwd, script, in_progress_message )
|
|
106
|
-
|
|
107
72
|
# Output success message
|
|
108
73
|
console.success "All #{complete_message} for #{Nutella.current_app.config['name']}"
|
|
109
74
|
end
|
|
110
75
|
|
|
111
76
|
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Runs a script for each bot in a certain directory.
|
|
81
|
+
# Message is displayed in case something goes wrong
|
|
82
|
+
def run_script_for_all_bots_in( dir, script, message )
|
|
83
|
+
ComponentsList.for_each_component_in_dir dir do |bot|
|
|
84
|
+
# Skip bot if there is no script
|
|
85
|
+
next unless File.exist? "#{dir}/bots/#{bot}/#{script}"
|
|
86
|
+
# Output message
|
|
87
|
+
console.info "#{message} bot #{bot}."
|
|
88
|
+
# Execute 'script' script
|
|
89
|
+
cur_dir = Dir.pwd
|
|
90
|
+
Dir.chdir "#{dir}/bots/#{bot}"
|
|
91
|
+
system "./#{script}"
|
|
92
|
+
Dir.chdir cur_dir
|
|
93
|
+
end
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
|
|
112
97
|
end
|
|
113
98
|
|
|
114
99
|
end
|
data/lib/commands/start.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require 'commands/meta/run_command'
|
|
2
|
-
require '
|
|
2
|
+
require 'commands/util/components_starter'
|
|
3
3
|
|
|
4
4
|
module Nutella
|
|
5
5
|
class Start < RunCommand
|
|
@@ -14,7 +14,7 @@ module Nutella
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
begin
|
|
17
|
-
run_id, params =
|
|
17
|
+
run_id, params = parse_cli_arguments args
|
|
18
18
|
rescue StandardError => e
|
|
19
19
|
console.error e.message
|
|
20
20
|
return
|
|
@@ -41,7 +41,7 @@ module Nutella
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
# Parses command line arguments
|
|
44
|
-
def
|
|
44
|
+
def parse_cli_arguments( args )
|
|
45
45
|
# Parse run_id
|
|
46
46
|
run_id = parse_run_id_from args
|
|
47
47
|
# Extract parameters
|
|
@@ -65,54 +65,10 @@ module Nutella
|
|
|
65
65
|
# Returns true if both the list of run level bots is empty and the app bots
|
|
66
66
|
# have been started already
|
|
67
67
|
def no_bot_to_start(app_id, app_path, params)
|
|
68
|
-
|
|
68
|
+
ComponentsList.run_level_bots_list(app_path, params).empty? && app_bots_started?(app_id)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
# Returns the list of run-level bots for this run
|
|
73
|
-
# Depending on the mode we are in, we want to start only some bots, exclude only some bots or start all bots
|
|
74
|
-
def run_level_bots_list( app_path, params )
|
|
75
|
-
# Fetch the list of all components in the bots dir
|
|
76
|
-
all_bots = components_in_dir "#{app_path}/bots/"
|
|
77
|
-
# Fetch the list of app bots
|
|
78
|
-
app_bots = Nutella.current_app.config['app_bots']
|
|
79
|
-
# Return correct list based on the mode we are in
|
|
80
|
-
case start_mode(params)
|
|
81
|
-
when :WITH
|
|
82
|
-
return get_with_bots_list params[:with], app_bots
|
|
83
|
-
when :WO
|
|
84
|
-
return get_wo_bots_list all_bots, app_bots, params[:without]
|
|
85
|
-
when :ALL
|
|
86
|
-
return get_all_bots_list all_bots, app_bots
|
|
87
|
-
else
|
|
88
|
-
# If we get here it means we are both in with and without mode and something went very wrong...
|
|
89
|
-
raise 'You are using simultaneously with and without modes. This should not happen. Please contact developers.'
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def start_mode(params)
|
|
94
|
-
return :WITH unless params[:with].empty?
|
|
95
|
-
return :WO unless params[:without].empty?
|
|
96
|
-
:ALL if params[:with].empty? && params[:without].empty?
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# If we are in "with mode", we want to run only the bots in the "with list" (minus the ones in app_bots_list)
|
|
100
|
-
def get_with_bots_list( incl_bots, app_bots)
|
|
101
|
-
return app_bots.nil? ? incl_bots : incl_bots - app_bots
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# If we are in "without mode", we want to run all the bots in the bots_list minus the ones in the "without list" and in the "app bots list"
|
|
105
|
-
def get_wo_bots_list( all_bots, app_bots, excl_bots )
|
|
106
|
-
return app_bots.nil? ? all_bots - excl_bots : all_bots - excl_bots - app_bots
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# If we are in "all mode", we want to run all the bots minus the ones in the "app bots list"
|
|
110
|
-
def get_all_bots_list( all_bots, app_bots )
|
|
111
|
-
return app_bots.nil? ? all_bots : all_bots - app_bots
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
72
|
# Returns true if the app bots have been started already
|
|
117
73
|
def app_bots_started?( app_id )
|
|
118
74
|
Tmux.session_exist? Tmux.app_bot_session_name app_id
|
|
@@ -137,162 +93,17 @@ module Nutella
|
|
|
137
93
|
# Starts all the components at all levels for this run
|
|
138
94
|
def start_all_components( app_id, app_path, run_id, params )
|
|
139
95
|
# Start the internal broker
|
|
140
|
-
return false unless start_internal_broker
|
|
96
|
+
return false unless ComponentsStarter.start_internal_broker
|
|
141
97
|
# Start all framework-level components (if needed)
|
|
142
|
-
return false unless start_framework_components
|
|
98
|
+
return false unless ComponentsStarter.start_framework_components
|
|
143
99
|
# Start all app-level bots (if any, if needed)
|
|
144
|
-
return false unless start_app_bots(app_id, app_path)
|
|
100
|
+
return false unless ComponentsStarter.start_app_bots(app_id, app_path)
|
|
145
101
|
# Start all run-level bots
|
|
146
|
-
false unless start_run_bots( app_path, app_id, run_id
|
|
147
|
-
true
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def start_internal_broker
|
|
152
|
-
pid_file_path = "#{Nutella.config['broker_dir']}bin/.pid"
|
|
153
|
-
# Check if the process with pid indicated in the pidfile is alive
|
|
154
|
-
return true if sanitize_pid_file pid_file_path
|
|
155
|
-
# Check that broker is not running 'unsupervised' (i.e. check port 1883), if it is, return
|
|
156
|
-
return true unless broker_port_free?
|
|
157
|
-
# Broker is not running and there is no pid file so we try to start
|
|
158
|
-
# the internal broker and create a new pid file. Note that the pid file is created by
|
|
159
|
-
# the `startup` script, not here.
|
|
160
|
-
pid = fork
|
|
161
|
-
exec("#{Nutella.config['broker_dir']}/startup") if pid.nil?
|
|
162
|
-
# Wait a bit to give the chance to the broker to actually start up
|
|
163
|
-
sleep 1
|
|
164
|
-
# All went well so we return true
|
|
165
|
-
true
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
# Cleans the pid file of a given process
|
|
170
|
-
# @param [String] pid_file_path the file storing the pid file of the process
|
|
171
|
-
# @return [Boolean] true if the pid file exists AND the process with that pid is still alive
|
|
172
|
-
def sanitize_pid_file( pid_file_path )
|
|
173
|
-
# Does the pid file exist?
|
|
174
|
-
# If it does we try to see if the process with that pid is still alive
|
|
175
|
-
if File.exist? pid_file_path
|
|
176
|
-
pid_file = File.open(pid_file_path, 'rb')
|
|
177
|
-
pid = pid_file.read.to_i
|
|
178
|
-
pid_file.close
|
|
179
|
-
begin
|
|
180
|
-
# If this statement doesn't throw an exception then a process with
|
|
181
|
-
# this pid is still alive so we do nothing and just return true
|
|
182
|
-
Process.getpgid pid
|
|
183
|
-
return true
|
|
184
|
-
rescue
|
|
185
|
-
# If there is an exception, there is no process with this pid
|
|
186
|
-
# so we have a stale pid file that we need to remove
|
|
187
|
-
File.delete pid_file_path
|
|
188
|
-
return false
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
# If there is no pid file, there is no process running
|
|
192
|
-
false
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
# Checks if port 1883 (MQTT broker port) is free
|
|
197
|
-
# or some other service is already listening on it
|
|
198
|
-
def broker_port_free?
|
|
199
|
-
begin
|
|
200
|
-
s = TCPServer.new('0.0.0.0', 1883)
|
|
201
|
-
s.close
|
|
202
|
-
rescue
|
|
203
|
-
return false
|
|
204
|
-
end
|
|
102
|
+
false unless ComponentsStarter.start_run_bots(ComponentsList.run_level_bots_list(app_path, params), app_path, app_id, run_id)
|
|
205
103
|
true
|
|
206
104
|
end
|
|
207
105
|
|
|
208
106
|
|
|
209
|
-
def start_framework_components
|
|
210
|
-
nutella_components_dir = "#{Nutella::NUTELLA_HOME}framework_components"
|
|
211
|
-
if File.exist? "#{nutella_components_dir}/order.json"
|
|
212
|
-
components_list = JSON.parse IO.read "#{nutella_components_dir}/order.json"
|
|
213
|
-
else
|
|
214
|
-
components_list = components_in_dir(nutella_components_dir)
|
|
215
|
-
end
|
|
216
|
-
components_list.each do |component|
|
|
217
|
-
if File.exist? "#{nutella_components_dir}/#{component}/startup"
|
|
218
|
-
unless start_framework_component "#{nutella_components_dir}/#{component}"
|
|
219
|
-
return false
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
true
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def start_framework_component( component_dir )
|
|
228
|
-
pid_file_path = "#{component_dir}/.pid"
|
|
229
|
-
return true if sanitize_pid_file pid_file_path
|
|
230
|
-
# Component is not running and there is no pid file so we try to start it
|
|
231
|
-
# and create a new pid file. Note that the pid file is created by
|
|
232
|
-
# the startup script!
|
|
233
|
-
nutella_config_file = "#{Nutella.config['config_dir']}config.json"
|
|
234
|
-
runs_list_file = "#{Nutella.config['config_dir']}runlist.json"
|
|
235
|
-
if nutella_config_file==nil || runs_list_file==nil
|
|
236
|
-
return false
|
|
237
|
-
end
|
|
238
|
-
# We are passing the configuration file and the run list files paths to the framework components
|
|
239
|
-
command = "#{component_dir}/startup #{nutella_config_file} #{runs_list_file}"
|
|
240
|
-
pid = fork
|
|
241
|
-
exec(command) if pid.nil?
|
|
242
|
-
# Give it a second so they can start properly
|
|
243
|
-
sleep 1
|
|
244
|
-
# All went well so we return true
|
|
245
|
-
true
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
def start_app_bots( app_id, app_path )
|
|
250
|
-
app_bots_list = Nutella.current_app.config['app_bots']
|
|
251
|
-
bots_dir = "#{app_path}/bots/"
|
|
252
|
-
# If app bots have been started already, then do nothing
|
|
253
|
-
unless Tmux.session_exist? Tmux.app_bot_session_name app_id
|
|
254
|
-
# Start all app bots in the list into a new tmux session
|
|
255
|
-
tmux = Tmux.new app_id, nil
|
|
256
|
-
for_each_component_in_dir bots_dir do |bot|
|
|
257
|
-
unless app_bots_list.nil? || !app_bots_list.include?( bot )
|
|
258
|
-
# If there is no 'startup' script output a warning (because
|
|
259
|
-
# startup is mandatory) and skip the bot
|
|
260
|
-
unless File.exist?("#{bots_dir}#{bot}/startup")
|
|
261
|
-
console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script."
|
|
262
|
-
next
|
|
263
|
-
end
|
|
264
|
-
# Create a new window in the session for this run
|
|
265
|
-
tmux.new_app_bot_window bot
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
end
|
|
269
|
-
true
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
def start_run_bots( app_path, app_id, run_id, params )
|
|
274
|
-
# Create a new tmux instance for this run
|
|
275
|
-
tmux = Tmux.new app_id, run_id
|
|
276
|
-
# Fetch bots dir
|
|
277
|
-
bots_dir = "#{app_path}/bots/"
|
|
278
|
-
# Start the appropriate bots
|
|
279
|
-
run_level_bots_list( app_path, params ).each { |bot| start_run_level_bot(bots_dir, bot, tmux) }
|
|
280
|
-
true
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
# Starts a run level bot
|
|
284
|
-
def start_run_level_bot( bots_dir, bot, tmux )
|
|
285
|
-
# If there is no 'startup' script output a warning (because
|
|
286
|
-
# startup is mandatory) and skip the bot
|
|
287
|
-
unless File.exist?("#{bots_dir}#{bot}/startup")
|
|
288
|
-
console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script."
|
|
289
|
-
return
|
|
290
|
-
end
|
|
291
|
-
# Create a new window in the session for this run
|
|
292
|
-
tmux.new_bot_window bot
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
|
|
296
107
|
def print_confirmation( run_id, params, app_id, app_path )
|
|
297
108
|
# If there are no run-level bots to start, do not create the run and error out
|
|
298
109
|
if run_level_bots_list(app_path, params).empty?
|
|
@@ -316,8 +127,7 @@ module Nutella
|
|
|
316
127
|
console.success "Do `tmux attach-session -t #{Tmux.session_name(app_id,run_id)}` to monitor your bots."
|
|
317
128
|
console.success "Go to http://localhost:#{Nutella.config['main_interface_port']}/#{app_id}/#{run_id} to access your interfaces"
|
|
318
129
|
end
|
|
319
|
-
|
|
320
|
-
|
|
130
|
+
|
|
321
131
|
end
|
|
322
132
|
|
|
323
133
|
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Utility methods to list components
|
|
2
|
+
class ComponentsList
|
|
3
|
+
|
|
4
|
+
# Returns all the components in a certain directory
|
|
5
|
+
def self.components_in_dir( dir )
|
|
6
|
+
Dir.entries(dir).select {|entry| File.directory?(File.join(dir, entry)) && !(entry =='.' || entry == '..') }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Executes a code block for each component in a certain directory
|
|
11
|
+
# @param [String] dir directory where we are iterating
|
|
12
|
+
# @yield [component] Passes the component name to the block
|
|
13
|
+
def self.for_each_component_in_dir( dir, &block )
|
|
14
|
+
components_in_dir(dir).each { |component| block.call component }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Returns the list of run-level bots for this run
|
|
19
|
+
# Depending on the mode we are in, we want to start only some bots, exclude only some bots or start all bots
|
|
20
|
+
def self.run_level_bots_list( app_path, params )
|
|
21
|
+
# Fetch the list of all components in the bots dir
|
|
22
|
+
all_bots = components_in_dir "#{app_path}/bots/"
|
|
23
|
+
# Fetch the list of app bots
|
|
24
|
+
app_bots = Nutella.current_app.config['app_bots']
|
|
25
|
+
# Return correct list based on the mode we are in
|
|
26
|
+
case start_mode(params)
|
|
27
|
+
when :WITH
|
|
28
|
+
return get_with_bots_list params[:with], app_bots
|
|
29
|
+
when :WO
|
|
30
|
+
return get_wo_bots_list all_bots, app_bots, params[:without]
|
|
31
|
+
when :ALL
|
|
32
|
+
return get_all_bots_list all_bots, app_bots
|
|
33
|
+
else
|
|
34
|
+
# If we get here it means we are both in with and without mode and something went very wrong...
|
|
35
|
+
raise 'You are using simultaneously with and without modes. This should not happen. Please contact developers.'
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
#--- Private class methods --------------
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def self.start_mode(params)
|
|
44
|
+
return :WITH unless params[:with].empty?
|
|
45
|
+
return :WO unless params[:without].empty?
|
|
46
|
+
:ALL if params[:with].empty? && params[:without].empty?
|
|
47
|
+
end
|
|
48
|
+
private_class_method :start_mode
|
|
49
|
+
|
|
50
|
+
# If we are in "with mode", we want to run only the bots in the "with list" (minus the ones in app_bots_list)
|
|
51
|
+
def self.get_with_bots_list( incl_bots, app_bots)
|
|
52
|
+
return app_bots.nil? ? incl_bots : incl_bots - app_bots
|
|
53
|
+
end
|
|
54
|
+
private_class_method :get_with_bots_list
|
|
55
|
+
|
|
56
|
+
# If we are in "without mode", we want to run all the bots in the bots_list minus the ones in the "without list" and in the "app bots list"
|
|
57
|
+
def self.get_wo_bots_list( all_bots, app_bots, excl_bots )
|
|
58
|
+
return app_bots.nil? ? all_bots - excl_bots : all_bots - excl_bots - app_bots
|
|
59
|
+
end
|
|
60
|
+
private_class_method :get_wo_bots_list
|
|
61
|
+
|
|
62
|
+
# If we are in "all mode", we want to run all the bots minus the ones in the "app bots list"
|
|
63
|
+
def self.get_all_bots_list( all_bots, app_bots )
|
|
64
|
+
return app_bots.nil? ? all_bots : all_bots - app_bots
|
|
65
|
+
end
|
|
66
|
+
private_class_method :get_all_bots_list
|
|
67
|
+
|
|
68
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
require 'commands/util/components_list'
|
|
2
|
+
require 'tmux/tmux'
|
|
3
|
+
|
|
4
|
+
# Utility functions to start components
|
|
5
|
+
class ComponentsStarter
|
|
6
|
+
|
|
7
|
+
# Starts the internal broker if it's not started already
|
|
8
|
+
# @return [boolean] true if the broker is correctly started, false otherwise
|
|
9
|
+
def self.start_internal_broker
|
|
10
|
+
pid_file_path = "#{Nutella.config['broker_dir']}bin/.pid"
|
|
11
|
+
# Check if the process with pid indicated in the pidfile is alive
|
|
12
|
+
return true if sanitize_pid_file pid_file_path
|
|
13
|
+
# Check that broker is not running 'unsupervised' (i.e. check port 1883), if it is, return
|
|
14
|
+
return true unless broker_port_free?
|
|
15
|
+
# Broker is not running and there is no pid file so we try to start
|
|
16
|
+
# the internal broker and create a new pid file. Note that the pid file is created by
|
|
17
|
+
# the `startup` script, not here.
|
|
18
|
+
pid = fork
|
|
19
|
+
exec("#{Nutella.config['broker_dir']}/startup") if pid.nil?
|
|
20
|
+
# Wait a bit to give the chance to the broker to actually start up
|
|
21
|
+
sleep 1
|
|
22
|
+
# All went well so we return true
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Starts all framework components. If order.json is present, components are started
|
|
28
|
+
# in that order.
|
|
29
|
+
# @return [boolean] true if all components are started correctly, false otherwise
|
|
30
|
+
def self.start_framework_components
|
|
31
|
+
nutella_components_dir = "#{Nutella::NUTELLA_HOME}framework_components"
|
|
32
|
+
if File.exist? "#{nutella_components_dir}/order.json"
|
|
33
|
+
components_list = JSON.parse IO.read "#{nutella_components_dir}/order.json"
|
|
34
|
+
else
|
|
35
|
+
components_list = ComponentsList.components_in_dir nutella_components_dir
|
|
36
|
+
end
|
|
37
|
+
components_list.each do |component|
|
|
38
|
+
if File.exist? "#{nutella_components_dir}/#{component}/startup"
|
|
39
|
+
unless start_framework_component "#{nutella_components_dir}/#{component}"
|
|
40
|
+
return false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Starts the application level bots
|
|
49
|
+
# @return [boolean] true if all bots are started correctly, false otherwise
|
|
50
|
+
def self.start_app_bots( app_id, app_path )
|
|
51
|
+
app_bots_list = Nutella.current_app.config['app_bots']
|
|
52
|
+
bots_dir = "#{app_path}/bots/"
|
|
53
|
+
# If app bots have been started already, then do nothing
|
|
54
|
+
unless Tmux.session_exist? Tmux.app_bot_session_name app_id
|
|
55
|
+
# Start all app bots in the list into a new tmux session
|
|
56
|
+
tmux = Tmux.new app_id, nil
|
|
57
|
+
ComponentsList.for_each_component_in_dir bots_dir do |bot|
|
|
58
|
+
unless app_bots_list.nil? || !app_bots_list.include?( bot )
|
|
59
|
+
# If there is no 'startup' script output a warning (because
|
|
60
|
+
# startup is mandatory) and skip the bot
|
|
61
|
+
unless File.exist?("#{bots_dir}#{bot}/startup")
|
|
62
|
+
console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script."
|
|
63
|
+
next
|
|
64
|
+
end
|
|
65
|
+
# Create a new window in the session for this run
|
|
66
|
+
tmux.new_app_bot_window bot
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def self.start_run_bots( bots_list, app_path, app_id, run_id )
|
|
75
|
+
# Create a new tmux instance for this run
|
|
76
|
+
tmux = Tmux.new app_id, run_id
|
|
77
|
+
# Fetch bots dir
|
|
78
|
+
bots_dir = "#{app_path}/bots/"
|
|
79
|
+
# Start the appropriate bots
|
|
80
|
+
bots_list.each { |bot| start_run_level_bot(bots_dir, bot, tmux) }
|
|
81
|
+
true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
#--- Private class methods --------------
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Cleans the pid file of a given process
|
|
89
|
+
# @param [String] pid_file_path the file storing the pid file of the process
|
|
90
|
+
# @return [Boolean] true if the pid file exists AND the process with that pid is still alive
|
|
91
|
+
def self.sanitize_pid_file( pid_file_path )
|
|
92
|
+
# Does the pid file exist?
|
|
93
|
+
# If it does we try to see if the process with that pid is still alive
|
|
94
|
+
if File.exist? pid_file_path
|
|
95
|
+
pid_file = File.open(pid_file_path, 'rb')
|
|
96
|
+
pid = pid_file.read.to_i
|
|
97
|
+
pid_file.close
|
|
98
|
+
begin
|
|
99
|
+
# If this statement doesn't throw an exception then a process with
|
|
100
|
+
# this pid is still alive so we do nothing and just return true
|
|
101
|
+
Process.getpgid pid
|
|
102
|
+
return true
|
|
103
|
+
rescue
|
|
104
|
+
# If there is an exception, there is no process with this pid
|
|
105
|
+
# so we have a stale pid file that we need to remove
|
|
106
|
+
File.delete pid_file_path
|
|
107
|
+
return false
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
# If there is no pid file, there is no process running
|
|
111
|
+
false
|
|
112
|
+
end
|
|
113
|
+
private_class_method :sanitize_pid_file
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# Checks if port 1883 (MQTT broker port) is free
|
|
117
|
+
# or some other service is already listening on it
|
|
118
|
+
# @return [boolean] true if there is no broker listening on port 1883, false otherwise
|
|
119
|
+
def self.broker_port_free?
|
|
120
|
+
begin
|
|
121
|
+
s = TCPServer.new('0.0.0.0', 1883)
|
|
122
|
+
s.close
|
|
123
|
+
rescue
|
|
124
|
+
return false
|
|
125
|
+
end
|
|
126
|
+
true
|
|
127
|
+
end
|
|
128
|
+
private_class_method :broker_port_free?
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# Starts a single framework component
|
|
132
|
+
# @return [boolean] true if the component has been started successfully, false otherwise
|
|
133
|
+
def self.start_framework_component( component_dir )
|
|
134
|
+
pid_file_path = "#{component_dir}/.pid"
|
|
135
|
+
return true if sanitize_pid_file pid_file_path
|
|
136
|
+
# Component is not running and there is no pid file so we try to start it
|
|
137
|
+
# and create a new pid file. Note that the pid file is created by
|
|
138
|
+
# the startup script!
|
|
139
|
+
nutella_config_file = "#{Nutella.config['config_dir']}config.json"
|
|
140
|
+
runs_list_file = "#{Nutella.config['config_dir']}runlist.json"
|
|
141
|
+
if nutella_config_file==nil || runs_list_file==nil
|
|
142
|
+
return false
|
|
143
|
+
end
|
|
144
|
+
# We are passing the configuration file and the run list files paths to the framework components
|
|
145
|
+
command = "#{component_dir}/startup #{nutella_config_file} #{runs_list_file}"
|
|
146
|
+
pid = fork
|
|
147
|
+
exec(command) if pid.nil?
|
|
148
|
+
# Give it a second so they can start properly
|
|
149
|
+
sleep 1
|
|
150
|
+
# All went well so we return true
|
|
151
|
+
true
|
|
152
|
+
end
|
|
153
|
+
private_class_method :start_framework_component
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# Starts a run level bot
|
|
157
|
+
def self.start_run_level_bot( bots_dir, bot, tmux )
|
|
158
|
+
# If there is no 'startup' script output a warning (because
|
|
159
|
+
# startup is mandatory) and skip the bot
|
|
160
|
+
unless File.exist?("#{bots_dir}#{bot}/startup")
|
|
161
|
+
console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script."
|
|
162
|
+
return
|
|
163
|
+
end
|
|
164
|
+
# Create a new window in the session for this run
|
|
165
|
+
tmux.new_bot_window bot
|
|
166
|
+
end
|
|
167
|
+
private_class_method :start_run_level_bot
|
|
168
|
+
|
|
169
|
+
end
|