<commit_before>#!/usr/bin/env ruby
# encoding: UTF-8
#
# Copyright © 2012-2014 <NAME>, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'json'
require 'optparse'
require 'rest_client'
require 'socket'
require 'logger'
require 'fileutils'

require_relative 'worker/utils'
require_relative 'worker/pluginmanager'
require_relative 'worker/provider'
require_relative 'worker/automator'
require_relative 'worker/cli'
require_relative 'rest-helper'

require_relative 'config'
require_relative 'logging'
require_relative 'constants'


$stdout.sync = true

module Coopr
  class Worker
    include Logging

    # Passed in options and configuration
    attr_reader :options, :config

    # Options that must be passed (cmdline) or set (by master)
    attr_accessor :tenant, :file, :provisioner_id, :name, :register, :once

    def initialize(options, config)
      @options = options
      @config = config

      # Log configuration
      log.debug 'Provisioner starting up'
      config.properties.each do |k, v|
        log.debug "  #{k}: #{v}"
      end

      # Process options, only in case of cmdline startup
      log.debug 'Cmdline options' unless options.empty?
      options.each do |k, v|
        instance_variable_set("@#{k}", v)
        log.debug "  #{k}: #{v}"
      end

      # Initialize PluginManager
      @pluginmanager = Coopr::Worker::PluginManager.new

      # TODO: re-evaluate if this is needed as it appears to only be used by shell automator
      # Env passed to all plugins
      # TODO: this generates a mixture of strings and symbols (only symbols used currently)
      @plugin_env = @config.properties.merge(@options)
      # TODO: plugins refer to the prior cmdline option names, should use constants instead
      @plugin_env[:work_dir] = @config.get(PROVISIONER_WORK_DIR)

      # Run validation checks
      validate
    end

    def validate
      if @pluginmanager.providermap.empty? or @pluginmanager.automatormap.empty?
        log.fatal 'Error: at least one provider plugin and one automator plugin must be installed'
        exit(1)
      end
    end

    # Cmdline entry point
    def self.run(options)
      # Read configuration xml
      config = Coopr::Config.new(options)
      require 'pp'
      pp config
      config.load
      # initialize logging
      Coopr::Logging.configure(config.get(PROVISIONER_LOG_DIR) ? "#{config.get(PROVISIONER_LOG_DIR)}/provisioner.log" : nil)
      Coopr::Logging.level = config.get(PROVISIONER_LOG_LEVEL)
      Coopr::Logging.shift_age = config.get(PROVISIONER_LOG_ROTATION_SHIFT_AGE)
      Coopr::Logging.shift_size = config.get(PROVISIONER_LOG_ROTATION_SHIFT_SIZE)

      worker = Coopr::Worker.new(options, config)
      if options[:register]
        worker.register_plugins
      elsif options[:file]
        #worker.run_task_from_file
      else
        #worker.work
      end
    end

    # Register plugins with the server
    def register_plugins
      @pluginmanager.register_plugins(@config.get(PROVISIONER_SERVER_URI))
      if @pluginmanager.load_errors?
        log.error 'There was at least one provisioner plugin load failure'
        exit(1)
      end
      if @pluginmanager.register_errors?
        log.error 'There was at least one provisioner plugin register failure'
        exit(1)
      end
      exit(0)
    end

    # Instantiate and run an instance of given plugin for a task
    def _run_plugin(clazz, env, cwd, task)
      clusterId = task['clusterId']
      hostname = task['config']['hostname']
      provider = task['config']['provider']['description']
      imagetype = task['config']['imagetype']
      hardware = task['config']['hardwaretype']
      taskName = task['taskName'].downcase
      log.info "Creating node #{hostname} on #{provider} for #{clusterId} using #{imagetype} on #{hardware}" if taskName == 'create'

      object = clazz.new(env, task)
      FileUtils.mkdir_p(cwd)
      Dir.chdir(cwd) do
        result = object.runTask
        log.info "#{clusterId} on #{hostname} could not be deleted: #{result['message']}" if taskName == 'delete' && result['status'] != 0
        result
      end
    end

    def delegate_task(task, pluginmanager)
      providerName = nil # rubocop:disable UselessAssignment
      automatorName = nil # rubocop:disable UselessAssignment
      clazz = nil # rubocop:disable UselessAssignment
      object = nil
      result = nil
      classes = nil
      task_id = task['taskId']

      log.debug "Processing task with id #{task_id} ..."

      taskName = task['taskName'].downcase
      # depending on task, these may be nil
      # automator take pecedence as presence indicates a 'software' task
      providerName = task['config']['provider']['providertype'] rescue nil
      automatorName = task['config']['service']['action']['type'] rescue nil

      case taskName.downcase
        when 'create', 'confirm', 'delete'
          clazz = Object.const_get(pluginmanager.getHandlerActionObjectForProvider(providerName))
          cwd = File.join(@plugin_env[:work_dir], @plugin_env[:tenant], 'providertypes', providerName)
          result = _run_plugin(clazz, @plugin_env, cwd, task)
        when 'install', 'configure', 'initialize', 'start', 'stop', 'remove'
          clazz = Object.const_get(pluginmanager.getHandlerActionObjectForAutomator(automatorName))
          cwd = File.join(@plugin_env[:work_dir], @plugin_env[:tenant], 'automatortypes', automatorName)
          result = _run_plugin(clazz, @plugin_env, cwd, task)
        when 'bootstrap'
          combinedresult = {}
          classes = []
          if task['config'].key? 'automators' and !task['config']['automators'].empty?
            # server must specify which bootstrap handlers need to run
            log.debug "Task #{task_id} running specified bootstrap handlers: #{task['config']['automators']}"
            task['config']['automators'].each do |automator|
              clazz = Object.const_get(pluginmanager.getHandlerActionObjectForAutomator(automator))
              cwd = File.join(@plugin_env[:work_dir], @plugin_env[:tenant], 'automatortypes', automator)
              result = _run_plugin(clazz, @plugin_env, cwd, task)
              combinedresult.merge!(result)
            end
          else
            log.warn 'No automators specified to bootstrap'
          end
          result = combinedresult
        else
          log.error "Unhandled task of type #{task['taskName']}"
          fail "Unhandled task of type #{task['taskName']}"
      end
      result
    end

  end
