Top Level Namespace

Instance Method Summary collapse

Instance Method Details

#add_key(options) ⇒ Object



221
222
223
# File 'lib/appscake_utils.rb', line 221

def add_key(options)
  puts "Generating RSA keys..."
end

#deploy(options) ⇒ Object



214
215
216
217
218
219
# File 'lib/appscake_utils.rb', line 214

def deploy(options)
  30.times do |i|
    puts "Deploying..."
    sleep(1)
  end
end

#deploy_on_ec2(params, run_instances_options, cert_timestamp) ⇒ Object



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/appscake_utils.rb', line 289

def deploy_on_ec2(params, run_instances_options, cert_timestamp)
  if lock
    begin
      timestamp = Time.now.to_i
      pid = fork do
        ENV['EC2_REGION'] = params[:region]
        ENV['EC2_PRIVATE_KEY'] = File.join(File.dirname(__FILE__), "..", "certificates",
                                    "#{params[:username]}_#{cert_timestamp}_pk.pem")
        ENV['EC2_CERT'] = File.join(File.dirname(__FILE__), "..", "certificates",
                                           "#{params[:username]}_#{cert_timestamp}_cert.pem")
        ENV['EC2_ACCESS_KEY'] = params[:access_key]
        ENV['EC2_SECRET_KEY'] = params[:secret_key]
        ENV['EC2_JVM_ARGS'] = "-Djavax.net.ssl.trustStore=#{ENV['JAVA_HOME']}/lib/security/cacerts"
        ENV['EC2_URL'] = "https://ec2.#{params[:region]}.amazonaws.com"
        ENV['S3_URL'] = "https://s3.amazonaws.com:443"
        begin
          redirect_standard_io(timestamp) do
            AppScaleTools.run_instances(run_instances_options)
          end
        ensure
          # If the fork was successful, the sub-process should release the lock
          unlock
        end
      end
      Process.detach(pid)
      return [true, timestamp, pid]
    rescue Exception => e
      # If something went wrong with the fork, release the lock immediately and return
      unlock
      return [false, "Unexpected Runtime Error", "Runtime error while executing" +
          " appscale tools: #{e.message}"]
    end
  else
    return [false, "Server Busy", "AppsCake is currently busy deploying a cloud." +
        " Please try again later."]
  end
end

#deploy_on_virtual_cluster(params, add_key_options, run_instances_options) ⇒ Object

Initiates a task in the background to deploy AppScale on a virtualized cluster. Returns a 3-element array as the result of the operation. The first element of the array is a boolean value indicating success or failure. In case of success, the second value will be the timestamp on which the task was launched. The third value will be the process ID of the newly launched task. In case of failure the second and third values will provide detailed error information.



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/appscake_utils.rb', line 254

def deploy_on_virtual_cluster(params, add_key_options, run_instances_options)
  if lock
    begin
      timestamp = Time.now.to_i
      pid = fork do
        begin
          redirect_standard_io(timestamp) do
            key_file = File.expand_path("~/.appscale/#{params[:virtual_keyname]}")
            if File.exists?(key_file)
              puts "AppScale key '#{params[:virtual_keyname]}' found on the disk. Reusing..."
            else
              puts "AppScale key '#{params[:virtual_keyname]}' not found on the disk. Generating..."
              AppScaleTools.add_keypair(add_key_options)
            end
            AppScaleTools.run_instances(run_instances_options)
          end
        ensure
          # If the fork was successful, the sub-process should release the lock
          unlock
        end
      end
      Process.detach(pid)
      return [true, timestamp, pid]
    rescue Exception => e
      # If something went wrong with the fork, release the lock immediately and return
      unlock
      return [false, "Unexpected Runtime Error", "Runtime error while executing" +
          " appscale tools: #{e.message}"]
    end
  else
    return [false, "Server Busy", "AppsCake is currently busy deploying a cloud." +
        " Please try again later."]
  end
end

#lockObject



190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/appscake_utils.rb', line 190

def lock
  $mutex.synchronize do
    if !File.exists?('appscake.lock')
      File.open('appscake.lock', 'w') do |file|
        file.write('AppsCake.lock')
      end
      return true
    else
      return false
    end
  end
