nuri 0.5.2 → 0.5.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/.travis.yml +1 -0
- data/VERSION +1 -1
- data/bin/nuri +110 -82
- data/bin/{install_module → nuri-install-module} +7 -4
- data/bin/{sfw2graph → nuri-sfwgraph} +7 -6
- data/bin/nuri-uninstall-module +45 -0
- data/examples/.gitignore +1 -0
- data/examples/mockcloud/generator.rb +14 -58
- data/examples/mockcloud/generator.rb.bak +66 -0
- data/examples/mockcloud/hadoop2.sfp.template +25 -0
- data/lib/nuri.rb +5 -7
- data/lib/nuri/choreographer.rb +8 -8
- data/lib/nuri/helper.rb +10 -3
- data/lib/nuri/master.rb +53 -15
- data/lib/nuri/orchestrator.rb +34 -29
- data/modules/apache2/apache2.rb +9 -0
- data/modules/apache2/apache2.sfp +54 -0
- data/modules/file/file.rb +1 -1
- data/modules/file/file.sfp +1 -1
- data/modules/machine/machine.rb +17 -0
- data/modules/package2/package2.rb +85 -0
- data/modules/package2/package2.sfp +23 -0
- data/modules/pyfile/main +133 -0
- data/modules/pyfile/pyfile.sfp +23 -0
- data/modules/service2/service2.rb +62 -0
- data/modules/service2/service2.sfp +24 -0
- data/nuri.gemspec +1 -1
- metadata +17 -11
- data/bin/delete_modules +0 -35
- data/bin/nuri.old +0 -183
- data/modules/install_module +0 -54
- data/modules/service/model.json +0 -1
- data/modules/service/test.sfp +0 -6
data/lib/nuri/orchestrator.rb
CHANGED
@@ -3,29 +3,31 @@ require 'thread'
|
|
3
3
|
module Nuri::Orchestrator
|
4
4
|
include Nuri::Helper
|
5
5
|
|
6
|
-
def execute_plan(
|
7
|
-
raise Exception, "Plan file is not exist!" if not File.exist?(
|
8
|
-
raise Exception, "Plan is not exist (parameter :plan must be given)!" if !
|
6
|
+
def execute_plan(options={})
|
7
|
+
raise Exception, "Plan file is not exist!" if not File.exist?(options[:execute].to_s) and !options[:plan]
|
8
|
+
raise Exception, "Plan is not exist (parameter :plan must be given)!" if !options[:plan]
|
9
9
|
|
10
10
|
push_agents_list
|
11
11
|
|
12
12
|
success = false
|
13
13
|
benchmark = Benchmark.measure {
|
14
|
-
plan = (
|
15
|
-
raise Exception, "
|
14
|
+
plan = (options[:plan] ? options[:plan] : JSON[File.read(options[:execute])])
|
15
|
+
raise Exception, "No plan." if plan['workflow'].nil?
|
16
16
|
if plan.is_a?(Hash) and plan['type'] == 'sequential'
|
17
|
-
success = execute_sequential_plan(plan,
|
17
|
+
success = execute_sequential_plan(plan, options)
|
18
18
|
elsif plan.is_a?(Hash) and plan['type'] == 'parallel'
|
19
|
-
success = execute_parallel_plan(plan,
|
19
|
+
success = execute_parallel_plan(plan, options)
|
20
|
+
else
|
21
|
+
raise Exception, "Invalid plan."
|
20
22
|
end
|
21
23
|
}
|
22
|
-
puts "Execution
|
24
|
+
puts "Execution " + format_benchmark(benchmark)
|
23
25
|
|
24
26
|
success
|
25
27
|
end
|
26
28
|
|
27
29
|
protected
|
28
|
-
def send_action_data(action, address, port)
|
30
|
+
def send_action_data(action, address, port, options={})
|
29
31
|
action['parameters'].each do |name,value|
|
30
32
|
next if !value.is_a?(String) or !value.isref or value.split('.').length > 2
|
31
33
|
_, target_name = value.split('.', 2)
|
@@ -38,7 +40,8 @@ module Nuri::Orchestrator
|
|
38
40
|
rescue
|
39
41
|
end
|
40
42
|
if code != '200'
|
41
|
-
$stderr.
|
43
|
+
$stderr.print "Sending action data of #{value} to #{address}:#{port} "
|
44
|
+
$stderr.puts (options[:color] ? "[Failed]".red : "[Failed]")
|
42
45
|
return false
|
43
46
|
end
|
44
47
|
end
|
@@ -46,7 +49,7 @@ module Nuri::Orchestrator
|
|
46
49
|
true
|
47
50
|
end
|
48
51
|
|
49
|
-
def execute_action(action)
|
52
|
+
def execute_action(action, options={})
|
50
53
|
_, agent_name, _ = action['name'].split('.', 3)
|
51
54
|
agents = get_agents
|
52
55
|
|
@@ -58,7 +61,7 @@ module Nuri::Orchestrator
|
|
58
61
|
raise Exception, "Cannot find address:port of agent #{agent_name}" if
|
59
62
|
address.length <= 0 or port.length <= 0
|
60
63
|
|
61
|
-
send_action_data(action, address, port)
|
64
|
+
send_action_data(action, address, port, options)
|
62
65
|
|
63
66
|
data = {'action' => JSON.generate(action)}
|
64
67
|
code, _ = post_data(address, port, '/execute', data)
|
@@ -97,22 +100,22 @@ module Nuri::Orchestrator
|
|
97
100
|
}
|
98
101
|
end
|
99
102
|
|
100
|
-
def execute_sequential_plan(plan,
|
103
|
+
def execute_sequential_plan(plan, options)
|
101
104
|
begin
|
102
105
|
index = 1
|
103
106
|
plan['workflow'].each do |action|
|
104
|
-
|
105
|
-
if execute_action(action)
|
106
|
-
puts "[OK]".green
|
107
|
+
puts "#{index}. #{action['name']} #{JSON.generate(action['parameters'])} " + (options[:color] ? "[Wait]".yellow : "[Wait]")
|
108
|
+
if execute_action(action, options)
|
109
|
+
puts "#{index}. #{action['name']} #{JSON.generate(action['parameters'])} " + (options[:color] ? "[OK]".green : "[OK]")
|
107
110
|
else
|
108
|
-
puts "[Failed]".red
|
111
|
+
puts "#{index}. #{action['name']} #{JSON.generate(action['parameters'])} " + (options[:color] ? "[Failed]".red : "[Failed]")
|
109
112
|
return false
|
110
113
|
end
|
111
114
|
index += 1
|
112
115
|
end
|
113
116
|
return true
|
114
117
|
rescue Exception => e
|
115
|
-
puts "#{e}\n#{e.backtrace.join("\n")}"
|
118
|
+
$stderr.puts "#{e}\n#{e.backtrace.join("\n")}"
|
116
119
|
end
|
117
120
|
false
|
118
121
|
end
|
@@ -140,27 +143,28 @@ module Nuri::Orchestrator
|
|
140
143
|
id
|
141
144
|
end
|
142
145
|
|
143
|
-
def assign_action_with_id(id)
|
146
|
+
def assign_action_with_id(id, options={})
|
144
147
|
thread_id = next_thread_id
|
145
148
|
action = @actions[id]
|
146
149
|
action[:executor] = thread_id
|
147
|
-
self.thread_execute_action(thread_id, action)
|
150
|
+
self.thread_execute_action(thread_id, action, options)
|
148
151
|
end
|
149
152
|
|
150
|
-
def thread_execute_action(tid, action)
|
153
|
+
def thread_execute_action(tid, action, options={})
|
151
154
|
t = Thread.new {
|
152
155
|
# Register the thread
|
153
156
|
@mutex.synchronize { @threads << tid }
|
154
157
|
|
155
158
|
while not @failed and not action[:executed]
|
156
159
|
# Try to execute the action
|
157
|
-
puts "Executing #{action[:string]} - thread ##{tid} [
|
160
|
+
puts "Executing #{action[:string]} - thread ##{tid} " + (options[:color] ? "[Wait]".yellow : "[Wait]")
|
158
161
|
success = false
|
159
162
|
1.upto(@retries) do |i|
|
160
163
|
begin
|
161
|
-
success = execute_action(action)
|
164
|
+
success = execute_action(action, options)
|
162
165
|
rescue Exception => exp
|
163
|
-
puts "Executing(#{i}) #{action[:string]} - thread ##{tid} [
|
166
|
+
puts "Executing(#{i}) #{action[:string]} - thread ##{tid} " + (options[:color] ? "[Failed]".red : "[Failed]")
|
167
|
+
puts "#{exp}\n#{exp.backtrace.join("\n")}"
|
164
168
|
end
|
165
169
|
break if success
|
166
170
|
end
|
@@ -168,10 +172,10 @@ module Nuri::Orchestrator
|
|
168
172
|
# Check if execution failed
|
169
173
|
if success
|
170
174
|
# Execution was success
|
171
|
-
puts "Executing #{action[:string]} - thread ##{tid} [OK]".green
|
175
|
+
puts "Executing #{action[:string]} - thread ##{tid} " + (options[:color] ? "[OK]".green : "[OK]")
|
172
176
|
next_actions = []
|
173
177
|
@mutex.synchronize {
|
174
|
-
action[:executed] = true # set executed
|
178
|
+
action[:executed] = true # set executed flag
|
175
179
|
# select next action to be executed from successor actions list
|
176
180
|
# select a successor action that has not been assigned to any thread yet
|
177
181
|
if action['successors'].length > 0
|
@@ -196,14 +200,14 @@ module Nuri::Orchestrator
|
|
196
200
|
if next_actions.length > 1
|
197
201
|
# execute other next actions to other threads
|
198
202
|
for i in 1..(next_actions.length-1)
|
199
|
-
assign_action_with_id(next_actions[i])
|
203
|
+
assign_action_with_id(next_actions[i], options)
|
200
204
|
end
|
201
205
|
end
|
202
206
|
end
|
203
207
|
|
204
208
|
else
|
205
209
|
# Execution was failed
|
206
|
-
puts "Executing #{action[:string]} - thread ##{tid} [
|
210
|
+
puts "Executing #{action[:string]} - thread ##{tid} " + (options[:color] ? "[Failed]".red : "[Failed]")
|
207
211
|
@mutex.synchronize {
|
208
212
|
@failed = true # set global flag to stop the execution
|
209
213
|
@actions_failed << action
|
@@ -215,7 +219,8 @@ module Nuri::Orchestrator
|
|
215
219
|
}
|
216
220
|
end
|
217
221
|
|
218
|
-
|
222
|
+
### execute actions without predecessor
|
223
|
+
plan['init'].each { |id| assign_action_with_id(id, options) }
|
219
224
|
begin
|
220
225
|
sleep 1
|
221
226
|
end while @threads.length > 0
|
@@ -0,0 +1,54 @@
|
|
1
|
+
include "../package2/package2.sfp"
|
2
|
+
include "../service2/service2.sfp"
|
3
|
+
|
4
|
+
schema Apache2 {
|
5
|
+
installed : Bool
|
6
|
+
running : Bool
|
7
|
+
|
8
|
+
package isa Package2 {
|
9
|
+
name = "apache2"
|
10
|
+
version = "latest"
|
11
|
+
provider = "apt"
|
12
|
+
|
13
|
+
synchronized sub install {
|
14
|
+
condition {
|
15
|
+
this.parent.installed != true
|
16
|
+
}
|
17
|
+
effect {
|
18
|
+
this.parent.installed = true
|
19
|
+
this.parent.running = true
|
20
|
+
}
|
21
|
+
}
|
22
|
+
synchronized sub uninstall {
|
23
|
+
condition {
|
24
|
+
this.parent.installed = true
|
25
|
+
this.parent.running = false
|
26
|
+
}
|
27
|
+
effect {
|
28
|
+
this.parent.installed = false
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
service isa Service2 {
|
34
|
+
name = "apache2"
|
35
|
+
|
36
|
+
sub start {
|
37
|
+
condition {
|
38
|
+
this.parent.installed = true
|
39
|
+
}
|
40
|
+
effect {
|
41
|
+
this.parent.running = true
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
sub stop {
|
46
|
+
condition {
|
47
|
+
this.parent.running = true
|
48
|
+
}
|
49
|
+
effect {
|
50
|
+
this.parent.running = false
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
data/modules/file/file.rb
CHANGED
data/modules/file/file.sfp
CHANGED
data/modules/machine/machine.rb
CHANGED
@@ -4,6 +4,8 @@ class Sfp::Module::Machine
|
|
4
4
|
def update_state
|
5
5
|
to_model
|
6
6
|
|
7
|
+
load_kernel_modules(['acpiphp'])
|
8
|
+
|
7
9
|
@state['sfpAddress'] = @model['sfpAddress']
|
8
10
|
@state['sfpPort'] = @model['sfpPort']
|
9
11
|
@state['created'] = true
|
@@ -29,6 +31,21 @@ class Sfp::Module::Machine
|
|
29
31
|
|
30
32
|
protected
|
31
33
|
|
34
|
+
def load_kernel_modules(modules=[])
|
35
|
+
loaded = []
|
36
|
+
`lsmod`.each_line do |line|
|
37
|
+
next if line.strip.length <= 0
|
38
|
+
name, size, used, by = line.split(' ', 4)
|
39
|
+
next if name.to_s.downcase == 'module'
|
40
|
+
loaded << name
|
41
|
+
end
|
42
|
+
|
43
|
+
unloaded = modules - loaded
|
44
|
+
unloaded.each do |name|
|
45
|
+
shell "modprobe #{name}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
32
49
|
# generate the disks' state, try to automatically mount the disk to target directory
|
33
50
|
#
|
34
51
|
def get_disk_state
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class Sfp::Module::Package2
|
4
|
+
include Sfp::Resource
|
5
|
+
|
6
|
+
def update_state
|
7
|
+
to_model
|
8
|
+
|
9
|
+
@state['installed'] = installed?
|
10
|
+
@state['version'] = version?
|
11
|
+
end
|
12
|
+
|
13
|
+
##############################
|
14
|
+
#
|
15
|
+
# Action methods (see Package2.sfp)
|
16
|
+
#
|
17
|
+
##############################
|
18
|
+
|
19
|
+
@@lock = Mutex.new
|
20
|
+
|
21
|
+
def installed?
|
22
|
+
name = @model['name'].to_s.strip
|
23
|
+
if name.length > 0
|
24
|
+
data = `/usr/bin/dpkg-query -W #{name} 2>/dev/null`.strip.chop.split(' ')
|
25
|
+
(data[0].to_s == name)
|
26
|
+
else
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def version?
|
32
|
+
name = @model['name'].to_s.strip
|
33
|
+
if name.length > 0
|
34
|
+
data = `/usr/bin/dpkg-query -W #{name} 2>/dev/null`.strip.chop.split(' ')
|
35
|
+
(data[0].to_s == name ? data[1] : "")
|
36
|
+
else
|
37
|
+
""
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def install(p={})
|
42
|
+
name = @model['name'].to_s.strip
|
43
|
+
if name.length <= 0
|
44
|
+
false
|
45
|
+
elsif installed?
|
46
|
+
true
|
47
|
+
else
|
48
|
+
shell "dpkg --configure -a; apt-get -y --purge autoremove"
|
49
|
+
if shell "apt-get -y install #{name}"
|
50
|
+
true
|
51
|
+
else
|
52
|
+
shell "dpkg --configure -a; apt-get -y update && apt-get -y install #{name}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def uninstall(package)
|
58
|
+
name = @model['name'].to_s.strip
|
59
|
+
if name.length <= 0
|
60
|
+
false
|
61
|
+
elsif not installed?
|
62
|
+
true
|
63
|
+
else
|
64
|
+
shell "dpkg --configure -a; apt-get -y --purge autoremove"
|
65
|
+
shell "apt-get -y --purge remove #{name} && apt-get -y --purge autoremove && apt-get -y --purge autoremove"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
=begin
|
70
|
+
protected
|
71
|
+
def installed?
|
72
|
+
Sfp::Module::AptPackage.installed?(@model['package_name'].to_s.strip)
|
73
|
+
end
|
74
|
+
|
75
|
+
def version?
|
76
|
+
package = @model['package_name'].to_s.strip
|
77
|
+
return "" if package.length <= 0
|
78
|
+
installed = `apt-cache policy #{package} | grep Installed`.strip.split(' ', 2)[1].to_s.strip
|
79
|
+
return "" if installed.length <= 0
|
80
|
+
candidate = `apt-cache policy #{package} | grep Candidate`.strip.split(' ', 2)[1].to_s.strip
|
81
|
+
return "latest" if candidate == installed
|
82
|
+
installed
|
83
|
+
end
|
84
|
+
=end
|
85
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
schema Package2 {
|
2
|
+
installed : Bool
|
3
|
+
|
4
|
+
final name = ""
|
5
|
+
final version = "latest"
|
6
|
+
final source = "default"
|
7
|
+
final provider = "apt"
|
8
|
+
|
9
|
+
synchronized sub install {
|
10
|
+
effect {
|
11
|
+
this.installed = true
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
synchronized sub uninstall {
|
16
|
+
condition {
|
17
|
+
this.installed = true
|
18
|
+
}
|
19
|
+
effect {
|
20
|
+
this.installed = false
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
data/modules/pyfile/main
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
import sys
|
4
|
+
import json
|
5
|
+
import os
|
6
|
+
import hashlib
|
7
|
+
import io
|
8
|
+
from subprocess import call
|
9
|
+
|
10
|
+
|
11
|
+
###############
|
12
|
+
#
|
13
|
+
# Method for generating current state
|
14
|
+
#
|
15
|
+
###############
|
16
|
+
def get_state(model):
|
17
|
+
state = {}
|
18
|
+
state['path'] = model['path'] if 'path' in model else ''
|
19
|
+
try:
|
20
|
+
m = hashlib.md5()
|
21
|
+
with open(state['path']) as f:
|
22
|
+
buf = f.read(128)
|
23
|
+
while len(buf) > 0:
|
24
|
+
m.update(buf)
|
25
|
+
buf = f.read(128)
|
26
|
+
state['created'] = True
|
27
|
+
state['content'] = '{md5}' + m.hexdigest()
|
28
|
+
except IOError:
|
29
|
+
state['created'] = False
|
30
|
+
state['content'] = ''
|
31
|
+
|
32
|
+
if state['created'] and 'content' in model:
|
33
|
+
m = hashlib.md5()
|
34
|
+
m.update(model['content'])
|
35
|
+
state['created'] = (('{md5}' + m.hexdigest()) == state['content'])
|
36
|
+
|
37
|
+
### dump current state
|
38
|
+
print json.dumps(state)
|
39
|
+
|
40
|
+
|
41
|
+
###############
|
42
|
+
#
|
43
|
+
# Implementation of procedure 'del' (see pyfile.sfp)
|
44
|
+
#
|
45
|
+
###############
|
46
|
+
def delete(model):
|
47
|
+
if 'path' in model:
|
48
|
+
os.remove(model['path'])
|
49
|
+
else:
|
50
|
+
return False
|
51
|
+
return True
|
52
|
+
|
53
|
+
|
54
|
+
###############
|
55
|
+
#
|
56
|
+
# Implementation of procedure 'create' (see pyfile.sfp)
|
57
|
+
#
|
58
|
+
###############
|
59
|
+
def create(model):
|
60
|
+
if 'path' in model:
|
61
|
+
content = model['content'] if 'content' in model else ''
|
62
|
+
try:
|
63
|
+
with open(model['path'], 'w') as f:
|
64
|
+
f.write(content)
|
65
|
+
except IOError, e:
|
66
|
+
print str(e)
|
67
|
+
return False
|
68
|
+
else:
|
69
|
+
return False
|
70
|
+
|
71
|
+
if 'permission' in model:
|
72
|
+
call(["chmod", model['permission'], model['path']])
|
73
|
+
|
74
|
+
return True
|
75
|
+
|
76
|
+
|
77
|
+
###############
|
78
|
+
#
|
79
|
+
# Function to redirect procedure execution request by
|
80
|
+
# calling target function
|
81
|
+
#
|
82
|
+
###############
|
83
|
+
def execute(procedure, parameters, model):
|
84
|
+
success = False
|
85
|
+
description = ''
|
86
|
+
if procedure == 'del':
|
87
|
+
success = delete(model)
|
88
|
+
elif procedure == 'create':
|
89
|
+
success = create(model)
|
90
|
+
else:
|
91
|
+
description = 'unrecognized procedure ' + procedure
|
92
|
+
status = 'ok' if success else 'failed'
|
93
|
+
print json.dumps({'status': status, 'description': description})
|
94
|
+
|
95
|
+
|
96
|
+
###############
|
97
|
+
#
|
98
|
+
# Main logic for handling any request
|
99
|
+
#
|
100
|
+
###############
|
101
|
+
if len(sys.argv) <= 1:
|
102
|
+
### invalid request
|
103
|
+
print '{"status":"error","description":"invalid command line argument"}'
|
104
|
+
exit(1)
|
105
|
+
else:
|
106
|
+
### parse request data in JSON
|
107
|
+
data = json.loads(sys.argv[1])
|
108
|
+
|
109
|
+
if 'command' in data:
|
110
|
+
try:
|
111
|
+
if data['command'] == 'state' and 'model' in data:
|
112
|
+
### request for current state
|
113
|
+
get_state(data['model'])
|
114
|
+
|
115
|
+
elif data['command'] == 'execute' and 'procedure' in data and 'parameters' in data and 'model' in data:
|
116
|
+
### request for executing a procedure
|
117
|
+
execute(data['procedure'], data['parameters'], data['model'])
|
118
|
+
|
119
|
+
else:
|
120
|
+
### invalid request
|
121
|
+
print '{"status":"error","description":"invalid command"}'
|
122
|
+
exit(1)
|
123
|
+
|
124
|
+
except Exception, e:
|
125
|
+
### dump exception
|
126
|
+
print json.dumps({'status':'error','description': str(e)})
|
127
|
+
|
128
|
+
else:
|
129
|
+
### invalid request
|
130
|
+
print '{"status":"error","description":"no command"}'
|
131
|
+
exit(1)
|
132
|
+
|
133
|
+
exit(0)
|