Class: Toaster::SystemState

Inherits:
Object
  • Object
show all
Defined in:
lib/toaster/state/system_state.rb

Constant Summary collapse

@@initialized =
false
@@include_default_state_props =
false
@@required_builtin_ohai_plugins =
["languages.rb", "ruby.rb", "kernel.rb", "os.rb"]
@@original_ohai_plugin_paths =
[]
@@ohai_dir =
File.join(File.expand_path(File.dirname(__FILE__)), '..', 'ohai')
@@max_arglist_length =

maximum number of characters to pass as cmdline parameter to ohai

5000

Class Method Summary collapse

Class Method Details

.get_flat_attributes(current = nil, name_so_far = "", list_so_far = {}) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/toaster/state/system_state.rb', line 283

def self.get_flat_attributes(current=nil, name_so_far="", list_so_far={})
  if current.nil?
    name_so_far = name_so_far[1..-1] if name_so_far[0] == "."
    list_so_far[name_so_far] = nil
    return list_so_far
  end
  if !current.kind_of?(Hash)
    name_so_far = name_so_far[1..-1] if name_so_far[0] == "."
    list_so_far[name_so_far] = current
    return list_so_far
  end
  current.each do |name,value|
    name = "#{name_so_far}.'#{name}'"
    get_flat_attributes(value, name, list_so_far)
  end
  return list_so_far
end

.get_state_diff(s_before, s_after) ⇒ Object

Compute the difference between two system state snapshots. Returns an array of StateChange objects.



165
166
167
168
169
170
171
172
173
# File 'lib/toaster/state/system_state.rb', line 165

def self.get_state_diff(s_before, s_after)
  tmp = preprocess_state_diff(s_before, s_after)
  s_before = tmp[0]
  s_after = tmp[1]
  prop_changes = tmp[2]

  prop_changes.concat(MarkupUtil.hash_diff_as_prop_changes(s_before, s_after))
  return prop_changes
end

.get_statechange_config_from_state(state) ⇒ Object



204
205
206
207
208
209
210
211
212
213
# File 'lib/toaster/state/system_state.rb', line 204

def self.get_statechange_config_from_state(state)
  cfg = {}
  if state["files"]
    cfg["files"] = {"paths" => []}
    state["files"].each do |path,info|
      cfg["files"]["paths"] << path
    end
  end
  return cfg
end

.get_system_state(state_change_config = {}) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/toaster/state/system_state.rb', line 22

def self.get_system_state(state_change_config = {})
  puts "INFO: Taking snapshot of system state..."
  puts "DEBUG: State snapshot configuration: #{state_change_config}" 

  if !@@initialized
    @@original_ohai_plugin_paths.concat(Ohai::Config[:plugin_path])
    @@original_ohai_plugin_paths.each do |path|
      @@required_builtin_ohai_plugins.dup.each do |pl|
        required_file = File.join(path, pl)
        if File.exist?(required_file)
          @@required_builtin_ohai_plugins.delete(pl)
          @@required_builtin_ohai_plugins << required_file
        end
      end
    end
    @@initialized = true
  end

  Ohai::Config[:plugin_path] = []
  if @@include_default_state_props
    Ohai::Config[:plugin_path].concat(@@original_ohai_plugin_paths)
  end

  state_change_config.each do |name,config|
    # register tailor-made Ohai extensions
    path = File.expand_path(File.join(@@ohai_dir, name))
    # puts "Registering ohai extensions within directory #{path}"
    Ohai::Config[:plugin_path].push(path)
  end

  ENV["OHAI_PARAMS"] = state_change_config.to_json().to_s

  # NOTE: We need to be careful here. OHAI_PARAMS is passed as
  # command line argument to ohai, and if this hash becomes
  # too big, we end up with an "Argument list too long" error.
  # If the hash becomes too big, save it to a file and read it 
  # from there afterwards!
  if ENV["OHAI_PARAMS"].size > @@max_arglist_length
    params_file = "/tmp/toaster.ohai_params.tmp"
    Util.write(params_file, ENV["OHAI_PARAMS"], true)
    ENV["OHAI_PARAMS"] = "{\"__read_from_file__\": \"#{params_file}\"}"
  end

  ohai = Ohai::System.new
  @@required_builtin_ohai_plugins.each do |plugin_file|
    begin
      if !defined?(ohai.from_file(plugin_file))
        # some ohai versions don't seem to mix-in "from_file"
        Ohai::System.send(:include, Chef::Mixin::FromFile)
      end
      ohai.from_file(plugin_file)
    rescue => ex
      puts "WARN: Unable to include ohai plugin file '#{plugin_file}': #{ex}: #{ex.backtrace.join("\n")}"
      throw ex
    end
  end

  if Ohai::Config[:file]
    ohai.from_file(Ohai::Config[:file])
  else
    ohai.all_plugins
  end
  json = JSON.parse(ohai.to_json)
  filter_unimportant_properties(json)
  #puts "DEBUG: System state json: #{json.inspect}"

  return json
end

.preprocess_state_diff(state1, state2) ⇒ Object

Given two states, preprocess the state difference computation by removing those state properties which are usually very large and infeasible to process with the generic approach (structural diff of state property trees).

  • Returns: an array [s1,s2,diffs] with the (potentially) modified states (“s1” and “s2”) and part of the differences (“diffs”) between the original states.



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/toaster/state/system_state.rb', line 99

