redfish_tools 0.2.6 → 0.2.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d8e4fd83aeaa6c8af2e97fce4d2e92802cc97281
4
- data.tar.gz: d2e92b0b838758132ba345a45ab6d8c4b423dcf3
2
+ SHA256:
3
+ metadata.gz: d6eb47b4ce78b595e4465c9efcda2ca2a8700b22257bd039cd4651411b0b5e9d
4
+ data.tar.gz: d6fb33b3c654c4edd44279bfb107ecbc941d0d456bac7c0652062a88449d9fcd
5
5
  SHA512:
6
- metadata.gz: f8c3805f96c543a24c787edb52ec70a60ccf097b547e9a6ca87909219f810f091852479ba4237439c2ae146222536f9a30fdc2f814fd3b4491c1d9296ebbf184
7
- data.tar.gz: f02fc22a3b4600a5b41621026bf26c98160f6c72665d23290b6df19dd538f209a267fca3ebfd2ece864e76e7840d16dc08f3a9013b5418631726a1bbfd678aa4
6
+ metadata.gz: b6a62c917f853e1812c9ce2a2266aeebfdcff73d313a92a7b5d1434e596084d8d50eea6a0acf4c05d8935c44326aac2dd75705ff7f05a5fd62ceb2c9cd43d234
7
+ data.tar.gz: c88535b973f5010dcdd50b9b218882fc45240b63b2a22fb93cffe868bf4e6c9fc3b6601668680cf9c4407ccdb662ab1bff474813e7674319b86746440aaa857e
@@ -0,0 +1,7 @@
1
+ module RedfishTools
2
+ module Exceptions
3
+ class RedfishServerError < StandardError; end
4
+
5
+ class MergeConflict < RedfishServerError; end
6
+ end
7
+ end
@@ -36,13 +36,30 @@ module RedfishTools
36
36
  end
37
37
 
38
38
  def process_oid(oid)
39
- start = Time.now
40
- resource = @client.find(oid)
41
- duration = Time.now - start
42
- return Set.new unless resource
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
- persist_to_disk(oid, resource.raw, resource.headers, duration)
45
- extract_oids(resource.raw)
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 extract_oids_from_hash(data)
93
- when Array then data.reduce(Set.new) { |a, v| a.merge(extract_oids(v)) }
94
- else Set.new
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
@@ -28,13 +28,26 @@ module RedfishTools
28
28
  Base64.strict_encode64("#{username}:#{password}")
29
29
  end
30
30
 
31
- def system_actions
31
+ def actions
32
32
  # Hash of action_id => system_id
33
- @system_actions ||= load_all_system_actions
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, system_id: id }
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, :system_actions
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
- "On" => {
23
- "GracefullShutdown" => "Off",
24
- "ForceOff" => "Off",
25
- "PushPowerButton" => "Off",
26
- "Nmi" => "Off",
27
- "GracefullRestart" => "On",
28
- "ForceRestart" => "On",
29
- "PowerCycle" => "On",
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
- "Off" => {
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 = system_actions[request.path]
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(_request, response)
82
- response.status = 501
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
- # TODO(@tadeboro): This method currently only handles reset action.
101
- system = datastore.get(action[:system_id]).body
102
- action = system["Actions"][action[:name]]
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"), nill, 400
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"][0]["EventTimestamp"] = DateTime.now.to_s
34
+ make_events_unique(event["Events"])
34
35
  socket.print("id: #{id}\ndata: #{event.to_json}\n\n")
35
- sleep(rand(1..10))
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RedfishTools
4
- VERSION = "0.2.6"
4
+ VERSION = "0.2.7"
5
5
  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.6
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-28 00:00:00.000000000 Z
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.6.14
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.