<commit_before>
#
# Copyright (c) 2009 RightScale Inc
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

require 'fileutils'

module RightScale

  # Manages instance state 
  class InstanceState

    # States that will be audited when transitioned to
    RECORDED_STATES   = %w{ booting operational stranded decommissioning }

    # States that cause the system MOTD/banner to indicate that everything is OK
    SUCCESSFUL_STATES = %w{ operational }

    # States that cause the system MOTD/banner to indicate that something is wrong
    FAILED_STATES     = %w{ stranded }

    # Recorded states and additional states local to instance agent
    STATES            = RECORDED_STATES + %w{ decommissioned }

    STATE_DIR         = RightScale::RightLinkConfig[:agent_state_dir]

    # Path to JSON file where current instance state is serialized
    STATE_FILE        = File.join(STATE_DIR, 'state.js')

    # Path to JSON file where past scripts are serialized
    SCRIPTS_FILE      = File.join(STATE_DIR, 'past_scripts.js')

    # Path to JSON file where authorized login users are defined
    LOGIN_POLICY_FILE = File.join(STATE_DIR, 'login_policy.js')

    # Path to boot log
    BOOT_LOG_FILE     = File.join(RightLinkConfig[:platform].filesystem.log_dir, 'install')

    # Path to decommission log
    DECOMMISSION_LOG_FILE = File.join(RightLinkConfig[:platform].filesystem.log_dir, 'decommission')

    # Number of seconds to wait for cloud to shutdown instance
    FORCE_SHUTDOWN_DELAY = 180

    # (String) One of STATES
    def self.value
      @@value
    end

    # (String) Instance agent identity
    def self.identity
      @@identity
    end

    # (Array[(String)]) Scripts that have already executed
    def self.past_scripts
      @@past_scripts
    end

    # (LoginPolicy) The most recently enacted login policy
    def self.login_policy
      @@login_policy
    end

    # Set instance id with given id
    # Load persisted state if any, compare instance ids and force boot if instance ID
    # is different OR if system booted_at is different from persisted booted_at.
    #
    # === Parameters
    # identity(String):: Instance identity
    #
    # === Return
    # true:: Always return true
    def self.init(identity)
      @@identity = identity
      @@startup_tags = []
      @@initial_boot = false
      @@aws_id = nil
      dir = File.dirname(STATE_FILE)
      FileUtils.mkdir_p(dir) unless File.directory?(dir)

      if File.file?(STATE_FILE)
        state = read_json(STATE_FILE)
        RightLinkLog.debug("Initializing instance #{identity} with #{state.inspect}")

        # Initial state reconciliation: use recorded state and boot timestamp to determine how we last stopped.
        # There are four basic scenarios to worry about:
        #  1) first run      -- Agent is starting up for the first time after a fresh install
        #  2) reboot/restart -- Agent already ran; agent ID not changed; reboot detected: transition back to booting
        #  3) bundled boot   -- Agent already ran; agent ID changed: transition back to booting
        #  4) decomm/crash   -- Agent exited anyway; ID not changed; no reboot; keep old state entirely
        if (state['identity'] != identity) || !state['booted_at'] || (booted_at != state['booted_at'])
          # CASE 2/3 -- identity or boot time has changed; may be reboot or bundled boot
          RightLinkLog.debug("Reboot/bundle/start detected; transitioning state to booting")
          self.value = 'booting'
        else
          # CASE 4 -- Restart without reboot; don't do anything special.
          @@value = state['value']
          @@startup_tags = state['startup_tags']
          update_logger
        end
      else
        # CASE 1 -- state file does not exist; initial boot, create state file
        RightLinkLog.debug("Initializing instance #{identity} with booting")
        self.value = 'booting'
        @@initial_boot = true
      end

      if File.file?(SCRIPTS_FILE)
        @@past_scripts = read_json(SCRIPTS_FILE)
      else
        @@past_scripts = []
      end
      RightLinkLog.debug("Past scripts: #{@@past_scripts.inspect}")

      if File.file?(LOGIN_POLICY_FILE)
        @@login_policy = read_json(LOGIN_POLICY_FILE) rescue nil #corrupt file here is not important enough to fail
      else
        @@login_policy = nil
      end
      RightLinkLog.debug("Existing login users: #{@@login_policy.users.length} recorded") if @@login_policy

      begin
        meta_data_file = ::File.join(RightScale::RightLinkConfig[:cloud_state_dir], 'meta-data.rb')
        # metadata does not exist on all clouds, hence the conditional
        load(meta_data_file) if File.file?(meta_data_file)
        @@aws_id = ENV['EC2_INSTANCE_ID']
      rescue Exception => e
        RightScale::RightLinkLog.warn("Failed to load metadata: #{e.message}, #{e.backtrace[0]}")
      end

      true
    end

    # Set instance state
    #
    # === Parameters
    # val(String) One of STATES
    #
    # === Return
    # val(String) new state
    #
    # === Raise
    # RightScale::Exceptions::Argument:: Invalid new value
    def self.value=(val)
      raise RightScale::Exceptions::Argument, "Invalid instance state '#{val}'" unless STATES.include?(val)
      RightLinkLog.info("Transitioning state from #{@@value rescue 'pending'} to #{val}")
      @@value = val<commit_msg>Don't parse self-invoking EC2 metadata fetcher (used cached copy instead, if it exists).<commit_after>
#
# Copyright (c) 2009 RightScale Inc
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

require 'fileutils'

