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 +45 -0
- data/README.txt +56 -0
- data/Rakefile +42 -0
- data/bin/appscake +164 -4
- data/certificates/cert-appscake.pem +25 -0
- data/certificates/pk-appscake.pem +27 -0
- data/lib/appscake_utils.rb +325 -0
- data/public/bootstrap/css/bootstrap-responsive.css +1058 -0
- data/public/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/public/bootstrap/css/bootstrap.css +5774 -0
- data/public/bootstrap/css/bootstrap.min.css +9 -0
- data/public/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/public/bootstrap/img/glyphicons-halflings.png +0 -0
- data/public/bootstrap/js/bootstrap.js +2027 -0
- data/public/bootstrap/js/bootstrap.min.js +6 -0
- data/public/css/bootstrap-responsive.css +1058 -0
- data/public/css/bootstrap-responsive.min.css +9 -0
- data/public/css/bootstrap.css +5774 -0
- data/public/css/bootstrap.min.css +16 -0
- data/public/img/glyphicons-halflings-white.png +0 -0
- data/public/img/glyphicons-halflings.png +0 -0
- data/public/js/appscake_validator.js +93 -0
- data/public/js/bootstrap.js +2027 -0
- data/public/js/bootstrap.min.js +6 -0
- data/public/js/jquery-1.7.1.min.js +4 -0
- data/public/js/jquery.validate.min.js +51 -0
- data/views/_common.erb +69 -0
- data/views/_ec2.erb +129 -0
- data/views/_virtual.erb +34 -0
- data/views/error.erb +29 -0
- data/views/index.erb +56 -0
- data/views/success.erb +24 -0
- data/views/view_log.erb +166 -0
- metadata +37 -5
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/
|
2
|
-
#
|
3
|
-
#
|
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
|
-
|
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
|