Class: CemAcpt::NodeInventory

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/cem_acpt/shared_objects.rb

Overview

Provides a thread-safe inventory of test nodes.

Constant Summary

Constants included from Logging

Logging::LEVEL_MAP

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

current_log_config, #current_log_config, current_log_format, #current_log_format, current_log_level, #current_log_level, included, logger, #logger, new_log_config, #new_log_config, new_log_formatter, #new_log_formatter, new_log_level, #new_log_level, new_logger, #new_logger

Constructor Details

#initializeNodeInventory

Returns a new instance of NodeInventory.



187
188
189
190
191
192
193
194
# File 'lib/cem_acpt/shared_objects.rb', line 187

def initialize
  @inventory = Concurrent::Map.new
  @lock = Concurrent::ReadWriteLock.new
  @claimed = Concurrent::Set.new
  @save_on_claim = false
  @save_file_path = 'spec/fixtures/node_inventory'
  @loaded_node_inv = nil
end

Instance Attribute Details

#save_file_pathObject

Returns the value of attribute save_file_path.



185
186
187
# File 'lib/cem_acpt/shared_objects.rb', line 185

def save_file_path
  @save_file_path
end

Instance Method Details

#add(node_name, node_data) ⇒ Object

Adds a new node to the inventory.

Parameters:

  • node_name (String)

    The name of the node to add.

  • node_data (Hash)

    The node data to add.



209
210
211
# File 'lib/cem_acpt/shared_objects.rb', line 209

def add(node_name, node_data)
  @inventory.put_if_absent(node_name, node_data)
end

#claim(node_name) ⇒ String

Claims a node, which returns the node data and adds that node to the claimed set. Raises an error if the node is already claimed or does not exist. This is used during acceptance testing to ensure that nodes are not reused.

Parameters:

  • node_name (String)

    The name of the node to claim.

Returns:

  • (String)

    The name of the node that was claimed.

Raises:



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/cem_acpt/shared_objects.rb', line 292

def claim(node_name)
  with_lock_retry(:write) do
    unless @inventory.keys.include?(node_name)
      raise NodeDoesNotExistError, "Node #{node_name} does not exist in inventory"
    end

    if @claimed.include?(node_name)
      raise NodeClaimedError, "Node #{node_name} is already tainted and cannot be claimed"
    end

    @claimed.add(node_name)
    save_no_lock!(@save_file_path) if @save_on_claim
    node_name
  end
end

#claim_by_property(property_path, value) ⇒ String

Claims a node based on a property of the node data and a value that property should equal. If a node is found but is already claimed, the method will continue to search for an unclaimed node. Raises an error if no valid node is found. This is used during acceptance testing to ensure that nodes are not reused.

Parameters:

  • property_path (String)

    The dot-separated property path to check.

  • value (Object)

    The value to check for.

Returns:

  • (String)

    The name of the node that was claimed.

Raises:



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/cem_acpt/shared_objects.rb', line 316

def claim_by_property(property_path, value)
  attempts ||= 1
  claim_name = nil
  @inventory.each_pair do |node_name, node_data|
    next if @claimed.include?(node_name)

    claim_name = node_name if node_data.dot_dig(property_path) == value
  end
  raise NodeDoesNotExistError, "No node found with property #{property_path} == #{value}" if claim_name.nil?

  claim(claim_name)
rescue NodeDoesNotExistError => e
  # We sleep than retry three times to help mitigate race conditions
  if (attempts += 1) <= 3
    sleep(1)
    retry
  else
    raise e
  end
end

#claimed?(node_name) ⇒ Boolean

Checks if a node is claimed.

Parameters:

  • node_name (String)

    The name of the node to check.

Returns:

  • (Boolean)

    True if the node is claimed, false otherwise.

Raises:



278
279
280
281
282
# File 'lib/cem_acpt/shared_objects.rb', line 278

def claimed?(node_name)
  raise NodeDoesNotExistError, "Node #{node_name} does not exist" unless @inventory.keys.include?(node_name)

  @claimed.include?(node_name)
end

#clean_local_files(file_path = @save_file_path) ⇒ Object



448
449
450
451
452
# File 'lib/cem_acpt/shared_objects.rb', line 448

def clean_local_files(file_path = @save_file_path)
  Dir.glob('*.yaml', base: file_path) do |file_name|
    File.delete(File.expand_path(File.join(file_path, file_name)))
  end
