judo 0.1.0 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/TODO ADDED
@@ -0,0 +1,45 @@
1
+ ### NEEDED for new gem launch
2
+
3
+ ### [ ] judo snapshots
4
+ ### [ ] judo snapshot foo bar
5
+ ### [ ] judo clone foo bar2
6
+ ### [ ] snapshots/init/virgin
7
+ ### [ ] judo stop --force
8
+ ### [ ] judo swap (x) (y) -> swaps elastic IP's and name
9
+ ### [ ] judo swap_ip (x) (y) -> swaps elastic IP's and name
10
+
11
+ ### [ ] judo does not work with ruby 1.8.6 - :(
12
+ ### [ ] saw a timeout on volume allocation - make sure we build in re-tries - need to allocate the server all together as much as possible
13
+ ### [ ] there is a feature to allow for using that block_mapping feature - faster startup
14
+
15
+ ### [ ] two phase delete (1 hr)
16
+ ### [-] refactor availability_zone (2 hrs)
17
+ ### [ ] pick availability zone from config "X":"Y" or "X":["Y","Z"]
18
+ ### [ ] assign to state on creation ( could delay till volume creation )
19
+ ### [ ] implement auto security_group creation and setup (6 hrs)
20
+ ### [ ] bind kuzushi gem version version
21
+
22
+ ### [ ] should be able to do ALL actions except commit without the repo!
23
+ ### [ ] store git commit hash with commit to block a judo commit if there is newer material stored
24
+ ### [ ] remove the tarball - store files a sha hashes in the bucket - makes for faster commits if the files have not changed
25
+
26
+ ### [ ] use a logger service (1 hr)
27
+ ### [ ] write specs (5 hr)
28
+
29
+ ### Error Handling
30
+ ### [ ] no availability zone before making disks
31
+ ### [ ] security group does not exists
32
+
33
+ ### Do Later
34
+ ### [ ] use amazon's new conditional write tools so we never have problems from concurrent updates
35
+ ### [ ] is thor really what we want to use here?
36
+ ### [ ] need to be able to pin a config to a version of kuzushi - gem updates can/will break a lot of things
37
+ ### [ ] I want a "judo monitor" command that will make start servers if they go down, and poke a listed port to make sure a service is listening, would be cool if it also detects wrong ami, wrong secuirity group, missing/extra volumes, missing/extra elastic_ip - might not want to force a reboot quite yet in these cases
38
+ ### [ ] Implement "judo snapshot [NAME]" to take a snapshot of the ebs's blocks
39
+ ### [ ] ruby 1.9.1 support
40
+ ### [ ] find a good way to set the hostname or prompt to :name
41
+ ### [ ] remove fog/s3 dependancy
42
+ ### [ ] enforce template files end in .erb to make room for other possible templates as defined by the extensions
43
+ ### [ ] zerigo integration for automatic DNS setup
44
+ ### [ ] How cool would it be if this was all reimplemented in eventmachine and could start lots of boxes in parallel? Would need to evented AWS api calls... Never seen a library to do that - would have to write our own... "Fog Machine?"
45
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.4
data/bin/judo CHANGED
@@ -30,6 +30,11 @@ Usage: judo launch [options] SERVER ...
30
30
 
31
31
  judo commit [options] GROUP
32
32
 
33
+ judo snapshot [options] SERVER SNAPSHOT ## take an ebs snapshot of a server
34
+ judo snapshots [options] [SERVER ...] ## show current snapshots on servers
35
+ judo clone [options] SNAPSHOT SERVER ## create a new server from a snapshot
36
+ judo erase [options] SNAPSHOT ## erase an old snapshot
37
+
33
38
  judo info [options] [SERVER ...]
34
39
  judo console [options] [SERVER ...] ## shows AWS console output
35
40
  judo ssh [options] [SERVER ...] ## ssh's into the server
@@ -53,6 +58,9 @@ banner
53
58
  defaults[:judo_dir] = dir
54
59
  defaults.merge(Judo::Base.default_options(Dir.pwd, dir))
55
60
  end
61
+ opts.on( '-f', '--force', 'Force a stop or restart (immediately force detach volumes)' ) do
62
+ options[:force] = true
63
+ end
56
64
  opts.on( '-r', '--repo DIR', 'Specify the location of the repo dir' ) do |dir|
57
65
  options[:repo] = dir
58
66
  end
@@ -68,6 +76,9 @@ banner
68
76
  opts.on( '-g', '--group GROUP', 'Specify the default group of the repo dir' ) do |group|
69
77
  options[:group] = group
70
78
  end
79
+ opts.on( '-v', '--version VERSION', 'Update the servers config version on create/start/launch' ) do |version|
80
+ options[:version] = version
81
+ end
71
82
  opts.on( '-h', '--help', 'Display this screen' ) do
72
83
  puts opts
73
84
  exit
@@ -77,6 +88,7 @@ end
77
88
  optparse.parse!
78
89
 
79
90
  judo = Judo::Base.new(defaults.merge(options))
91
+ judo.check_version
80
92
 
81
93
  begin
82
94
  case action
@@ -85,14 +97,35 @@ begin
85
97
  when "list" then do_list(judo, ARGV)
86
98
  when "groups" then do_groups(judo)
87
99
  when "info" then find_servers(judo, ARGV) { |s| do_info(judo, s) }
88
- when "console" then find_servers(judo, ARGV) { |s| puts server.console_output }
100
+ when "console" then find_servers(judo, ARGV) { |s| puts s.console_output }
89
101
  when "commit" then find_groups(judo, ARGV) { |g| g.compile }
90
102
  when "ssh" then find_servers(judo, ARGV) { |s| s.connect_ssh }
91
- when "start" then find_servers(judo, ARGV) { |s| s.start }
92
- when "restart" then find_servers(judo, ARGV) { |s| s.restart }
93
- when "stop" then find_servers(judo, ARGV) { |s| s.stop }
94
- when "create" then mk_servers(judo, ARGV) { |s| s.create }
95
- when "launch" then mk_servers(judo, ARGV) { |s| s.create; s.start }
103
+ when "start" then find_servers(judo, ARGV) { |s| s.start(options[:version]) }
104
+ when "restart" then find_servers(judo, ARGV) { |s| s.restart(options[:force]) }
105
+ when "stop" then find_servers(judo, ARGV) { |s| s.stop(options[:force]) }
106
+ when "create" then mk_servers(judo, ARGV) { |s| s.create(options[:version]) }
107
+ when "launch" then mk_servers(judo, ARGV) { |s| s.create(options[:version]); s.start }
108
+ when "snapshots" then do_snapshots(judo, ARGV)
109
+ when "erase" then
110
+ raise JudoError, "usage: judo erase SNAPSHOT" unless ARGV.size == 1
111
+ snapshot_name = ARGV.shift
112
+ judo.snapshots.select { |s| s.name == snapshot_name }.each { |s| s.destroy }
113
+ when "snapshot" then
114
+ raise JudoError, "usage: judo snapshot SERVER SNAPSHOT" unless ARGV.size == 2
115
+ server_name = ARGV.shift
116
+ snapshot_name = ARGV.shift
117
+ servers = find_servers(judo, [server_name])
118
+ raise JudoError, "You must specify a valid server to snapshot" if servers.empty?
119
+ raise JudoError, "You can only snapshot one server at a time" if servers.size > 1
120
+ servers[0].snapshot(snapshot_name)
121
+ when "clone" then
122
+ snapshot_name = ARGV.shift
123
+ raise JudoError, "You must specify a snapshot name" unless snapshot_name
124
+ new_server = ARGV.shift
125
+ raise JudoError, "You must specify a new server name" unless new_server
126
+ snapshot = judo.snapshots.detect { |s| s.name == snapshot_name }
127
+ raise JudoError, "No such snapshot #{snapshot_name}" unless snapshot
128
+ snapshot.clone(new_server,options[:version])
96
129
  when "destroy" then
97
130
  raise JudoError, "You must specify what servers to destroy" if ARGV.empty?
98
131
  find_either(judo, ARGV) do |i|
@@ -7,6 +7,8 @@ require 'right_aws'
7
7
  require 'json'
8
8
  require 'pp'
9
9
 
10
+ raise "Judo Currently Requires Ruby 1.8.7" unless RUBY_VERSION == "1.8.7"
11
+
10
12
  class JudoError < RuntimeError ; end
11
13
  class JudoInvalid < RuntimeError ; end
12
14
 
@@ -14,5 +16,6 @@ require File.dirname(__FILE__) + '/judo/base'
14
16
  require File.dirname(__FILE__) + '/judo/config'
15
17
  require File.dirname(__FILE__) + '/judo/group'
16
18
  require File.dirname(__FILE__) + '/judo/server'
19
+ require File.dirname(__FILE__) + '/judo/snapshot'
17
20
  require File.dirname(__FILE__) + '/judo/setup'
18
21
 
@@ -1,3 +1,27 @@
1
+
2
+ =begin
3
+ module Aws
4
+ class Ec2
5
+ API_VERSION = "2009-11-30" ## this didnt work
6
+ def start_instances(instance_id)
7
+ link = generate_request("StartInstances", { 'InstanceId' => instance_id } )
8
+ request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger))
9
+ rescue Exception
10
+ on_exception
11
+ end
12
+
13
+ def stop_instances(instance_id)
14
+ link = generate_request("StopInstances", { 'InstanceId' => instance_id } )
15
+ puts link.inspect
16
+ result = request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger))
17
+ puts result.inspect
18
+ rescue Exception
19
+ on_exception
20
+ end
21
+ end
22
+ end
23
+ =end
24
+
1
25
  module Judo
2
26
  class Base
3
27
  attr_accessor :judo_dir, :repo, :group
@@ -12,7 +36,7 @@ module Judo
12
36
  end
13
37
 
14
38
  def volumes
15
- @volumes ||= ec2.describe_volumes.map do |v|
39
+ @volumes ||= ec2_volumes.map do |v|
16
40
  {
17
41
  :id => v[:aws_id],
18
42
  :size => v[:aws_size],
@@ -69,6 +93,16 @@ module Judo
69
93
  @sdb ||= Aws::SdbInterface.new(access_id, access_secret, :logger => Logger.new(nil))
70
94
  end
71
95
 
96
+ def fetch_snapshots_state
97
+ s = {}
98
+ sdb.select("select * from #{Judo::Snapshot.domain}")[:items].each do |group|
99
+ group.each do |key,val|
100
+ s[key] = val
101
+ end
102
+ end
103
+ s
104
+ end
105
+
72
106
  def fetch_servers_state
73
107
  s = {}
74
108
  sdb.select("select * from #{Judo::Server.domain}")[:items].each do |group|
@@ -79,10 +113,18 @@ module Judo
79
113
  s
80
114
  end
81
115
 
116
+ def snapshots_state
117
+ @snapshots_state ||= fetch_snapshots_state
118
+ end
119
+
82
120
  def servers_state
83
121
  @servers_state ||= fetch_servers_state
84
122
  end
85
123
 
124
+ def snapshots
125
+ @snapshots ||= snapshots_state.map { |name,data| Judo::Snapshot.new(self, name, data["server"].first) }
126
+ end
127
+
86
128
  def servers
87
129
  @servers ||= servers_state.map { |name,data| Judo::Server.new(self, name, data["group"].first) }
88
130
  end
@@ -92,9 +134,15 @@ module Judo
92
134
  servers << s
93
135
  s
94
136
  end
95
-
137
+
138
+ def new_snapshot(name, server)
139
+ s = Judo::Snapshot.new(self, name, server)
140
+ snapshots << s
141
+ s
142
+ end
143
+
96
144
  def get_group(name)
97
- group = groups.detect { |g| g.to_s == name }
145
+ group = groups.detect { |g| g.name == name }
98
146
  group ||= Judo::Group.new(self, name, 0)
99
147
  group
100
148
  end
@@ -119,6 +167,14 @@ module Judo
119
167
  @ec2_instance = nil
120
168
  end
121
169
 
170
+ def ec2_volumes
171
+ @ec2_volumes ||= ec2.describe_volumes
172
+ end
173
+
174
+ def ec2_snapshots
175
+ @ec2_snapshots ||= ec2.describe_snapshots
176
+ end
177
+
122
178
  def ec2_instances
123
179
  @ec2_instance ||= ec2.describe_instances
124
180
  end
@@ -182,18 +238,34 @@ module Judo
182
238
  end
183
239
 
184
240
  def db_version
185
- 1
241
+ 2
242
+ end
243
+
244
+ def upgrade_db
245
+ case get_db_version
246
+ when 0
247
+ raise JudoError, "Your db appears to be unititialized. You will need to do a judo init"
248
+ when 1
249
+ task("Upgrading Judo: Creating Snapshots SDB Domain") do
250
+ sdb.create_domain(Judo::Snapshot.domain)
251
+ set_db_version(2)
252
+ end
253
+ else
254
+ raise JduoError, "judo db is newer than the current gem - upgrade judo and try again"
255
+ end
256
+ end
257
+
258
+ def set_db_version(new_version)
259
+ @db_version = new_version
260
+ sdb.put_attributes("judo_config", "judo", { "dbversion" => new_version }, :replace)
186
261
  end
187
262
 
188
263
  def get_db_version
189
- version = @sdb.get_attributes("judo_config", "judo")[:attributes]["dbversion"]
190
- version and version.first.to_i or db_version
264
+ @db_version ||= sdb.get_attributes("judo_config", "judo")[:attributes]["dbversion"].first.to_i
191
265
  end
192
266
 
193
267
  def check_version
194
- ## FIXME - call this somewhere
195
- raise JduoError, "judo db is newer than the current gem - upgrade judo and try again" if get_db_version > db_version
268
+ upgrade_db if get_db_version != db_version
196
269
  end
197
-
198
270
  end
199
271
  end
@@ -16,7 +16,7 @@ module JudoCommandLineHelpers
16
16
  end
17
17
 
18
18
  def find_groups(judo, args, &blk)
19
- raise JudoError, "No groups #{specified}" if args.empty? and judo.group.nil?
19
+ raise JudoError, "No groups specified" if args.empty? and judo.group.nil?
20
20
 
21
21
  args << ":#{judo.group}" if args.empty? ## use default group if none specified
22
22
 
@@ -65,9 +65,10 @@ module JudoCommandLineHelpers
65
65
  raise JudoError, "No servers" if servers.empty?
66
66
 
67
67
  servers.each { |s| judo_yield(s,blk) if blk }
68
+ servers
68
69
  end
69
70
 
70
- def find_server(judo, arg, use_default)
71
+ def find_server(judo, arg, use_default = false)
71
72
  ## this assumes names are unique no matter the group
72
73
  name,group = split(arg)
73
74
  if name != ""
@@ -118,12 +119,23 @@ module JudoCommandLineHelpers
118
119
  end
119
120
  end
120
121
 
122
+ def do_snapshots(judo, args)
123
+ servers = find_servers(judo, args)
124
+ printf " SNAPSHOTS\n"
125
+ printf "%s\n", ("-" * 80)
126
+ ## FIXME - listing snapshots for deleted servers?
127
+ judo.snapshots.each do |snapshot|
128
+ # next if args and not servers.map { |s| s.name }.include?(snapshot.server_name)
129
+ printf "%-15s %-25s %-15s %-10s %s\n", snapshot.name, snapshot.server_name, snapshot.group_name, snapshot.version_desc, "#{snapshot.num_ec2_snapshots}v"
130
+ end
131
+ end
132
+
121
133
  def do_list(judo, args)
122
134
  servers = find_servers(judo, args)
123
135
  printf " SERVERS\n"
124
136
  printf "%s\n", ("-" * 80)
125
137
  servers.sort.each do |s|
126
- printf "%-18s %-12s %-7s %-11s %-11s %-13s %-10s %-10s %s\n", s.name, s.group.name, s.version_desc, s.state["instance_id"], s.size_desc, s.ami, s.ec2_state, "#{s.volumes.keys.size} volumes", s.ip
138
+ printf "%-32s %-12s %-7s %-11s %-11s %-10s %-3s %s\n", s.name, s.group.name, s.version_desc, s.state["instance_id"], s.size_desc, s.ec2_state, "#{s.volumes.keys.size}v", s.has_ip? ? "ip" : ""
127
139
  end
128
140
  end
129
141
 
@@ -145,7 +157,5 @@ module JudoCommandLineHelpers
145
157
  v[:aws_attachment_status],
146
158
  v[:aws_device]
147
159
  end
148
- puts "\t[ CONFIG ]"
149
- pp server.config
150
160
  end
151
161
  end
@@ -2,47 +2,11 @@ module Judo
2
2
  class Group
3
3
  attr_accessor :name, :version
4
4
 
5
- # def self.dirs
6
- # Dir["#{@base.repor}/*/config.json"].map { |d| File.dirname(d) }
7
- # end
8
- #
9
- # def self.all
10
- # @@all ||= (dirs.map { |d| new(d) })
11
- # end
12
- #
13
- # def self.find(name)
14
- # all.detect { |d| d.name == name }
15
- # end
16
- #
17
- # def self.[](name)
18
- # find(name)
19
- # end
20
- #
21
- # def self.current
22
- # all.detect { |d| Dir.pwd == d.dir or Dir.pwd =~ /^#{d.dir}\// }
23
- # end
24
-
25
- # def initialize(base, dir, name = File.basename(dir))
26
5
  def initialize(base, name, version)
27
6
  @base = base
28
7
  @name = name
29
8
  @version = version
30
- # @dir = dir
31
- end
32
-
33
- # def create_server(server_name)
34
- # abort("Server needs a name") if server_name.nil?
35
- #
36
- ## abort("Already a server named #{server_name}") if Judo::Server.find_by_name(attrs[:name]) ## FIXME
37
- ## @base.read_config(attrs[:group]) ## make sure the config is valid ## FIXME
38
- #
39
- # server = Judo::Server.new base, server_name, name
40
- # server.task("Creating server #{server_name}") do
41
- # server.update "name" => server_name, "group" => name, "virgin" => true, "secret" => rand(2 ** 128).to_s(36)
42
- # @base.sdb.put_attributes("judo_config", "groups", name => server_name)
43
- # end
44
- # server
45
- # end
9
+ end
46
10
 
47
11
  def config
48
12
  @config ||= load_config
@@ -60,25 +24,20 @@ module Judo
60
24
  @base.servers.select { |s| server_names.include?(s.name) }
61
25
  end
62
26
 
63
- # def version
64
- # @version ||= (@base.group_versions[@name] || ["0"]).first.to_i
65
- # end
66
-
67
27
  def load_config
68
28
  JSON.load @base.s3_get(version_config_file)
69
29
  rescue Aws::AwsError
70
30
  raise JudoError, "No config stored: try 'judo commit #{to_s}'"
71
31
  end
72
32
 
73
- def set_version(new_version)
74
- @version = new_version
75
- @base.sdb.put_attributes("judo_config", "group_versions", { name => new_version }, :replace)
33
+ def set_version
34
+ @base.sdb.put_attributes("judo_config", "group_versions", { name => version }, :replace)
76
35
  end
77
36
 
78
37
  def compile
79
38
  tmpdir = "/tmp/kuzushi/#{name}"
80
- set_version(version + 1)
81
39
  puts "Compiling #{self} version #{version}"
40
+ @version = @version + 1
82
41
  FileUtils.rm_rf(tmpdir)
83
42
  FileUtils.mkdir_p(tmpdir)
84
43
  new_config = build_config
@@ -101,6 +60,7 @@ module Judo
101
60
  @base.s3_put(version_config_file, new_config.to_json)
102
61
  end
103
62
  end
63
+ set_version
104
64
  end
105
65
 
106
66
  def version_config_file
@@ -121,7 +81,6 @@ module Judo
121
81
 
122
82
  def extract_file(type, name, files)
123
83
  path = "#{dir}/#{type}s/#{name}"
124
- puts "[#{name}] #{path}"
125
84
  found = Dir[path]
126
85
  if not found.empty?
127
86
  found.each { |f| files["#{type}s/#{File.basename(f)}"] = f }
@@ -147,12 +106,15 @@ module Judo
147
106
  when "local_packages"
148
107
  extract_file(:package, "#{v}_i386.deb", files)
149
108
  extract_file(:package, "#{v}_amd64.deb", files)
109
+ when "git"
110
+ ## do nothing - is a remote
150
111
  when "template"
151
112
  extract_file(:template, v, files)
152
113
  when "source"
153
114
  extract_file(:file, v, files) unless config["template"] or config["package"]
154
115
  when "file"
155
- extract_file(:file, File.basename(v), files) unless config["template"] or config["source"]
116
+ ## this is getting messy
117
+ extract_file(:file, File.basename(v), files) unless config["template"] or config["source"] or config["git"]
156
118
  end
157
119
  end
158
120
  end
@@ -216,6 +178,14 @@ module Judo
216
178
  def sdb
217
179
  @base.sdb
218
180
  end
181
+
182
+ def version_desc(v)
183
+ if v == version
184
+ "v#{v}"
185
+ else
186
+ "v#{v}/#{version}"
187
+ end
188
+ end
219
189
  end
220
190
  end
221
191
 
@@ -1,51 +1,14 @@
1
- ### NEEDED for new gem launch
2
-
3
- ### [ ] return right away.. (1 hr)
4
- ### [ ] two phase delete (1 hr)
5
- ### [-] refactor availability_zone (2 hrs)
6
- ### [ ] pick availability zone from config "X":"Y" or "X":["Y","Z"]
7
- ### [ ] assign to state on creation ( could delay till volume creation )
8
- ### [ ] implement auto security_group creation and setup (6 hrs)
9
- ### [ ] write some examples - simple postgres/redis/couchdb server (5hrs)
10
- ### [ ] write new README (4 hrs)
11
- ### [ ] bind kuzushi gem version version
12
- ### [ ] realase new gem! (1 hr)
13
-
14
- ### [ ] should be able to do ALL actions except commit without the repo!
15
- ### [ ] store git commit hash with commit to block a judo commit if there is newer material stored
16
- ### [ ] remove the tarball - store files a sha hashes in the bucket - makes for faster commits if the files have not changed
17
-
18
- ### [ ] use a logger service (1 hr)
19
- ### [ ] write specs (5 hr)
20
-
21
- ### Error Handling
22
- ### [ ] no availability zone before making disks
23
- ### [ ] security group does not exists
24
-
25
- ### Do Later
26
- ### [ ] use amazon's new conditional write tools so we never have problems from concurrent updates
27
- ### [ ] is thor really what we want to use here?
28
- ### [ ] need to be able to pin a config to a version of kuzushi - gem updates can/will break a lot of things
29
- ### [ ] I want a "judo monitor" command that will make start servers if they go down, and poke a listed port to make sure a service is listening, would be cool if it also detects wrong ami, wrong secuirity group, missing/extra volumes, missing/extra elastic_ip - might not want to force a reboot quite yet in these cases
30
- ### [ ] Implement "judo snapshot [NAME]" to take a snapshot of the ebs's blocks
31
- ### [ ] ruby 1.9.1 support
32
- ### [ ] find a good way to set the hostname or prompt to :name
33
- ### [ ] remove fog/s3 dependancy
34
- ### [ ] enforce template files end in .erb to make room for other possible templates as defined by the extensions
35
- ### [ ] zerigo integration for automatic DNS setup
36
- ### [ ] How cool would it be if this was all reimplemented in eventmachine and could start lots of boxes in parallel? Would need to evented AWS api calls... Never seen a library to do that - would have to write our own... "Fog Machine?"
37
-
38
1
  module Judo
39
2
  class Server
40
3
  attr_accessor :name
41
4
 
42
- def initialize(base, name, group)
5
+ def initialize(base, name, group, version = nil)
43
6
  @base = base
44
7
  @name = name
45
8
  @group_name = group
46
9
  end
47
10
 
48
- def create
11
+ def create(version = group.version, snapshots = nil)
49
12
  raise JudoError, "no group specified" unless @group_name
50
13
 
51
14
  if @name.nil?
@@ -56,11 +19,12 @@ module Judo
56
19
  raise JudoError, "there is already a server named #{name}" if @base.servers.detect { |s| s.name == @name and s != self}
57
20
 
58
21
  task("Creating server #{name}") do
59
- update "name" => name, "group" => @group_name, "virgin" => true, "secret" => rand(2 ** 128).to_s(36)
22
+ update "name" => name, "group" => @group_name, "virgin" => true, "secret" => rand(2 ** 128).to_s(36), "version" => version
60
23
  @base.sdb.put_attributes("judo_config", "groups", @group_name => name)
61
24
  end
62
25
 
63
- allocate_resources
26
+ allocate_disk(snapshots)
27
+ allocate_ip
64
28
 
65
29
  self
66
30
  end
@@ -98,18 +62,37 @@ module Judo
98
62
  end
99
63
 
100
64
  def version_desc
101
- return "" unless running?
102
- if version == group.version
103
- "v#{version}"
104
- else
105
- "v#{version}/#{group.version}"
106
- end
65
+ group.version_desc(version)
107
66
  end
108
67
 
109
68
  def version
110
69
  get("version").to_i
111
70
  end
112
71
 
72
+ def update_version(new_version)
73
+ update "version" => new_version
74
+ end
75
+
76
+ def kuzushi_action
77
+ if virgin?
78
+ if cloned?
79
+ "start"
80
+ else
81
+ "init"
82
+ end
83
+ else
84
+ "start"
85
+ end
86
+ end
87
+
88
+ def clone
89
+ get("clone")
90
+ end
91
+
92
+ def cloned?
93
+ !!clone
94
+ end
95
+
113
96
  def virgin?
114
97
  get("virgin").to_s == "true" ## I'm going to set it to true and it will come back from the db as "true" -> could be "false" or false or nil also
115
98
  end
@@ -118,6 +101,10 @@ module Judo
118
101
  get "secret"
119
102
  end
120
103
 
104
+ def snapshots
105
+ base.snapshots.select { |s| s.server == self }
106
+ end
107
+
121
108
  def volumes
122
109
  Hash[ (state["volumes"] || []).map { |a| a.split(":") } ]
123
110
  end
@@ -147,7 +134,7 @@ module Judo
147
134
  end
148
135
 
149
136
  def delete
150
- group.delete_server(self)
137
+ group.delete_server(self) if group
151
138
  @base.sdb.delete_attributes(self.class.domain, name)
152
139
  end
153
140
 
@@ -165,7 +152,24 @@ module Judo
165
152
  "#{name}:#{@group_name}"
166
153
  end
167
154
 
168
- def allocate_resources
155
+ def allocate_disk(snapshots)
156
+ if snapshots
157
+ clone_snapshots(snapshots)
158
+ else
159
+ create_volumes
160
+ end
161
+ end
162
+
163
+ def clone_snapshots(snapshots)
164
+ snapshots.each do |device,snap_id|
165
+ task("Creating EC2 Volume #{device} from #{snap_id}") do
166
+ volume_id = @base.ec2.create_volume(snap_id, nil, config["availability_zone"])[:aws_id]
167
+ add_volume(volume_id, device)
168
+ end
169
+ end
170
+ end
171
+
172
+ def create_volumes
169
173
  if config["volumes"]
170
174
  [config["volumes"]].flatten.each do |volume_config|
171
175
  device = volume_config["device"]
@@ -185,7 +189,9 @@ module Judo
185
189
  end
186
190
  end
187
191
  end
192
+ end
188
193
 
194
+ def allocate_ip
189
195
  begin
190
196
  if config["elastic_ip"] and not elastic_ip
191
197
  ### EC2 allocate_address
@@ -259,17 +265,18 @@ module Judo
259
265
  ["pending", "running", "shutting_down", "degraded"].include?(ec2_state)
260
266
  end
261
267
 
262
- def start
268
+ def start(new_version = nil)
263
269
  invalid "Already running" if running?
264
270
  invalid "No config has been commited yet, type 'judo commit'" unless group.version > 0
271
+ task("Updating server version") { update_version(new_version) } if new_version
265
272
  task("Starting server #{name}") { launch_ec2 }
266
273
  task("Wait for server") { wait_for_running } if elastic_ip or has_volumes?
267
274
  task("Attaching ip") { attach_ip } if elastic_ip
268
275
  task("Attaching volumes") { attach_volumes } if has_volumes?
269
276
  end
270
277
 
271
- def restart
272
- stop if running?
278
+ def restart(force = false)
279
+ stop(force) if running?
273
280
  start
274
281
  end
275
282
 
@@ -285,10 +292,19 @@ module Judo
285
292
  raise JudoInvalid, str
286
293
  end
287
294
 
288
- def stop
295
+ def force_detach_volumes
296
+ volumes.each do |device,volume_id|
297
+ task("Force detaching #{volume_id}") do
298
+ @base.ec2.detach_volume(volume_id, instance_id, device, true)
299
+ end
300
+ end
301
+ end
302
+
303
+ def stop(force = false)
289
304
  invalid "not running" unless running?
290
305
  ## EC2 terminate_isntaces
291
306
  task("Terminating instance") { @base.ec2.terminate_instances([ instance_id ]) }
307
+ force_detach_volumes if force
292
308
  task("Wait for volumes to detach") { wait_for_volumes_detached } if volumes.size > 0
293
309
  remove "instance_id"
294
310
  end
@@ -305,8 +321,7 @@ module Judo
305
321
  :key_name => config["key_name"],
306
322
  :group_ids => security_groups,
307
323
  :user_data => ud).first
308
-
309
- update "instance_id" => result[:aws_instance_id], "virgin" => false, "version" => group.version
324
+ update "instance_id" => result[:aws_instance_id], "virgin" => false
310
325
  end
311
326
 
312
327
  def debug(str)
@@ -356,10 +371,15 @@ module Judo
356
371
  end
357
372
 
358
373
  def wait_for_volumes_detached
359
- ## FIXME - force if it takes too long
360
- loop do
361
- break if ec2_volumes.reject { |v| v[:aws_status] == "available" }.empty?
362
- sleep 2
374
+ begin
375
+ Timeout::timeout(30) do
376
+ loop do
377
+ break if ec2_volumes.reject { |v| v[:aws_status] == "available" }.empty?
378
+ sleep 2
379
+ end
380
+ end
381
+ rescue Timeout::Error
382
+ force_detach_volumes
363
383
  end
364
384
  end
365
385
 
@@ -505,6 +525,11 @@ USER_DATA
505
525
  end
506
526
  end
507
527
 
528
+ def snapshot(name)
529
+ snap = @base.new_snapshot(name, self.name)
530
+ snap.create
531
+ end
532
+
508
533
  def <=>(s)
509
534
  [group.name, name] <=> [s.group.name, s.name]
510
535
  end
@@ -63,7 +63,8 @@ DEFAULT
63
63
 
64
64
  def setup_db
65
65
  puts "Trying to connect to SimpleDB with #{@aws_access_id}"
66
- sdb.create_domain("judo_servers")
66
+ sdb.create_domain(Judo::Server.domain)
67
+ sdb.create_domain(Judo::Snapshot.domain)
67
68
  sdb.create_domain("judo_config")
68
69
  olddb = sdb.get_attributes("judo_config", "judo")[:attributes]["dbversion"]
69
70
  abort "There is an existing judo database of a newer version - upgrade judo and try again" if olddb and olddb.first.to_i > Judo::Config.db_version
@@ -0,0 +1,108 @@
1
+ module Judo
2
+ class Snapshot
3
+ attr_accessor :name, :server_name
4
+
5
+ def self.domain
6
+ "judo_snapshots"
7
+ end
8
+
9
+ def initialize(base, name, server_name)
10
+ @base = base
11
+ @name = name
12
+ @server_name = server_name
13
+ end
14
+
15
+ def server
16
+ @server ||= @base.servers.detect { |s| s.name == @server_name }
17
+ end
18
+
19
+ def fetch_state
20
+ @base.sdb.get_attributes(self.class.domain, name)[:attributes]
21
+ end
22
+
23
+ def state
24
+ @base.snapshots_state[name] ||= fetch_state
25
+ end
26
+
27
+ def group_name
28
+ get("group")
29
+ end
30
+
31
+ def version
32
+ get("version").to_i
33
+ end
34
+
35
+ def num_ec2_snapshots
36
+ devs.size
37
+ end
38
+
39
+ def devs
40
+ Hash[ (state["devs"] || []).map { |a| a.split(":") } ]
41
+ end
42
+
43
+ def create
44
+ raise JudoError,"snapshot already exists" unless state.empty?
45
+ raise JudoError,"server has no disks to clone: #{server.volumes}" if server.volumes.empty?
46
+ task("Snapshotting #{server.name}") do
47
+ devs = server.volumes.map do |dev,vol|
48
+ "#{dev}:#{@base.ec2.create_snapshot(vol)[:aws_id]}"
49
+ end
50
+ @base.sdb.put_attributes(self.class.domain, name, { "version" => server.version, "devs" => devs, "server" => server.name, "group" => server.group.name }, :replace)
51
+ server.add "snapshots", name
52
+ end
53
+ end
54
+
55
+ def clone(new_server, version = self.version)
56
+ raise JudoError, "cannot clone, snapshotting not complete" unless completed?
57
+ server = @base.new_server(new_server, group_name)
58
+ server.create(version, devs)
59
+ server.update "clone" => name ##, "secret" => rand(2 ** 128).to_s(36) ## cant change this till kuzushi knows about a post-clone operation
60
+ end
61
+
62
+ def delete
63
+ @base.sdb.delete_attributes(self.class.domain, name)
64
+ server.remove "snapshots", name
65
+ end
66
+
67
+ def get(key)
68
+ state[key] && [state[key]].flatten.first
69
+ end
70
+
71
+ def destroy
72
+ devs.each do |dev,snapshot_id|
73
+ task("Deleting #{dev} #{snapshot_id}") do
74
+ begin
75
+ @base.ec2.delete_snapshot(snapshot_id)
76
+ rescue Object => e
77
+ puts "Error destrotying snapshot #{e.message}"
78
+ end
79
+ end
80
+ end
81
+ delete
82
+ end
83
+
84
+ def ec2_ids
85
+ devs.values
86
+ end
87
+
88
+ def completed?
89
+ not @base.ec2_snapshots.select { |s| ec2_ids.include? s[:aws_id] }.detect { |s| s[:aws_status] != "completed" }
90
+ end
91
+
92
+ def size(snap_id)
93
+ @base.ec2_snapshots.detect { |s| s[:aws_id] == snap_id }
94
+ end
95
+
96
+ def version_desc
97
+ group.version_desc(version)
98
+ end
99
+
100
+ def group
101
+ @group ||= @base.groups.detect { |g| g.name == group_name }
102
+ end
103
+
104
+ def fetch_state
105
+ @base.sdb.get_attributes(self.class.domain, name)[:attributes]
106
+ end
107
+ end
108
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 0
9
- version: 0.1.0
8
+ - 4
9
+ version: 0.1.4
10
10
  platform: ruby
11
11
  authors:
12
12
  - Orion Henry
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-08 00:00:00 -04:00
17
+ date: 2010-05-06 00:00:00 -04:00
18
18
  default_executable: judo
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -51,9 +51,11 @@ extensions: []
51
51
 
52
52
  extra_rdoc_files:
53
53
  - README.markdown
54
+ - TODO
54
55
  files:
55
56
  - README.markdown
56
57
  - Rakefile
58
+ - TODO
57
59
  - VERSION
58
60
  - bin/judo
59
61
  - lib/judo.rb
@@ -63,6 +65,7 @@ files:
63
65
  - lib/judo/group.rb
64
66
  - lib/judo/server.rb
65
67
  - lib/judo/setup.rb
68
+ - lib/judo/snapshot.rb
66
69
  - spec/base.rb
67
70
  - spec/base_spec.rb
68
71
  - spec/server_spec.rb