appscake 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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