end

#clear!Object

Clears the inventory and removes claimed nodes. Thread-safe.



356
357
358
359
360
# File 'lib/cem_acpt/shared_objects.rb', line 356

def clear!
  with_lock_retry(:write) do
    clear_no_lock!
  end
end

#clear_no_lock!Object

Clears the inventory and removes claimed nodes. DOES NOT USE LOCK. If this is called outside of a lock, it will not be thread-safe.



350
351
352
353
# File 'lib/cem_acpt/shared_objects.rb', line 350

def clear_no_lock!
  nodes.each { |node| delete(node) }
  @claimed.clear
end

#delete(node_name) ⇒ Object

Deletes a node from the inventory.

Parameters:

  • node_name (String)

    The name of the node to delete.



339
340
341
# File 'lib/cem_acpt/shared_objects.rb', line 339

def delete(node_name)
  @inventory.delete(node_name)
end

#get(node_name) ⇒ Object

Returns the node data for a given node.

Parameters:

  • node_name (String)

    The name of the node to get data for.



219
220
221
# File 'lib/cem_acpt/shared_objects.rb', line 219

def get(node_name)
  @inventory[node_name]
end

#get_all_properties(property_path) ⇒ Array

Returns all property values of all nodes for the given property path

Parameters:

  • property_path (String)

    The dot-separated property path to check.

Returns:

  • (Array)

    A frozen array of found property values.

Raises:



245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/cem_acpt/shared_objects.rb', line 245

def get_all_properties(property_path)
  found = []
  @inventory.each_pair do |_, node_data|
    prop = node_data.dot_dig(property_path)
    next unless prop

    found << prop
  end
  raise PropertyNotFoundError, "No property with path #{property_path} found in nodes" if found.empty?

  found.freeze
end

#get_by_property(property_path, value) ⇒ Array

Returns the node_name and node_data for a given node that has a matching value for the given property.

Parameters:

  • property_path (String)

    The dot-separated property path to check.

  • value (Object)

    The value to check for.

Returns:

  • (Array)

    Two-item frozen array of node name and node data.

Raises:



229
230
231
232
233
234
235
236
237
238
239
# File 'lib/cem_acpt/shared_objects.rb', line 229

def get_by_property(property_path, value)
  found = []
  @inventory.each_pair do |node_name, node_data|
    next unless node_data.dot_dig(property_path) == value

    found = [node_name, node_data].freeze
  end
  raise NodeDoesNotExistError, "No node found with property #{property_path} == #{value}" if found.empty?

  found
end

#load(file_path = @save_file_path) ⇒ Object

Loads a node inventory from a yaml file. Thread-safe.

Parameters:

  • file_path (String) (defaults to: @save_file_path)

    The path to the file to load from.



442
443
444
445
446
# File 'lib/cem_acpt/shared_objects.rb', line 442

def load(file_path = @save_file_path)
  with_lock_retry(:write) do
    load_no_lock!(file_path)
  end
end

#load_no_lock!(file_path = @save_file_path) ⇒ Object

Loads a node inventory from a file. DOES NOT USE LOCK. If this is called outside of a lock, it will not be thread-safe.



423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/cem_acpt/shared_objects.rb', line 423

def load_no_lock!(file_path = @save_file_path)
  require 'net/ssh/proxy/command' # If ProxyCommand is used in ssh options, this is required.
  attempts ||= 1
  Dir.glob('*.yaml', base: file_path) do |file_name|
    node_name = File.basename(file_name, '.yaml')
    node_data = YAML.load_file(File.join(file_path, file_name))
    add(node_name, node_data) || update(node_name, node_data)
  end
rescue StandardError => e
  raise e unless (attempts += 1) <= 3

  sleep(1)
  retry
end

#merge!(node_name, new_node_data) ⇒ Object

Merges new node data in with the existing node data for the given node.

Parameters:

  • node_name (String)

    The name of the node to merge data for.

  • new_node_data (Hash)

    The new node data to merge.



269
270
271
272
273
# File 'lib/cem_acpt/shared_objects.rb', line 269

def merge!(node_name, new_node_data)
  @inventory.merge_pair(node_name, new_node_data) do |old_data|
    old_data.deep_merge(new_node_data)
  end
