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



45
46
47
48
# File 'lib/awsborn/server.rb', line 45

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

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



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

def cluster (name = ServerCluster.next_name, &block)
  ServerCluster.build(self, name, &block)
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'


18
19
20
21
22
23
24
# File 'lib/awsborn/server.rb', line 18

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

.individual_security_group(*args) ⇒ Object



33
34
35
36
# File 'lib/awsborn/server.rb', line 33

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

.instance_type(*args) ⇒ Object



25
26
27
28
# File 'lib/awsborn/server.rb', line 25

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

.keys(*args) ⇒ Object



37
38
39
40
# File 'lib/awsborn/server.rb', line 37

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

.monitor(*args) ⇒ Object



49
50
51
52
# File 'lib/awsborn/server.rb', line 49

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

.security_group(*args) ⇒ Object



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

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

.sudo_user(*args) ⇒ Object



41
42
43
44
# File 'lib/awsborn/server.rb', line 41

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

Instance Method Details

#associate_addressObject



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

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

#attach_volumesObject



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

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



181
182
183
184
185
186
187
# File 'lib/awsborn/server.rb', line 181

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



198
199
200
201
202
# File 'lib/awsborn/server.rb', line 198

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

#copy_sudo_users_keys_to_rootObject



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

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

#ec2Object



224
225
226
# File 'lib/awsborn/server.rb', line 224

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

#find_instance_id_by_nameObject



62
63
64
65
66
67
68
69
70
71
# File 'lib/awsborn/server.rb', line 62

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



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

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)


151
152
153
154
155
156
# File 'lib/awsborn/server.rb', line 151

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



158
159
160
161
162
163
# File 'lib/awsborn/server.rb', line 158

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



165
166
167
168
169
# File 'lib/awsborn/server.rb', line 165

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



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/awsborn/server.rb', line 130

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



331
332
333
# File 'lib/awsborn/server.rb', line 331

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

#refreshObject



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

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



218
219
220
221
222
# File 'lib/awsborn/server.rb', line 218

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)


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

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



115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/awsborn/server.rb', line 115

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



107
108
109
110
111
112
113
# File 'lib/awsborn/server.rb', line 107

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

#update_known_hostsObject



147
148
149
# File 'lib/awsborn/server.rb', line 147

def update_known_hosts
  KnownHostsUpdater.update_for_server self
end

#upload_cookbooksObject



204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/awsborn/server.rb', line 204

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 --delete --exclude '.*' ./ root@#{host_name}:#{Awsborn.remote_chef_path}"
ensure
  FileUtils.rm_f("config/dna.json")
  File.delete("cookbooks") if temp_link
end