end



__END__
<commit_msg>get work_dir and tenant directly from instance vars<commit_after>#!/usr/bin/env ruby
# encoding: UTF-8
#
# Copyright © 2012-2014 Cask Data, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'json'
require 'optparse'
require 'rest_client'
require 'socket'
require 'logger'
require 'fileutils'

require_relative 'worker/utils'
require_relative 'worker/pluginmanager'
require_relative 'worker/provider'
require_relative 'worker/automator'
require_relative 'worker/cli'
require_relative 'rest-helper'

require_relative 'config'
require_relative 'logging'
require_relative 'constants'


$stdout.sync = true

module Coopr
  class Worker
    include Logging

    # Passed in options and configuration
    attr_reader :options, :config

    # Options that must be passed (cmdline) or set (by master)
    attr_accessor :tenant, :file, :provisioner_id, :name, :register, :once

    def initialize(options, config)
      @options = options
      @config = config

      # Log configuration
      log.debug 'Provisioner starting up'
      config.properties.each do |k, v|
        log.debug "  #{k}: #{v}"
      end

      # Process options, only in case of cmdline startup
      log.debug 'Cmdline options' unless options.empty?
      options.each do |k, v|
        instance_variable_set("@#{k}", v)
        log.debug "  #{k}: #{v}"
      end

      # Initialize PluginManager
      @pluginmanager = Coopr::Worker::PluginManager.new

      # TODO: re-evaluate if this is needed as it appears to only be used by shell automator
      # Env passed to all plugins
      # TODO: this generates a mixture of strings and symbols (only symbols used currently)
      @plugin_env = @config.properties.merge(@options)
      # TODO: plugins refer to the prior cmdline option names, should use constants instead
      @plugin_env[:work_dir] = @config.get(PROVISIONER_WORK_DIR)

      # Run validation checks
      validate
    end

    def validate
      if @pluginmanager.providermap.empty? or @pluginmanager.automatormap.empty?
        log.fatal 'Error: at least one provider plugin and one automator plugin must be installed'
        exit(1)
      end
    end

    # Cmdline entry point
    def self.run(options)
      # Read configuration xml
      config = Coopr::Config.new(options)
      require 'pp'
      pp config
      config.load
      # initialize logging
      Coopr::Logging.configure(config.get(PROVISIONER_LOG_DIR) ? "#{config.get(PROVISIONER_LOG_DIR)}/provisioner.log" : nil)
      Coopr::Logging.level = config.get(PROVISIONER_LOG_LEVEL)
      Coopr::Logging.shift_age = config.get(PROVISIONER_LOG_ROTATION_SHIFT_AGE)
      Coopr::Logging.shift_size = config.get(PROVISIONER_LOG_ROTATION_SHIFT_SIZE)

      worker = Coopr::Worker.new(options, config)
      if options[:register]
        worker.register_plugins
      elsif options[:file]
        #worker.run_task_from_file
      else
        #worker.work
      end
    end

    # Register plugins with the server
    def register_plugins
      @pluginmanager.register_plugins(@config.get(PROVISIONER_SERVER_URI))
      if @pluginmanager.load_errors?
        log.error 'There was at least one provisioner plugin load failure'
        exit(1)
      end
      if @pluginmanager.register_errors?
        log.error 'There was at least one provisioner plugin register failure'
        exit(1)
      end
      exit(0)
    end

    # Instantiate and run an instance of given plugin for a task
    def _run_plugin(clazz, env, cwd, task)
      clusterId = task['clusterId']
      hostname = task['config']['hostname']
      provider = task['config']['provider']['description']
      imagetype = task['config']['imagetype']
      hardware = task['config']['hardwaretype']
      taskName = task['taskName'].downcase
      log.info "Creating node #{hostname} on #{provider} for #{clusterId} using #{imagetype} on #{hardware}" if taskName == 'create'

      object = clazz.new(env, task)
      FileUtils.mkdir_p(cwd)
      Dir.chdir(cwd) do
        result = object.runTask
        log.info "#{clusterId} on #{hostname} could not be deleted: #{result['message']}" if taskName == 'delete' && result['status'] != 0
        result
      end
    end

    def delegate_task(task, pluginmanager)
      providerName = nil # rubocop:disable UselessAssignment
      automatorName = nil # rubocop:disable UselessAssignment
      clazz = nil # rubocop:disable UselessAssignment
      object = nil
      result = nil
      classes = nil
      task_id = task['taskId']

      log.debug "Processing task with id #{task_id} ..."

      taskName = task['taskName'].downcase
      # depending on task, these may be nil
      # automator take pecedence as presence indicates a 'software' task
      providerName = task['config']['provider']['providertype'] rescue nil
      automatorName = task['config']['service']['action']['type'] rescue nil

      case taskName.downcase
        when 'create', 'confirm', 'delete'
          clazz = Object.const_get(pluginmanager.getHandlerActionObjectForProvider(providerName))
          cwd = File.join(@config.get(PROVISIONER_WORK_DIR), @tenant, 'providertypes', providerName)
          result = _run_plugin(clazz, @plugin_env, cwd, task)
        when 'install', 'configure', 'initialize', 'start', 'stop', 'remove'
          clazz = Object.const_get(pluginmanager.getHandlerActionObjectForAutomator(automatorName))
          cwd = File.join(@config.get(PROVISIONER_WORK_DIR), @tenant, 'automatortypes', automatorName)
          result = _run_plugin(clazz, @plugin_env, cwd, task)
        when 'bootstrap'
          combinedresult = {}
          classes = []
          if task['config'].key? 'automators' and !task['config']['automators'].empty?
            # server must specify which bootstrap handlers need to run
            log.debug "Task #{task_id} running specified bootstrap handlers: #{task['config']['automators']}"
            task['config']['automators'].each do |automator|
              clazz = Object.const_get(pluginmanager.getHandlerActionObjectForAutomator(automator))
              cwd = File.join(@config.get(PROVISIONER_WORK_DIR), @tenant, 'automatortypes', automator)
              result = _run_plugin(clazz, @plugin_env, cwd, task)
              combinedresult.merge!(result)
            end
          else
            log.warn 'No automators specified to bootstrap'
          end
          result = combinedresult
        else
          log.error "Unhandled task of type #{task['taskName']}"
          fail "Unhandled task of type #{task['taskName']}"
      end
      result
    end

  end
end



__END__

{'fraction_non_alphanumeric': 0.1005298481102084, 'fraction_numerical': 0.002543270929000353, 'mean_word_length': 3.1079512478235634, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7358999', 'n_tokens_mistral': 4068, 'n_tokens_neox': 4001, 'n_words': 1180}