end

#node_to_json(node_name, *args) ⇒ Object

Returns a JSON string of a specific node's node data



385
386
387
# File 'lib/cem_acpt/shared_objects.rb', line 385

def node_to_json(node_name, *args)
  get(node_name).to_h.to_json(*args)
end

#node_to_yaml(node_name) ⇒ Object

Returns a YAML string of a specific node's node data



380
381
382
# File 'lib/cem_acpt/shared_objects.rb', line 380

def node_to_yaml(node_name)
  get(node_name).to_h.to_yaml
end

#nodesObject

Returns all node names in the current inventory.



344
345
346
# File 'lib/cem_acpt/shared_objects.rb', line 344

def nodes
  @inventory.keys
end

#property?(node_name, property_path) ⇒ Boolean

Checks if the inventory contains a node with the given property.

Parameters:

  • node_name (String)

    The name of the node to check.

  • property_path (String)

    The dot-separated property path to check.

Returns:

  • (Boolean)


365
366
367
# File 'lib/cem_acpt/shared_objects.rb', line 365

def property?(node_name, property_path)
  !!@inventory[node_name].dot_dig(property_path)
end

#save(file_path = @save_file_path) ⇒ Object

Saves the current node inventory to a yaml file. Thread-safe.

Parameters:

  • file_path (String) (defaults to: @save_file_path)

    The path to the file to save to.



415
416
417
418
419
# File 'lib/cem_acpt/shared_objects.rb', line 415

def save(file_path = @save_file_path)
  with_lock_retry(:write) do
    save_no_lock!(file_path)
  end
end

#save_no_lock!(file_path = @save_file_path) ⇒ Object

Saves the current node inventory to a file. DOES NOT USE LOCK. If this is called outside of a lock, it will not be thread-safe.



401
402
403
404
405
406
407
408
409
410
411
# File 'lib/cem_acpt/shared_objects.rb', line 401

def save_no_lock!(file_path = @save_file_path)
  Dir.mkdir(file_path) unless Dir.exist?(file_path)
  @inventory.each_pair do |node_name, node_data|
    path = File.join(file_path, "#{node_name}.yaml")
    next if File.file?(path)

    File.open(path, 'w') do |f|
      f.write(node_data.to_h.to_yaml)
    end
  end
end

#save_on_claimObject

When called, enables saving the inventory to a file on claim.



197
198
199
# File 'lib/cem_acpt/shared_objects.rb', line 197

def save_on_claim
  @save_on_claim = true
end

#save_on_claim?Boolean

Checks if save_on_claim is enabled.

Returns:

  • (Boolean)


202
203
204
# File 'lib/cem_acpt/shared_objects.rb', line 202

def save_on_claim?
  @save_on_claim
end

#set(node_name, property_path, value) ⇒ Object

Sets a specific property on a node in the inventory.

Parameters:

  • node_name (String)

    The name of the node to set the property on.

  • property_path (String)

    The dot-separated property path to set.

  • value (Object)

    The value to set.



262
263
264
# File 'lib/cem_acpt/shared_objects.rb', line 262

def set(node_name, property_path, value)
  @inventory[node_name].dot_store(property_path, value)
end

#to_hObject

Returns a hash of the inventory.



370
371
372
373
374
375
376
377
# File 'lib/cem_acpt/shared_objects.rb', line 370

def to_h
  h = {}
  @inventory.each_pair do |node_name, node_data|
    h[node_name] = node_data.to_h
  end
  h[:claimed] = @claimed.to_a
  h
end

#to_json(*args) ⇒ Object

Returns a JSON string of the inventory.



395
396
397
# File 'lib/cem_acpt/shared_objects.rb', line 395

def to_json(*args)
  to_h.to_json(*args)
end

#to_yamlObject

Returns a YAML string of the inventory.



390
391
392
# File 'lib/cem_acpt/shared_objects.rb', line 390

def to_yaml
  to_h.to_yaml
end

#update(node_name, node_data) ⇒ Object



213
214
215
# File 'lib/cem_acpt/shared_objects.rb', line 213

def update(node_name, node_data)
  @inventory.replace_if_exists(node_name, node_data)
end

#update_no_lock!Object



438
# File 'lib/cem_acpt/shared_objects.rb', line 438

def update_no_lock!; end