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 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.