redfish_tools 0.2.6 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/redfish_tools/exceptions.rb +7 -0
- data/lib/redfish_tools/recorder.rb +34 -15
- data/lib/redfish_tools/server.rb +28 -3
- data/lib/redfish_tools/servlet.rb +84 -17
- data/lib/redfish_tools/sse_server.rb +10 -2
- data/lib/redfish_tools/utils.rb +24 -0
- data/lib/redfish_tools/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d6eb47b4ce78b595e4465c9efcda2ca2a8700b22257bd039cd4651411b0b5e9d
|
4
|
+
data.tar.gz: d6fb33b3c654c4edd44279bfb107ecbc941d0d456bac7c0652062a88449d9fcd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6a62c917f853e1812c9ce2a2266aeebfdcff73d313a92a7b5d1434e596084d8d50eea6a0acf4c05d8935c44326aac2dd75705ff7f05a5fd62ceb2c9cd43d234
|
7
|
+
data.tar.gz: c88535b973f5010dcdd50b9b218882fc45240b63b2a22fb93cffe868bf4e6c9fc3b6601668680cf9c4407ccdb662ab1bff474813e7674319b86746440aaa857e
|
@@ -36,13 +36,30 @@ module RedfishTools
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def process_oid(oid)
|
39
|
-
|
40
|
-
resource
|
41
|
-
|
42
|
-
|
39
|
+
resource, duration = fetch_resource(oid)
|
40
|
+
if resource
|
41
|
+
persist_to_disk(oid, resource.raw, resource.headers, duration)
|
42
|
+
extract_oids(resource.raw)
|
43
|
+
else
|
44
|
+
puts("!!!! FAILED TO FETCH #{oid} !!!!")
|
45
|
+
Set.new
|
46
|
+
end
|
47
|
+
end
|
43
48
|
|
44
|
-
|
45
|
-
|
49
|
+
def fetch_resource(oid)
|
50
|
+
3.times do
|
51
|
+
begin
|
52
|
+
start = Time.now
|
53
|
+
resource = @client.find(oid)
|
54
|
+
duration = Time.now - start
|
55
|
+
return [resource, duration] if resource
|
56
|
+
rescue JSON::ParserError => e
|
57
|
+
return [nil, nil]
|
58
|
+
end
|
59
|
+
|
60
|
+
sleep(2)
|
61
|
+
end
|
62
|
+
[nil, nil]
|
46
63
|
end
|
47
64
|
|
48
65
|
def persist_to_disk(oid, body, headers, duration)
|
@@ -81,18 +98,20 @@ module RedfishTools
|
|
81
98
|
File.open(path, "w") { |f| f.write(data) }
|
82
99
|
end
|
83
100
|
|
84
|
-
def extract_oids_from_hash(data)
|
85
|
-
ids = Set.new
|
86
|
-
ids.add(data["@odata.id"]) if data["@odata.id"]
|
87
|
-
data.values.reduce(ids) { |a, v| a.merge(extract_oids(v)) }
|
88
|
-
end
|
89
|
-
|
90
101
|
def extract_oids(data)
|
102
|
+
result = Set.new
|
103
|
+
|
91
104
|
case data
|
92
|
-
when Hash then
|
93
|
-
when Array then data.
|
94
|
-
|
105
|
+
when Hash then data.values.each { |v| result.merge(extract_oids(v)) }
|
106
|
+
when Array then data.each { |v| result.merge(extract_oids(v)) }
|
107
|
+
when String then result.add(data) if path?(data)
|
95
108
|
end
|
109
|
+
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
def path?(data)
|
114
|
+
/^\/redfish\/v1\/[^# ]*$/.match(data)
|
96
115
|
end
|
97
116
|
end
|
98
117
|
end
|
data/lib/redfish_tools/server.rb
CHANGED
@@ -28,13 +28,26 @@ module RedfishTools
|
|
28
28
|
Base64.strict_encode64("#{username}:#{password}")
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
31
|
+
def actions
|
32
32
|
# Hash of action_id => system_id
|
33
|
-
@
|
33
|
+
@actions ||= load_all_actions
|
34
|
+
end
|
35
|
+
|
36
|
+
def systems
|
37
|
+
@systems ||= load_all_system_ids
|
34
38
|
end
|
35
39
|
|
36
40
|
private
|
37
41
|
|
42
|
+
def load_all_system_ids
|
43
|
+
systems_id = datastore.get("/redfish/v1").body["Systems"]["@odata.id"]
|
44
|
+
datastore.get(systems_id).body["Members"].map { |l| l["@odata.id"] }
|
45
|
+
end
|
46
|
+
|
47
|
+
def load_all_actions
|
48
|
+
load_all_system_actions.merge(load_update_actions)
|
49
|
+
end
|
50
|
+
|
38
51
|
def load_all_system_actions
|
39
52
|
systems_id = datastore.get("/redfish/v1").body["Systems"]["@odata.id"]
|
40
53
|
datastore.get(systems_id).body["Members"].reduce({}) do |acc, link|
|
@@ -45,7 +58,19 @@ module RedfishTools
|
|
45
58
|
def load_system_actions(id)
|
46
59
|
actions = datastore.get(id).body.dig("Actions") || {}
|
47
60
|
actions.each_with_object({}) do |(name, data), acc|
|
48
|
-
acc[data["target"]] = { name: name,
|
61
|
+
acc[data["target"]] = { name: name, id: id }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_update_actions
|
66
|
+
update_service_id = datastore.get("/redfish/v1").body.dig(
|
67
|
+
"UpdateService", "@odata.id",
|
68
|
+
)
|
69
|
+
return {} if update_service_id.nil?
|
70
|
+
|
71
|
+
actions = datastore.get(update_service_id).body["Actions"] || {}
|
72
|
+
actions.each_with_object({}) do |(name, data), acc|
|
73
|
+
acc[data["target"]] = { name: name, id: update_service_id }
|
49
74
|
end
|
50
75
|
end
|
51
76
|
end
|
@@ -5,6 +5,8 @@ require "json"
|
|
5
5
|
require "securerandom"
|
6
6
|
require "set"
|
7
7
|
require "webrick"
|
8
|
+
require "redfish_tools/utils"
|
9
|
+
require "redfish_tools/exceptions"
|
8
10
|
|
9
11
|
module RedfishTools
|
10
12
|
class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
@@ -12,28 +14,36 @@ module RedfishTools
|
|
12
14
|
|
13
15
|
def_delegators :@server,
|
14
16
|
:datastore, :login_path, :username, :password,
|
15
|
-
:basic_auth_header, :
|
17
|
+
:basic_auth_header, :actions, :systems
|
16
18
|
|
17
19
|
BAD_HEADERS = Set.new(["connection", "content-length", "keep-alive"])
|
18
20
|
DEFAULT_HEADERS = {
|
19
21
|
"content-type" => "application/json"
|
20
22
|
}.freeze
|
21
23
|
TRANSITIONS = {
|
22
|
-
"
|
23
|
-
|
24
|
-
"
|
25
|
-
"
|
26
|
-
"
|
27
|
-
"
|
28
|
-
"
|
29
|
-
"
|
24
|
+
"PoweringOn" => {}.freeze,
|
25
|
+
"On" => {
|
26
|
+
"GracefulShutdown" => "Off",
|
27
|
+
"ForceOff" => "Off",
|
28
|
+
"PushPowerButton" => "Off",
|
29
|
+
"Nmi" => "Off",
|
30
|
+
"GracefulRestart" => "On",
|
31
|
+
"ForceRestart" => "On",
|
32
|
+
"PowerCycle" => "On",
|
30
33
|
}.freeze,
|
31
|
-
"
|
34
|
+
"PoweringOff" => {}.freeze,
|
35
|
+
"Off" => {
|
32
36
|
"On" => "On",
|
33
37
|
"ForceOn" => "On",
|
34
38
|
"PushPowerButton" => "On",
|
35
39
|
}.freeze,
|
36
40
|
}.freeze
|
41
|
+
TASK_TRANSITIONS = {
|
42
|
+
"New" => "Starting",
|
43
|
+
"Starting" => "Running",
|
44
|
+
"Running" => "Completed",
|
45
|
+
"Completed" => "Completed",
|
46
|
+
}
|
37
47
|
|
38
48
|
def service(request, response)
|
39
49
|
return response.status = 401 unless authorized?(request)
|
@@ -46,12 +56,22 @@ module RedfishTools
|
|
46
56
|
|
47
57
|
item = datastore.get(request.path)
|
48
58
|
response.status = 200
|
59
|
+
|
60
|
+
if request.path.chomp("/").end_with?("monitor") && item.body["TaskState"]
|
61
|
+
item.body["TaskState"] = TASK_TRANSITIONS[item.body["TaskState"]]
|
62
|
+
response.status = 202
|
63
|
+
if item.body["TaskState"] == "Completed"
|
64
|
+
response.status = 200
|
65
|
+
item.body["EndTime"] = Time.now.utc.iso8601
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
49
69
|
set_headers(response, item.headers)
|
50
70
|
response.body = item.body.to_json
|
51
71
|
end
|
52
72
|
|
53
73
|
def do_POST(request, response)
|
54
|
-
action =
|
74
|
+
action = actions[request.path]
|
55
75
|
item = datastore.get(request.path)
|
56
76
|
return response.status = 404 unless action || item.body
|
57
77
|
return response.status = 405 if action.nil? && item.body["Members"].nil?
|
@@ -78,8 +98,15 @@ module RedfishTools
|
|
78
98
|
response.status = 501
|
79
99
|
end
|
80
100
|
|
81
|
-
def do_PATCH(
|
82
|
-
|
101
|
+
def do_PATCH(request, response)
|
102
|
+
system = datastore.get(request.path)
|
103
|
+
return response.status = 404 unless system.body
|
104
|
+
system.body = Utils.combine_hashes(system.body, JSON.parse(request.body))
|
105
|
+
response.status = 200
|
106
|
+
rescue Exceptions::MergeConflict => error
|
107
|
+
response.status = 405
|
108
|
+
set_headers(response)
|
109
|
+
response.body = error_body(error).to_json
|
83
110
|
end
|
84
111
|
|
85
112
|
def do_DELETE(request, response)
|
@@ -97,13 +124,22 @@ module RedfishTools
|
|
97
124
|
end
|
98
125
|
|
99
126
|
def execute_action(action, data)
|
100
|
-
|
101
|
-
|
102
|
-
|
127
|
+
resource = datastore.get(action[:id]).body
|
128
|
+
case action[:name]
|
129
|
+
when "#ComputerSystem.Reset"
|
130
|
+
execute_computer_system_reset(resource, data)
|
131
|
+
when "#UpdateService.SimpleUpdate"
|
132
|
+
execute_update_service_simple_update(resource, data)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def execute_computer_system_reset(system, data)
|
137
|
+
action = system["Actions"]["#ComputerSystem.Reset"]
|
103
138
|
reset = data["ResetType"]
|
104
139
|
|
140
|
+
# TODO(@tadeboro): Handle ActionInfo case also.
|
105
141
|
unless action["ResetType@Redfish.AllowableValues"].include?(reset)
|
106
|
-
return error_body("Invalid reset type"),
|
142
|
+
return error_body("Invalid reset type"), nil, 400
|
107
143
|
end
|
108
144
|
|
109
145
|
unless TRANSITIONS[system["PowerState"]].key?(reset)
|
@@ -116,6 +152,37 @@ module RedfishTools
|
|
116
152
|
[error_body("Success"), nil, 200]
|
117
153
|
end
|
118
154
|
|
155
|
+
def execute_update_service_simple_update(service, data)
|
156
|
+
action = service["Actions"]["#UpdateService.SimpleUpdate"]
|
157
|
+
proto = data["TransferProtocol"]
|
158
|
+
targets = data["Targets"] || []
|
159
|
+
|
160
|
+
# TODO(@tadeboro): Handle ActionInfo case also.
|
161
|
+
unless action["TransferProtocol@Redfish.AllowableValues"].include?(proto)
|
162
|
+
return error_body("Invalid transfer protocol value"), nil, 400
|
163
|
+
end
|
164
|
+
|
165
|
+
unless (targets - systems).empty?
|
166
|
+
return error_body("Invalid targets: #{targets - systems}"), nil, 400
|
167
|
+
end
|
168
|
+
|
169
|
+
task_service_id = datastore.get("/redfish/v1").body["Tasks"]["@odata.id"]
|
170
|
+
task_col_id = datastore.get(task_service_id).body["Tasks"]["@odata.id"]
|
171
|
+
|
172
|
+
body, _, _ = new_item(datastore.get(task_col_id), {
|
173
|
+
"TaskState" => "New",
|
174
|
+
"StartTime" => Time.new.utc.iso8601,
|
175
|
+
})
|
176
|
+
|
177
|
+
task_monitor_path = body["@odata.id"] + "/monitor"
|
178
|
+
headers = DEFAULT_HEADERS.merge("location" => task_monitor_path)
|
179
|
+
|
180
|
+
body["TaskMonitor"] = task_monitor_path
|
181
|
+
datastore.set(task_monitor_path, body, headers: headers)
|
182
|
+
|
183
|
+
[body, headers, 202]
|
184
|
+
end
|
185
|
+
|
119
186
|
def login_path?(path)
|
120
187
|
login_path == path.chomp("/")
|
121
188
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "date"
|
4
4
|
require "json"
|
5
|
+
require "securerandom"
|
5
6
|
require "socket"
|
6
7
|
|
7
8
|
module RedfishTools
|
@@ -30,13 +31,20 @@ module RedfishTools
|
|
30
31
|
id = 0
|
31
32
|
loop do
|
32
33
|
event = @events.sample
|
33
|
-
event["Events"]
|
34
|
+
make_events_unique(event["Events"])
|
34
35
|
socket.print("id: #{id}\ndata: #{event.to_json}\n\n")
|
35
|
-
sleep(rand(1..
|
36
|
+
sleep(rand(1..60))
|
36
37
|
id += 1
|
37
38
|
end
|
38
39
|
ensure
|
39
40
|
socket.close
|
40
41
|
end
|
42
|
+
|
43
|
+
def make_events_unique(events)
|
44
|
+
events.each do |event|
|
45
|
+
event["EventTimestamp"] = Time.now.utc.to_s
|
46
|
+
event["EventId"] = SecureRandom.uuid
|
47
|
+
end
|
48
|
+
end
|
41
49
|
end
|
42
50
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "redfish_tools/exceptions"
|
2
|
+
|
3
|
+
module RedfishTools
|
4
|
+
module Utils
|
5
|
+
def self.combine_hashes(original, b, path: nil)
|
6
|
+
path ||= []
|
7
|
+
a = original.clone
|
8
|
+
b.each {|key, value|
|
9
|
+
if a.include?(key)
|
10
|
+
if (a[key].is_a? Hash) && (b[key].is_a? Hash)
|
11
|
+
a[key] = combine_hashes(a[key], b[key], path: path + [key.to_s])
|
12
|
+
elsif a[key].is_a? b[key].class
|
13
|
+
a[key] = b[key]
|
14
|
+
else
|
15
|
+
raise Exceptions::MergeConflict, "Conflict at '%s'" % (path + [key.to_s]).join(".")
|
16
|
+
end
|
17
|
+
else
|
18
|
+
raise Exceptions::MergeConflict, "Key '%s' does not exists in source hash" % (path + [key.to_s]).join(".")
|
19
|
+
end
|
20
|
+
}
|
21
|
+
a
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redfish_tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tadej Borovšak
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-02
|
11
|
+
date: 2019-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redfish_client
|
@@ -148,11 +148,13 @@ files:
|
|
148
148
|
- lib/redfish_tools/cli/serve.rb
|
149
149
|
- lib/redfish_tools/cli/serve_sse.rb
|
150
150
|
- lib/redfish_tools/datastore.rb
|
151
|
+
- lib/redfish_tools/exceptions.rb
|
151
152
|
- lib/redfish_tools/recorder.rb
|
152
153
|
- lib/redfish_tools/server.rb
|
153
154
|
- lib/redfish_tools/servlet.rb
|
154
155
|
- lib/redfish_tools/sse_client.rb
|
155
156
|
- lib/redfish_tools/sse_server.rb
|
157
|
+
- lib/redfish_tools/utils.rb
|
156
158
|
- lib/redfish_tools/version.rb
|
157
159
|
- redfish_tools.gemspec
|
158
160
|
homepage: https://github.com/xlab-si/redfish-tools-ruby
|
@@ -176,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
176
178
|
version: '0'
|
177
179
|
requirements: []
|
178
180
|
rubyforge_project:
|
179
|
-
rubygems_version: 2.
|
181
|
+
rubygems_version: 2.7.7
|
180
182
|
signing_key:
|
181
183
|
specification_version: 4
|
182
184
|
summary: Collection of tools for working with Redfish services.
|