end

#locked?Boolean

Returns:

  • (Boolean)


184
185
186
187
188
# File 'lib/appscake_utils.rb', line 184

def locked?
  $mutex.synchronize do
    return File.exists?('appscake.lock')
  end
end

#redirect_standard_io(timestamp) ⇒ Object



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/appscake_utils.rb', line 225

def redirect_standard_io(timestamp)
  begin
    orig_stderr = $stderr.clone
    orig_stdout = $stdout.clone
    log_path = File.join(File.expand_path(File.dirname(__FILE__)), "..", "logs")
    $stderr.reopen File.new(File.join(log_path, "deploy-#{timestamp}.log"), "w")
    $stderr.sync = true
    $stdout.reopen File.new(File.join(log_path, "deploy-#{timestamp}.log"), "w")
    $stdout.sync = true
    retval = yield
  rescue Exception => e
    puts "[__ERROR__] Runtime error in deployment process: #{e.message}"
    $stdout.reopen orig_stdout
    $stderr.reopen orig_stderr
    raise e
  ensure
    $stdout.reopen orig_stdout
    $stderr.reopen orig_stderr
  end
  retval
end

#report_error(title, msg) ⇒ Object



22
23
24
25
26
# File 'lib/appscake_utils.rb', line 22

def report_error(title, msg)
  @title = title
  @error = msg
  return erb :error
end

#unlockObject



203
204
205
206
207
208
209
210
211
212
# File 'lib/appscake_utils.rb', line 203

def unlock
  $mutex.synchronize do
    if File.exists?('appscake.lock')
      File.delete('appscake.lock')
      return true
    else
      return false
    end
  end
end

#validate_appscale_credentials(user, pass1, pass2) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/appscake_utils.rb', line 28

def validate_appscale_credentials(user, pass1, pass2)
  if user.nil? or user.length == 0
    return [false, "Administrator username not provided"]
  elsif pass1.nil? or pass2.nil? or pass1.length == 0 or pass2.length == 0
    return [false, "Administrator password not provided"]
  elsif pass1 != pass2
    return [false, "Password entries do not match"]
  elsif pass1.length < 6
    return [false, "Password must contain at least 6 characters"]
  else
    return [true, '']
  end
end

#validate_ec2_certificate_uploads(username, pk_upload, cert_upload) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/appscake_utils.rb', line 158

def validate_ec2_certificate_uploads(username, pk_upload, cert_upload)
  if username.nil? or username.length == 0
    return [false, "EC2 username not specified"]
  elsif pk_upload.nil?
    return [false, "Primary key not uploaded"]
  elsif pk_upload[:type] != "application/x-x509-ca-cert" and
      pk_upload[:type] != "application/x-pem-file"
    return [false, "Invalid primary key format: #{pk_upload[:type]}"]
  elsif cert_upload.nil?
    return [false, "X509 certificate not uploaded"]
  elsif cert_upload[:type] != "application/x-x509-ca-cert" and
      cert_upload[:type] != "application/x-pem-file"
    return [false, "Invalid certificate format: #{cert_upload[:type]}"]
  else
    timestamp = Time.now.to_i
    cert_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "certificates"))
    File.open(File.join(cert_dir, "#{username}_#{timestamp}_pk.pem"), "w") do |f|
      f.write(pk_upload[:tempfile].read)
    end
    File.open(File.join(cert_dir, "#{username}_#{timestamp}_cert.pem"), "w") do |f|
      f.write(cert_upload[:tempfile].read)
    end
  end
  [true, timestamp]
end

#validate_ec2_cluster_settings(min, max, ami) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/appscake_utils.rb', line 120

def validate_ec2_cluster_settings(min, max, ami)
  if min.nil? or min.length == 0
    return [false, "Minimum number of nodes unspecified"]
  elsif max.nil? or max.length == 0
    return [false, "Maximum number of nodes unspecified"]
  elsif ami.nil? or ami.length == 0
    return [false, "AMI ID not specified"]
  elsif min.to_i <= 0
    return [false, "Minimum number of nodes must be positive"]
  elsif max.to_i < min.to_i
    return [false, "Maximum number of nodes must not be smaller than the minimum numberof nodes"]
  else
    return [true, ""]
  end
