appscake 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,45 @@
1
+ Software License Agreement (BSD License)
2
+
3
+ Copyright (c) 2008, Regents of the University of California
4
+ All rights reserved.
5
+
6
+ Redistribution and use of this software in source and binary forms, with or
7
+ without modification, are permitted provided that the following conditions
8
+ are met:
9
+
10
+ * Redistributions of source code must retain the above
11
+ copyright notice, this list of conditions and the
12
+ following disclaimer.
13
+
14
+ * Redistributions in binary form must reproduce the above
15
+ copyright notice, this list of conditions and the
16
+ following disclaimer in the documentation and/or other
17
+ materials provided with the distribution.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE
30
+ PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, COPYRIGHTED MATERIAL OR
31
+ PATENTED MATERIAL IN THIS SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY
32
+ DISCOVERING IT MAY INFORM DR. CHANDRA KRINTZ AT THE UNIVERSITY OF CALIFORNIA,
33
+ SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH
34
+ IN THE REGENTS’ DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT
35
+ OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR WITHDRAWAL
36
+ OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH ANY SUCH LICENSES
37
+ OR RIGHTS.
38
+
39
+ This software makes use of third party software libraries released under different
40
+ licenses. The table below lists all the contained libraries and the licenses under
41
+ which they are provided to you.
42
+
43
+ * Twitter Bootstrap - Apache Software License 2.0
44
+ * jQuery Validation Plugin - MIT License
45
+
data/README.txt ADDED
@@ -0,0 +1,56 @@
1
+ AppsCake - Web Frontend for AppScale Tools
2
+ ==========================================
3
+
4
+ "AppsCake makes deploying AppScale a piece of cake"
5
+
6
+ AppsCake is a simple and lightweight web application that allows users to
7
+ interact with AppScale tools over the web. This way even those users who
8
+ are not familiar with general cloud principles or those who are not
9
+ comfortable with working with a traditional command line interface can
10
+ get started with deploying AppScale clouds and AppScale cloud applications.
11
+
12
+ AppsCake has been developed using the Ruby programming language and is
13
+ based on Sinatra. So far it has been tested in Xen and EC2 cloud
14
+ environments.
15
+
16
+ Prerequisites
17
+ =============
18
+
19
+ Following software is required to install and run AppsCake:
20
+
21
+ 1. Ruby interpreter
22
+ 2. Sinatra gem
23
+ 3. AppScale tools gem (or alternatively you can install AppScale tools
24
+ binary distribution on your machine/VM and put them in the PATH)
25
+
26
+ Installation
27
+ ============
28
+
29
+ There are 2 ways to install AppsCake:
30
+
31
+ 1. Pull the source from the Github
32
+ - https://github.com/AppScale/appscake
33
+
34
+ 2. Install the AppsCake gem
35
+ sudo gem install appscake
36
+
37
+ Running AppsCake
38
+ ================
39
+
40
+ To launch AppsCake, simply go into the bin directory and execute the
41
+ 'appscake' script:
42
+
43
+ ./appscake
44
+
45
+ Once the server has started up, fire up your web browser and navigate
46
+ to https://<appscake-host>:8443
47
+
48
+ If you're installing AppsCake on an EC2 image make sure to modify your
49
+ security group so that it allows inbound traffic on the port 8443.
50
+
51
+
52
+
53
+ ======================================================================
54
+ AppsCake is a project by the UCSB RACELab
55
+ http://appscake.cs.ucsb.edu
56
+
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/testtask'
5
+
6
+
7
+ # TODO(cgb): This probably should be moved into a Gemfile and out of this file.
8
+ spec = Gem::Specification.new do |s|
9
+ s.name = 'appscake'
10
+ s.version = '0.0.2'
11
+
12
+ s.summary = "A web interface to the AppScale command-line tools."
13
+ s.description = <<-EOF
14
+ AppsCake provides a pretty web interface that can be used to deploy
15
+ AppScale over machines in Xen, KVM, Amazon EC2, or Eucalyptus. In
16
+ short, it makes deploying AppScale a piece of cake!
17
+ EOF
18
+
19
+ s.author = "Hiranya Jayathilaka"
20
+ s.email = "appscale_community@googlegroups.com"
21
+ s.homepage = "http://appscale.cs.ucsb.edu"
22
+
23
+ s.executables = ["appscake"]
24
+ s.default_executable = 'appscake'
25
+ s.platform = Gem::Platform::RUBY
26
+
27
+ candidates = Dir.glob("**/*")
28
+ s.files = candidates.delete_if do |item|
29
+ item.include?(".git") || item.include?("rdoc") || item.include?("pkg")
30
+ end
31
+ s.require_path = "lib"
32
+ s.autorequire = "appscake_utils"
33
+
34
+ s.has_rdoc = false
35
+ s.add_dependency('net-ssh', '>= 2.6.0')
36
+ end
37
+
38
+
39
+ # responds to 'rake gem'
40
+ Rake::GemPackageTask.new(spec) do |pkg|
41
+ pkg.need_tar = true
42
+ end
data/bin/appscake CHANGED
@@ -1,5 +1,165 @@
1
- #!/bin/bash
2
- # Programmer: Chris Bunch (cgb@cs.ucsb.edu)
3
- # Makes AppsCake exec'able, nice when you install via gem
1
+ #!/usr/bin/ruby
2
+ # Author: Hiranya Jayathilaka (hiranya@cs.ucsb.edu)
3
+ # AppsCake web interface for deploying and launching AppScale clouds
4
+ # AppsCake = Makes deploying AppScale a 'piece of cake'
4
5
 
5
- ruby ../appscake.rb
6
+ require 'rubygems'
7
+ require 'sinatra/base'
8
+ require 'webrick'
9
+ require 'webrick/https'
10
+ require 'openssl'
11
+
12
+ CERT_DIR = File.expand_path(File.join(File.dirname(__FILE__), "..", "certificates"))
13
+
14
+ LIB_DIR = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
15
+ $:.unshift LIB_DIR
16
+ require 'appscake_utils'
17
+
18
+ puts "\nAppsCake - Makes deploying AppScale a 'piece of cake'!\n\n"
19
+
20
+ class AppsCake < Sinatra::Base
21
+ set :views, settings.root + '/../views'
22
+ set :public_folder, settings.root + '/../public'
23
+
24
+ get '/' do
25
+ erb :index
26
+ end
27
+
28
+ post '/virtual.do' do
29
+ if locked?
30
+ return report_error("Server Busy", "AppsCake is currently busy deploying a cloud." +
31
+ " Please try again later.")
32
+ end
33
+
34
+ status, yaml_result, yaml = validate_yaml(params[:ips])
35
+ if !status
36
+ return report_error("IP Configuration Error", yaml_result)
37
+ end
38
+
39
+ status,acc_result = validate_appscale_credentials(params[:virtual_user],
40
+ params[:virtual_pass], params[:virtual_pass2])
41
+ if !status
42
+ return report_error("AppScale Administrator Account Configuration Error", acc_result)
43
+ end
44
+
45
+ status,ssh_result = validate_ssh_credentials(params[:virtual_keyname], params[:root_password], yaml)
46
+ if !status
47
+ return report_error("AppScale SSH Configuration Error", ssh_result)
48
+ end
49
+
50
+ add_key_options = {
51
+ 'ips' => yaml,
52
+ 'keyname' => params[:virtual_keyname],
53
+ 'auto' => true,
54
+ 'root_password' => params[:root_password]
55
+ }
56
+
57
+ app_name = nil
58
+ file_location = nil
59
+ if !params[:target_app].nil? and params[:target_app] != '_none_'
60
+ puts params[:target_app]
61
+ app_name = params[:target_app]
62
+ file_location = File.join(File.dirname(__FILE__), "repository", params[:target_app])
63
+ end
64
+
65
+ run_instances_options = {
66
+ 'ips' => yaml,
67
+ 'keyname' => params[:virtual_keyname],
68
+ 'file_location' => file_location,
69
+ 'appname' => app_name,
70
+ 'appengine' => 1,
71
+ 'autoscale' => true,
72
+ 'separate' => false,
73
+ 'confirm' => false,
74
+ 'table' => 'cassandra',
75
+ 'infrastructure' => nil,
76
+ 'admin_user' => params[:virtual_user],
77
+ 'admin_pass' => params[:virtual_pass]
78
+ }
79
+
80
+ deploy_on_virtual_cluster(params, add_key_options, run_instances_options, yaml_result)
81
+ end
82
+
83
+ post '/iaas_ec2.do' do
84
+ status, result = validate_ec2_cluster_settings(params[:min], params[:max], params[:ami])
85
+ if !status
86
+ return report_error("Cluster Configuration Error", result)
87
+ end
88
+
89
+ status, result = validate_ec2_credentials(params[:username], params[:access_key],
90
+ params[:secret_key], params[:region])
91
+ if !status
92
+ return report_error("EC2 Security Configuration Error", result)
93
+ end
94
+
95
+ status, result = validate_ec2_certificate_uploads(params[:username], params[:private_key],
96
+ params[:cert])
97
+ if !status
98
+ return report_error("EC2 Security Configuration Error", result)
99
+ end
100
+ cert_timestamp = result
101
+
102
+ status,acc_result = validate_appscale_credentials(params[:ec2_user],
103
+ params[:ec2_pass], params[:ec2_pass2])
104
+ if !status
105
+ return report_error("AppScale Administrator Account Configuration Error", acc_result)
106
+ end
107
+
108
+ app_name = nil
109
+ file_location = nil
110
+ if !params[:target_app].nil? and params[:target_app] != '_none_'
111
+ app_name = params[:target_app]
112
+ file_location = File.join(File.dirname(__FILE__), "repository", params[:target_app])
113
+ end
114
+
115
+ run_instances_options = {
116
+ 'keyname' => params[:ec2_keyname],
117
+ 'group' => params[:ec2_keyname],
118
+ 'file_location' => file_location,
119
+ 'appname' => app_name,
120
+ 'appengine' => 1,
121
+ 'autoscale' => true,
122
+ 'separate' => false,
123
+ 'confirm' => false,
124
+ 'table' => 'cassandra',
125
+ 'infrastructure' => 'ec2',
126
+ 'min_images' => params[:min].to_i,
127
+ 'max_images' => params[:max].to_i,
128
+ 'instance_type' => params[:instance_type],
129
+ 'machine' => params[:ami],
130
+ 'admin_user' => params[:ec2_user],
131
+ 'admin_pass' => params[:ec2_pass],
132
+ }
133
+ deploy_on_ec2(params, run_instances_options, cert_timestamp)
134
+ end
135
+
136
+ get '/view_logs' do
137
+ timestamp = params[:ts]
138
+ if timestamp.nil? or timestamp.length == 0
139
+ return report_error("Invalid URL Request", "No timestamp information found in the request")
140
+ end
141
+ @timestamp = timestamp
142
+ erb :view_log
143
+ end
144
+ end
145
+
146
+ webrick_options = {
147
+ :Port => 8443,
148
+ :Logger => WEBrick::Log::new($stderr, WEBrick::Log::INFO),
149
+ :DocumentRoot => "/ruby/htdocs",
150
+ :SSLEnable => true,
151
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
152
+ :SSLCertificate => OpenSSL::X509::Certificate.new(
153
+ File.open(File.join(CERT_DIR, "cert-appscake.pem")).read),
154
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new(
155
+ File.open(File.join(CERT_DIR, "pk-appscake.pem")).read),
156
+ :SSLCertName => [["CN", WEBrick::Utils::getservername]],
157
+ :app => AppsCake,
158
+ :server => 'webrick'
159
+ }
160
+
161
+ Rack::Server.start webrick_options
162
+
163
+ at_exit do
164
+ puts "Terminating AppsCake..."
165
+ end
@@ -0,0 +1,25 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIELzCCAxegAwIBAgIJAOIyrR3Lx40pMA0GCSqGSIb3DQEBBQUAMIGtMQswCQYD
3
+ VQQGEwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbnRhIEJhcmJhcmExGTAX
4
+ BgNVBAoMEFVDIFNhbnRhIEJhcmJhcmExJzAlBgNVBAsMHkRlcGFydG1lbnQgb2Yg
5
+ Q29tcHV0ZXIgU2NpZW5jZTERMA8GA1UEAwwIQXBwU2NhbGUxIjAgBgkqhkiG9w0B
6
+ CQEWE2hpcmFueWFAY3MudWNzYi5lZHUwHhcNMTIwOTI1MjEyOTIzWhcNMTIxMDI1
7
+ MjEyOTIzWjCBrTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1T
8
+ YW50YSBCYXJiYXJhMRkwFwYDVQQKDBBVQyBTYW50YSBCYXJiYXJhMScwJQYDVQQL
9
+ DB5EZXBhcnRtZW50IG9mIENvbXB1dGVyIFNjaWVuY2UxETAPBgNVBAMMCEFwcFNj
10
+ YWxlMSIwIAYJKoZIhvcNAQkBFhNoaXJhbnlhQGNzLnVjc2IuZWR1MIIBIjANBgkq
11
+ hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0uzoMSPlUZrn7eAnwVYT4D8DgkHEU+W5
12
+ BuHp5fby9Wj3/WerEr//ie61w29r1XPdF1KP2O+nj6LSk3AzluwEi03pJQBhWNmm
13
+ FKU8zavKwvRJtOYesNttwx6GDTQ/3cGSQpJ/5T9o+Xk/iOA9IFiOhCEH8L8OYoIb
14
+ SmmoR020cAOA7RpJ/T30DH7JNfDP4Q4BfyOimzKzpMz0w3scEJ3WAYz1LR0pc0MF
15
+ Kcz/NVYWouGO6l3S5kLANkXn7zWqH34dopOOSBWKqo9zEDVihlu0IzzZeJ4x7OSw
16
+ WlWH+DcPqvH8R7UCp0SooCMmrlniJp8AUMmHZ/p/vHZrwpQKWRnFbQIDAQABo1Aw
17
+ TjAdBgNVHQ4EFgQUbuGZ86sFEYgjUWClz2cr7NwIUHQwHwYDVR0jBBgwFoAUbuGZ
18
+ 86sFEYgjUWClz2cr7NwIUHQwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC
19
+ AQEAnVXP7n5Hf/mRQU0Bm7ZFcJ726hHG3Kt9b80zhBbBCI4ZBPR1HlWR8JclBINX
20
+ ES+/T0AoNU5C8XR5sJ6sFTL6VpYd2PyX/wfXnvzaejR5TyEv81LjK2PHjS/fFBMh
21
+ zjMxCM+ABWNZZEAdBkqvaFKL+pz73ges7pIBtxpT7PcEj3M1oTMZUUUlbEHv45HX
22
+ er3sUZb2rn3DztqZRdwzZUIdGOvP5h2jSJcNdD/3JqoROufHQbRWlGtMBYUfWCGf
23
+ Z7wOgs73OhMFBd+9Mvw4+s321yH8TBvGWH2vdQ7llg0ev4J7f+3p8hVEZN3IjlDL
24
+ cUOI1rlQiIJZCaQPaqlODOpvNg==
25
+ -----END CERTIFICATE-----
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEogIBAAKCAQEA0uzoMSPlUZrn7eAnwVYT4D8DgkHEU+W5BuHp5fby9Wj3/Wer
3
+ Er//ie61w29r1XPdF1KP2O+nj6LSk3AzluwEi03pJQBhWNmmFKU8zavKwvRJtOYe
4
+ sNttwx6GDTQ/3cGSQpJ/5T9o+Xk/iOA9IFiOhCEH8L8OYoIbSmmoR020cAOA7RpJ
5
+ /T30DH7JNfDP4Q4BfyOimzKzpMz0w3scEJ3WAYz1LR0pc0MFKcz/NVYWouGO6l3S
6
+ 5kLANkXn7zWqH34dopOOSBWKqo9zEDVihlu0IzzZeJ4x7OSwWlWH+DcPqvH8R7UC
7
+ p0SooCMmrlniJp8AUMmHZ/p/vHZrwpQKWRnFbQIDAQABAoIBAEsFdHi1+cSSwld7
8
+ WOiNQziJcSgNWFU26h6mj9j5guUC1uHM064xmCRpQUEoCkS7lzHKbduNMh4GnbtP
9
+ NypA/ETIC1rbzcQaddX2B7BnoBDDbsvm5ZemFF5IJwnfQbAQP4NqNA9IBIBnPc/j
10
+ Yhp1JQud7AMXEXi8KhTHi9EAtGL6Vq/SsVmK63BosFK8PFyaZ/EYAIL7QTJQBdwW
11
+ DWjW5FUVBP0NY+iJXuEt1slV32Xhx3eSPk53L2CG9o2aMkT+dbx/mC13edYI7UU7
12
+ 4bjq+F7Gxz93w+ZsRJ2YkciSHHk8qQ/FZrqpSr0GYul9eUSGkm8l8LZGNbzHsl8D
13
+ yAQrJUECgYEA7myy2+Ha0RG0icf001OPr+T3JGJK93R+VIC7LGF/rCuU6RLll7Nl
14
+ rYpMuYKvmiGaORpJYVc5zVwzeY5k90IyaVPa2xKtVo9tRfeNEaTaqlB6gTm+m5E3
15
+ iJFYym+G+KwuTuuhVVU9nV7KWODSBfLglEgOSpLTCGui0QN+qbY2ZVUCgYEA4nlF
16
+ s4NjQfUZiGGRbFKNqosVp2rgtFR95HXXRLjYTWtK/FS0ieMJyJUEFWJiNi0j9P+/
17
+ J9CmQy4r66MpqFooYHquMYRDukXCQ27KdfLe4UHM+bTQOAoCY7Tl/ds7mVHSp2cI
18
+ P/T2W3ehrxNPB2KNeU3OgJd9IPYNW/wwHotwX7kCgYBt7+stHmByZLKVkYDfbLll
19
+ hrM6sKQWpD2YI1+rIC3pqpLYQeFh6NOqiInGRG9KJ9JgIDHT04+QlMIbe8AsjvaF
20
+ wKe6ukr5Ddt6FqKSjyxQuhkyuvib7QLpUvPZLEHVKjeUJmxW1544kTvGbawKGCrb
21
+ 1LnaQwdR66fArtbZ1G4SnQKBgCcHOynKdKqDMJk+Jy+BsoQ3X83wLzUkcmWSoTxo
22
+ lm4RFWUSu+IfTCpS89czkzU+5jlscWbNIDnnlQ4Qmjc3AkpOGgLShlFtgCLazu0w
23
+ o5QyIL7PmCpwHyVLoW7z/vtXDHRo3xUWg/YTUbu4GiBtrW/AJtwmPxwVCwxVE33Q
24
+ DdeRAoGABCXxlPDzGW5qeHDDGWD6FdnRo6MqFS5W72MZM13t9AXSkP0Mnfds5smV
25
+ q0ejip4UNuvgfckU+ZnECTV/gU++1ePICyyY+4ERmjZq2YaI67AtH8KYGCDhlM2L
26
+ XHdEbTcJKqBzsBKi22xAISaSvUpZ8UPFoaRQBYsAKeQB52DJLEk=
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,325 @@
1
+ require 'thread'
2
+ require 'net/ssh'
3
+ require 'yaml'
4
+
5
+ tools_home = `which appscale-run-instances`
6
+ if tools_home.length == 0
7
+ # If AppScale-Tools are not installed on the local machine, use the Gem
8
+ require 'appscale-tools'
9
+ else
10
+ # Give priority to the the local copy of AppScale Tools
11
+ $:.unshift File.join(File.dirname(tools_home), "..", "lib")
12
+ require 'appscale_tools'
13
+ end
14
+
15
+ $mutex = Mutex.new
16
+
17
+ def report_error(title, msg)
18
+ @title = title
19
+ @error = msg
20
+ return erb :error
21
+ end
22
+
23
+ def validate_appscale_credentials(user, pass1, pass2)
24
+ if user.nil? or user.length == 0
25
+ return [false, "Administrator username not provided"]
26
+ elsif pass1.nil? or pass2.nil? or pass1.length == 0 or pass2.length == 0
27
+ return [false, "Administrator password not provided"]
28
+ elsif pass1 != pass2
29
+ return [false, "Password entries do not match"]
30
+ elsif pass1.length < 6
31
+ return [false, "Password must contain at least 6 characters"]
32
+ else
33
+ return [true, '']
34
+ end
35
+ end
36
+
37
+ def validate_ssh_credentials(keyname, root_password, ips_yaml)
38
+ if keyname.nil? or keyname.length == 0
39
+ return [false, "AppScale key name not provided"]
40
+ elsif root_password.nil? or root_password == 0
41
+ return [false, "Root password for AppScale machines not provided"]
42
+ else
43
+ # Randomly pick a server and try to connect to it via SSH using the
44
+ # provided credentials. This will guard againt invalid root passwords
45
+ # entered by the user and any obvious network level issues which might
46
+ # prevent AppsCake from connecting to the specified machines. The test
47
+ # assumes that all hosts can be accessed using the same root password
48
+ # and they are on the same network (if one is reachable, then all are
49
+ # reachable) which is the case for most typical AppScale deployments.
50
+ # In scenarios where the above assumption is not the case (i.e. some
51
+ # machines are reachable and some are not) the test may pass, but the
52
+ # final deployment may fail. These runtime errors will go to the deployment
53
+ # logs generated by AppsCake per each deployment which can also be accessed
54
+ # over the web.
55
+ node_layout = NodeLayout.new(ips_yaml, {:database => "cassandra"})
56
+ ips = node_layout.nodes.collect { |node| node.id }
57
+ begin
58
+ Net::SSH.start(ips[0], 'root', :password => root_password, :timeout => 10) do |ssh|
59
+ ssh.exec('ls')
60
+ end
61
+ rescue Timeout::Error
62
+ return [false, "Connection timed out for #{ips[0]}"]
63
+ rescue Errno::EHOSTUNREACH
64
+ return [false, "Host unreachable error for #{ips[0]}"]
65
+ rescue Errno::ECONNREFUSED
66
+ return [false, "Connection refused for #{ips[0]}"]
67
+ rescue Net::SSH::AuthenticationFailed
68
+ return [false, "Authentication failed for #{ips[0]} - Please ensure that the specified" +
69
+ " root password is correct"]
70
+ rescue Exception => e
71
+ return [false, "Unexpected runtime error connecting to #{ips[0]}"]
72
+ end
73
+ return [true, '']
74
+ end
75
+ end
76
+
77
+ def validate_yaml(yaml_str)
78
+ if yaml_str.nil? or yaml_str.length == 0
79
+ return [false, "ips.yaml configuration not provided"]
80
+ end
81
+
82
+ yaml = YAML.load(yaml_str)
83
+ critical_roles = %w[ appengine loadbalancer database login shadow zookeeper ]
84
+ aggregate_roles = {
85
+ 'master' => %w[ shadow loadbalancer zookeeper login ],
86
+ 'controller' => %w[ shadow loadbalancer zookeeper database login ],
87
+ 'servers' => %w[ appengine database loadbalancer ]
88
+ }
89
+ optional_roles = %w[ open memcache ]
90
+
91
+ success_result = ''
92
+ yaml.each do |symbol, value|
93
+ role = symbol.to_s
94
+ if !critical_roles.include? role and !aggregate_roles.has_key? role and
95
+ !optional_roles.include? role
96
+ return [false, "Unknown AppScale server role: #{role}"]
97
+ else
98
+ critical_roles.delete_if { |r|
99
+ r == role or (aggregate_roles.has_key? role and aggregate_roles[role].include? r)
100
+ }
101
+ success_result += "<p>#{role}</p><ul>"
102
+ if value.kind_of?(Array)
103
+ value.each do |val|
104
+ success_result += "<li>#{val}</li>"
105
+ end
106
+ else
107
+ success_result += "<li>#{value}</li>"
108
+ end
109
+ success_result += '</ul>'
110
+ end
111
+ end
112
+
113
+ if critical_roles.length > 0
114
+ result = "Following required roles are not configured: "
115
+ critical_roles.each do |role|
116
+ result += "#{role}, "
117
+ end
118
+ return [false, result[0..-3]]
119
+ end
120
+
121
+ [true, success_result, yaml]
122
+ end
123
+
124
+ def validate_ec2_cluster_settings(min, max, ami)
125
+ if min.nil? or min.length == 0
126
+ return [false, "Minimum number of nodes unspecified"]
127
+ elsif max.nil? or max.length == 0
128
+ return [false, "Maximum number of nodes unspecified"]
129
+ elsif ami.nil? or ami.length == 0
130
+ return [false, "AMI ID not specified"]
131
+ elsif min.to_i <= 0
132
+ return [false, "Minimum number of nodes must be positive"]
133
+ elsif max.to_i < min.to_i
134
+ return [false, "Maximum number of nodes must not be smaller than the minimum numberof nodes"]
135
+ else
136
+ return [true, ""]
137
+ end
138
+ end
139
+
140
+ def validate_ec2_credentials(username, access_key, secret_key, region)
141
+ if username.nil? or username.length == 0
142
+ return [false, "EC2 username not specified"]
143
+ elsif access_key.nil? or access_key.length == 0
144
+ return [false, "EC2 access key not specified"]
145
+ elsif secret_key.nil? or secret_key.length == 0
146
+ return [false, "EC2 secret key not specified"]
147
+ elsif region.nil? or region.length == 0
148
+ return [false, "EC2 region not specified"]
149
+ else
150
+ output = CommonFunctions::shell("ec2-describe-regions -O #{access_key} -W #{secret_key}")
151
+ if output.nil? or output.length == 0
152
+ return [false, "Unable to execute EC2 command line tools"]
153
+ elsif output.include?"AuthFailure"
154
+ return [false, "EC2 authentication failed. Invalid EC2 access key or/and secret key."]
155
+ elsif !output.include?region
156
+ return [false, "Invalid EC2 region. This region is not available for your account."]
157
+ end
158
+ end
159
+ [true, ""]
160
+ end
161
+
162
+ def validate_ec2_certificate_uploads(username, pk_upload, cert_upload)
163
+ if pk_upload.nil?
164
+ return [false, "Primary key not uploaded"]
165
+ elsif pk_upload[:type] != "application/x-x509-ca-cert" and
166
+ pk_upload[:type] != "application/x-pem-file"
167
+ return [false, "Invalid primary key format: #{pk_upload[:type]}"]
168
+ elsif cert_upload.nil?
169
+ return [false, "X509 certificate not uploaded"]
170
+ elsif cert_upload[:type] != "application/x-x509-ca-cert" and
171
+ cert_upload[:type] != "application/x-pem-file"
172
+ return [false, "Invalid certificate format: #{cert_upload[:type]}"]
173
+ else
174
+ timestamp = Time.now.to_i
175
+ File.open("certificates/#{username}_#{timestamp}_pk.pem", "w") do |f|
176
+ f.write(pk_upload[:tempfile].read)
177
+ end
178
+ File.open("certificates/#{username}_#{timestamp}_cert.pem", "w") do |f|
179
+ f.write(cert_upload[:tempfile].read)
180
+ end
181
+ end
182
+ [true, timestamp]
183
+ end
184
+
185
+ def locked?
186
+ $mutex.synchronize do
187
+ return File.exists?('appscake.lock')
188
+ end
189
+ end
190
+
191
+ def lock
192
+ $mutex.synchronize do
193
+ if !File.exists?('appscake.lock')
194
+ File.open('appscake.lock', 'w') do |file|
195
+ file.write('AppsCake.lock')
196
+ end
197
+ return true
198
+ else
199
+ return false
200
+ end
201
+ end
202
+ end
203
+
204
+ def unlock
205
+ $mutex.synchronize do
206
+ if File.exists?('appscake.lock')
207
+ File.delete('appscake.lock')
208
+ return true
209
+ else
210
+ return false
211
+ end
212
+ end
213
+ end
214
+
215
+ def deploy(options)
216
+ 30.times do |i|
217
+ puts "Deploying..."
218
+ sleep(1)
219
+ end
220
+ end
221
+
222
+ def add_key(options)
223
+ puts "Generating RSA keys..."
224
+ end
225
+
226
+ def redirect_standard_io(timestamp)
227
+ begin
228
+ orig_stderr = $stderr.clone
229
+ orig_stdout = $stdout.clone
230
+ log_path = File.join(File.expand_path(File.dirname(__FILE__)), "logs")
231
+ $stderr.reopen File.new(File.join(log_path, "deploy-#{timestamp}.log"), "w")
232
+ $stderr.sync = true
233
+ $stdout.reopen File.new(File.join(log_path, "deploy-#{timestamp}.log"), "w")
234
+ $stdout.sync = true
235
+ retval = yield
236
+ rescue Exception => e
237
+ puts "[__ERROR__] Runtime error in deployment process: #{e.message}"
238
+ $stdout.reopen orig_stdout
239
+ $stderr.reopen orig_stderr
240
+ raise e
241
+ ensure
242
+ $stdout.reopen orig_stdout
243
+ $stderr.reopen orig_stderr
244
+ end
245
+ retval
246
+ end
247
+
248
+ def deploy_on_virtual_cluster(params, add_key_options, run_instances_options, success_msg)
249
+ if lock
250
+ begin
251
+ timestamp = Time.now.to_i
252
+ pid = fork do
253
+ begin
254
+ redirect_standard_io(timestamp) do
255
+ key_file = File.expand_path("~/.appscale/#{params[:keyname]}")
256
+ if File.exists?(key_file)
257
+ puts "AppScale key '#{params[:keyname]}' found on the disk. Reusing..."
258
+ else
259
+ puts "AppScale key '#{params[:keyname]}' not found on the disk. Generating..."
260
+ AppScaleTools.add_keypair(add_key_options)
261
+ end
262
+ AppScaleTools.run_instances(run_instances_options)
263
+ end
264
+ ensure
265
+ # If the fork was successful, the sub-process should release the lock
266
+ unlock
267
+ end
268
+ end
269
+ Process.detach(pid)
270
+ @timestamp = timestamp
271
+ @pid = pid
272
+ @html = success_msg
273
+ return erb :success
274
+ rescue Exception => e
275
+ # If something went wrong with the fork, release the lock immediately and return
276
+ unlock
277
+ return report_error("Unexpected Runtime Error", "Runtime error while executing" +
278
+ " appscale tools: #{e.message}")
279
+ end
280
+ else
281
+ return report_error("Server Busy", "AppsCake is currently busy deploying a cloud." +
282
+ " Please try again later.")
283
+ end
284
+ end
285
+
286
+ def deploy_on_ec2(params, run_instances_options, cert_timestamp)
287
+ if lock
288
+ begin
289
+ timestamp = Time.now.to_i
290
+ pid = fork do
291
+ ENV['EC2_REGION'] = params[:region]
292
+ ENV['EC2_PRIVATE_KEY'] = File.join(File.dirname(__FILE__), "certificates",
293
+ "#{params[:username]}_#{cert_timestamp}_pk.pem")
294
+ ENV['EC2_CERT'] = File.join(File.dirname(__FILE__), "certificates",
295
+ "#{params[:username]}_#{cert_timestamp}_cert.pem")
296
+ ENV['EC2_ACCESS_KEY'] = params[:access_key]
297
+ ENV['EC2_SECRET_KEY'] = params[:secret_key]
298
+ ENV['EC2_JVM_ARGS'] = "-Djavax.net.ssl.trustStore=#{ENV['JAVA_HOME']}/lib/security/cacerts"
299
+ ENV['EC2_URL'] = "https://ec2.#{params[:region]}.amazonaws.com"
300
+ ENV['S3_URL'] = "https://s3.amazonaws.com:443"
301
+ begin
302
+ redirect_standard_io(timestamp) do
303
+ AppScaleTools.run_instances(run_instances_options)
304
+ end
305
+ ensure
306
+ # If the fork was successful, the sub-process should release the lock
307
+ unlock
308
+ end
309
+ end
310
+ Process.detach(pid)
311
+ @timestamp = timestamp
312
+ @pid = pid
313
+ @html = ""
314
+ return erb :success
315
+ rescue Exception => e
316
+ # If something went wrong with the fork, release the lock immediately and return
317
+ unlock
318
+ return report_error("Unexpected Runtime Error", "Runtime error while executing" +
319
+ " appscale tools: #{e.message}")
320
+ end
321
+ else
322
+ return report_error("Server Busy", "AppsCake is currently busy deploying a cloud." +
323
+ " Please try again later.")
324
+ end
325
+ end