class MU::Cloud::AWS::Alarm

A alarm as configured in {MU::Config::BasketofKittens::alarms}

Public Class Methods

cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {}) click to toggle source

Remove all alarms associated with the currently loaded deployment. @param noop [Boolean]: If true, will only print what would be done @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server @param region [String]: The cloud provider region @return [void]

# File modules/mu/providers/aws/alarm.rb, line 127
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
  MU.log "AWS::Alarm.cleanup: need to support flags['known']", MU::DEBUG, details: flags
  MU.log "Placeholder: AWS Alarm artifacts do not support tags, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: ignoremaster
  alarms = []
  # We don't have a way to tag alarms, so we try to delete them by the deploy ID.
  # This can miss alarms in some cases (eg. cache_cluster) so we might want to delete alarms from each API as well.
  MU::Cloud::AWS.cloudwatch(credentials: credentials, region: region).describe_alarms.each { |page|
    page.metric_alarms.map(&:alarm_name).each { |alarm_name|
      alarms << alarm_name if alarm_name.match(deploy_id)
    }
  }

  if !alarms.empty?
    MU::Cloud::AWS.cloudwatch(credentials: credentials, region: region).delete_alarms(alarm_names: alarms) unless noop
    MU.log "Deleted alarms #{alarms.join(', ')}"
  end
end
createMetric(namespace: nil, metric_data: [], region: MU.curRegion) click to toggle source

Publish logging data, or create a new custom container/group for your logging data @param namespace [String]: The name of the container, or group the data will be added to to. @param metric_data [Array]: The data points describing your new metric. @param region [String]: The cloud provider region.

# File modules/mu/providers/aws/alarm.rb, line 245
def self.createMetric(namespace: nil, metric_data: [], region: MU.curRegion)
  MU::Cloud::AWS.cloudwatch(region: region).put_metric_data(namespace: namespace, metric_data: metric_data, region: region)
end
enableAlarmAction(name, region: MU.curRegion) click to toggle source

Enable the state of the alarm @param name [String]: The cloud provider's identifier for this alarm. @param region [String]: The cloud provider region.

# File modules/mu/providers/aws/alarm.rb, line 252
def self.enableAlarmAction(name, region: MU.curRegion)
  MU::Cloud::AWS.cloudwatch(region: region).enable_alarm_actions(alarm_names: [name])
end
find(**args) click to toggle source

Locate an existing alarm. @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching alarms

# File modules/mu/providers/aws/alarm.rb, line 153
def self.find(**args)
  found = {}
  if args[:cloud_id]
    found[args[:cloud_id]] = MU::Cloud::AWS::Alarm.getAlarmByName(args[:cloud_id], region: args[:region], credentials: args[:credentials])
  else
    resp = MU::Cloud::AWS.cloudwatch(region: args[:region], credentials: args[:credentials]).describe_alarms
    if resp and resp.metric_alarms
      resp.metric_alarms.each { |a|
        found[a.alarm_name] = a
      }
    end
  end

  found
end
getAlarmByName(name, region: MU.curRegion, credentials: nil) click to toggle source

Retrieve the complete cloud provider description of a alarm. @param name [String]: The cloud provider's identifier for this alarm. @param region [String]: The cloud provider region @return [OpenStruct]

# File modules/mu/providers/aws/alarm.rb, line 237
def self.getAlarmByName(name, region: MU.curRegion, credentials: nil)
  MU::Cloud::AWS.cloudwatch(region: region, credentials: credentials).describe_alarms(alarm_names: [name]).metric_alarms.first
end
isGlobal?() click to toggle source

Does this resource type exist as a global (cloud-wide) artifact, or is it localized to a region/zone? @return [Boolean]

# File modules/mu/providers/aws/alarm.rb, line 118
def self.isGlobal?
  false
end
new(**args) click to toggle source

Initialize this cloud resource object. Calling super will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like +@vpc+, for us. @param args [Hash]: Hash of named arguments passed via Ruby's double-splat

Calls superclass method
# File modules/mu/providers/aws/alarm.rb, line 23
def initialize(**args)
  super
  @mu_name ||= @deploy.getResourceName(@config["name"])
end
quality() click to toggle source

Denote whether this resource implementation is experiment, ready for testing, or ready for production use.

# File modules/mu/providers/aws/alarm.rb, line 147
def self.quality
  MU::Cloud::RELEASE
end
schema(_config) click to toggle source

Cloud-specific configuration properties. @param _config [MU::Config]: The calling MU::Config object @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource

# File modules/mu/providers/aws/alarm.rb, line 259
def self.schema(_config)
  toplevel_required = []
  schema = {}
  [toplevel_required, schema]
