Sindbad~EG File Manager

Current Path : /opt/microsoft/omsagent/plugin/
Upload File :
Current File : //opt/microsoft/omsagent/plugin/out_oms_api.rb

module Fluent

  class OutputOMSApi < BufferedOutput

    Plugin.register_output('out_oms_api', self)

    def initialize
      super

      require 'base64'
      require 'digest'
      require 'json'
      require 'net/http'
      require 'net/https'
      require 'openssl'
      require 'socket'
      require 'time'
      require 'uri'
      require 'zlib'
      require 'securerandom'
      require_relative 'omslog'
      require_relative 'oms_configuration'
      require_relative 'oms_common'
      require_relative 'agent_telemetry_script'
    end

    config_param :omsadmin_conf_path, :string, :default => '/etc/opt/microsoft/omsagent/conf/omsadmin.conf'
    config_param :cert_path, :string, :default => '/etc/opt/microsoft/omsagent/certs/oms.crt'
    config_param :key_path, :string, :default => '/etc/opt/microsoft/omsagent/certs/oms.key'
    config_param :proxy_conf_path, :string, :default => '/etc/opt/microsoft/omsagent/proxy.conf'
    config_param :api_version, :string, :default => '2016-04-01'
    config_param :compress, :bool, :default => true
    config_param :time_generated_field, :string, :default => ''
    config_param :run_in_background, :bool, :default => false

    def configure(conf)
      super
    end

    def start
      super
      @proxy_config = OMS::Configuration.get_proxy_config(@proxy_conf_path)
      @logtype_regex = Regexp.new('^[A-Za-z][A-Za-z0-9_]{1,100}$')
    end

    def shutdown
      super
      OMS::BackgroundJobs.instance.cleanup
    end

    ####################################################################################################
    # Methods
    ####################################################################################################

    # post data to the service
    # parameters:
    #   log_type: string. log type
    #   time_generated_field_name: string. name of the time generated field
    #   records: hash[]. an array of data
    def post_data(log_type, time_generated_field_name, records, request_id)
      headers = {}
      headers[OMS::CaseSensitiveString.new("Log-Type")] = log_type
      headers[OMS::CaseSensitiveString.new("x-ms-date")] = Time.now.utc.httpdate()

      azure_resource_id = OMS::Configuration.azure_resource_id if defined?(OMS::Configuration.azure_resource_id)
      if !azure_resource_id.to_s.empty?
        headers[OMS::CaseSensitiveString.new("x-ms-AzureResourceId")] = azure_resource_id
      end
      
      azure_region = OMS::Configuration.azure_region if defined?(OMS::Configuration.azure_region)
      if !azure_region.to_s.empty?
        headers[OMS::CaseSensitiveString.new("x-ms-AzureRegion")] = azure_region
      end

      omscloud_id = OMS::Configuration.omscloud_id if defined?(OMS::Configuration.omscloud_id)
      if !omscloud_id.to_s.empty?
        headers[OMS::CaseSensitiveString.new("x-ms-OMSCloudId")] = omscloud_id
      end

      uuid = OMS::Configuration.uuid if defined?(OMS::Configuration.uuid)
      if !uuid.to_s.empty?
        headers[OMS::CaseSensitiveString.new("x-ms-UUID")] = uuid
      end

      headers[OMS::CaseSensitiveString.new("X-Request-ID")] = request_id

      if time_generated_field_name != ''
        headers[OMS::CaseSensitiveString.new("time-generated-field")] = time_generated_field_name
      end

      api_endpoint = OMS::Configuration.ods_endpoint.clone
      api_endpoint.query = "api-version=#{@api_version}"
      
      req = OMS::Common.create_ods_request(api_endpoint.request_uri, records, @compress, headers, lambda { |data| OMS::Common.safe_dump_simple_hash_array(data) })

      unless req.nil?
        http = OMS::Common.create_ods_http(api_endpoint, @proxy_config)
        OMS::Common.start_request(req, http)

        return req.body.bytesize
      end

      return 0
    end # post_data
    
    def write_status_file(success, message)
      fn = '/var/opt/microsoft/omsagent/log/ODSIngestionAPI.status'
      status = '{ "operation": "ODSIngestionAPI", "success": "%s", "message": "%s" }' % [success, message]
      begin
        File.open(fn,'w') { |file| file.write(status) }
      rescue => e
        @log.debug "Error:'#{e}'"
      end
    end

    # parse the tag to get the settings and append the message to blob
    # parameters:
    #   tag: string. the tag of the item
    #   records: hash[]. an arrary of data
    def handle_records(tag, records)
      @log.trace "Handling record : #{tag} x #{records.count}"
      tags = tag.split('.')
      if tags.size >= 3
        # tag should have 3 or 4 parts at least:
        # tags[0]: oms
        # tags[1]: api
        # tags[2]: log type
        # tags[3]: optional. name of the time generated field

        log_type = tags[2]
        time_generated_field_name = @time_generated_field

        # if the tag contains 4 parts, use tags[3] as the
        # time generated field name
        # otherwise, use the configure param value
        # whose default value is empty string
        if tags.size == 4
          time_generated_field_name = tags[3]
        end

        if @logtype_regex =~ log_type
          start = Time.now
          request_id = SecureRandom.uuid
          dataSize = post_data(log_type, time_generated_field_name, records, request_id)
          time = Time.now - start
          @log.debug "Success sending #{dataSize} bytes of data through API #{time.round(3)}s"
          write_status_file("true", "Sending success")
          return OMS::Telemetry.push_qos_event(OMS::SEND_BATCH, "true", "", tag, records, records.count, time)
        else
          raise "The log type '#{log_type}' is not valid. it should match #{@logtype_regex}"
        end
      else
        raise "The tag does not have at least 3 parts #{tag}"
      end
    rescue OMS::RetryRequestException => e
      @log.info "Encountered retryable exception. Will retry sending data later."
      OMS::Log.error_once("Error for Request-ID: #{request_id} Error: #{e}")
      @log.debug "Error:'#{e}'"
      # Re-raise the exception to inform the fluentd engine we want to retry sending this chunk of data later.
      write_status_file("false", "Retryable exception")
      raise e.message, "Request-ID: #{request_id}"
    rescue => e
      # We encountered something unexpected. We drop the data because
      # if bad data caused the exception, the engine will continuously
      # try and fail to resend it. (Infinite failure loop)
      msg = "Unexpected exception, dropping data. Error:'#{e}'"
      OMS::Log.error_once(msg)
      write_status_file("false","Unexpected exception")
      return msg
    end # handle_record

    # This method is called when an event reaches to Fluentd.
    # Convert the event to a raw string.
    def format(tag, time, record)
      [tag, record].to_msgpack
    end

    def self_write(chunk)
      # Group records based on their datatype because OMS does not support a single request with multiple datatypes.
      datatypes = {}
      chunk.msgpack_each {|(tag, record)|
        if !record.to_s.empty?
          if !datatypes.has_key?(tag)
            datatypes[tag] = []
          end

          if record.is_a?(Array)
            record.each do |r|
              datatypes[tag] << r if !r.to_s.empty? and r.is_a?(Hash)
            end
          elsif record.is_a?(Hash)
            datatypes[tag] << record
          end
        end
      }
      ret = []
      datatypes.each do |key, records|
        ret << {'source': key, 'event': handle_records(key, records)}
      end
      return ret
    end

    # This method is called every flush interval. Send the buffer chunk to OMS. 
    # 'chunk' is a buffer chunk that includes multiple formatted
    # NOTE! This method is called by internal thread, not Fluentd's main thread. So IO wait doesn't affect other plugins.
    def write(chunk)
      # Quick exit if we are missing something
      if !OMS::Configuration.load_configuration(omsadmin_conf_path, cert_path, key_path)
        raise 'Missing configuration. Make sure to onboard. Will continue to buffer data.'
      end

      if run_in_background
        OMS::BackgroundJobs.instance.run_job_and_wait { self_write(chunk) }
      else
        self_write(chunk)
      end


    end

  end # Class

end # Module


Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists