Class: Awsborn::Server

Inherits:
Object
  • Object
show all
Includes:
AwsConstants
Defined in:
lib/awsborn/server.rb

Constant Summary

Constants included from AwsConstants

AwsConstants::AVAILABILITY_ZONES, AwsConstants::INSTANCE_TYPES, AwsConstants::INSTANCE_TYPES_32_BIT, AwsConstants::INSTANCE_TYPES_64_BIT, AwsConstants::REGIONS, AwsConstants::SYMBOL_CONSTANT_MAP

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from AwsConstants

#aws_constant, #aws_instance_type_to_symbol, #aws_zone_to_symbol, #endpoint_for_zone_and_service, #symbol_to_aws_instance_type, #symbol_to_aws_zone, #zone_to_aws_region

Constructor Details

#initialize(name, options = {}) ⇒ Server

Returns a new instance of Server.



5
6
7
8
9
# File 'lib/awsborn/server.rb', line 5

def initialize (name, options = {})
  @name = name
  @options = options.dup
  self.host_name = elastic_ip
end

Class Attribute Details

.loggerObject

Returns the value of attribute logger.



12
13
14
# File 'lib/awsborn/server.rb', line 12

def logger
  @logger
end

Class Method Details

.bootstrap_script(*args) ⇒ Object



55
56
57
58
# File 'lib/awsborn/server.rb', line 55

def bootstrap_script (*args)
  @bootstrap_script = args.first unless args.empty?
  get_class_attr(:bootstrap_script)
end

.cluster(name = ServerCluster.next_name, &block) ⇒ Object



64
65
66
# File 'lib/awsborn/server.rb', line 64

def cluster (name = ServerCluster.next_name, &block)
  ServerCluster.build(self, name, &block)
end

.get_class_attr(class_attr) ⇒ Object



14
15
16
17
18
19
20
21
22
# File 'lib/awsborn/server.rb', line 14

def get_class_attr(class_attr)
  if (class_var = instance_variable_get("@#{class_attr}"))
    class_var
  elsif self != ::Awsborn::Server
    superclass.send(class_attr)
  else
    nil
  end
end

.image_id(*args) ⇒ Object

Set image_id. Examples:

image_id 'ami-123123'
image_id 'ami-123123', :sudo_user => 'ubuntu'
image_id :i386 => 'ami-323232', :x64 => 'ami-646464', :sudo_user => 'ubuntu'


28
29
30
31
32
33
34
# File 'lib/awsborn/server.rb', line 28

def image_id (*args)
  unless args.empty?
    @image_id = args.first
    @sudo_user = args.last[:sudo_user] if args.last.is_a?(Hash)
  end
  get_class_attr(:image_id)
end

.individual_security_group(*args) ⇒ Object



43
44
45
46
# File 'lib/awsborn/server.rb', line 43

def individual_security_group (*args)
  @individual_security_group = args.first unless args.empty?
  get_class_attr(:individual_security_group)
end

.instance_type(*args) ⇒ Object



35
36
37
38
# File 'lib/awsborn/server.rb', line 35

def instance_type (*args)
  @instance_type = args.first unless args.empty?
  get_class_attr(:instance_type)
end

.keys(*args) ⇒ Object



47
48
49
50
# File 'lib/awsborn/server.rb', line 47

def keys (*args)
  @keys = args unless args.empty?
  get_class_attr(:keys)
end

.monitor(*args) ⇒ Object



59
60
61
62
# File 'lib/awsborn/server.rb', line 59

def monitor (*args)
  @monitor = args.first unless args.empty?
  get_class_attr(:monitor)
end

.security_group(*args) ⇒ Object



39
40
41
42
# File 'lib/awsborn/server.rb', line 39

def security_group (*args)
  @security_group = args unless args.empty?
  get_class_attr(:security_group)
end

.sudo_user(*args) ⇒ Object



51
52
53
54
# File 'lib/awsborn/server.rb', line 51

def sudo_user (*args)
  @sudo_user = args.first unless args.empty?
  get_class_attr(:sudo_user)
end

Instance Method Details

#associate_addressObject



185
186
187
188
189
# File 'lib/awsborn/server.rb', line 185

def associate_address
  logger.debug "Associating address #{elastic_ip} to #{name}"
  ec2.associate_address(elastic_ip)
  self.host_name = elastic_ip
end

#attach_volumesObject



199
200
201
202
203
204
205
206
# File 'lib/awsborn/server.rb', line 199

def attach_volumes
  logger.debug "Attaching volumes #{disk.values.join(', ')} to #{name}" unless disk.empty?
  disk.each_pair do |device, str_or_ary|
    volume = str_or_ary.is_a?(Array) ? str_or_ary.first : str_or_ary
    device = "/dev/#{device}" if device.is_a?(Symbol) || ! device.match('/')
    res = ec2.attach_volume(volume, device)
  end
end

#bootstrapObject



191
192
193
194
195
196
197
# File 'lib/awsborn/server.rb', line 191

def bootstrap
  logger.debug "Bootstrapping #{name}"
  script = bootstrap_script
  basename = File.basename(script)
  system "scp #{script} root@#{host_name}:/tmp"
  system "ssh root@#{host_name} 'cd /tmp && chmod 700 #{basename} && ./#{basename}'"
end

#cookObject



208
209
210
211
212
# File 'lib/awsborn/server.rb', line 208

def cook
  raise "#{host_name} not running" unless running?
  upload_cookbooks
  run_chef
end

#copy_sudo_users_keys_to_rootObject



181
182
183
# File 'lib/awsborn/server.rb', line 181

def copy_sudo_users_keys_to_root
  system("ssh #{sudo_user}@#{host_name} 'sudo cp .ssh/authorized_keys /root/.ssh/authorized_keys'")
end

#ec2Object



234
235
236
# File 'lib/awsborn/server.rb', line 234

def ec2
  @ec2 ||= Ec2.new(zone)
end

#find_instance_id_by_nameObject



72
73
74
75
76
77
78
79
80
81
# File 'lib/awsborn/server.rb', line 72

def find_instance_id_by_name
  instances = ec2.connection.describe_instances(
    :filters => {'tag:Name' => full_name,
                 'instance-state-name' => ['pending', 'running']}
  )
  if instances.count > 1
    raise ServerError, "Found multiple instances with full_name = #{full_name}."
  end
  instances.empty? ? nil : instances.first[:aws_instance_id]
end

#find_instance_id_by_volumeObject



83
84
85
86
87
88
89
90
91
# File 'lib/awsborn/server.rb', line 83

def find_instance_id_by_volume
  map = {}
  disk_volume_ids.each { |vol_id| map[vol_id] = ec2.instance_id_for_volume(vol_id) }
  ids = map.values.uniq
  if ids.size > 1
    raise ServerError, "Volumes for #{self.class.name}:#{name} are connected to several instances: #{map.inspect}"
  end
  ids.first
end

#install_ssh_keys(temp_key_pair = nil) ⇒ Object

Raises:

  • (ArgumentError)


161
162
163
164
165
166
# File 'lib/awsborn/server.rb', line 161

def install_ssh_keys (temp_key_pair = nil)
  logger.debug "Installing ssh keys on #{name}"
  raise ArgumentError, "No host_name for #{name}" unless host_name
  install_ssh_keys_for_sudo_user_or_root(temp_key_pair)
  copy_sudo_users_keys_to_root if sudo_user
end

#install_ssh_keys_for_sudo_user_or_root(temp_key_pair) ⇒ Object



168
169
170
171
172
173
# File 'lib/awsborn/server.rb', line 168

def install_ssh_keys_for_sudo_user_or_root (temp_key_pair)
  current_key = "-i #{temp_key_pair.path}" if temp_key_pair
  IO.popen("ssh #{current_key} #{sudo_user || 'root'}@#{host_name} 'cat > .ssh/authorized_keys'", "w") do |pipe|
    pipe.puts key_data
  end
end

#key_dataObject



175
176
177
178
179
# File 'lib/awsborn/server.rb', line 175

def key_data
  Dir[*keys].inject([]) do |memo, file_name|
    memo + File.readlines(file_name).map { |line| line.chomp }
  end.join("\n")
end

#launch_instance(key_pair) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/awsborn/server.rb', line 140

def launch_instance (key_pair)
  @launch_response = ec2.launch_instance(image_id,
    :instance_type => symbol_to_aws_instance_type(instance_type),
    :availability_zone => symbol_to_aws_zone(zone),
    :key_name => key_pair.name,
    :group_ids => security_group,
    :monitoring_enabled => monitor,
    :user_data => user_data
  )
  logger.debug @launch_response

  ec2.set_instance_name full_name

  Awsborn.wait_for("instance #{instance_id} (#{name}) to start", 10) { instance_running? }
  self.host_name = aws_dns_name
end

#loggerObject



341
342
343
# File 'lib/awsborn/server.rb', line 341

def logger
  @logger ||= self.class.logger
end

#refreshObject



104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/awsborn/server.rb', line 104

def refresh
  start_or_stop_monitoring unless monitor.nil?
  associate_address if elastic_ip

  begin
    update_known_hosts
    install_ssh_keys if keys
  rescue SecurityError => e
    logger.warn "Could not update known_hosts for #{name}:"
    logger.warn e
  end
end

#run_chefObject



228
229
230
231
232
# File 'lib/awsborn/server.rb', line 228

def run_chef
  logger.info "Running chef on #{host_name}"
  # Absolute path to config files to avoid a nasty irrational bug.
  sh "ssh root@#{host_name} \"cd #{Awsborn.remote_chef_path}; chef-solo -l #{Awsborn.chef_log_level} -c #{Awsborn.remote_chef_path}/config/solo.rb -j #{Awsborn.remote_chef_path}/config/dna.json\""
end

#running?Boolean

Returns:

  • (Boolean)


93
94
95
96
97
98
99
100
101
102
# File 'lib/awsborn/server.rb', line 93

def running?
  name_id = find_instance_id_by_name
  volume_id = find_instance_id_by_volume

  if name_id && volume_id && name_id != volume_id
    raise ServerError, "Volumes #{disk_volume_ids} are attached to #{volume_id}, not to instance #{name_id} with name #{full_name}"
  else
    ec2.instance_id = name_id || volume_id
  end
end

#start(key_pair) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/awsborn/server.rb', line 125

def start (key_pair)
  launch_instance(key_pair)

  update_known_hosts
  install_ssh_keys(key_pair) if keys

  if elastic_ip
    associate_address
    update_known_hosts
  end

  bootstrap if bootstrap_script
  attach_volumes
end

#start_or_stop_monitoringObject



117
118
119
120
121
122
123
# File 'lib/awsborn/server.rb', line 117

def start_or_stop_monitoring
  if monitor && ! ec2.monitoring?
    ec2.monitor
  elsif ec2.monitoring? && ! monitor
    ec2.unmonitor
  end
end

#update_known_hostsObject



157
158
159
# File 'lib/awsborn/server.rb', line 157

def update_known_hosts
  KnownHostsUpdater.update_for_server self
end

#upload_cookbooksObject



214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/awsborn/server.rb', line 214

def upload_cookbooks
  logger.info "Uploading cookbooks to #{host_name}"

  cookbooks_dir = '../cookbooks' # Hard coded for now
  temp_link = File.directory?(cookbooks_dir) && ! File.directory?('cookbooks')
  File.symlink(cookbooks_dir, 'cookbooks') if temp_link

  File.open("config/dna.json", "w") { |f| f.write(chef_dna.to_json) }
  system "rsync -rL --chmod=o-rw -p --delete --exclude '.*' ./ root@#{host_name}:#{Awsborn.remote_chef_path}"
ensure
  FileUtils.rm_f("config/dna.json")
  File.delete("cookbooks") if temp_link
end