Class: EC2Launcher::Terminator

Inherits:
Object
  • Object
show all
Includes:
AWSInitializer, BackoffRunner
Defined in:
lib/ec2launcher/terminator.rb

Instance Method Summary collapse

Methods included from BackoffRunner

#run_with_backoff, #test_with_backoff

Methods included from AWSInitializer

#initialize_aws

Constructor Details

#initialize(config_directory) ⇒ Terminator

Returns a new instance of Terminator.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/ec2launcher/terminator.rb', line 17

def initialize(config_directory)
  @log = Logger.new 'ec2launcher'
  log_output = Outputter.stdout
  log_output.formatter = PatternFormatter.new :pattern => "%m"
  @log.outputters = log_output

  ##############################
  # Load configuration data
  ##############################
  config_wrapper = ConfigWrapper.new(config_directory)

  @config = config_wrapper.config
  @environments = config_wrapper.environments
end

Instance Method Details

#remove_snapshots(ec2, attachments) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/ec2launcher/terminator.rb', line 125

def remove_snapshots(ec2, attachments)
  # Iterate over over volumes to find snapshots
  @log.info("Searching for snapshots...")
  snapshots = []
  attachments.each do |attachment|
    volume_snaps = ec2.snapshots.filter("volume-id", attachment.volume.id)
    volume_snaps.each {|volume_snapshot| snapshots << volume_snapshot }
  end

  @log.info("Deleting #{snapshots.size} snapshots...")
  snapshots.each do |snap|
    run_with_backoff(30, 1, "Deleting snapshot #{snap.id}") do
      snap.delete
    end
  end
end

#remove_volume(ec2, instance, device, volume) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/ec2launcher/terminator.rb', line 142

def remove_volume(ec2, instance, device, volume)
  @log.info("  Detaching #{volume.id}...")
  run_with_backoff(30, 1, "detaching #{volume.id}") do
    volume.detach_from(instance, device)
  end

  # Wait for volume to fully detach
  detached = test_with_backoff(120, 1, "waiting for #{volume.id} to detach") do
    volume.status == :available
  end

  # Volume failed to detach - do a force detatch instead
  unless detached
    @log.info("  Failed to detach #{volume.id}")
    run_with_backoff(60, 1, "force detaching #{volume.id}") do
      unless volume.status == :available
        volume.detach_from(instance, device, {:force => true})
      end
    end
    # Wait for volume to fully detach
    detached = test_with_backoff(120, 1, "waiting for #{volume.id} to force detach") do
      volume.status == :available
    end
  end

  @log.info("  Deleting volume #{volume.id}")
  run_with_backoff(30, 1, "delete volume #{volume.id}") do
    volume.delete
  end
end

#remove_volumes(ec2, attachments) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/ec2launcher/terminator.rb', line 173

def remove_volumes(ec2, attachments)
  @log.info("Cleaning up volumes...")

  AWS.memoize do
    removal_threads = []
    attachments.each do |attachment|
      if attachment.exists? && ! attachment.delete_on_termination
        removal_threads << Thread.new {
          remove_volume(ec2, attachment.instance, attachment.device, attachment.volume)
        }
      end
    end

    removal_threads.each {|t| t.join }
  end
end

#terminate(server_name, access_key, secret, snapshot_removal = true, force = false) ⇒ Object

Terminates a given server instance.

@param server_name Name of the server instance @param access_key Amazon IAM access key @param secret Amazon IAM secret key @param snapshot_removal Remove EBS snapshots for EBS volumes attached to the instance. @param force Force instance termination even if environment is not found.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/ec2launcher/terminator.rb', line 39

def terminate(server_name, access_key, secret, snapshot_removal = true, force = false)
  ##############################
  # Initialize AWS and create EC2 connection
  ##############################
  initialize_aws(access_key, secret)
  ec2 = AWS::EC2.new

  ##############################
  # Find instance
  ##############################
  instance = nil
  AWS.memoize do
    instances = ec2.instances.filter("tag:Name", server_name)
    instances.each do |i|
      unless i.status == :shutting_down || i.status == :terminated
        instance = i
        break
      end # unless status
    end # instance loop
  end # memoize

  if instance
    environment_name = nil
    AWS.memoize do
      environment_name = instance.tags["environment"].strip if instance.tags["environment"]
    end

    ##############################
    # ENVIRONMENT
    ##############################
    if environment_name.nil? && ! force
      @log.fatal "No environment tag found for host. Use the --force option to override and terminate."
      exit 3
    end

    if (! @environments.has_key?(environment_name)) && (! force)
      @log.fatal "Environment not found: '#{environment_name}'"
      exit 2
    end
    @environment = @environments[environment_name] if environment_name

    ##############################
    # Create Route53 connection
    ##############################
    aws_route53 = nil
    if @environment && @environment.route53_zone_id
      aws_route53 = AWS::Route53.new
      route53 = EC2Launcher::Route53.new(aws_route53, @environment.route53_zone_id, @log)
    end

    ##############################
    # EBS Volumes
    ##############################
    # Find EBS volumes
    attachments = nil
    AWS.memoize do
      attachments = instance.block_device_mappings.values

      # Remove snapshots
      remove_snapshots(ec2, attachments) if snapshot_removal

      # Remove volumes, if necessary
      remove_volumes(ec2, attachments)
    end

    private_ip_address = instance.private_ip_address
    
    run_with_backoff(30, 1, "terminating instance: #{server_name} [#{instance.instance_id}]") do
      instance.terminate
    end

    if route53
      @log.info("Deleting A record from Route53: #{server_name} => #{private_ip_address}")
      route53.delete_record_by_name(server_name, 'A')
    end

    @log.info("Deleting node/client from Chef: #{server_name}")
    node_result = `echo "Y" |knife node delete #{server_name}`
    client_result = `echo "Y" |knife client delete #{server_name}`
    @log.debug("Deleted Chef node: #{node_result}")
    @log.debug("Deleted Chef client: #{client_result}")
  else
    @log.error("Unable to find instance: #{server_name}")
  end
end