end
setAlarm( name: nil, ok_actions: [], alarm_actions: [], insufficient_data_actions: [], metric_name: nil, namespace: nil, statistic: nil, dimensions: [], period: nil, unit: nil, evaluation_periods: nil, threshold: nil, comparison_operator: nil, region: MU.curRegion, credentials: nil ) click to toggle source

Create an alarm.

# File modules/mu/providers/aws/alarm.rb, line 170
def self.setAlarm(
        name: nil, ok_actions: [], alarm_actions: [], insufficient_data_actions: [], metric_name: nil, namespace: nil, statistic: nil,
        dimensions: [], period: nil, unit: nil, evaluation_periods: nil, threshold: nil, comparison_operator: nil, region: MU.curRegion, credentials: nil
       )

  # If the alarm already exists, then assume we're updating it and
  # munge in potentially new arguments.
  ext_alarm = getAlarmByName(name, region: region, credentials: credentials)
  if ext_alarm
    if !ext_alarm.dimensions.empty?
      ext_alarm.dimensions.each { |dim|
        dimensions << dim.to_h
      }
      dimensions.uniq!
    end
    if alarm_actions
      alarm_actions.concat(ext_alarm.alarm_actions)
      alarm_actions.uniq!
    end
    if ok_actions
      ok_actions.concat(ext_alarm.ok_actions)
      ok_actions.uniq!
    end
    if insufficient_data_actions
      insufficient_data_actions.concat(ext_alarm.insufficient_data_actions)
      insufficient_data_actions.uniq!
    end
    MU.log "Modifying alarm #{name}"
  else
    MU.log "Creating alarm #{name}"
  end

  begin
    MU::Cloud::AWS.cloudwatch(region: region, credentials: credentials).put_metric_alarm(
      alarm_name: name,
      alarm_description: name,
      actions_enabled: true,
      ok_actions: ok_actions,
      alarm_actions: alarm_actions,
      insufficient_data_actions: insufficient_data_actions,
      metric_name: metric_name,
      namespace: namespace,
      statistic: statistic,
      dimensions: dimensions,
      period: period,
      unit: unit,
      evaluation_periods: evaluation_periods,
      threshold: threshold,
      comparison_operator: comparison_operator
    )
  rescue Aws::CloudWatch::Errors::ValidationError => e
    # Dopey but ultimately harmless race condition
    if e.message.match(/A separate request to update this alarm is in progress/)
      MU.log "Duplicate request to create alarm #{name}. This one came from #{caller[0]}", MU::WARN
      sleep 15
      retry
    else
      raise e
    end
  end

end
validateConfig(alarm, configurator) click to toggle source

Cloud-specific pre-processing of {MU::Config::BasketofKittens::alarms}, bare and unvalidated. @param alarm [Hash]: The resource to process and validate @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member @return [Boolean]: True if validation succeeded, False otherwise

# File modules/mu/providers/aws/alarm.rb, line 269
def self.validateConfig(alarm, configurator)
  ok = true
  alarm["dimensions"] ||= []

  if alarm["#TARGETCLASS"] == "cache_cluster"
    alarm['dimensions'] << { "name" => alarm["#TARGETNAME"], "cloud_class" => "CacheClusterId" }
    alarm["namespace"] = "AWS/ElastiCache" if alarm["namespace"].nil?
  elsif alarm["#TARGETCLASS"] == "server"
    alarm['dimensions'] << { "name" => alarm["#TARGETNAME"], "cloud_class" => "InstanceId" }
    alarm["namespace"] = "AWS/EC2" if alarm["namespace"].nil?
  elsif alarm["#TARGETCLASS"] == "database"
    alarm['dimensions'] << { "name" => alarm["#TARGETNAME"], "cloud_class" => "DBInstanceIdentifier" }
    alarm["namespace"] = "AWS/RDS" if alarm["namespace"].nil?
  end

  alarm.delete("#TARGETCLASS")
  alarm.delete("#TARGETNAME")

  if alarm["dimensions"]
    alarm["dimensions"].each{ |dimension|
      if dimension["cloud_class"].nil?
        MU.log "You must specify 'cloud_class'", MU::ERR
        ok = false
      end
  
      alarm["namespace"], depclass = 
        if ["InstanceId", "server", "Server"].include?(dimension["cloud_class"])
          dimension["cloud_class"] = "InstanceId"
          ["AWS/EC2", "server"]
        elsif ["AutoScalingGroupName", "server_pool", "ServerPool"].include?(dimension["cloud_class"])
          dimension["cloud_class"] = "AutoScalingGroupName"
          ["AWS/EC2", "server_pool"]
        elsif ["DBInstanceIdentifier", "database", "Database"].include?(dimension["cloud_class"])
          dimension["cloud_class"] = "DBInstanceIdentifier"
          ["AWS/RDS", "database"]
        elsif ["LoadBalancerName", "loadbalancer", "LoadBalancer"].include?(dimension["cloud_class"])
          dimension["cloud_class"] = "LoadBalancerName"
          ["AWS/ELB", "loadbalancer"]
        elsif ["CacheClusterId", "cache_cluster", "CacheCluster"].include?(dimension["cloud_class"])
          dimension["cloud_class"] = "CacheClusterId"
          ["AWS/ElastiCache", "cache_cluster"]
        elsif ["VolumeId", "volume", "Volume"].include?(dimension["cloud_class"])
          dimension["cloud_class"] = "VolumeId"
          ["AWS/EBS", nil]
        elsif ["BucketName", "bucket", "Bucket"].include?(dimension["cloud_class"])
          dimension["cloud_class"] = "BucketName"
          ["AWS/S3", nil]
        elsif ["TopicName", "notification", "Notification"].include?(dimension["cloud_class"])
          dimension["cloud_class"] = "TopicName"
          ["AWS/SNS", nil]
        end
  
      if !depclass.nil?
        dimension["depclass"] = depclass
        if !dimension["name"].nil? and !dimension["name"].empty?
          MU::Config.addDependency(alarm, dimension["name"], depclass)
        end
      end
    }
  end

  ok = false unless MU::Config::Alarm.validate(alarm, configurator) # XXX the stuff in this method is probably also AWS-specific

  ok