end

#validate_ec2_credentials(username, access_key, secret_key, region) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/appscake_utils.rb', line 136

def validate_ec2_credentials(username, access_key, secret_key, region)
  if username.nil? or username.length == 0
    return [false, "EC2 username not specified"]
  elsif access_key.nil? or access_key.length == 0
    return [false, "EC2 access key not specified"]
  elsif secret_key.nil? or secret_key.length == 0
    return [false, "EC2 secret key not specified"]
  elsif region.nil? or region.length == 0
    return [false, "EC2 region not specified"]
  else
    output = CommonFunctions::shell("ec2-describe-regions -O #{access_key}  -W #{secret_key}")
    if output.nil? or output.length == 0
      return [false, "Unable to execute EC2 command line tools"]
    elsif output.include?"AuthFailure"
      return [false, "EC2 authentication failed. Invalid EC2 access key or/and secret key."]
    elsif !output.include?region
      return [false, "Invalid EC2 region. This region is not available for your account."]
    end
  end
  [true, ""]
end

#validate_ssh_credentials(keyname, root_password, ips_yaml) ⇒ Object



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
# File 'lib/appscake_utils.rb', line 42

def validate_ssh_credentials(keyname, root_password, ips_yaml)
  if keyname.nil? or keyname.length == 0
    return [false, "AppScale key name not provided"]
  elsif root_password.nil? or root_password == 0
    return [false, "Root password for AppScale machines not provided"]
  else
    # Randomly pick a server and try to connect to it via SSH using the
    # provided credentials. This will guard againt invalid root passwords
    # entered by the user and any obvious network level issues which might
    # prevent AppsCake from connecting to the specified machines. The test
    # assumes that all hosts can be accessed using the same root password
    # and they are on the same network (if one is reachable, then all are
    # reachable) which is the case for most typical AppScale deployments.
    # In scenarios where the above assumption is not the case (i.e. some
    # machines are reachable and some are not) the test may pass, but the 
    # final deployment may fail. These runtime errors will go to the deployment 
    # logs generated by AppsCake per each deployment which can also be accessed 
    # over the web.
    node_layout = NodeLayout.new(ips_yaml, {:database => "cassandra"})
    ips = node_layout.nodes.collect { |node| node.id }
    begin
      Net::SSH.start(ips[0], 'root', :password => root_password, :timeout => 10) do |ssh|
        ssh.exec('ls')
      end
    rescue Timeout::Error
      return [false, "Connection timed out for #{ips[0]}"]
    rescue Errno::EHOSTUNREACH
      return [false, "Host unreachable error for #{ips[0]}"]
    rescue Errno::ECONNREFUSED
      return [false, "Connection refused for #{ips[0]}"]
    rescue Net::SSH::AuthenticationFailed
      return [false, "Authentication failed for #{ips[0]} - Please ensure that the specified" +
          " root password is correct"]
    rescue Exception => e
      return [false, "Unexpected runtime error connecting to #{ips[0]}"]
    end
    return [true, '']
  end
end

#validate_yaml(yaml_str) ⇒ Object



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
# File 'lib/appscake_utils.rb', line 82

def validate_yaml(yaml_str)
  if yaml_str.nil? or yaml_str.length == 0
    return [false, "ips.yaml configuration not provided"]
  end

  yaml = YAML.load(yaml_str)
  node_layout = NodeLayout.new(yaml, {})
  if !node_layout.valid?
    errors = node_layout.errors
    error_result = ""
    for error in errors
      if !error.nil? and error.length > 0
        if error_result.length > 0
          error_result += ", "
        end
        error_result += error
      end
    end
    return [false, error_result]
  end

  success_result = ""
  yaml.each do |symbol, value|
    role = symbol.to_s
    success_result += "<p>#{role}</p><ul>"
    if value.kind_of?(Array)
      value.each do |val|
        success_result += "<li>#{val}</li>"
      end
    else
      success_result += "<li>#{value}</li>"
    end
    success_result += "</ul>"
  end

  [true, success_result, yaml]
end