module RightScale

  # Manages instance state 
  class InstanceState

    # States that will be audited when transitioned to
    RECORDED_STATES   = %w{ booting operational stranded decommissioning }

    # States that cause the system MOTD/banner to indicate that everything is OK
    SUCCESSFUL_STATES = %w{ operational }

    # States that cause the system MOTD/banner to indicate that something is wrong
    FAILED_STATES     = %w{ stranded }

    # Recorded states and additional states local to instance agent
    STATES            = RECORDED_STATES + %w{ decommissioned }

    STATE_DIR         = RightScale::RightLinkConfig[:agent_state_dir]

    # Path to JSON file where current instance state is serialized
    STATE_FILE        = File.join(STATE_DIR, 'state.js')

    # Path to JSON file where past scripts are serialized
    SCRIPTS_FILE      = File.join(STATE_DIR, 'past_scripts.js')

    # Path to JSON file where authorized login users are defined
    LOGIN_POLICY_FILE = File.join(STATE_DIR, 'login_policy.js')

    # Path to boot log
    BOOT_LOG_FILE     = File.join(RightLinkConfig[:platform].filesystem.log_dir, 'install')

    # Path to decommission log
    DECOMMISSION_LOG_FILE = File.join(RightLinkConfig[:platform].filesystem.log_dir, 'decommission')

    # Number of seconds to wait for cloud to shutdown instance
    FORCE_SHUTDOWN_DELAY = 180

    # (String) One of STATES
    def self.value
      @@value
    end

    # (String) Instance agent identity
    def self.identity
      @@identity
    end

    # (Array[(String)]) Scripts that have already executed
    def self.past_scripts
      @@past_scripts
    end

    # (LoginPolicy) The most recently enacted login policy
    def self.login_policy
      @@login_policy
    end

    # Set instance id with given id
    # Load persisted state if any, compare instance ids and force boot if instance ID
    # is different OR if system booted_at is different from persisted booted_at.
    #
    # === Parameters
    # identity(String):: Instance identity
    #
    # === Return
    # true:: Always return true
    def self.init(identity)
      @@identity = identity
      @@startup_tags = []
      @@initial_boot = false
      @@aws_id = nil
      dir = File.dirname(STATE_FILE)
      FileUtils.mkdir_p(dir) unless File.directory?(dir)

      if File.file?(STATE_FILE)
        state = read_json(STATE_FILE)
        RightLinkLog.debug("Initializing instance #{identity} with #{state.inspect}")

        # Initial state reconciliation: use recorded state and boot timestamp to determine how we last stopped.
        # There are four basic scenarios to worry about:
        #  1) first run      -- Agent is starting up for the first time after a fresh install
        #  2) reboot/restart -- Agent already ran; agent ID not changed; reboot detected: transition back to booting
        #  3) bundled boot   -- Agent already ran; agent ID changed: transition back to booting
        #  4) decomm/crash   -- Agent exited anyway; ID not changed; no reboot; keep old state entirely
        if (state['identity'] != identity) || !state['booted_at'] || (booted_at != state['booted_at'])
          # CASE 2/3 -- identity or boot time has changed; may be reboot or bundled boot
          RightLinkLog.debug("Reboot/bundle/start detected; transitioning state to booting")
          self.value = 'booting'
        else
          # CASE 4 -- Restart without reboot; don't do anything special.
          @@value = state['value']
          @@startup_tags = state['startup_tags']
          update_logger
        end
      else
        # CASE 1 -- state file does not exist; initial boot, create state file
        RightLinkLog.debug("Initializing instance #{identity} with booting")
        self.value = 'booting'
        @@initial_boot = true
      end

      if File.file?(SCRIPTS_FILE)
        @@past_scripts = read_json(SCRIPTS_FILE)
      else
        @@past_scripts = []
      end
      RightLinkLog.debug("Past scripts: #{@@past_scripts.inspect}")

      if File.file?(LOGIN_POLICY_FILE)
        @@login_policy = read_json(LOGIN_POLICY_FILE) rescue nil #corrupt file here is not important enough to fail
      else
        @@login_policy = nil
      end
      RightLinkLog.debug("Existing login users: #{@@login_policy.users.length} recorded") if @@login_policy

      begin
        meta_data_file = ::File.join(RightScale::RightLinkConfig[:cloud_state_dir], 'meta-data-cache.rb')
        # metadata does not exist on all clouds, hence the conditional
        load(meta_data_file) if File.file?(meta_data_file)
        @@aws_id = ENV['EC2_INSTANCE_ID']
      rescue Exception => e
        RightScale::RightLinkLog.warn("Failed to load metadata: #{e.message}, #{e.backtrace[0]}")
      end

      true
    end

    # Set instance state
    #
    # === Parameters
    # val(String) One of STATES
    #
    # === Return
    # val(String) new state
    #
    # === Raise
    # RightScale::Exceptions::Argument:: Invalid new value
    def self.value=(val)
      raise RightScale::Exceptions::Argument, "Invalid instance state '#{val}'" unless STATES.include?(val)
      RightLinkLog.info("Transitioning state from #{@@value rescue 'pending'} to #{val}")
      @@value = val
{'fraction_non_alphanumeric': 0.0831320607713284, 'fraction_numerical': 0.0027269185820023374, 'mean_word_length': 3.054327226784586, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, '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': '3815231', 'n_tokens_mistral': 3653, 'n_tokens_neox': 3461, 'n_words': 1313}