def self.preprocess_state_diff(state1, state2)
  diffs = []
  keys1 = state1.keys.dup
  keys2 = state2.keys.dup

  keys1.each do |k|
    file = File.join(@@ohai_dir, k, "_meta.rb")
    if File.exist?(file)
      require file
      s1 = state1[k]
      s2 = state2[k]
      if !s1 || !s2
        next
      end
      tmp_result = nil
      begin
        eval("tmp_result = diff__#{k}(s1, s2)")
        if tmp_result && tmp_result.kind_of?(Array)
          state1.delete(k)
          state2.delete(k)
          diffs.concat(tmp_result)
        end
      rescue => ex
        puts "WARN: Unable to compute diff of state property '#{k}' using code in file #{file}:"
        Util.print_backtrace(ex, 10)
      end
    end
  end

  return [state1, state2, diffs]
end

.read_ignore_propertiesObject



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/toaster/state/system_state.rb', line 215

def self.read_ignore_properties()
  require "toaster/model/ignore_property"
  result = Set.new
  Dir["#{@@ohai_dir}/*"].each do |dir|
    file = File.join(dir, "_meta.rb")
    if File.exist?(file)
      require file
      tmp_result = nil
      name = dir.sub(/.*\/([a-z0-9A-Z_\-]+)\/*/, '\1')
      begin
        eval("tmp_result = ignore_properties__#{name}()")
        tmp_result = [tmp_result] if !tmp_result.kind_of?(Array)
        tmp_result.each do |r|
          result << IgnoreProperty.new(:key => r.to_s)
        end
      rescue => ex
        puts "WARN: Unable to get ignore properties using code in file #{file}:"
        Util.print_backtrace(ex, 10)
      end
    end
  end
  return result.to_a()
end

.reconstruct_state_from_change_seq(state_change_seq, initial_state = {}) ⇒ Object

Reconstruct the final post-state that results from a sequence of state changes



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/toaster/state/system_state.rb', line 145

def self.reconstruct_state_from_change_seq(state_change_seq, initial_state={})
  state = initial_state
  state_change_seq.each do |change_set|
    change_set.each do |ch|
      if ch.action == StateChange::ACTION_DELETE
        #puts "==> delete property #{ch.property}"
        MarkupUtil.delete_value_by_path(state, ch.property)
      elsif ch.action == StateChange::ACTION_INSERT ||
          ch.action == StateChange::ACTION_MODIFY
        #puts "==> set property #{ch.property} = #{ch.value}"
        MarkupUtil.set_value_by_path(state, ch.property, ch.value)
      end
    end
  end
  #puts "DEBUG: Reconstructed state from state change sequence: #{state.to_s}"
  return state
end

.reconstruct_state_from_execs_seq(execs_seq) ⇒ Object

Reconstruct the final post-state that results from a sequence of task executions and their individual state changes



134
135
136
137
138
139
140
141
142
# File 'lib/toaster/state/system_state.rb', line 134

def self.reconstruct_state_from_execs_seq(execs_seq)
  state = execs_seq[0].state_before
  #puts "DEBUG: Eliminate map entries from state: #{state}"
  MarkupUtil.eliminate_inserted_map_entries!(state)
  return reconstruct_state_from_change_seq(
    execs_seq.collect{ |ex| ex.state_changes },
    state
  )
end

.reduce_state_size(state1, state2) ⇒ Object

Given two states which are “too” big, reduce the size of both states by removing properties that are equal in both states and hence not relevant for the state change computation.



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/toaster/state/system_state.rb', line 178

def self.reduce_state_size(state1, state2)
  state1_copy = state1.dup
  state2_copy = state2.dup

  state1.keys.each do |k|
    file = File.join(@@ohai_dir, k, "_meta.rb")
    if File.exist?(file)
      require file
      s1 = state1[k]
      s2 = state2[k]
      tmp_result = nil
      begin
        eval("tmp_result = reduce__#{k}(s1, s2)")
        if tmp_result && tmp_result.kind_of?(Array)
          state1_copy[k] = tmp_result[0]
          state2_copy[k] = tmp_result[1]
        end
      rescue => ex
        puts "WARN: Unable to compute reduced hash of state property '#{k}' using code in file #{file}:"
        Util.print_backtrace(ex, 10)
      end
    end
  end
  return [state1_copy, state2_copy]
end

.remove_ignore_props!(props_hash, ignore_prop_names = nil, key_path = [], print_info = false) ⇒ Object



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/toaster/state/system_state.rb', line 239

def self.remove_ignore_props!(props_hash, ignore_prop_names=nil, key_path=[], print_info=false)
  if !ignore_prop_names
    ignore_prop_names = read_ignore_properties()
  end
  #puts "TRACE: ignore_prop_names #{ignore_prop_names}"
  ignore_prop_names.each do |key|
    key = key.key if key.respond_to?(:key) # get IgnoreProperty.key
    if props_hash.kind_of?(Array)
      props_hash.dup.each do |k|

        # check if we have an array of StateChange
        if k.kind_of?(StateChange)
          if k.property.eql?(key) ||
          Util.starts_with?(k.property, "#{key}.") ||
          k.property.match(key)
            props_hash.delete(k)
          end

        else
          # this is not a StateChange, but an
          # array of values or hashes --> to be implemented
          puts "WARN: SystemState.remove_ignore_props(..) not implemented for non-StateChange arrays!"
        end
      end

    else
      # assume this is an actual state properties hash
      props_hash.keys.dup.each do |k|
        new_path = key_path.dup
        new_path << k
        long_key = "'#{new_path.join("'.'")}'"
        #puts "TRACE: long key #{long_key}"
        if k == "#{key}" || Util.starts_with?(k, "#{key}.") || k.match("#{key}") ||
        long_key == "#{key}" || Util.starts_with?(long_key, "#{key}.") || long_key.match("#{key}")
          deleted = props_hash.delete(k)
        elsif props_hash[k].kind_of?(Hash)
          # --> recursion!
          remove_ignore_props!(props_hash[k], ignore_prop_names, new_path)
        end
      end
    end
  end
end