end

Public Instance Methods

arn() click to toggle source

Canonical Amazon Resource Number for this resource @return [String]

# File modules/mu/providers/aws/alarm.rb, line 91
def arn
  cloud_desc.alarm_arn
end
create() click to toggle source

Called automatically by {MU::Deploy#createResources}

# File modules/mu/providers/aws/alarm.rb, line 29
def create
  if @config["dimensions"]
    dimensions = []
    @config["dimensions"].each { |dimension|
      cloudid = 
        if dimension["name"] and dimension["depclass"]
          if @dependencies.has_key?(dimension["depclass"])
            @dependencies[dimension["depclass"]][dimension["name"]].cloudobj.cloud_id
          end
        elsif dimension["mu_name"] and dimension["deploy_id"]
          found = MU::MommaCat.findStray("AWS", deps_class, deploy_id: dimension["deploy_id"], mu_name: dimension["mu_name"], region: @region)
          raise MuError, "Couldn't find #{deps_class} #{dimension["mu_name"]}" if found.nil? || found.empty?
          resp = found.first.deploydata["cloud_id"]
          resp.downcase if %w{database cache_cluster}.include?(deps_class)
        else
          dimension["cloud_id"]
        end
      dimensions << {:name => dimension["cloud_class"], :value => cloudid}
    }
    @config["dimensions"] = dimensions
  end

  @config["alarm_actions"] = [] if @config["alarm_actions"].nil?
  @config["ok_actions"] = [] if @config["ok_actions"].nil?
  if @config["enable_notifications"]

    topic_arn = if @config["notification_group"].match(/^arn:/)
      @config["notification_group"]
    else
      topic = @deploy.findLitterMate(name: @config["notification_group"], type: "notifiers")
      topic.cloudobj.arn
    end

    @config["alarm_actions"] << topic_arn
    @config["ok_actions"] << topic_arn
  end
  @config["ok_actions"].uniq!
  @config["alarm_actions"].uniq!

  MU::Cloud::AWS::Alarm.setAlarm(
    name: @mu_name,
    ok_actions: @config["ok_actions"],
    alarm_actions: @config["alarm_actions"],
    insufficient_data_actions: @config["no_data_actions"],
    metric_name: @config["metric_name"],
    namespace: @config["namespace"],
    statistic: @config["statistic"],
    dimensions: @config["dimensions"],
    period: @config["period"],
    unit: @config["unit"],
    evaluation_periods: @config["evaluation_periods"],
    threshold: @config["threshold"],
    comparison_operator: @config["comparison_operator"],
    region: @region,
    credentials: @credentials
  )

  @cloud_id = @mu_name
end
notify() click to toggle source

Return the metadata for this Alarm rule @return [Hash]

# File modules/mu/providers/aws/alarm.rb, line 97
def notify
  deploy_struct = {
    "ok_actions" => @config["ok_actions"],
    "alarm_actions" => @config["alarm_actions"],
    "insufficient_data_actions" => @config["no_data_actions"],
    "metric_name" => @config["metric_name"],
    "namespace" => @config["namespace"],
    "statistic" => @config["statistic"],
    "dimensions" => @config["dimensions"],
    "period" => @config["period"],
    "unit" => @config["unit"],
    "evaluation_periods" => @config["evaluation_periods"],
    "threshold" => @config["threshold"],
    "comparison_operator" => @config["comparison_operator"]
  }
  return deploy_struct
end