ManageIQ/inventory_refresh

View on GitHub
lib/inventory_refresh/persister.rb

Summary

Maintainability
A
35 mins
Test Coverage
A
100%
module InventoryRefresh
  class Persister
    require 'json'
    require 'yaml'

    attr_reader :manager, :target, :collections

    attr_accessor :refresh_state_uuid, :refresh_state_part_uuid, :total_parts, :sweep_scope, :retry_count, :retry_max

    # @param manager [ManageIQ::Providers::BaseManager] A manager object
    # @param target [Object] A refresh Target object
    def initialize(manager, target = nil)
      @manager = manager
      @target  = target

      @collections = {}

      initialize_inventory_collections
    end

    # Interface for creating InventoryCollection under @collections
    #
    # @param builder_class    [ManageIQ::Providers::Inventory::Persister::Builder] or subclasses
    # @param collection_name  [Symbol || Array] used as InventoryCollection:association
    # @param extra_properties [Hash]   props from InventoryCollection.initialize list
    #         - adds/overwrites properties added by builder
    #
    # @param settings [Hash] builder settings
    #         - @see ManageIQ::Providers::Inventory::Persister::Builder.default_options
    #         - @see make_builder_settings()
    #
    # @example
    #   add_collection(:vms, ManageIQ::Providers::Inventory::Persister::Builder::CloudManager) do |builder|
    #     builder.add_properties(
    #       :strategy => :local_db_cache_all,
    #     )
    #   )
    #
    # @see documentation https://github.com/ManageIQ/guides/tree/master/providers/persister/inventory_collections.md
    #
    def add_collection(collection_name, builder_class = inventory_collection_builder, extra_properties = {}, settings = {}, &block)
      builder = builder_class.prepare_data(collection_name,
                                           self.class,
                                           builder_settings(settings),
                                           &block)

      builder.add_properties(extra_properties) if extra_properties.present?

      builder.add_properties({:manager_uuids => target.try(:references, collection_name) || []}, :if_missing) if targeted?

      builder.evaluate_lambdas!(self)

      collections[collection_name] = builder.to_inventory_collection
    end

    # @return [Array<InventoryRefresh::InventoryCollection>] array of InventoryCollection objects of the persister
    def inventory_collections
      collections.values
    end

    # @return [Array<Symbol>] array of InventoryCollection object names of the persister
    def inventory_collections_names
      collections.keys
    end

    # @return [InventoryRefresh::InventoryCollection] returns a defined InventoryCollection or undefined method
    def method_missing(method_name, *arguments, &block)
      if inventory_collections_names.include?(method_name)
        define_collections_reader(method_name)
        send(method_name)
      else
        super
      end
    end

    # @return [Boolean] true if InventoryCollection with passed method_name name is defined
    def respond_to_missing?(method_name, _include_private = false)
      inventory_collections_names.include?(method_name) || super
    end

    # Defines a new attr reader returning InventoryCollection object
    def define_collections_reader(collection_key)
      define_singleton_method(collection_key) do
        collections[collection_key]
      end
    end

    def inventory_collection_builder
      ::InventoryRefresh::InventoryCollection::Builder
    end

    # Persists InventoryCollection objects into the DB
    def persist!
      InventoryRefresh::SaveInventory.save_inventory(manager, inventory_collections)
    end

    # Returns serialized Persisted object to JSON
    # @return [String] serialized Persisted object to JSON
    def to_json(*_args)
      JSON.dump(to_hash)
    end

    # @return [Hash] entire Persister object serialized to hash
    def to_hash
      collections_data = collections.map do |_, collection|
        next if collection.data.blank? &&
                collection.targeted_scope.primary_references.blank? &&
                collection.all_manager_uuids.nil? &&
                collection.skeletal_primary_index.index_data.blank?

        collection.to_hash
      end.compact

      {
        :refresh_state_uuid      => refresh_state_uuid,
        :refresh_state_part_uuid => refresh_state_part_uuid,
        :retry_count             => retry_count,
        :retry_max               => retry_max,
        :total_parts             => total_parts,
        :sweep_scope             => sweep_scope,
        :collections             => collections_data,
      }
    end

    class << self
      # Returns Persister object loaded from a passed JSON
      #
      # @param json_data [String] input JSON data
      # @return [ManageIQ::Providers::Inventory::Persister] Persister object loaded from a passed JSON
      def from_json(json_data, manager, target = nil)
        from_hash(JSON.parse(json_data), manager, target)
      end

      # Returns Persister object built from serialized data
      #
      # @param persister_data [Hash] serialized Persister object in hash
      # @return [ManageIQ::Providers::Inventory::Persister] Persister object built from serialized data
      def from_hash(persister_data, manager, target = nil)
        # TODO(lsmola) we need to pass serialized targeted scope here
        target ||= InventoryRefresh::TargetCollection.new(:manager => manager)

        new(manager, target).tap do |persister|
          persister_data['collections'].each do |collection|
            inventory_collection = persister.collections[collection['name'].try(:to_sym)]
            raise "Unrecognized InventoryCollection name: #{inventory_collection}" if inventory_collection.blank?

            inventory_collection.from_hash(collection, persister.collections)
          end

          persister.refresh_state_uuid      = persister_data['refresh_state_uuid']
          persister.refresh_state_part_uuid = persister_data['refresh_state_part_uuid']
          persister.retry_count             = persister_data['retry_count']
          persister.retry_max               = persister_data['retry_max']
          persister.total_parts             = persister_data['total_parts']
          persister.sweep_scope             = persister_data['sweep_scope']
        end
      end
    end

    protected

    def initialize_inventory_collections
      # can be implemented in a subclass
    end

    # @param extra_settings [Hash]
    #   :auto_inventory_attributes
    #     - auto creates inventory_object_attributes from target model_class setters
    #     - attributes used in InventoryObject.add_attributes
    #   :without_model_class
    #     - if false and no model_class derived or specified, throws exception
    #     - doesn't try to derive model class automatically
    #     - @see method ManageIQ::Providers::Inventory::Persister::Builder.auto_model_class
    def builder_settings(extra_settings = {})
      opts = inventory_collection_builder.default_options

      opts[:shared_properties]         = shared_options
      opts[:auto_inventory_attributes] = true
      opts[:without_model_class]       = false

      opts.merge(extra_settings)
    end

    def strategy
      nil
    end

    def saver_strategy
      :default
    end

    # Persisters for targeted refresh can override to true
    def targeted?
      false
    end

    def assert_graph_integrity?
      false
    end

    # @return [Hash] kwargs shared for all InventoryCollection objects
    def shared_options
      {
        :saver_strategy         => saver_strategy,
        :strategy               => strategy,
        :targeted               => targeted?,
        :parent                 => manager.presence,
        :assert_graph_integrity => assert_graph_integrity?,
      }
    end
  end
end